diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 0000000..e69de29 diff --git a/404.html b/404.html new file mode 100644 index 0000000..eb2a309 --- /dev/null +++ b/404.html @@ -0,0 +1,1504 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + SparkFun DataLogger IoT Hookup Guide + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ + + + + + +
+ + + + + + + + + + + +
+ +
+ + + + +
+
+ + + +
+
+
+ + + + + + + + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+ + + + + + + +
+ +

404 - Not found

+ +
+
+ + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/20594-SparkFun_DataLogger_IoT-9DOF_bottom.jpg b/assets/20594-SparkFun_DataLogger_IoT-9DOF_bottom.jpg new file mode 100644 index 0000000..5f93291 Binary files /dev/null and b/assets/20594-SparkFun_DataLogger_IoT-9DOF_bottom.jpg differ diff --git a/assets/20594-SparkFun_DataLogger_IoT-9DOF_top.jpg b/assets/20594-SparkFun_DataLogger_IoT-9DOF_top.jpg new file mode 100644 index 0000000..a6c3ca8 Binary files /dev/null and b/assets/20594-SparkFun_DataLogger_IoT-9DOF_top.jpg differ diff --git a/assets/20594-SparkFun_DataLogger_IoT-ESP32.jpg b/assets/20594-SparkFun_DataLogger_IoT-ESP32.jpg new file mode 100644 index 0000000..1d7832c Binary files /dev/null and b/assets/20594-SparkFun_DataLogger_IoT-ESP32.jpg differ diff --git a/assets/20594-SparkFun_DataLogger_IoT-ESP32_01.jpg b/assets/20594-SparkFun_DataLogger_IoT-ESP32_01.jpg new file mode 100644 index 0000000..09c9bd7 Binary files /dev/null and b/assets/20594-SparkFun_DataLogger_IoT-ESP32_01.jpg differ diff --git a/assets/20594-SparkFun_DataLogger_IoT-ESP32_Bottom_3V3_System_Qwiic.jpg b/assets/20594-SparkFun_DataLogger_IoT-ESP32_Bottom_3V3_System_Qwiic.jpg new file mode 100644 index 0000000..654ba14 Binary files /dev/null and b/assets/20594-SparkFun_DataLogger_IoT-ESP32_Bottom_3V3_System_Qwiic.jpg differ diff --git a/assets/20594-SparkFun_DataLogger_IoT-ESP32_Bottom_Analog.jpg b/assets/20594-SparkFun_DataLogger_IoT-ESP32_Bottom_Analog.jpg new file mode 100644 index 0000000..265c8b7 Binary files /dev/null and b/assets/20594-SparkFun_DataLogger_IoT-ESP32_Bottom_Analog.jpg differ diff --git a/assets/20594-SparkFun_DataLogger_IoT-ESP32_Bottom_Battery_Input.jpg b/assets/20594-SparkFun_DataLogger_IoT-ESP32_Bottom_Battery_Input.jpg new file mode 100644 index 0000000..2b3684f Binary files /dev/null and b/assets/20594-SparkFun_DataLogger_IoT-ESP32_Bottom_Battery_Input.jpg differ diff --git a/assets/20594-SparkFun_DataLogger_IoT-ESP32_Bottom_Digital_Pin.jpg b/assets/20594-SparkFun_DataLogger_IoT-ESP32_Bottom_Digital_Pin.jpg new file mode 100644 index 0000000..e2bd5d4 Binary files /dev/null and b/assets/20594-SparkFun_DataLogger_IoT-ESP32_Bottom_Digital_Pin.jpg differ diff --git a/assets/20594-SparkFun_DataLogger_IoT-ESP32_Bottom_Jumpers.jpg b/assets/20594-SparkFun_DataLogger_IoT-ESP32_Bottom_Jumpers.jpg new file mode 100644 index 0000000..afc7b62 Binary files /dev/null and b/assets/20594-SparkFun_DataLogger_IoT-ESP32_Bottom_Jumpers.jpg differ diff --git a/assets/20594-SparkFun_DataLogger_IoT-ESP32_Bottom_Power_In_5V_PTH.jpg b/assets/20594-SparkFun_DataLogger_IoT-ESP32_Bottom_Power_In_5V_PTH.jpg new file mode 100644 index 0000000..7abf572 Binary files /dev/null and b/assets/20594-SparkFun_DataLogger_IoT-ESP32_Bottom_Power_In_5V_PTH.jpg differ diff --git a/assets/20594-SparkFun_DataLogger_IoT-ESP32_Bottom_Qwiic_I2C.jpg b/assets/20594-SparkFun_DataLogger_IoT-ESP32_Bottom_Qwiic_I2C.jpg new file mode 100644 index 0000000..8242d6e Binary files /dev/null and b/assets/20594-SparkFun_DataLogger_IoT-ESP32_Bottom_Qwiic_I2C.jpg differ diff --git a/assets/20594-SparkFun_DataLogger_IoT-ESP32_Bottom_Reset_Boot_Buttons.jpg b/assets/20594-SparkFun_DataLogger_IoT-ESP32_Bottom_Reset_Boot_Buttons.jpg new file mode 100644 index 0000000..7274777 Binary files /dev/null and b/assets/20594-SparkFun_DataLogger_IoT-ESP32_Bottom_Reset_Boot_Buttons.jpg differ diff --git a/assets/20594-SparkFun_DataLogger_IoT-ESP32_Bottom_SPI.jpg b/assets/20594-SparkFun_DataLogger_IoT-ESP32_Bottom_SPI.jpg new file mode 100644 index 0000000..7274777 Binary files /dev/null and b/assets/20594-SparkFun_DataLogger_IoT-ESP32_Bottom_SPI.jpg differ diff --git a/assets/20594-SparkFun_DataLogger_IoT-ESP32_Bottom_UART.jpg b/assets/20594-SparkFun_DataLogger_IoT-ESP32_Bottom_UART.jpg new file mode 100644 index 0000000..a9cba6a Binary files /dev/null and b/assets/20594-SparkFun_DataLogger_IoT-ESP32_Bottom_UART.jpg differ diff --git a/assets/20594-SparkFun_DataLogger_IoT-ESP32_Bottom_User_LED_RGB_WS2812.jpg b/assets/20594-SparkFun_DataLogger_IoT-ESP32_Bottom_User_LED_RGB_WS2812.jpg new file mode 100644 index 0000000..d231905 Binary files /dev/null and b/assets/20594-SparkFun_DataLogger_IoT-ESP32_Bottom_User_LED_RGB_WS2812.jpg differ diff --git a/assets/20594-SparkFun_DataLogger_IoT-ESP32_Bottom_microSD_Card.jpg b/assets/20594-SparkFun_DataLogger_IoT-ESP32_Bottom_microSD_Card.jpg new file mode 100644 index 0000000..eafbf74 Binary files /dev/null and b/assets/20594-SparkFun_DataLogger_IoT-ESP32_Bottom_microSD_Card.jpg differ diff --git a/assets/20594-SparkFun_DataLogger_IoT-ESP32_Top_3V3_System_Qwiic.jpg b/assets/20594-SparkFun_DataLogger_IoT-ESP32_Top_3V3_System_Qwiic.jpg new file mode 100644 index 0000000..73594ce Binary files /dev/null and b/assets/20594-SparkFun_DataLogger_IoT-ESP32_Top_3V3_System_Qwiic.jpg differ diff --git a/assets/20594-SparkFun_DataLogger_IoT-ESP32_Top_9DoF_IMU_Accelerometer_Gyro_Magnetometer.jpg b/assets/20594-SparkFun_DataLogger_IoT-ESP32_Top_9DoF_IMU_Accelerometer_Gyro_Magnetometer.jpg new file mode 100644 index 0000000..b2233c1 Binary files /dev/null and b/assets/20594-SparkFun_DataLogger_IoT-ESP32_Top_9DoF_IMU_Accelerometer_Gyro_Magnetometer.jpg differ diff --git a/assets/20594-SparkFun_DataLogger_IoT-ESP32_Top_Analog.jpg b/assets/20594-SparkFun_DataLogger_IoT-ESP32_Top_Analog.jpg new file mode 100644 index 0000000..02bc349 Binary files /dev/null and b/assets/20594-SparkFun_DataLogger_IoT-ESP32_Top_Analog.jpg differ diff --git a/assets/20594-SparkFun_DataLogger_IoT-ESP32_Top_CH340_USB-to-Serial.jpg b/assets/20594-SparkFun_DataLogger_IoT-ESP32_Top_CH340_USB-to-Serial.jpg new file mode 100644 index 0000000..181fd58 Binary files /dev/null and b/assets/20594-SparkFun_DataLogger_IoT-ESP32_Top_CH340_USB-to-Serial.jpg differ diff --git a/assets/20594-SparkFun_DataLogger_IoT-ESP32_Top_Digital_Pin.jpg b/assets/20594-SparkFun_DataLogger_IoT-ESP32_Top_Digital_Pin.jpg new file mode 100644 index 0000000..ddcccb4 Binary files /dev/null and b/assets/20594-SparkFun_DataLogger_IoT-ESP32_Top_Digital_Pin.jpg differ diff --git a/assets/20594-SparkFun_DataLogger_IoT-ESP32_Top_MAX17043_Fuel_Gauge_LiPo_Charger_MCP73831_Battery_Input.jpg b/assets/20594-SparkFun_DataLogger_IoT-ESP32_Top_MAX17043_Fuel_Gauge_LiPo_Charger_MCP73831_Battery_Input.jpg new file mode 100644 index 0000000..0607e6b Binary files /dev/null and b/assets/20594-SparkFun_DataLogger_IoT-ESP32_Top_MAX17043_Fuel_Gauge_LiPo_Charger_MCP73831_Battery_Input.jpg differ diff --git a/assets/20594-SparkFun_DataLogger_IoT-ESP32_Top_Power_In_USB_5V_PTH.jpg b/assets/20594-SparkFun_DataLogger_IoT-ESP32_Top_Power_In_USB_5V_PTH.jpg new file mode 100644 index 0000000..3d352af Binary files /dev/null and b/assets/20594-SparkFun_DataLogger_IoT-ESP32_Top_Power_In_USB_5V_PTH.jpg differ diff --git a/assets/20594-SparkFun_DataLogger_IoT-ESP32_Top_Qwiic_I2C.jpg b/assets/20594-SparkFun_DataLogger_IoT-ESP32_Top_Qwiic_I2C.jpg new file mode 100644 index 0000000..e3d4920 Binary files /dev/null and b/assets/20594-SparkFun_DataLogger_IoT-ESP32_Top_Qwiic_I2C.jpg differ diff --git a/assets/20594-SparkFun_DataLogger_IoT-ESP32_Top_Reset_Boot_Buttons.jpg b/assets/20594-SparkFun_DataLogger_IoT-ESP32_Top_Reset_Boot_Buttons.jpg new file mode 100644 index 0000000..3fbc46a Binary files /dev/null and b/assets/20594-SparkFun_DataLogger_IoT-ESP32_Top_Reset_Boot_Buttons.jpg differ diff --git a/assets/20594-SparkFun_DataLogger_IoT-ESP32_Top_SPI.jpg b/assets/20594-SparkFun_DataLogger_IoT-ESP32_Top_SPI.jpg new file mode 100644 index 0000000..01b27dc Binary files /dev/null and b/assets/20594-SparkFun_DataLogger_IoT-ESP32_Top_SPI.jpg differ diff --git a/assets/20594-SparkFun_DataLogger_IoT-ESP32_Top_UARTs.jpg b/assets/20594-SparkFun_DataLogger_IoT-ESP32_Top_UARTs.jpg new file mode 100644 index 0000000..4b93137 Binary files /dev/null and b/assets/20594-SparkFun_DataLogger_IoT-ESP32_Top_UARTs.jpg differ diff --git a/assets/20594-SparkFun_DataLogger_IoT-ESP32_Top_User_LED_RGB_WS2812.jpg b/assets/20594-SparkFun_DataLogger_IoT-ESP32_Top_User_LED_RGB_WS2812.jpg new file mode 100644 index 0000000..a8cdc85 Binary files /dev/null and b/assets/20594-SparkFun_DataLogger_IoT-ESP32_Top_User_LED_RGB_WS2812.jpg differ diff --git a/assets/22462-DEV_SparkFun_DataLogger_IoT-Bottom.jpg b/assets/22462-DEV_SparkFun_DataLogger_IoT-Bottom.jpg new file mode 100644 index 0000000..4c7c846 Binary files /dev/null and b/assets/22462-DEV_SparkFun_DataLogger_IoT-Bottom.jpg differ diff --git a/assets/22462-DEV_SparkFun_DataLogger_IoT-Top.jpg b/assets/22462-DEV_SparkFun_DataLogger_IoT-Top.jpg new file mode 100644 index 0000000..6c3642b Binary files /dev/null and b/assets/22462-DEV_SparkFun_DataLogger_IoT-Top.jpg differ diff --git a/assets/22462-DEV_SparkFun_DataLogger_IoT_MEAS_Jumper-Bottom.jpg b/assets/22462-DEV_SparkFun_DataLogger_IoT_MEAS_Jumper-Bottom.jpg new file mode 100644 index 0000000..7fcef0a Binary files /dev/null and b/assets/22462-DEV_SparkFun_DataLogger_IoT_MEAS_Jumper-Bottom.jpg differ diff --git a/assets/22462-DEV_SparkFun_DataLogger_IoT_MEAS_Jumper-Top.jpg b/assets/22462-DEV_SparkFun_DataLogger_IoT_MEAS_Jumper-Top.jpg new file mode 100644 index 0000000..2e40ae4 Binary files /dev/null and b/assets/22462-DEV_SparkFun_DataLogger_IoT_MEAS_Jumper-Top.jpg differ diff --git a/assets/22462-DEV_SparkFun_DataLogger_IoT_Pin_5-Bottom.jpg b/assets/22462-DEV_SparkFun_DataLogger_IoT_Pin_5-Bottom.jpg new file mode 100644 index 0000000..5fa2730 Binary files /dev/null and b/assets/22462-DEV_SparkFun_DataLogger_IoT_Pin_5-Bottom.jpg differ diff --git a/assets/22462-DEV_SparkFun_DataLogger_IoT_Pin_5-Top.jpg b/assets/22462-DEV_SparkFun_DataLogger_IoT_Pin_5-Top.jpg new file mode 100644 index 0000000..b7751db Binary files /dev/null and b/assets/22462-DEV_SparkFun_DataLogger_IoT_Pin_5-Top.jpg differ diff --git a/assets/22462-_DEV-_SparkFun_DataLogger_IoT_RGB_LED.jpg b/assets/22462-_DEV-_SparkFun_DataLogger_IoT_RGB_LED.jpg new file mode 100644 index 0000000..4bb38a2 Binary files /dev/null and b/assets/22462-_DEV-_SparkFun_DataLogger_IoT_RGB_LED.jpg differ diff --git a/assets/22462-_DEV_SparkFun_DataLogger_IoT-1.jpg b/assets/22462-_DEV_SparkFun_DataLogger_IoT-1.jpg new file mode 100644 index 0000000..e6aea60 Binary files /dev/null and b/assets/22462-_DEV_SparkFun_DataLogger_IoT-1.jpg differ diff --git a/assets/Additional_Time_Sources_GNSS_RTC.PNG b/assets/Additional_Time_Sources_GNSS_RTC.PNG new file mode 100644 index 0000000..58b1a4a Binary files /dev/null and b/assets/Additional_Time_Sources_GNSS_RTC.PNG differ diff --git a/assets/Arduion_IoT_Error_Connecting_Incorrect_Configuration.JPG b/assets/Arduion_IoT_Error_Connecting_Incorrect_Configuration.JPG new file mode 100644 index 0000000..1075321 Binary files /dev/null and b/assets/Arduion_IoT_Error_Connecting_Incorrect_Configuration.JPG differ diff --git a/assets/Combined_Arduino_Logo_Cloud.png b/assets/Combined_Arduino_Logo_Cloud.png new file mode 100644 index 0000000..d4e6fbc Binary files /dev/null and b/assets/Combined_Arduino_Logo_Cloud.png differ diff --git a/assets/Configure_Timestamp.PNG b/assets/Configure_Timestamp.PNG new file mode 100644 index 0000000..fa5a9f9 Binary files /dev/null and b/assets/Configure_Timestamp.PNG differ diff --git a/assets/Configure_WiFi_Network.PNG b/assets/Configure_WiFi_Network.PNG new file mode 100644 index 0000000..48bb39d Binary files /dev/null and b/assets/Configure_WiFi_Network.PNG differ diff --git a/assets/Configure_WiFi_Password.PNG b/assets/Configure_WiFi_Password.PNG new file mode 100644 index 0000000..e26aa7e Binary files /dev/null and b/assets/Configure_WiFi_Password.PNG differ diff --git a/assets/DataLogger_Connected_ThingSpeak.JPG b/assets/DataLogger_Connected_ThingSpeak.JPG new file mode 100644 index 0000000..60e07b1 Binary files /dev/null and b/assets/DataLogger_Connected_ThingSpeak.JPG differ diff --git a/assets/DataLogger_IoT_Device_Name_Address_Loaded.JPG b/assets/DataLogger_IoT_Device_Name_Address_Loaded.JPG new file mode 100644 index 0000000..e758fa4 Binary files /dev/null and b/assets/DataLogger_IoT_Device_Name_Address_Loaded.JPG differ diff --git a/assets/DataLogger_IoT_Device_Name_Loaded.JPG b/assets/DataLogger_IoT_Device_Name_Loaded.JPG new file mode 100644 index 0000000..2f73523 Binary files /dev/null and b/assets/DataLogger_IoT_Device_Name_Loaded.JPG differ diff --git a/assets/DataLogger_IoT_Insert_MicroSD_Card.jpg b/assets/DataLogger_IoT_Insert_MicroSD_Card.jpg new file mode 100644 index 0000000..de3937a Binary files /dev/null and b/assets/DataLogger_IoT_Insert_MicroSD_Card.jpg differ diff --git a/assets/DataLogger_IoT_LiPo_Battery.jpg b/assets/DataLogger_IoT_LiPo_Battery.jpg new file mode 100644 index 0000000..7d5f90b Binary files /dev/null and b/assets/DataLogger_IoT_LiPo_Battery.jpg differ diff --git a/assets/DataLogger_IoT_OTA_Update_1.PNG b/assets/DataLogger_IoT_OTA_Update_1.PNG new file mode 100644 index 0000000..dce8aa1 Binary files /dev/null and b/assets/DataLogger_IoT_OTA_Update_1.PNG differ diff --git a/assets/DataLogger_IoT_OTA_Update_2.PNG b/assets/DataLogger_IoT_OTA_Update_2.PNG new file mode 100644 index 0000000..c9b31f5 Binary files /dev/null and b/assets/DataLogger_IoT_OTA_Update_2.PNG differ diff --git a/assets/DataLogger_IoT_OTA_Update_3.PNG b/assets/DataLogger_IoT_OTA_Update_3.PNG new file mode 100644 index 0000000..25bc48c Binary files /dev/null and b/assets/DataLogger_IoT_OTA_Update_3.PNG differ diff --git a/assets/DataLogger_IoT_Qwiic_Daisy_Chained.jpg b/assets/DataLogger_IoT_Qwiic_Daisy_Chained.jpg new file mode 100644 index 0000000..186c8f6 Binary files /dev/null and b/assets/DataLogger_IoT_Qwiic_Daisy_Chained.jpg differ diff --git a/assets/DataLogger_IoT_Qwiic_RTC.jpg b/assets/DataLogger_IoT_Qwiic_RTC.jpg new file mode 100644 index 0000000..04f7647 Binary files /dev/null and b/assets/DataLogger_IoT_Qwiic_RTC.jpg differ diff --git a/assets/DataLogger_IoT_Qwiic_u-blox_GNSS_SAM-M10Q_Module.jpg b/assets/DataLogger_IoT_Qwiic_u-blox_GNSS_SAM-M10Q_Module.jpg new file mode 100644 index 0000000..abcc308 Binary files /dev/null and b/assets/DataLogger_IoT_Qwiic_u-blox_GNSS_SAM-M10Q_Module.jpg differ diff --git a/assets/DataLogger_IoT_Stacked_Standoffs.jpg b/assets/DataLogger_IoT_Stacked_Standoffs.jpg new file mode 100644 index 0000000..90e6b57 Binary files /dev/null and b/assets/DataLogger_IoT_Stacked_Standoffs.jpg differ diff --git a/assets/DataLogger_IoT_USB.jpg b/assets/DataLogger_IoT_USB.jpg new file mode 100644 index 0000000..6f61b98 Binary files /dev/null and b/assets/DataLogger_IoT_USB.jpg differ diff --git a/assets/Datalogger_IoT_Preferences_saved_fallback_file.JPG b/assets/Datalogger_IoT_Preferences_saved_fallback_file.JPG new file mode 100644 index 0000000..712f496 Binary files /dev/null and b/assets/Datalogger_IoT_Preferences_saved_fallback_file.JPG differ diff --git a/assets/Datalogger_IoT_text_file.JPG b/assets/Datalogger_IoT_text_file.JPG new file mode 100644 index 0000000..a4d9cc8 Binary files /dev/null and b/assets/Datalogger_IoT_text_file.JPG differ diff --git a/assets/Device_Name_ENS160.JPG b/assets/Device_Name_ENS160.JPG new file mode 100644 index 0000000..967208b Binary files /dev/null and b/assets/Device_Name_ENS160.JPG differ diff --git a/assets/Explorer_MicroSD_Card_Firmware_Files.PNG b/assets/Explorer_MicroSD_Card_Firmware_Files.PNG new file mode 100644 index 0000000..00105db Binary files /dev/null and b/assets/Explorer_MicroSD_Card_Firmware_Files.PNG differ diff --git a/assets/Google_Sheets_Logo.png b/assets/Google_Sheets_Logo.png new file mode 100644 index 0000000..67a9fb5 Binary files /dev/null and b/assets/Google_Sheets_Logo.png differ diff --git a/assets/Incorrect_Configuration_Connection_Failed_IoT_Service_AWS.JPG b/assets/Incorrect_Configuration_Connection_Failed_IoT_Service_AWS.JPG new file mode 100644 index 0000000..6c1d63a Binary files /dev/null and b/assets/Incorrect_Configuration_Connection_Failed_IoT_Service_AWS.JPG differ diff --git a/assets/Incorrect_Configuration_Connection_Failed_IoT_Service_ThingSpeak.JPG b/assets/Incorrect_Configuration_Connection_Failed_IoT_Service_ThingSpeak.JPG new file mode 100644 index 0000000..266511f Binary files /dev/null and b/assets/Incorrect_Configuration_Connection_Failed_IoT_Service_ThingSpeak.JPG differ diff --git a/assets/MQTT_Logo.png b/assets/MQTT_Logo.png new file mode 100644 index 0000000..ccbdb52 Binary files /dev/null and b/assets/MQTT_Logo.png differ diff --git a/assets/Microsoft_Azure_Logo.png b/assets/Microsoft_Azure_Logo.png new file mode 100644 index 0000000..525e6d3 Binary files /dev/null and b/assets/Microsoft_Azure_Logo.png differ diff --git a/assets/No_GNSS_Satellite_Lock.PNG b/assets/No_GNSS_Satellite_Lock.PNG new file mode 100644 index 0000000..a73ed0f Binary files /dev/null and b/assets/No_GNSS_Satellite_Lock.PNG differ diff --git a/assets/Qwiic-registered-updated.png b/assets/Qwiic-registered-updated.png new file mode 100644 index 0000000..7bbfc2a Binary files /dev/null and b/assets/Qwiic-registered-updated.png differ diff --git a/assets/Raspberry_Pi_Imager.JPG b/assets/Raspberry_Pi_Imager.JPG new file mode 100644 index 0000000..35643ba Binary files /dev/null and b/assets/Raspberry_Pi_Imager.JPG differ diff --git a/assets/Raspberry_Pi_Imager_Erase_Format.JPG b/assets/Raspberry_Pi_Imager_Erase_Format.JPG new file mode 100644 index 0000000..d0a845f Binary files /dev/null and b/assets/Raspberry_Pi_Imager_Erase_Format.JPG differ diff --git a/assets/Raspberry_Pi_Imager_Select_Drive.JPG b/assets/Raspberry_Pi_Imager_Select_Drive.JPG new file mode 100644 index 0000000..34a8954 Binary files /dev/null and b/assets/Raspberry_Pi_Imager_Select_Drive.JPG differ diff --git a/assets/Raspberry_Pi_Imager_Write.JPG b/assets/Raspberry_Pi_Imager_Write.JPG new file mode 100644 index 0000000..ce5fced Binary files /dev/null and b/assets/Raspberry_Pi_Imager_Write.JPG differ diff --git a/assets/SparkFun_DataLogger_IoT-ESP32_Start-Up_Menu.jpg b/assets/SparkFun_DataLogger_IoT-ESP32_Start-Up_Menu.jpg new file mode 100644 index 0000000..f89f497 Binary files /dev/null and b/assets/SparkFun_DataLogger_IoT-ESP32_Start-Up_Menu.jpg differ diff --git a/assets/SparkFun_DataLogger_IoT_Arduino_IoT_Cloud_Menu.JPG b/assets/SparkFun_DataLogger_IoT_Arduino_IoT_Cloud_Menu.JPG new file mode 100644 index 0000000..7708da0 Binary files /dev/null and b/assets/SparkFun_DataLogger_IoT_Arduino_IoT_Cloud_Menu.JPG differ diff --git a/assets/SparkFun_Datalogger_IoT_AWS_Menu.JPG b/assets/SparkFun_Datalogger_IoT_AWS_Menu.JPG new file mode 100644 index 0000000..4620dce Binary files /dev/null and b/assets/SparkFun_Datalogger_IoT_AWS_Menu.JPG differ diff --git a/assets/SparkFun_Datalogger_IoT_Applications_Settings_Menu.JPG b/assets/SparkFun_Datalogger_IoT_Applications_Settings_Menu.JPG new file mode 100644 index 0000000..62990f4 Binary files /dev/null and b/assets/SparkFun_Datalogger_IoT_Applications_Settings_Menu.JPG differ diff --git a/assets/SparkFun_Datalogger_IoT_Applications_Settings_Menu_2-v01p02p00.JPG b/assets/SparkFun_Datalogger_IoT_Applications_Settings_Menu_2-v01p02p00.JPG new file mode 100644 index 0000000..701a71c Binary files /dev/null and b/assets/SparkFun_Datalogger_IoT_Applications_Settings_Menu_2-v01p02p00.JPG differ diff --git a/assets/SparkFun_Datalogger_IoT_Applications_Settings_Menu_2.JPG b/assets/SparkFun_Datalogger_IoT_Applications_Settings_Menu_2.JPG new file mode 100644 index 0000000..31c52c0 Binary files /dev/null and b/assets/SparkFun_Datalogger_IoT_Applications_Settings_Menu_2.JPG differ diff --git a/assets/SparkFun_Datalogger_IoT_Azure_Menu.JPG b/assets/SparkFun_Datalogger_IoT_Azure_Menu.JPG new file mode 100644 index 0000000..1c9e41b Binary files /dev/null and b/assets/SparkFun_Datalogger_IoT_Azure_Menu.JPG differ diff --git a/assets/SparkFun_Datalogger_IoT_Device_Settings_Menu.JPG b/assets/SparkFun_Datalogger_IoT_Device_Settings_Menu.JPG new file mode 100644 index 0000000..44df1fb Binary files /dev/null and b/assets/SparkFun_Datalogger_IoT_Device_Settings_Menu.JPG differ diff --git a/assets/SparkFun_Datalogger_IoT_Device_Settings_Menu_Additional_Devices.JPG b/assets/SparkFun_Datalogger_IoT_Device_Settings_Menu_Additional_Devices.JPG new file mode 100644 index 0000000..1184f2a Binary files /dev/null and b/assets/SparkFun_Datalogger_IoT_Device_Settings_Menu_Additional_Devices.JPG differ diff --git a/assets/SparkFun_Datalogger_IoT_File_Rotation_Menu.JPG b/assets/SparkFun_Datalogger_IoT_File_Rotation_Menu.JPG new file mode 100644 index 0000000..31b27d3 Binary files /dev/null and b/assets/SparkFun_Datalogger_IoT_File_Rotation_Menu.JPG differ diff --git a/assets/SparkFun_Datalogger_IoT_HTTP_Menu.JPG b/assets/SparkFun_Datalogger_IoT_HTTP_Menu.JPG new file mode 100644 index 0000000..55cc888 Binary files /dev/null and b/assets/SparkFun_Datalogger_IoT_HTTP_Menu.JPG differ diff --git a/assets/SparkFun_Datalogger_IoT_Initializing.JPG b/assets/SparkFun_Datalogger_IoT_Initializing.JPG new file mode 100644 index 0000000..15a866c Binary files /dev/null and b/assets/SparkFun_Datalogger_IoT_Initializing.JPG differ diff --git a/assets/SparkFun_Datalogger_IoT_Initializing_Compactv1p02p00.JPG b/assets/SparkFun_Datalogger_IoT_Initializing_Compactv1p02p00.JPG new file mode 100644 index 0000000..16fb828 Binary files /dev/null and b/assets/SparkFun_Datalogger_IoT_Initializing_Compactv1p02p00.JPG differ diff --git a/assets/SparkFun_Datalogger_IoT_Initializing_WiFI_Connected_IoT_Web_Server_Enabled_v01p02p00.JPG b/assets/SparkFun_Datalogger_IoT_Initializing_WiFI_Connected_IoT_Web_Server_Enabled_v01p02p00.JPG new file mode 100644 index 0000000..c06d39f Binary files /dev/null and b/assets/SparkFun_Datalogger_IoT_Initializing_WiFI_Connected_IoT_Web_Server_Enabled_v01p02p00.JPG differ diff --git a/assets/SparkFun_Datalogger_IoT_Initializing_WiFi_Connected.JPG b/assets/SparkFun_Datalogger_IoT_Initializing_WiFi_Connected.JPG new file mode 100644 index 0000000..3fae5a3 Binary files /dev/null and b/assets/SparkFun_Datalogger_IoT_Initializing_WiFi_Connected.JPG differ diff --git a/assets/SparkFun_Datalogger_IoT_Initializing_v1p02p00.JPG b/assets/SparkFun_Datalogger_IoT_Initializing_v1p02p00.JPG new file mode 100644 index 0000000..b0719e1 Binary files /dev/null and b/assets/SparkFun_Datalogger_IoT_Initializing_v1p02p00.JPG differ diff --git a/assets/SparkFun_Datalogger_IoT_Logger_Menu.JPG b/assets/SparkFun_Datalogger_IoT_Logger_Menu.JPG new file mode 100644 index 0000000..2a4b567 Binary files /dev/null and b/assets/SparkFun_Datalogger_IoT_Logger_Menu.JPG differ diff --git a/assets/SparkFun_Datalogger_IoT_Logger_Menu_v01p02p00.JPG b/assets/SparkFun_Datalogger_IoT_Logger_Menu_v01p02p00.JPG new file mode 100644 index 0000000..ad25e35 Binary files /dev/null and b/assets/SparkFun_Datalogger_IoT_Logger_Menu_v01p02p00.JPG differ diff --git a/assets/SparkFun_Datalogger_IoT_Logger_Timer_Menu.JPG b/assets/SparkFun_Datalogger_IoT_Logger_Timer_Menu.JPG new file mode 100644 index 0000000..cc81bcb Binary files /dev/null and b/assets/SparkFun_Datalogger_IoT_Logger_Timer_Menu.JPG differ diff --git a/assets/SparkFun_Datalogger_IoT_MQTT_Client_Menu.JPG b/assets/SparkFun_Datalogger_IoT_MQTT_Client_Menu.JPG new file mode 100644 index 0000000..6134cea Binary files /dev/null and b/assets/SparkFun_Datalogger_IoT_MQTT_Client_Menu.JPG differ diff --git a/assets/SparkFun_Datalogger_IoT_MQTT_Secure_Client_Menu.JPG b/assets/SparkFun_Datalogger_IoT_MQTT_Secure_Client_Menu.JPG new file mode 100644 index 0000000..d46eebc Binary files /dev/null and b/assets/SparkFun_Datalogger_IoT_MQTT_Secure_Client_Menu.JPG differ diff --git a/assets/SparkFun_Datalogger_IoT_MachineChat_Menu.JPG b/assets/SparkFun_Datalogger_IoT_MachineChat_Menu.JPG new file mode 100644 index 0000000..2b1b011 Binary files /dev/null and b/assets/SparkFun_Datalogger_IoT_MachineChat_Menu.JPG differ diff --git a/assets/SparkFun_Datalogger_IoT_Main_Menu.JPG b/assets/SparkFun_Datalogger_IoT_Main_Menu.JPG new file mode 100644 index 0000000..673c354 Binary files /dev/null and b/assets/SparkFun_Datalogger_IoT_Main_Menu.JPG differ diff --git a/assets/SparkFun_Datalogger_IoT_NTP_Client_Menu.JPG b/assets/SparkFun_Datalogger_IoT_NTP_Client_Menu.JPG new file mode 100644 index 0000000..81f171b Binary files /dev/null and b/assets/SparkFun_Datalogger_IoT_NTP_Client_Menu.JPG differ diff --git a/assets/SparkFun_Datalogger_IoT_Output.JPG b/assets/SparkFun_Datalogger_IoT_Output.JPG new file mode 100644 index 0000000..e18099e Binary files /dev/null and b/assets/SparkFun_Datalogger_IoT_Output.JPG differ diff --git a/assets/SparkFun_Datalogger_IoT_Output_JSON.JPG b/assets/SparkFun_Datalogger_IoT_Output_JSON.JPG new file mode 100644 index 0000000..9770413 Binary files /dev/null and b/assets/SparkFun_Datalogger_IoT_Output_JSON.JPG differ diff --git a/assets/SparkFun_Datalogger_IoT_Output_JSON_v01p02p00.JPG b/assets/SparkFun_Datalogger_IoT_Output_JSON_v01p02p00.JPG new file mode 100644 index 0000000..6636860 Binary files /dev/null and b/assets/SparkFun_Datalogger_IoT_Output_JSON_v01p02p00.JPG differ diff --git a/assets/SparkFun_Datalogger_IoT_Output_Quick_Command.JPG b/assets/SparkFun_Datalogger_IoT_Output_Quick_Command.JPG new file mode 100644 index 0000000..3bdcb2e Binary files /dev/null and b/assets/SparkFun_Datalogger_IoT_Output_Quick_Command.JPG differ diff --git a/assets/SparkFun_Datalogger_IoT_Output_Timestamp.JPG b/assets/SparkFun_Datalogger_IoT_Output_Timestamp.JPG new file mode 100644 index 0000000..f33cc3d Binary files /dev/null and b/assets/SparkFun_Datalogger_IoT_Output_Timestamp.JPG differ diff --git a/assets/SparkFun_Datalogger_IoT_Output_v1p02p00.JPG b/assets/SparkFun_Datalogger_IoT_Output_v1p02p00.JPG new file mode 100644 index 0000000..5d57727 Binary files /dev/null and b/assets/SparkFun_Datalogger_IoT_Output_v1p02p00.JPG differ diff --git a/assets/SparkFun_Datalogger_IoT_Save_Settings_Menu.JPG b/assets/SparkFun_Datalogger_IoT_Save_Settings_Menu.JPG new file mode 100644 index 0000000..cd0d3a5 Binary files /dev/null and b/assets/SparkFun_Datalogger_IoT_Save_Settings_Menu.JPG differ diff --git a/assets/SparkFun_Datalogger_IoT_Settings_Menu.JPG b/assets/SparkFun_Datalogger_IoT_Settings_Menu.JPG new file mode 100644 index 0000000..57dfa6d Binary files /dev/null and b/assets/SparkFun_Datalogger_IoT_Settings_Menu.JPG differ diff --git a/assets/SparkFun_Datalogger_IoT_Settings_Menu_v01p02p00.JPG b/assets/SparkFun_Datalogger_IoT_Settings_Menu_v01p02p00.JPG new file mode 100644 index 0000000..fb72da2 Binary files /dev/null and b/assets/SparkFun_Datalogger_IoT_Settings_Menu_v01p02p00.JPG differ diff --git a/assets/SparkFun_Datalogger_IoT_System_Escape_Menu.JPG b/assets/SparkFun_Datalogger_IoT_System_Escape_Menu.JPG new file mode 100644 index 0000000..585512c Binary files /dev/null and b/assets/SparkFun_Datalogger_IoT_System_Escape_Menu.JPG differ diff --git a/assets/SparkFun_Datalogger_IoT_System_No_User_Input_Exit_Menu.JPG b/assets/SparkFun_Datalogger_IoT_System_No_User_Input_Exit_Menu.JPG new file mode 100644 index 0000000..59c8911 Binary files /dev/null and b/assets/SparkFun_Datalogger_IoT_System_No_User_Input_Exit_Menu.JPG differ diff --git a/assets/SparkFun_Datalogger_IoT_System_Restart_Factory_Restore_Update_Firmware.JPG b/assets/SparkFun_Datalogger_IoT_System_Restart_Factory_Restore_Update_Firmware.JPG new file mode 100644 index 0000000..80005d7 Binary files /dev/null and b/assets/SparkFun_Datalogger_IoT_System_Restart_Factory_Restore_Update_Firmware.JPG differ diff --git a/assets/SparkFun_Datalogger_IoT_System_Restart_Factory_Restore_Update_Firmware_v01p02p00.JPG b/assets/SparkFun_Datalogger_IoT_System_Restart_Factory_Restore_Update_Firmware_v01p02p00.JPG new file mode 100644 index 0000000..a884d85 Binary files /dev/null and b/assets/SparkFun_Datalogger_IoT_System_Restart_Factory_Restore_Update_Firmware_v01p02p00.JPG differ diff --git a/assets/SparkFun_Datalogger_IoT_System_Save_Settings_Menu.JPG b/assets/SparkFun_Datalogger_IoT_System_Save_Settings_Menu.JPG new file mode 100644 index 0000000..eba21dd Binary files /dev/null and b/assets/SparkFun_Datalogger_IoT_System_Save_Settings_Menu.JPG differ diff --git a/assets/SparkFun_Datalogger_IoT_ThingSpeak_MQTT_Menu.JPG b/assets/SparkFun_Datalogger_IoT_ThingSpeak_MQTT_Menu.JPG new file mode 100644 index 0000000..87be524 Binary files /dev/null and b/assets/SparkFun_Datalogger_IoT_ThingSpeak_MQTT_Menu.JPG differ diff --git a/assets/SparkFun_Datalogger_IoT_Time_Sources_Menu.JPG b/assets/SparkFun_Datalogger_IoT_Time_Sources_Menu.JPG new file mode 100644 index 0000000..4ee5f86 Binary files /dev/null and b/assets/SparkFun_Datalogger_IoT_Time_Sources_Menu.JPG differ diff --git a/assets/SparkFun_Datalogger_IoT_Web_Server_Menu.JPG b/assets/SparkFun_Datalogger_IoT_Web_Server_Menu.JPG new file mode 100644 index 0000000..3712ab2 Binary files /dev/null and b/assets/SparkFun_Datalogger_IoT_Web_Server_Menu.JPG differ diff --git a/assets/SparkFun_Datalogger_IoT_WiFi_Network_Menu.JPG b/assets/SparkFun_Datalogger_IoT_WiFi_Network_Menu.JPG new file mode 100644 index 0000000..d87d3e0 Binary files /dev/null and b/assets/SparkFun_Datalogger_IoT_WiFi_Network_Menu.JPG differ diff --git a/assets/SparkFun_Datalogger_IoT_WiFi_Network_Menu_2.JPG b/assets/SparkFun_Datalogger_IoT_WiFi_Network_Menu_2.JPG new file mode 100644 index 0000000..4abb27a Binary files /dev/null and b/assets/SparkFun_Datalogger_IoT_WiFi_Network_Menu_2.JPG differ diff --git a/assets/ThingSpeak_Channels_Separate_Windows_BME680_BME688.JPG b/assets/ThingSpeak_Channels_Separate_Windows_BME680_BME688.JPG new file mode 100644 index 0000000..3c487b1 Binary files /dev/null and b/assets/ThingSpeak_Channels_Separate_Windows_BME680_BME688.JPG differ diff --git a/assets/ThingSpeak_Device_Name_Address.JPG b/assets/ThingSpeak_Device_Name_Address.JPG new file mode 100644 index 0000000..8fe949b Binary files /dev/null and b/assets/ThingSpeak_Device_Name_Address.JPG differ diff --git a/assets/ThingSpeak_ENS160_No_Data.JPG b/assets/ThingSpeak_ENS160_No_Data.JPG new file mode 100644 index 0000000..56141c7 Binary files /dev/null and b/assets/ThingSpeak_ENS160_No_Data.JPG differ diff --git a/assets/ThingSpeak_ENS160_with_Data.JPG b/assets/ThingSpeak_ENS160_with_Data.JPG new file mode 100644 index 0000000..9850774 Binary files /dev/null and b/assets/ThingSpeak_ENS160_with_Data.JPG differ diff --git a/assets/ThingSpeak_New_Channel_BME680_0x77.JPG b/assets/ThingSpeak_New_Channel_BME680_0x77.JPG new file mode 100644 index 0000000..c252582 Binary files /dev/null and b/assets/ThingSpeak_New_Channel_BME680_0x77.JPG differ diff --git a/assets/ThingSpeak_New_Channel_BME688_0x76.JPG b/assets/ThingSpeak_New_Channel_BME688_0x76.JPG new file mode 100644 index 0000000..d930335 Binary files /dev/null and b/assets/ThingSpeak_New_Channel_BME688_0x76.JPG differ diff --git a/assets/ThingSpeak_Same_Device_Name_Unique_Address.JPG b/assets/ThingSpeak_Same_Device_Name_Unique_Address.JPG new file mode 100644 index 0000000..8c7ec47 Binary files /dev/null and b/assets/ThingSpeak_Same_Device_Name_Unique_Address.JPG differ diff --git a/assets/ThingSpeak_Setting_Up_MQTT_Authorize_2x_Channels.JPG b/assets/ThingSpeak_Setting_Up_MQTT_Authorize_2x_Channels.JPG new file mode 100644 index 0000000..5dbfe5a Binary files /dev/null and b/assets/ThingSpeak_Setting_Up_MQTT_Authorize_2x_Channels.JPG differ diff --git a/assets/Timestamped_CSV.PNG b/assets/Timestamped_CSV.PNG new file mode 100644 index 0000000..4b56e67 Binary files /dev/null and b/assets/Timestamped_CSV.PNG differ diff --git a/assets/WiFi_Connected_NTP_Synced.PNG b/assets/WiFi_Connected_NTP_Synced.PNG new file mode 100644 index 0000000..e2099a0 Binary files /dev/null and b/assets/WiFi_Connected_NTP_Synced.PNG differ diff --git a/assets/act_sysfirm_entry.png b/assets/act_sysfirm_entry.png new file mode 100644 index 0000000..43eb200 Binary files /dev/null and b/assets/act_sysfirm_entry.png differ diff --git a/assets/act_sysfirm_menu.png b/assets/act_sysfirm_menu.png new file mode 100644 index 0000000..37ac275 Binary files /dev/null and b/assets/act_sysfirm_menu.png differ diff --git a/assets/act_sysfirm_reset.png b/assets/act_sysfirm_reset.png new file mode 100644 index 0000000..39b8085 Binary files /dev/null and b/assets/act_sysfirm_reset.png differ diff --git a/assets/act_sysfirm_reset_y.png b/assets/act_sysfirm_reset_y.png new file mode 100644 index 0000000..33b2633 Binary files /dev/null and b/assets/act_sysfirm_reset_y.png differ diff --git a/assets/act_sysfirm_up-boot.png b/assets/act_sysfirm_up-boot.png new file mode 100644 index 0000000..3d3d21c Binary files /dev/null and b/assets/act_sysfirm_up-boot.png differ diff --git a/assets/act_sysfirm_up_sel.png b/assets/act_sysfirm_up_sel.png new file mode 100644 index 0000000..a3b9eb2 Binary files /dev/null and b/assets/act_sysfirm_up_sel.png differ diff --git a/assets/act_sysfirm_updating.png b/assets/act_sysfirm_updating.png new file mode 100644 index 0000000..c50b0c5 Binary files /dev/null and b/assets/act_sysfirm_updating.png differ diff --git a/assets/aiot_cloud_api-k.jpg b/assets/aiot_cloud_api-k.jpg new file mode 100644 index 0000000..32362ca Binary files /dev/null and b/assets/aiot_cloud_api-k.jpg differ diff --git a/assets/aiot_cloud_create_key.jpg b/assets/aiot_cloud_create_key.jpg new file mode 100644 index 0000000..a6f374e Binary files /dev/null and b/assets/aiot_cloud_create_key.jpg differ diff --git a/assets/aiot_cloud_key_name.jpg b/assets/aiot_cloud_key_name.jpg new file mode 100644 index 0000000..b7202c9 Binary files /dev/null and b/assets/aiot_cloud_key_name.jpg differ diff --git a/assets/aiot_cloud_key_secret.jpg b/assets/aiot_cloud_key_secret.jpg new file mode 100644 index 0000000..343e346 Binary files /dev/null and b/assets/aiot_cloud_key_secret.jpg differ diff --git a/assets/aiot_cloud_sel_devices.jpg b/assets/aiot_cloud_sel_devices.jpg new file mode 100644 index 0000000..fccb33a Binary files /dev/null and b/assets/aiot_cloud_sel_devices.jpg differ diff --git a/assets/aiot_cloud_vars.jpg b/assets/aiot_cloud_vars.jpg new file mode 100644 index 0000000..c6caa46 Binary files /dev/null and b/assets/aiot_cloud_vars.jpg differ diff --git a/assets/aiot_dashboard.jpg b/assets/aiot_dashboard.jpg new file mode 100644 index 0000000..a9ff637 Binary files /dev/null and b/assets/aiot_dashboard.jpg differ diff --git a/assets/aiot_dashboard_link.jpg b/assets/aiot_dashboard_link.jpg new file mode 100644 index 0000000..ff7d9a0 Binary files /dev/null and b/assets/aiot_dashboard_link.jpg differ diff --git a/assets/aiot_dashboard_qwiic_env.jpg b/assets/aiot_dashboard_qwiic_env.jpg new file mode 100644 index 0000000..94ac272 Binary files /dev/null and b/assets/aiot_dashboard_qwiic_env.jpg differ diff --git a/assets/aiot_dev_add.jpg b/assets/aiot_dev_add.jpg new file mode 100644 index 0000000..3ca04d9 Binary files /dev/null and b/assets/aiot_dev_add.jpg differ diff --git a/assets/aiot_dev_name.jpg b/assets/aiot_dev_name.jpg new file mode 100644 index 0000000..093933b Binary files /dev/null and b/assets/aiot_dev_name.jpg differ diff --git a/assets/aiot_dev_secrets.jpg b/assets/aiot_dev_secrets.jpg new file mode 100644 index 0000000..a76ac8a Binary files /dev/null and b/assets/aiot_dev_secrets.jpg differ diff --git a/assets/aiot_dev_setup_sel.jpg b/assets/aiot_dev_setup_sel.jpg new file mode 100644 index 0000000..445ca9a Binary files /dev/null and b/assets/aiot_dev_setup_sel.jpg differ diff --git a/assets/aiot_overview.png b/assets/aiot_overview.png new file mode 100644 index 0000000..98e8be1 Binary files /dev/null and b/assets/aiot_overview.png differ diff --git a/assets/board_files/DataLogger_IoT/SparkFun_DataLogger_IoT-V11.zip b/assets/board_files/DataLogger_IoT/SparkFun_DataLogger_IoT-V11.zip new file mode 100644 index 0000000..a40259a Binary files /dev/null and b/assets/board_files/DataLogger_IoT/SparkFun_DataLogger_IoT-V11.zip differ diff --git a/assets/board_files/DataLogger_IoT/SparkFun_DataLogger_IoT_Board_Dimensions.png b/assets/board_files/DataLogger_IoT/SparkFun_DataLogger_IoT_Board_Dimensions.png new file mode 100644 index 0000000..3ad5838 Binary files /dev/null and b/assets/board_files/DataLogger_IoT/SparkFun_DataLogger_IoT_Board_Dimensions.png differ diff --git a/assets/board_files/DataLogger_IoT/SparkFun_DataLogger_IoT_Schematic_V11.pdf b/assets/board_files/DataLogger_IoT/SparkFun_DataLogger_IoT_Schematic_V11.pdf new file mode 100644 index 0000000..cdf33b6 Binary files /dev/null and b/assets/board_files/DataLogger_IoT/SparkFun_DataLogger_IoT_Schematic_V11.pdf differ diff --git a/assets/board_files/DataLogger_IoT_9DoF/SparkFun_DataLogger_IoT_9DOF_v11.zip b/assets/board_files/DataLogger_IoT_9DoF/SparkFun_DataLogger_IoT_9DOF_v11.zip new file mode 100644 index 0000000..69b57aa Binary files /dev/null and b/assets/board_files/DataLogger_IoT_9DoF/SparkFun_DataLogger_IoT_9DOF_v11.zip differ diff --git a/assets/board_files/DataLogger_IoT_9DoF/SparkFun_Datalogger_IoT_9DOF_Board_Dimensions.png b/assets/board_files/DataLogger_IoT_9DoF/SparkFun_Datalogger_IoT_9DOF_Board_Dimensions.png new file mode 100644 index 0000000..40234f8 Binary files /dev/null and b/assets/board_files/DataLogger_IoT_9DoF/SparkFun_Datalogger_IoT_9DOF_Board_Dimensions.png differ diff --git a/assets/board_files/DataLogger_IoT_9DoF/SparkFun_Datalogger_IoT_9DOF_Schematic_v11.pdf b/assets/board_files/DataLogger_IoT_9DoF/SparkFun_Datalogger_IoT_9DOF_Schematic_v11.pdf new file mode 100644 index 0000000..8d93873 Binary files /dev/null and b/assets/board_files/DataLogger_IoT_9DoF/SparkFun_Datalogger_IoT_9DOF_Schematic_v11.pdf differ diff --git a/assets/csv_to_spreadsheet_datalogger_iot_environment_dataset.png b/assets/csv_to_spreadsheet_datalogger_iot_environment_dataset.png new file mode 100644 index 0000000..fdcfed0 Binary files /dev/null and b/assets/csv_to_spreadsheet_datalogger_iot_environment_dataset.png differ diff --git a/assets/csv_to_spreadsheet_datalogger_iot_environment_dataset_12-hour_format.png b/assets/csv_to_spreadsheet_datalogger_iot_environment_dataset_12-hour_format.png new file mode 100644 index 0000000..04aecaf Binary files /dev/null and b/assets/csv_to_spreadsheet_datalogger_iot_environment_dataset_12-hour_format.png differ diff --git a/assets/csv_to_spreadsheet_imported.JPG b/assets/csv_to_spreadsheet_imported.JPG new file mode 100644 index 0000000..36b3f39 Binary files /dev/null and b/assets/csv_to_spreadsheet_imported.JPG differ diff --git a/assets/csv_to_spreadsheet_manual_paste.JPG b/assets/csv_to_spreadsheet_manual_paste.JPG new file mode 100644 index 0000000..d71e376 Binary files /dev/null and b/assets/csv_to_spreadsheet_manual_paste.JPG differ diff --git a/assets/csv_to_spreadsheet_select_columns_to_graph.JPG b/assets/csv_to_spreadsheet_select_columns_to_graph.JPG new file mode 100644 index 0000000..273878b Binary files /dev/null and b/assets/csv_to_spreadsheet_select_columns_to_graph.JPG differ diff --git a/assets/csv_to_spreadsheet_select_file.JPG b/assets/csv_to_spreadsheet_select_file.JPG new file mode 100644 index 0000000..62ed1c4 Binary files /dev/null and b/assets/csv_to_spreadsheet_select_file.JPG differ diff --git a/assets/csv_to_spreadsheet_select_unformatted_chart.JPG b/assets/csv_to_spreadsheet_select_unformatted_chart.JPG new file mode 100644 index 0000000..7e8294b Binary files /dev/null and b/assets/csv_to_spreadsheet_select_unformatted_chart.JPG differ diff --git a/assets/csv_to_spreadsheet_separator_type.JPG b/assets/csv_to_spreadsheet_separator_type.JPG new file mode 100644 index 0000000..b35806e Binary files /dev/null and b/assets/csv_to_spreadsheet_separator_type.JPG differ diff --git a/assets/csv_to_spreadsheet_split_text_to_columns.JPG b/assets/csv_to_spreadsheet_split_text_to_columns.JPG new file mode 100644 index 0000000..1dc4467 Binary files /dev/null and b/assets/csv_to_spreadsheet_split_text_to_columns.JPG differ diff --git a/assets/csv_to_spreadsheet_title_header_row.JPG b/assets/csv_to_spreadsheet_title_header_row.JPG new file mode 100644 index 0000000..f3f6fce Binary files /dev/null and b/assets/csv_to_spreadsheet_title_header_row.JPG differ diff --git a/assets/csv_to_spreadsheet_values_separated.JPG b/assets/csv_to_spreadsheet_values_separated.JPG new file mode 100644 index 0000000..2842346 Binary files /dev/null and b/assets/csv_to_spreadsheet_values_separated.JPG differ diff --git a/assets/images/favicon.png b/assets/images/favicon.png new file mode 100644 index 0000000..1cf13b9 Binary files /dev/null and b/assets/images/favicon.png differ diff --git a/assets/iot_aws_iot_attach_policy.png b/assets/iot_aws_iot_attach_policy.png new file mode 100644 index 0000000..cbd7231 Binary files /dev/null and b/assets/iot_aws_iot_attach_policy.png differ diff --git a/assets/iot_aws_iot_core.png b/assets/iot_aws_iot_core.png new file mode 100644 index 0000000..3c82ea5 Binary files /dev/null and b/assets/iot_aws_iot_core.png differ diff --git a/assets/iot_aws_iot_dev_attr.png b/assets/iot_aws_iot_dev_attr.png new file mode 100644 index 0000000..1df56ac Binary files /dev/null and b/assets/iot_aws_iot_dev_attr.png differ diff --git a/assets/iot_aws_iot_shadow_data.png b/assets/iot_aws_iot_shadow_data.png new file mode 100644 index 0000000..6fade80 Binary files /dev/null and b/assets/iot_aws_iot_shadow_data.png differ diff --git a/assets/iot_aws_iot_shadow_details.png b/assets/iot_aws_iot_shadow_details.png new file mode 100644 index 0000000..11fbbdb Binary files /dev/null and b/assets/iot_aws_iot_shadow_details.png differ diff --git a/assets/iot_aws_iot_shadow_updates.png b/assets/iot_aws_iot_shadow_updates.png new file mode 100644 index 0000000..136c2a0 Binary files /dev/null and b/assets/iot_aws_iot_shadow_updates.png differ diff --git a/assets/iot_aws_overview.png b/assets/iot_aws_overview.png new file mode 100644 index 0000000..c09956d Binary files /dev/null and b/assets/iot_aws_overview.png differ diff --git a/assets/iot_aws_thing_create.png b/assets/iot_aws_thing_create.png new file mode 100644 index 0000000..4be3c97 Binary files /dev/null and b/assets/iot_aws_thing_create.png differ diff --git a/assets/iot_aws_thing_create_policy.png b/assets/iot_aws_thing_create_policy.png new file mode 100644 index 0000000..6e0208c Binary files /dev/null and b/assets/iot_aws_thing_create_policy.png differ diff --git a/assets/iot_aws_thing_list.png b/assets/iot_aws_thing_list.png new file mode 100644 index 0000000..95f6b3c Binary files /dev/null and b/assets/iot_aws_thing_list.png differ diff --git a/assets/iot_aws_thing_policy.png b/assets/iot_aws_thing_policy.png new file mode 100644 index 0000000..e62f9b3 Binary files /dev/null and b/assets/iot_aws_thing_policy.png differ diff --git a/assets/iot_az_ca_file.png b/assets/iot_az_ca_file.png new file mode 100644 index 0000000..b8a34a5 Binary files /dev/null and b/assets/iot_az_ca_file.png differ diff --git a/assets/iot_az_create_device.png b/assets/iot_az_create_device.png new file mode 100644 index 0000000..de52cb4 Binary files /dev/null and b/assets/iot_az_create_device.png differ diff --git a/assets/iot_az_device_details.png b/assets/iot_az_device_details.png new file mode 100644 index 0000000..5aa9902 Binary files /dev/null and b/assets/iot_az_device_details.png differ diff --git a/assets/iot_az_device_form.png b/assets/iot_az_device_form.png new file mode 100644 index 0000000..19ccc72 Binary files /dev/null and b/assets/iot_az_device_form.png differ diff --git a/assets/iot_az_iot_hub.png b/assets/iot_az_iot_hub.png new file mode 100644 index 0000000..c84486e Binary files /dev/null and b/assets/iot_az_iot_hub.png differ diff --git a/assets/iot_az_iot_hub_details.png b/assets/iot_az_iot_hub_details.png new file mode 100644 index 0000000..a1e770f Binary files /dev/null and b/assets/iot_az_iot_hub_details.png differ diff --git a/assets/iot_az_iot_hub_exp.png b/assets/iot_az_iot_hub_exp.png new file mode 100644 index 0000000..61937fc Binary files /dev/null and b/assets/iot_az_iot_hub_exp.png differ diff --git a/assets/iot_az_iot_hub_ext.png b/assets/iot_az_iot_hub_ext.png new file mode 100644 index 0000000..b671137 Binary files /dev/null and b/assets/iot_az_iot_hub_ext.png differ diff --git a/assets/iot_az_iot_hub_sel.png b/assets/iot_az_iot_hub_sel.png new file mode 100644 index 0000000..d4996f0 Binary files /dev/null and b/assets/iot_az_iot_hub_sel.png differ diff --git a/assets/iot_az_iot_hub_sel_menu.png b/assets/iot_az_iot_hub_sel_menu.png new file mode 100644 index 0000000..99a1c0a Binary files /dev/null and b/assets/iot_az_iot_hub_sel_menu.png differ diff --git a/assets/iot_az_iot_mon_output.png b/assets/iot_az_iot_mon_output.png new file mode 100644 index 0000000..9511de5 Binary files /dev/null and b/assets/iot_az_iot_mon_output.png differ diff --git a/assets/iot_az_iot_overview.png b/assets/iot_az_iot_overview.png new file mode 100644 index 0000000..2c152e8 Binary files /dev/null and b/assets/iot_az_iot_overview.png differ diff --git a/assets/iot_az_iot_start_mon.png b/assets/iot_az_iot_start_mon.png new file mode 100644 index 0000000..160b827 Binary files /dev/null and b/assets/iot_az_iot_start_mon.png differ diff --git a/assets/iot_az_iot_stop_mon.png b/assets/iot_az_iot_stop_mon.png new file mode 100644 index 0000000..f5ac5b6 Binary files /dev/null and b/assets/iot_az_iot_stop_mon.png differ diff --git a/assets/iot_http_cert.png b/assets/iot_http_cert.png new file mode 100644 index 0000000..717d670 Binary files /dev/null and b/assets/iot_http_cert.png differ diff --git a/assets/iot_http_cert_1.png b/assets/iot_http_cert_1.png new file mode 100644 index 0000000..d80a95a Binary files /dev/null and b/assets/iot_http_cert_1.png differ diff --git a/assets/iot_http_cert_2.png b/assets/iot_http_cert_2.png new file mode 100644 index 0000000..ecc8935 Binary files /dev/null and b/assets/iot_http_cert_2.png differ diff --git a/assets/iot_http_cert_3.png b/assets/iot_http_cert_3.png new file mode 100644 index 0000000..f5f18b8 Binary files /dev/null and b/assets/iot_http_cert_3.png differ diff --git a/assets/iot_http_ex_http.png b/assets/iot_http_ex_http.png new file mode 100644 index 0000000..4d18b00 Binary files /dev/null and b/assets/iot_http_ex_http.png differ diff --git a/assets/iot_http_overview.png b/assets/iot_http_overview.png new file mode 100644 index 0000000..a7be51d Binary files /dev/null and b/assets/iot_http_overview.png differ diff --git a/assets/iot_http_url.PNG b/assets/iot_http_url.PNG new file mode 100644 index 0000000..53b4cf9 Binary files /dev/null and b/assets/iot_http_url.PNG differ diff --git a/assets/iot_mqtt_overview.png b/assets/iot_mqtt_overview.png new file mode 100644 index 0000000..1ed266b Binary files /dev/null and b/assets/iot_mqtt_overview.png differ diff --git a/assets/iot_ts_channel.png b/assets/iot_ts_channel.png new file mode 100644 index 0000000..553a325 Binary files /dev/null and b/assets/iot_ts_channel.png differ diff --git a/assets/iot_ts_channel_data.png b/assets/iot_ts_channel_data.png new file mode 100644 index 0000000..a2ff336 Binary files /dev/null and b/assets/iot_ts_channel_data.png differ diff --git a/assets/iot_ts_mapping.png b/assets/iot_ts_mapping.png new file mode 100644 index 0000000..b930dab Binary files /dev/null and b/assets/iot_ts_mapping.png differ diff --git a/assets/iot_ts_mqtt.png b/assets/iot_ts_mqtt.png new file mode 100644 index 0000000..e957a41 Binary files /dev/null and b/assets/iot_ts_mqtt.png differ diff --git a/assets/iot_ts_mqtt_channel_auth.png b/assets/iot_ts_mqtt_channel_auth.png new file mode 100644 index 0000000..e919b03 Binary files /dev/null and b/assets/iot_ts_mqtt_channel_auth.png differ diff --git a/assets/iot_ts_new_channel.png b/assets/iot_ts_new_channel.png new file mode 100644 index 0000000..6f2a7e7 Binary files /dev/null and b/assets/iot_ts_new_channel.png differ diff --git a/assets/iot_web_server-Chrome_Browser-Available_Log_Files.JPG b/assets/iot_web_server-Chrome_Browser-Available_Log_Files.JPG new file mode 100644 index 0000000..8a384ec Binary files /dev/null and b/assets/iot_web_server-Chrome_Browser-Available_Log_Files.JPG differ diff --git a/assets/iot_web_server-Chrome_Browser-Available_Log_Files_IP_Address.JPG b/assets/iot_web_server-Chrome_Browser-Available_Log_Files_IP_Address.JPG new file mode 100644 index 0000000..20bccb2 Binary files /dev/null and b/assets/iot_web_server-Chrome_Browser-Available_Log_Files_IP_Address.JPG differ diff --git a/assets/javascripts/bundle.203fd0bc.min.js b/assets/javascripts/bundle.203fd0bc.min.js new file mode 100644 index 0000000..6ff9630 --- /dev/null +++ b/assets/javascripts/bundle.203fd0bc.min.js @@ -0,0 +1,3 @@ +"use strict";(()=>{var Zi=Object.create;var _r=Object.defineProperty;var ea=Object.getOwnPropertyDescriptor;var ta=Object.getOwnPropertyNames,Gt=Object.getOwnPropertySymbols,ra=Object.getPrototypeOf,Ar=Object.prototype.hasOwnProperty,bo=Object.prototype.propertyIsEnumerable;var ho=(e,t,r)=>t in e?_r(e,t,{enumerable:!0,configurable:!0,writable:!0,value:r}):e[t]=r,R=(e,t)=>{for(var r in t||(t={}))Ar.call(t,r)&&ho(e,r,t[r]);if(Gt)for(var r of Gt(t))bo.call(t,r)&&ho(e,r,t[r]);return e};var vo=(e,t)=>{var r={};for(var o in e)Ar.call(e,o)&&t.indexOf(o)<0&&(r[o]=e[o]);if(e!=null&&Gt)for(var o of Gt(e))t.indexOf(o)<0&&bo.call(e,o)&&(r[o]=e[o]);return r};var Cr=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports);var oa=(e,t,r,o)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of ta(t))!Ar.call(e,n)&&n!==r&&_r(e,n,{get:()=>t[n],enumerable:!(o=ea(t,n))||o.enumerable});return e};var Rt=(e,t,r)=>(r=e!=null?Zi(ra(e)):{},oa(t||!e||!e.__esModule?_r(r,"default",{value:e,enumerable:!0}):r,e));var go=(e,t,r)=>new Promise((o,n)=>{var i=c=>{try{a(r.next(c))}catch(p){n(p)}},s=c=>{try{a(r.throw(c))}catch(p){n(p)}},a=c=>c.done?o(c.value):Promise.resolve(c.value).then(i,s);a((r=r.apply(e,t)).next())});var xo=Cr((kr,yo)=>{(function(e,t){typeof kr=="object"&&typeof yo!="undefined"?t():typeof define=="function"&&define.amd?define(t):t()})(kr,function(){"use strict";function e(r){var o=!0,n=!1,i=null,s={text:!0,search:!0,url:!0,tel:!0,email:!0,password:!0,number:!0,date:!0,month:!0,week:!0,time:!0,datetime:!0,"datetime-local":!0};function a(k){return!!(k&&k!==document&&k.nodeName!=="HTML"&&k.nodeName!=="BODY"&&"classList"in k&&"contains"in k.classList)}function c(k){var ut=k.type,je=k.tagName;return!!(je==="INPUT"&&s[ut]&&!k.readOnly||je==="TEXTAREA"&&!k.readOnly||k.isContentEditable)}function p(k){k.classList.contains("focus-visible")||(k.classList.add("focus-visible"),k.setAttribute("data-focus-visible-added",""))}function l(k){k.hasAttribute("data-focus-visible-added")&&(k.classList.remove("focus-visible"),k.removeAttribute("data-focus-visible-added"))}function f(k){k.metaKey||k.altKey||k.ctrlKey||(a(r.activeElement)&&p(r.activeElement),o=!0)}function u(k){o=!1}function d(k){a(k.target)&&(o||c(k.target))&&p(k.target)}function v(k){a(k.target)&&(k.target.classList.contains("focus-visible")||k.target.hasAttribute("data-focus-visible-added"))&&(n=!0,window.clearTimeout(i),i=window.setTimeout(function(){n=!1},100),l(k.target))}function S(k){document.visibilityState==="hidden"&&(n&&(o=!0),X())}function X(){document.addEventListener("mousemove",ee),document.addEventListener("mousedown",ee),document.addEventListener("mouseup",ee),document.addEventListener("pointermove",ee),document.addEventListener("pointerdown",ee),document.addEventListener("pointerup",ee),document.addEventListener("touchmove",ee),document.addEventListener("touchstart",ee),document.addEventListener("touchend",ee)}function re(){document.removeEventListener("mousemove",ee),document.removeEventListener("mousedown",ee),document.removeEventListener("mouseup",ee),document.removeEventListener("pointermove",ee),document.removeEventListener("pointerdown",ee),document.removeEventListener("pointerup",ee),document.removeEventListener("touchmove",ee),document.removeEventListener("touchstart",ee),document.removeEventListener("touchend",ee)}function ee(k){k.target.nodeName&&k.target.nodeName.toLowerCase()==="html"||(o=!1,re())}document.addEventListener("keydown",f,!0),document.addEventListener("mousedown",u,!0),document.addEventListener("pointerdown",u,!0),document.addEventListener("touchstart",u,!0),document.addEventListener("visibilitychange",S,!0),X(),r.addEventListener("focus",d,!0),r.addEventListener("blur",v,!0),r.nodeType===Node.DOCUMENT_FRAGMENT_NODE&&r.host?r.host.setAttribute("data-js-focus-visible",""):r.nodeType===Node.DOCUMENT_NODE&&(document.documentElement.classList.add("js-focus-visible"),document.documentElement.setAttribute("data-js-focus-visible",""))}if(typeof window!="undefined"&&typeof document!="undefined"){window.applyFocusVisiblePolyfill=e;var t;try{t=new CustomEvent("focus-visible-polyfill-ready")}catch(r){t=document.createEvent("CustomEvent"),t.initCustomEvent("focus-visible-polyfill-ready",!1,!1,{})}window.dispatchEvent(t)}typeof document!="undefined"&&e(document)})});var ro=Cr((Uy,Pn)=>{"use strict";var qa=/["'&<>]/;Pn.exports=Ka;function Ka(e){var t=""+e,r=qa.exec(t);if(!r)return t;var o,n="",i=0,s=0;for(i=r.index;i{(function(t,r){typeof zt=="object"&&typeof io=="object"?io.exports=r():typeof define=="function"&&define.amd?define([],r):typeof zt=="object"?zt.ClipboardJS=r():t.ClipboardJS=r()})(zt,function(){return function(){var e={686:function(o,n,i){"use strict";i.d(n,{default:function(){return Xi}});var s=i(279),a=i.n(s),c=i(370),p=i.n(c),l=i(817),f=i.n(l);function u(q){try{return document.execCommand(q)}catch(C){return!1}}var d=function(C){var _=f()(C);return u("cut"),_},v=d;function S(q){var C=document.documentElement.getAttribute("dir")==="rtl",_=document.createElement("textarea");_.style.fontSize="12pt",_.style.border="0",_.style.padding="0",_.style.margin="0",_.style.position="absolute",_.style[C?"right":"left"]="-9999px";var W=window.pageYOffset||document.documentElement.scrollTop;return _.style.top="".concat(W,"px"),_.setAttribute("readonly",""),_.value=q,_}var X=function(C,_){var W=S(C);_.container.appendChild(W);var N=f()(W);return u("copy"),W.remove(),N},re=function(C){var _=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{container:document.body},W="";return typeof C=="string"?W=X(C,_):C instanceof HTMLInputElement&&!["text","search","url","tel","password"].includes(C==null?void 0:C.type)?W=X(C.value,_):(W=f()(C),u("copy")),W},ee=re;function k(q){"@babel/helpers - typeof";return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?k=function(_){return typeof _}:k=function(_){return _&&typeof Symbol=="function"&&_.constructor===Symbol&&_!==Symbol.prototype?"symbol":typeof _},k(q)}var ut=function(){var C=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{},_=C.action,W=_===void 0?"copy":_,N=C.container,G=C.target,De=C.text;if(W!=="copy"&&W!=="cut")throw new Error('Invalid "action" value, use either "copy" or "cut"');if(G!==void 0)if(G&&k(G)==="object"&&G.nodeType===1){if(W==="copy"&&G.hasAttribute("disabled"))throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');if(W==="cut"&&(G.hasAttribute("readonly")||G.hasAttribute("disabled")))throw new Error(`Invalid "target" attribute. You can't cut text from elements with "readonly" or "disabled" attributes`)}else throw new Error('Invalid "target" value, use a valid Element');if(De)return ee(De,{container:N});if(G)return W==="cut"?v(G):ee(G,{container:N})},je=ut;function P(q){"@babel/helpers - typeof";return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?P=function(_){return typeof _}:P=function(_){return _&&typeof Symbol=="function"&&_.constructor===Symbol&&_!==Symbol.prototype?"symbol":typeof _},P(q)}function se(q,C){if(!(q instanceof C))throw new TypeError("Cannot call a class as a function")}function ce(q,C){for(var _=0;_0&&arguments[0]!==void 0?arguments[0]:{};this.action=typeof N.action=="function"?N.action:this.defaultAction,this.target=typeof N.target=="function"?N.target:this.defaultTarget,this.text=typeof N.text=="function"?N.text:this.defaultText,this.container=P(N.container)==="object"?N.container:document.body}},{key:"listenClick",value:function(N){var G=this;this.listener=p()(N,"click",function(De){return G.onClick(De)})}},{key:"onClick",value:function(N){var G=N.delegateTarget||N.currentTarget,De=this.action(G)||"copy",Bt=je({action:De,container:this.container,target:this.target(G),text:this.text(G)});this.emit(Bt?"success":"error",{action:De,text:Bt,trigger:G,clearSelection:function(){G&&G.focus(),window.getSelection().removeAllRanges()}})}},{key:"defaultAction",value:function(N){return Mr("action",N)}},{key:"defaultTarget",value:function(N){var G=Mr("target",N);if(G)return document.querySelector(G)}},{key:"defaultText",value:function(N){return Mr("text",N)}},{key:"destroy",value:function(){this.listener.destroy()}}],[{key:"copy",value:function(N){var G=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{container:document.body};return ee(N,G)}},{key:"cut",value:function(N){return v(N)}},{key:"isSupported",value:function(){var N=arguments.length>0&&arguments[0]!==void 0?arguments[0]:["copy","cut"],G=typeof N=="string"?[N]:N,De=!!document.queryCommandSupported;return G.forEach(function(Bt){De=De&&!!document.queryCommandSupported(Bt)}),De}}]),_}(a()),Xi=Ji},828:function(o){var n=9;if(typeof Element!="undefined"&&!Element.prototype.matches){var i=Element.prototype;i.matches=i.matchesSelector||i.mozMatchesSelector||i.msMatchesSelector||i.oMatchesSelector||i.webkitMatchesSelector}function s(a,c){for(;a&&a.nodeType!==n;){if(typeof a.matches=="function"&&a.matches(c))return a;a=a.parentNode}}o.exports=s},438:function(o,n,i){var s=i(828);function a(l,f,u,d,v){var S=p.apply(this,arguments);return l.addEventListener(u,S,v),{destroy:function(){l.removeEventListener(u,S,v)}}}function c(l,f,u,d,v){return typeof l.addEventListener=="function"?a.apply(null,arguments):typeof u=="function"?a.bind(null,document).apply(null,arguments):(typeof l=="string"&&(l=document.querySelectorAll(l)),Array.prototype.map.call(l,function(S){return a(S,f,u,d,v)}))}function p(l,f,u,d){return function(v){v.delegateTarget=s(v.target,f),v.delegateTarget&&d.call(l,v)}}o.exports=c},879:function(o,n){n.node=function(i){return i!==void 0&&i instanceof HTMLElement&&i.nodeType===1},n.nodeList=function(i){var s=Object.prototype.toString.call(i);return i!==void 0&&(s==="[object NodeList]"||s==="[object HTMLCollection]")&&"length"in i&&(i.length===0||n.node(i[0]))},n.string=function(i){return typeof i=="string"||i instanceof String},n.fn=function(i){var s=Object.prototype.toString.call(i);return s==="[object Function]"}},370:function(o,n,i){var s=i(879),a=i(438);function c(u,d,v){if(!u&&!d&&!v)throw new Error("Missing required arguments");if(!s.string(d))throw new TypeError("Second argument must be a String");if(!s.fn(v))throw new TypeError("Third argument must be a Function");if(s.node(u))return p(u,d,v);if(s.nodeList(u))return l(u,d,v);if(s.string(u))return f(u,d,v);throw new TypeError("First argument must be a String, HTMLElement, HTMLCollection, or NodeList")}function p(u,d,v){return u.addEventListener(d,v),{destroy:function(){u.removeEventListener(d,v)}}}function l(u,d,v){return Array.prototype.forEach.call(u,function(S){S.addEventListener(d,v)}),{destroy:function(){Array.prototype.forEach.call(u,function(S){S.removeEventListener(d,v)})}}}function f(u,d,v){return a(document.body,u,d,v)}o.exports=c},817:function(o){function n(i){var s;if(i.nodeName==="SELECT")i.focus(),s=i.value;else if(i.nodeName==="INPUT"||i.nodeName==="TEXTAREA"){var a=i.hasAttribute("readonly");a||i.setAttribute("readonly",""),i.select(),i.setSelectionRange(0,i.value.length),a||i.removeAttribute("readonly"),s=i.value}else{i.hasAttribute("contenteditable")&&i.focus();var c=window.getSelection(),p=document.createRange();p.selectNodeContents(i),c.removeAllRanges(),c.addRange(p),s=c.toString()}return s}o.exports=n},279:function(o){function n(){}n.prototype={on:function(i,s,a){var c=this.e||(this.e={});return(c[i]||(c[i]=[])).push({fn:s,ctx:a}),this},once:function(i,s,a){var c=this;function p(){c.off(i,p),s.apply(a,arguments)}return p._=s,this.on(i,p,a)},emit:function(i){var s=[].slice.call(arguments,1),a=((this.e||(this.e={}))[i]||[]).slice(),c=0,p=a.length;for(c;c0&&i[i.length-1])&&(p[0]===6||p[0]===2)){r=0;continue}if(p[0]===3&&(!i||p[1]>i[0]&&p[1]=e.length&&(e=void 0),{value:e&&e[o++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")}function K(e,t){var r=typeof Symbol=="function"&&e[Symbol.iterator];if(!r)return e;var o=r.call(e),n,i=[],s;try{for(;(t===void 0||t-- >0)&&!(n=o.next()).done;)i.push(n.value)}catch(a){s={error:a}}finally{try{n&&!n.done&&(r=o.return)&&r.call(o)}finally{if(s)throw s.error}}return i}function B(e,t,r){if(r||arguments.length===2)for(var o=0,n=t.length,i;o1||c(d,S)})},v&&(n[d]=v(n[d])))}function c(d,v){try{p(o[d](v))}catch(S){u(i[0][3],S)}}function p(d){d.value instanceof dt?Promise.resolve(d.value.v).then(l,f):u(i[0][2],d)}function l(d){c("next",d)}function f(d){c("throw",d)}function u(d,v){d(v),i.shift(),i.length&&c(i[0][0],i[0][1])}}function To(e){if(!Symbol.asyncIterator)throw new TypeError("Symbol.asyncIterator is not defined.");var t=e[Symbol.asyncIterator],r;return t?t.call(e):(e=typeof Oe=="function"?Oe(e):e[Symbol.iterator](),r={},o("next"),o("throw"),o("return"),r[Symbol.asyncIterator]=function(){return this},r);function o(i){r[i]=e[i]&&function(s){return new Promise(function(a,c){s=e[i](s),n(a,c,s.done,s.value)})}}function n(i,s,a,c){Promise.resolve(c).then(function(p){i({value:p,done:a})},s)}}function I(e){return typeof e=="function"}function yt(e){var t=function(o){Error.call(o),o.stack=new Error().stack},r=e(t);return r.prototype=Object.create(Error.prototype),r.prototype.constructor=r,r}var Xt=yt(function(e){return function(r){e(this),this.message=r?r.length+` errors occurred during unsubscription: +`+r.map(function(o,n){return n+1+") "+o.toString()}).join(` + `):"",this.name="UnsubscriptionError",this.errors=r}});function Ze(e,t){if(e){var r=e.indexOf(t);0<=r&&e.splice(r,1)}}var qe=function(){function e(t){this.initialTeardown=t,this.closed=!1,this._parentage=null,this._finalizers=null}return e.prototype.unsubscribe=function(){var t,r,o,n,i;if(!this.closed){this.closed=!0;var s=this._parentage;if(s)if(this._parentage=null,Array.isArray(s))try{for(var a=Oe(s),c=a.next();!c.done;c=a.next()){var p=c.value;p.remove(this)}}catch(S){t={error:S}}finally{try{c&&!c.done&&(r=a.return)&&r.call(a)}finally{if(t)throw t.error}}else s.remove(this);var l=this.initialTeardown;if(I(l))try{l()}catch(S){i=S instanceof Xt?S.errors:[S]}var f=this._finalizers;if(f){this._finalizers=null;try{for(var u=Oe(f),d=u.next();!d.done;d=u.next()){var v=d.value;try{So(v)}catch(S){i=i!=null?i:[],S instanceof Xt?i=B(B([],K(i)),K(S.errors)):i.push(S)}}}catch(S){o={error:S}}finally{try{d&&!d.done&&(n=u.return)&&n.call(u)}finally{if(o)throw o.error}}}if(i)throw new Xt(i)}},e.prototype.add=function(t){var r;if(t&&t!==this)if(this.closed)So(t);else{if(t instanceof e){if(t.closed||t._hasParent(this))return;t._addParent(this)}(this._finalizers=(r=this._finalizers)!==null&&r!==void 0?r:[]).push(t)}},e.prototype._hasParent=function(t){var r=this._parentage;return r===t||Array.isArray(r)&&r.includes(t)},e.prototype._addParent=function(t){var r=this._parentage;this._parentage=Array.isArray(r)?(r.push(t),r):r?[r,t]:t},e.prototype._removeParent=function(t){var r=this._parentage;r===t?this._parentage=null:Array.isArray(r)&&Ze(r,t)},e.prototype.remove=function(t){var r=this._finalizers;r&&Ze(r,t),t instanceof e&&t._removeParent(this)},e.EMPTY=function(){var t=new e;return t.closed=!0,t}(),e}();var $r=qe.EMPTY;function Zt(e){return e instanceof qe||e&&"closed"in e&&I(e.remove)&&I(e.add)&&I(e.unsubscribe)}function So(e){I(e)?e():e.unsubscribe()}var We={onUnhandledError:null,onStoppedNotification:null,Promise:void 0,useDeprecatedSynchronousErrorHandling:!1,useDeprecatedNextContext:!1};var xt={setTimeout:function(e,t){for(var r=[],o=2;o0},enumerable:!1,configurable:!0}),t.prototype._trySubscribe=function(r){return this._throwIfClosed(),e.prototype._trySubscribe.call(this,r)},t.prototype._subscribe=function(r){return this._throwIfClosed(),this._checkFinalizedStatuses(r),this._innerSubscribe(r)},t.prototype._innerSubscribe=function(r){var o=this,n=this,i=n.hasError,s=n.isStopped,a=n.observers;return i||s?$r:(this.currentObservers=null,a.push(r),new qe(function(){o.currentObservers=null,Ze(a,r)}))},t.prototype._checkFinalizedStatuses=function(r){var o=this,n=o.hasError,i=o.thrownError,s=o.isStopped;n?r.error(i):s&&r.complete()},t.prototype.asObservable=function(){var r=new F;return r.source=this,r},t.create=function(r,o){return new Ho(r,o)},t}(F);var Ho=function(e){ie(t,e);function t(r,o){var n=e.call(this)||this;return n.destination=r,n.source=o,n}return t.prototype.next=function(r){var o,n;(n=(o=this.destination)===null||o===void 0?void 0:o.next)===null||n===void 0||n.call(o,r)},t.prototype.error=function(r){var o,n;(n=(o=this.destination)===null||o===void 0?void 0:o.error)===null||n===void 0||n.call(o,r)},t.prototype.complete=function(){var r,o;(o=(r=this.destination)===null||r===void 0?void 0:r.complete)===null||o===void 0||o.call(r)},t.prototype._subscribe=function(r){var o,n;return(n=(o=this.source)===null||o===void 0?void 0:o.subscribe(r))!==null&&n!==void 0?n:$r},t}(T);var jr=function(e){ie(t,e);function t(r){var o=e.call(this)||this;return o._value=r,o}return Object.defineProperty(t.prototype,"value",{get:function(){return this.getValue()},enumerable:!1,configurable:!0}),t.prototype._subscribe=function(r){var o=e.prototype._subscribe.call(this,r);return!o.closed&&r.next(this._value),o},t.prototype.getValue=function(){var r=this,o=r.hasError,n=r.thrownError,i=r._value;if(o)throw n;return this._throwIfClosed(),i},t.prototype.next=function(r){e.prototype.next.call(this,this._value=r)},t}(T);var It={now:function(){return(It.delegate||Date).now()},delegate:void 0};var Ft=function(e){ie(t,e);function t(r,o,n){r===void 0&&(r=1/0),o===void 0&&(o=1/0),n===void 0&&(n=It);var i=e.call(this)||this;return i._bufferSize=r,i._windowTime=o,i._timestampProvider=n,i._buffer=[],i._infiniteTimeWindow=!0,i._infiniteTimeWindow=o===1/0,i._bufferSize=Math.max(1,r),i._windowTime=Math.max(1,o),i}return t.prototype.next=function(r){var o=this,n=o.isStopped,i=o._buffer,s=o._infiniteTimeWindow,a=o._timestampProvider,c=o._windowTime;n||(i.push(r),!s&&i.push(a.now()+c)),this._trimBuffer(),e.prototype.next.call(this,r)},t.prototype._subscribe=function(r){this._throwIfClosed(),this._trimBuffer();for(var o=this._innerSubscribe(r),n=this,i=n._infiniteTimeWindow,s=n._buffer,a=s.slice(),c=0;c0?e.prototype.schedule.call(this,r,o):(this.delay=o,this.state=r,this.scheduler.flush(this),this)},t.prototype.execute=function(r,o){return o>0||this.closed?e.prototype.execute.call(this,r,o):this._execute(r,o)},t.prototype.requestAsyncId=function(r,o,n){return n===void 0&&(n=0),n!=null&&n>0||n==null&&this.delay>0?e.prototype.requestAsyncId.call(this,r,o,n):(r.flush(this),0)},t}(St);var Po=function(e){ie(t,e);function t(){return e!==null&&e.apply(this,arguments)||this}return t}(Ot);var Wr=new Po(Ro);var Io=function(e){ie(t,e);function t(r,o){var n=e.call(this,r,o)||this;return n.scheduler=r,n.work=o,n}return t.prototype.requestAsyncId=function(r,o,n){return n===void 0&&(n=0),n!==null&&n>0?e.prototype.requestAsyncId.call(this,r,o,n):(r.actions.push(this),r._scheduled||(r._scheduled=Tt.requestAnimationFrame(function(){return r.flush(void 0)})))},t.prototype.recycleAsyncId=function(r,o,n){var i;if(n===void 0&&(n=0),n!=null?n>0:this.delay>0)return e.prototype.recycleAsyncId.call(this,r,o,n);var s=r.actions;o!=null&&((i=s[s.length-1])===null||i===void 0?void 0:i.id)!==o&&(Tt.cancelAnimationFrame(o),r._scheduled=void 0)},t}(St);var Fo=function(e){ie(t,e);function t(){return e!==null&&e.apply(this,arguments)||this}return t.prototype.flush=function(r){this._active=!0;var o=this._scheduled;this._scheduled=void 0;var n=this.actions,i;r=r||n.shift();do if(i=r.execute(r.state,r.delay))break;while((r=n[0])&&r.id===o&&n.shift());if(this._active=!1,i){for(;(r=n[0])&&r.id===o&&n.shift();)r.unsubscribe();throw i}},t}(Ot);var ye=new Fo(Io);var y=new F(function(e){return e.complete()});function rr(e){return e&&I(e.schedule)}function Vr(e){return e[e.length-1]}function pt(e){return I(Vr(e))?e.pop():void 0}function Fe(e){return rr(Vr(e))?e.pop():void 0}function or(e,t){return typeof Vr(e)=="number"?e.pop():t}var Lt=function(e){return e&&typeof e.length=="number"&&typeof e!="function"};function nr(e){return I(e==null?void 0:e.then)}function ir(e){return I(e[wt])}function ar(e){return Symbol.asyncIterator&&I(e==null?void 0:e[Symbol.asyncIterator])}function sr(e){return new TypeError("You provided "+(e!==null&&typeof e=="object"?"an invalid object":"'"+e+"'")+" where a stream was expected. You can provide an Observable, Promise, ReadableStream, Array, AsyncIterable, or Iterable.")}function fa(){return typeof Symbol!="function"||!Symbol.iterator?"@@iterator":Symbol.iterator}var cr=fa();function pr(e){return I(e==null?void 0:e[cr])}function lr(e){return wo(this,arguments,function(){var r,o,n,i;return Jt(this,function(s){switch(s.label){case 0:r=e.getReader(),s.label=1;case 1:s.trys.push([1,,9,10]),s.label=2;case 2:return[4,dt(r.read())];case 3:return o=s.sent(),n=o.value,i=o.done,i?[4,dt(void 0)]:[3,5];case 4:return[2,s.sent()];case 5:return[4,dt(n)];case 6:return[4,s.sent()];case 7:return s.sent(),[3,2];case 8:return[3,10];case 9:return r.releaseLock(),[7];case 10:return[2]}})})}function mr(e){return I(e==null?void 0:e.getReader)}function U(e){if(e instanceof F)return e;if(e!=null){if(ir(e))return ua(e);if(Lt(e))return da(e);if(nr(e))return ha(e);if(ar(e))return jo(e);if(pr(e))return ba(e);if(mr(e))return va(e)}throw sr(e)}function ua(e){return new F(function(t){var r=e[wt]();if(I(r.subscribe))return r.subscribe(t);throw new TypeError("Provided object does not correctly implement Symbol.observable")})}function da(e){return new F(function(t){for(var r=0;r=2;return function(o){return o.pipe(e?g(function(n,i){return e(n,i,o)}):be,Ee(1),r?Qe(t):tn(function(){return new ur}))}}function Yr(e){return e<=0?function(){return y}:E(function(t,r){var o=[];t.subscribe(w(r,function(n){o.push(n),e=2,!0))}function le(e){e===void 0&&(e={});var t=e.connector,r=t===void 0?function(){return new T}:t,o=e.resetOnError,n=o===void 0?!0:o,i=e.resetOnComplete,s=i===void 0?!0:i,a=e.resetOnRefCountZero,c=a===void 0?!0:a;return function(p){var l,f,u,d=0,v=!1,S=!1,X=function(){f==null||f.unsubscribe(),f=void 0},re=function(){X(),l=u=void 0,v=S=!1},ee=function(){var k=l;re(),k==null||k.unsubscribe()};return E(function(k,ut){d++,!S&&!v&&X();var je=u=u!=null?u:r();ut.add(function(){d--,d===0&&!S&&!v&&(f=Br(ee,c))}),je.subscribe(ut),!l&&d>0&&(l=new bt({next:function(P){return je.next(P)},error:function(P){S=!0,X(),f=Br(re,n,P),je.error(P)},complete:function(){v=!0,X(),f=Br(re,s),je.complete()}}),U(k).subscribe(l))})(p)}}function Br(e,t){for(var r=[],o=2;oe.next(document)),e}function M(e,t=document){return Array.from(t.querySelectorAll(e))}function j(e,t=document){let r=ue(e,t);if(typeof r=="undefined")throw new ReferenceError(`Missing element: expected "${e}" to be present`);return r}function ue(e,t=document){return t.querySelector(e)||void 0}function Ne(){var e,t,r,o;return(o=(r=(t=(e=document.activeElement)==null?void 0:e.shadowRoot)==null?void 0:t.activeElement)!=null?r:document.activeElement)!=null?o:void 0}var Pa=L(h(document.body,"focusin"),h(document.body,"focusout")).pipe(Ae(1),Q(void 0),m(()=>Ne()||document.body),Z(1));function Ye(e){return Pa.pipe(m(t=>e.contains(t)),Y())}function it(e,t){return H(()=>L(h(e,"mouseenter").pipe(m(()=>!0)),h(e,"mouseleave").pipe(m(()=>!1))).pipe(t?Ut(r=>He(+!r*t)):be,Q(e.matches(":hover"))))}function sn(e,t){if(typeof t=="string"||typeof t=="number")e.innerHTML+=t.toString();else if(t instanceof Node)e.appendChild(t);else if(Array.isArray(t))for(let r of t)sn(e,r)}function x(e,t,...r){let o=document.createElement(e);if(t)for(let n of Object.keys(t))typeof t[n]!="undefined"&&(typeof t[n]!="boolean"?o.setAttribute(n,t[n]):o.setAttribute(n,""));for(let n of r)sn(o,n);return o}function br(e){if(e>999){let t=+((e-950)%1e3>99);return`${((e+1e-6)/1e3).toFixed(t)}k`}else return e.toString()}function At(e){let t=x("script",{src:e});return H(()=>(document.head.appendChild(t),L(h(t,"load"),h(t,"error").pipe(b(()=>Nr(()=>new ReferenceError(`Invalid script: ${e}`))))).pipe(m(()=>{}),A(()=>document.head.removeChild(t)),Ee(1))))}var cn=new T,Ia=H(()=>typeof ResizeObserver=="undefined"?At("https://unpkg.com/resize-observer-polyfill"):$(void 0)).pipe(m(()=>new ResizeObserver(e=>e.forEach(t=>cn.next(t)))),b(e=>L(tt,$(e)).pipe(A(()=>e.disconnect()))),Z(1));function de(e){return{width:e.offsetWidth,height:e.offsetHeight}}function Le(e){let t=e;for(;t.clientWidth===0&&t.parentElement;)t=t.parentElement;return Ia.pipe(O(r=>r.observe(t)),b(r=>cn.pipe(g(o=>o.target===t),A(()=>r.unobserve(t)))),m(()=>de(e)),Q(de(e)))}function Ct(e){return{width:e.scrollWidth,height:e.scrollHeight}}function vr(e){let t=e.parentElement;for(;t&&(e.scrollWidth<=t.scrollWidth&&e.scrollHeight<=t.scrollHeight);)t=(e=t).parentElement;return t?e:void 0}function pn(e){let t=[],r=e.parentElement;for(;r;)(e.clientWidth>r.clientWidth||e.clientHeight>r.clientHeight)&&t.push(r),r=(e=r).parentElement;return t.length===0&&t.push(document.documentElement),t}function Be(e){return{x:e.offsetLeft,y:e.offsetTop}}function ln(e){let t=e.getBoundingClientRect();return{x:t.x+window.scrollX,y:t.y+window.scrollY}}function mn(e){return L(h(window,"load"),h(window,"resize")).pipe($e(0,ye),m(()=>Be(e)),Q(Be(e)))}function gr(e){return{x:e.scrollLeft,y:e.scrollTop}}function Ge(e){return L(h(e,"scroll"),h(window,"scroll"),h(window,"resize")).pipe($e(0,ye),m(()=>gr(e)),Q(gr(e)))}var fn=new T,Fa=H(()=>$(new IntersectionObserver(e=>{for(let t of e)fn.next(t)},{threshold:0}))).pipe(b(e=>L(tt,$(e)).pipe(A(()=>e.disconnect()))),Z(1));function mt(e){return Fa.pipe(O(t=>t.observe(e)),b(t=>fn.pipe(g(({target:r})=>r===e),A(()=>t.unobserve(e)),m(({isIntersecting:r})=>r))))}function un(e,t=16){return Ge(e).pipe(m(({y:r})=>{let o=de(e),n=Ct(e);return r>=n.height-o.height-t}),Y())}var yr={drawer:j("[data-md-toggle=drawer]"),search:j("[data-md-toggle=search]")};function dn(e){return yr[e].checked}function at(e,t){yr[e].checked!==t&&yr[e].click()}function Je(e){let t=yr[e];return h(t,"change").pipe(m(()=>t.checked),Q(t.checked))}function ja(e,t){switch(e.constructor){case HTMLInputElement:return e.type==="radio"?/^Arrow/.test(t):!0;case HTMLSelectElement:case HTMLTextAreaElement:return!0;default:return e.isContentEditable}}function Ua(){return L(h(window,"compositionstart").pipe(m(()=>!0)),h(window,"compositionend").pipe(m(()=>!1))).pipe(Q(!1))}function hn(){let e=h(window,"keydown").pipe(g(t=>!(t.metaKey||t.ctrlKey)),m(t=>({mode:dn("search")?"search":"global",type:t.key,claim(){t.preventDefault(),t.stopPropagation()}})),g(({mode:t,type:r})=>{if(t==="global"){let o=Ne();if(typeof o!="undefined")return!ja(o,r)}return!0}),le());return Ua().pipe(b(t=>t?y:e))}function we(){return new URL(location.href)}function st(e,t=!1){if(V("navigation.instant")&&!t){let r=x("a",{href:e.href});document.body.appendChild(r),r.click(),r.remove()}else location.href=e.href}function bn(){return new T}function vn(){return location.hash.slice(1)}function gn(e){let t=x("a",{href:e});t.addEventListener("click",r=>r.stopPropagation()),t.click()}function Zr(e){return L(h(window,"hashchange"),e).pipe(m(vn),Q(vn()),g(t=>t.length>0),Z(1))}function yn(e){return Zr(e).pipe(m(t=>ue(`[id="${t}"]`)),g(t=>typeof t!="undefined"))}function Wt(e){let t=matchMedia(e);return dr(r=>t.addListener(()=>r(t.matches))).pipe(Q(t.matches))}function xn(){let e=matchMedia("print");return L(h(window,"beforeprint").pipe(m(()=>!0)),h(window,"afterprint").pipe(m(()=>!1))).pipe(Q(e.matches))}function eo(e,t){return e.pipe(b(r=>r?t():y))}function to(e,t){return new F(r=>{let o=new XMLHttpRequest;return o.open("GET",`${e}`),o.responseType="blob",o.addEventListener("load",()=>{o.status>=200&&o.status<300?(r.next(o.response),r.complete()):r.error(new Error(o.statusText))}),o.addEventListener("error",()=>{r.error(new Error("Network error"))}),o.addEventListener("abort",()=>{r.complete()}),typeof(t==null?void 0:t.progress$)!="undefined"&&(o.addEventListener("progress",n=>{var i;if(n.lengthComputable)t.progress$.next(n.loaded/n.total*100);else{let s=(i=o.getResponseHeader("Content-Length"))!=null?i:0;t.progress$.next(n.loaded/+s*100)}}),t.progress$.next(5)),o.send(),()=>o.abort()})}function ze(e,t){return to(e,t).pipe(b(r=>r.text()),m(r=>JSON.parse(r)),Z(1))}function xr(e,t){let r=new DOMParser;return to(e,t).pipe(b(o=>o.text()),m(o=>r.parseFromString(o,"text/html")),Z(1))}function En(e,t){let r=new DOMParser;return to(e,t).pipe(b(o=>o.text()),m(o=>r.parseFromString(o,"text/xml")),Z(1))}function wn(){return{x:Math.max(0,scrollX),y:Math.max(0,scrollY)}}function Tn(){return L(h(window,"scroll",{passive:!0}),h(window,"resize",{passive:!0})).pipe(m(wn),Q(wn()))}function Sn(){return{width:innerWidth,height:innerHeight}}function On(){return h(window,"resize",{passive:!0}).pipe(m(Sn),Q(Sn()))}function Ln(){return z([Tn(),On()]).pipe(m(([e,t])=>({offset:e,size:t})),Z(1))}function Er(e,{viewport$:t,header$:r}){let o=t.pipe(ne("size")),n=z([o,r]).pipe(m(()=>Be(e)));return z([r,t,n]).pipe(m(([{height:i},{offset:s,size:a},{x:c,y:p}])=>({offset:{x:s.x-c,y:s.y-p+i},size:a})))}function Da(e){return h(e,"message",t=>t.data)}function Wa(e){let t=new T;return t.subscribe(r=>e.postMessage(r)),t}function Mn(e,t=new Worker(e)){let r=Da(t),o=Wa(t),n=new T;n.subscribe(o);let i=o.pipe(oe(),ae(!0));return n.pipe(oe(),Ve(r.pipe(D(i))),le())}var Va=j("#__config"),kt=JSON.parse(Va.textContent);kt.base=`${new URL(kt.base,we())}`;function Te(){return kt}function V(e){return kt.features.includes(e)}function Me(e,t){return typeof t!="undefined"?kt.translations[e].replace("#",t.toString()):kt.translations[e]}function Ce(e,t=document){return j(`[data-md-component=${e}]`,t)}function me(e,t=document){return M(`[data-md-component=${e}]`,t)}function Na(e){let t=j(".md-typeset > :first-child",e);return h(t,"click",{once:!0}).pipe(m(()=>j(".md-typeset",e)),m(r=>({hash:__md_hash(r.innerHTML)})))}function _n(e){if(!V("announce.dismiss")||!e.childElementCount)return y;if(!e.hidden){let t=j(".md-typeset",e);__md_hash(t.innerHTML)===__md_get("__announce")&&(e.hidden=!0)}return H(()=>{let t=new T;return t.subscribe(({hash:r})=>{e.hidden=!0,__md_set("__announce",r)}),Na(e).pipe(O(r=>t.next(r)),A(()=>t.complete()),m(r=>R({ref:e},r)))})}function za(e,{target$:t}){return t.pipe(m(r=>({hidden:r!==e})))}function An(e,t){let r=new T;return r.subscribe(({hidden:o})=>{e.hidden=o}),za(e,t).pipe(O(o=>r.next(o)),A(()=>r.complete()),m(o=>R({ref:e},o)))}function Vt(e,t){return t==="inline"?x("div",{class:"md-tooltip md-tooltip--inline",id:e,role:"tooltip"},x("div",{class:"md-tooltip__inner md-typeset"})):x("div",{class:"md-tooltip",id:e,role:"tooltip"},x("div",{class:"md-tooltip__inner md-typeset"}))}function wr(...e){return x("div",{class:"md-tooltip2",role:"dialog"},x("div",{class:"md-tooltip2__inner md-typeset"},e))}function Cn(...e){return x("div",{class:"md-tooltip2",role:"tooltip"},x("div",{class:"md-tooltip2__inner md-typeset"},e))}function kn(e,t){if(t=t?`${t}_annotation_${e}`:void 0,t){let r=t?`#${t}`:void 0;return x("aside",{class:"md-annotation",tabIndex:0},Vt(t),x("a",{href:r,class:"md-annotation__index",tabIndex:-1},x("span",{"data-md-annotation-id":e})))}else return x("aside",{class:"md-annotation",tabIndex:0},Vt(t),x("span",{class:"md-annotation__index",tabIndex:-1},x("span",{"data-md-annotation-id":e})))}function Hn(e){return x("button",{class:"md-code__button",title:Me("clipboard.copy"),"data-clipboard-target":`#${e} > code`,"data-md-type":"copy"})}function $n(){return x("button",{class:"md-code__button",title:"Toggle line selection","data-md-type":"select"})}function Rn(){return x("nav",{class:"md-code__nav"})}var In=Rt(ro());function oo(e,t){let r=t&2,o=t&1,n=Object.keys(e.terms).filter(c=>!e.terms[c]).reduce((c,p)=>[...c,x("del",null,(0,In.default)(p))," "],[]).slice(0,-1),i=Te(),s=new URL(e.location,i.base);V("search.highlight")&&s.searchParams.set("h",Object.entries(e.terms).filter(([,c])=>c).reduce((c,[p])=>`${c} ${p}`.trim(),""));let{tags:a}=Te();return x("a",{href:`${s}`,class:"md-search-result__link",tabIndex:-1},x("article",{class:"md-search-result__article md-typeset","data-md-score":e.score.toFixed(2)},r>0&&x("div",{class:"md-search-result__icon md-icon"}),r>0&&x("h1",null,e.title),r<=0&&x("h2",null,e.title),o>0&&e.text.length>0&&e.text,e.tags&&x("nav",{class:"md-tags"},e.tags.map(c=>{let p=a?c in a?`md-tag-icon md-tag--${a[c]}`:"md-tag-icon":"";return x("span",{class:`md-tag ${p}`},c)})),o>0&&n.length>0&&x("p",{class:"md-search-result__terms"},Me("search.result.term.missing"),": ",...n)))}function Fn(e){let t=e[0].score,r=[...e],o=Te(),n=r.findIndex(l=>!`${new URL(l.location,o.base)}`.includes("#")),[i]=r.splice(n,1),s=r.findIndex(l=>l.scoreoo(l,1)),...c.length?[x("details",{class:"md-search-result__more"},x("summary",{tabIndex:-1},x("div",null,c.length>0&&c.length===1?Me("search.result.more.one"):Me("search.result.more.other",c.length))),...c.map(l=>oo(l,1)))]:[]];return x("li",{class:"md-search-result__item"},p)}function jn(e){return x("ul",{class:"md-source__facts"},Object.entries(e).map(([t,r])=>x("li",{class:`md-source__fact md-source__fact--${t}`},typeof r=="number"?br(r):r)))}function no(e){let t=`tabbed-control tabbed-control--${e}`;return x("div",{class:t,hidden:!0},x("button",{class:"tabbed-button",tabIndex:-1,"aria-hidden":"true"}))}function Un(e){return x("div",{class:"md-typeset__scrollwrap"},x("div",{class:"md-typeset__table"},e))}function Qa(e){var o;let t=Te(),r=new URL(`../${e.version}/`,t.base);return x("li",{class:"md-version__item"},x("a",{href:`${r}`,class:"md-version__link"},e.title,((o=t.version)==null?void 0:o.alias)&&e.aliases.length>0&&x("span",{class:"md-version__alias"},e.aliases[0])))}function Dn(e,t){var o;let r=Te();return e=e.filter(n=>{var i;return!((i=n.properties)!=null&&i.hidden)}),x("div",{class:"md-version"},x("button",{class:"md-version__current","aria-label":Me("select.version")},t.title,((o=r.version)==null?void 0:o.alias)&&t.aliases.length>0&&x("span",{class:"md-version__alias"},t.aliases[0])),x("ul",{class:"md-version__list"},e.map(Qa)))}var Ya=0;function Ba(e,t=250){let r=z([Ye(e),it(e,t)]).pipe(m(([n,i])=>n||i),Y()),o=H(()=>pn(e)).pipe(J(Ge),gt(1),Re(r),m(()=>ln(e)));return r.pipe(Pe(n=>n),b(()=>z([r,o])),m(([n,i])=>({active:n,offset:i})),le())}function Nt(e,t,r=250){let{content$:o,viewport$:n}=t,i=`__tooltip2_${Ya++}`;return H(()=>{let s=new T,a=new jr(!1);s.pipe(oe(),ae(!1)).subscribe(a);let c=a.pipe(Ut(l=>He(+!l*250,Wr)),Y(),b(l=>l?o:y),O(l=>l.id=i),le());z([s.pipe(m(({active:l})=>l)),c.pipe(b(l=>it(l,250)),Q(!1))]).pipe(m(l=>l.some(f=>f))).subscribe(a);let p=a.pipe(g(l=>l),te(c,n),m(([l,f,{size:u}])=>{let d=e.getBoundingClientRect(),v=d.width/2;if(f.role==="tooltip")return{x:v,y:8+d.height};if(d.y>=u.height/2){let{height:S}=de(f);return{x:v,y:-16-S}}else return{x:v,y:16+d.height}}));return z([c,s,p]).subscribe(([l,{offset:f},u])=>{l.style.setProperty("--md-tooltip-host-x",`${f.x}px`),l.style.setProperty("--md-tooltip-host-y",`${f.y}px`),l.style.setProperty("--md-tooltip-x",`${u.x}px`),l.style.setProperty("--md-tooltip-y",`${u.y}px`),l.classList.toggle("md-tooltip2--top",u.y<0),l.classList.toggle("md-tooltip2--bottom",u.y>=0)}),a.pipe(g(l=>l),te(c,(l,f)=>f),g(l=>l.role==="tooltip")).subscribe(l=>{let f=de(j(":scope > *",l));l.style.setProperty("--md-tooltip-width",`${f.width}px`),l.style.setProperty("--md-tooltip-tail","0px")}),a.pipe(Y(),xe(ye),te(c)).subscribe(([l,f])=>{f.classList.toggle("md-tooltip2--active",l)}),z([a.pipe(g(l=>l)),c]).subscribe(([l,f])=>{f.role==="dialog"?(e.setAttribute("aria-controls",i),e.setAttribute("aria-haspopup","dialog")):e.setAttribute("aria-describedby",i)}),a.pipe(g(l=>!l)).subscribe(()=>{e.removeAttribute("aria-controls"),e.removeAttribute("aria-describedby"),e.removeAttribute("aria-haspopup")}),Ba(e,r).pipe(O(l=>s.next(l)),A(()=>s.complete()),m(l=>R({ref:e},l)))})}function Xe(e,{viewport$:t},r=document.body){return Nt(e,{content$:new F(o=>{let n=e.title,i=Cn(n);return o.next(i),e.removeAttribute("title"),r.append(i),()=>{i.remove(),e.setAttribute("title",n)}}),viewport$:t},0)}function Ga(e,t){let r=H(()=>z([mn(e),Ge(t)])).pipe(m(([{x:o,y:n},i])=>{let{width:s,height:a}=de(e);return{x:o-i.x+s/2,y:n-i.y+a/2}}));return Ye(e).pipe(b(o=>r.pipe(m(n=>({active:o,offset:n})),Ee(+!o||1/0))))}function Wn(e,t,{target$:r}){let[o,n]=Array.from(e.children);return H(()=>{let i=new T,s=i.pipe(oe(),ae(!0));return i.subscribe({next({offset:a}){e.style.setProperty("--md-tooltip-x",`${a.x}px`),e.style.setProperty("--md-tooltip-y",`${a.y}px`)},complete(){e.style.removeProperty("--md-tooltip-x"),e.style.removeProperty("--md-tooltip-y")}}),mt(e).pipe(D(s)).subscribe(a=>{e.toggleAttribute("data-md-visible",a)}),L(i.pipe(g(({active:a})=>a)),i.pipe(Ae(250),g(({active:a})=>!a))).subscribe({next({active:a}){a?e.prepend(o):o.remove()},complete(){e.prepend(o)}}),i.pipe($e(16,ye)).subscribe(({active:a})=>{o.classList.toggle("md-tooltip--active",a)}),i.pipe(gt(125,ye),g(()=>!!e.offsetParent),m(()=>e.offsetParent.getBoundingClientRect()),m(({x:a})=>a)).subscribe({next(a){a?e.style.setProperty("--md-tooltip-0",`${-a}px`):e.style.removeProperty("--md-tooltip-0")},complete(){e.style.removeProperty("--md-tooltip-0")}}),h(n,"click").pipe(D(s),g(a=>!(a.metaKey||a.ctrlKey))).subscribe(a=>{a.stopPropagation(),a.preventDefault()}),h(n,"mousedown").pipe(D(s),te(i)).subscribe(([a,{active:c}])=>{var p;if(a.button!==0||a.metaKey||a.ctrlKey)a.preventDefault();else if(c){a.preventDefault();let l=e.parentElement.closest(".md-annotation");l instanceof HTMLElement?l.focus():(p=Ne())==null||p.blur()}}),r.pipe(D(s),g(a=>a===o),nt(125)).subscribe(()=>e.focus()),Ga(e,t).pipe(O(a=>i.next(a)),A(()=>i.complete()),m(a=>R({ref:e},a)))})}function Ja(e){let t=Te();if(e.tagName!=="CODE")return[e];let r=[".c",".c1",".cm"];if(typeof t.annotate!="undefined"){let o=e.closest("[class|=language]");if(o)for(let n of Array.from(o.classList)){if(!n.startsWith("language-"))continue;let[,i]=n.split("-");i in t.annotate&&r.push(...t.annotate[i])}}return M(r.join(", "),e)}function Xa(e){let t=[];for(let r of Ja(e)){let o=[],n=document.createNodeIterator(r,NodeFilter.SHOW_TEXT);for(let i=n.nextNode();i;i=n.nextNode())o.push(i);for(let i of o){let s;for(;s=/(\(\d+\))(!)?/.exec(i.textContent);){let[,a,c]=s;if(typeof c=="undefined"){let p=i.splitText(s.index);i=p.splitText(a.length),t.push(p)}else{i.textContent=a,t.push(i);break}}}}return t}function Vn(e,t){t.append(...Array.from(e.childNodes))}function Tr(e,t,{target$:r,print$:o}){let n=t.closest("[id]"),i=n==null?void 0:n.id,s=new Map;for(let a of Xa(t)){let[,c]=a.textContent.match(/\((\d+)\)/);ue(`:scope > li:nth-child(${c})`,e)&&(s.set(c,kn(c,i)),a.replaceWith(s.get(c)))}return s.size===0?y:H(()=>{let a=new T,c=a.pipe(oe(),ae(!0)),p=[];for(let[l,f]of s)p.push([j(".md-typeset",f),j(`:scope > li:nth-child(${l})`,e)]);return o.pipe(D(c)).subscribe(l=>{e.hidden=!l,e.classList.toggle("md-annotation-list",l);for(let[f,u]of p)l?Vn(f,u):Vn(u,f)}),L(...[...s].map(([,l])=>Wn(l,t,{target$:r}))).pipe(A(()=>a.complete()),le())})}function Nn(e){if(e.nextElementSibling){let t=e.nextElementSibling;if(t.tagName==="OL")return t;if(t.tagName==="P"&&!t.children.length)return Nn(t)}}function zn(e,t){return H(()=>{let r=Nn(e);return typeof r!="undefined"?Tr(r,e,t):y})}var Kn=Rt(ao());var Za=0,qn=L(h(window,"keydown").pipe(m(()=>!0)),L(h(window,"keyup"),h(window,"contextmenu")).pipe(m(()=>!1))).pipe(Q(!1),Z(1));function Qn(e){if(e.nextElementSibling){let t=e.nextElementSibling;if(t.tagName==="OL")return t;if(t.tagName==="P"&&!t.children.length)return Qn(t)}}function es(e){return Le(e).pipe(m(({width:t})=>({scrollable:Ct(e).width>t})),ne("scrollable"))}function Yn(e,t){let{matches:r}=matchMedia("(hover)"),o=H(()=>{let n=new T,i=n.pipe(Yr(1));n.subscribe(({scrollable:d})=>{d&&r?e.setAttribute("tabindex","0"):e.removeAttribute("tabindex")});let s=[],a=e.closest("pre"),c=a.closest("[id]"),p=c?c.id:Za++;a.id=`__code_${p}`;let l=[],f=e.closest(".highlight");if(f instanceof HTMLElement){let d=Qn(f);if(typeof d!="undefined"&&(f.classList.contains("annotate")||V("content.code.annotate"))){let v=Tr(d,e,t);l.push(Le(f).pipe(D(i),m(({width:S,height:X})=>S&&X),Y(),b(S=>S?v:y)))}}let u=M(":scope > span[id]",e);if(u.length&&(e.classList.add("md-code__content"),e.closest(".select")||V("content.code.select")&&!e.closest(".no-select"))){let d=+u[0].id.split("-").pop(),v=$n();s.push(v),V("content.tooltips")&&l.push(Xe(v,{viewport$}));let S=h(v,"click").pipe(Dt(P=>!P,!1),O(()=>v.blur()),le());S.subscribe(P=>{v.classList.toggle("md-code__button--active",P)});let X=fe(u).pipe(J(P=>it(P).pipe(m(se=>[P,se]))));S.pipe(b(P=>P?X:y)).subscribe(([P,se])=>{let ce=ue(".hll.select",P);if(ce&&!se)ce.replaceWith(...Array.from(ce.childNodes));else if(!ce&&se){let he=document.createElement("span");he.className="hll select",he.append(...Array.from(P.childNodes).slice(1)),P.append(he)}});let re=fe(u).pipe(J(P=>h(P,"mousedown").pipe(O(se=>se.preventDefault()),m(()=>P)))),ee=S.pipe(b(P=>P?re:y),te(qn),m(([P,se])=>{var he;let ce=u.indexOf(P)+d;if(se===!1)return[ce,ce];{let Se=M(".hll",e).map(Ue=>u.indexOf(Ue.parentElement)+d);return(he=window.getSelection())==null||he.removeAllRanges(),[Math.min(ce,...Se),Math.max(ce,...Se)]}})),k=Zr(y).pipe(g(P=>P.startsWith(`__codelineno-${p}-`)));k.subscribe(P=>{let[,,se]=P.split("-"),ce=se.split(":").map(Se=>+Se-d+1);ce.length===1&&ce.push(ce[0]);for(let Se of M(".hll:not(.select)",e))Se.replaceWith(...Array.from(Se.childNodes));let he=u.slice(ce[0]-1,ce[1]);for(let Se of he){let Ue=document.createElement("span");Ue.className="hll",Ue.append(...Array.from(Se.childNodes).slice(1)),Se.append(Ue)}}),k.pipe(Ee(1),xe(pe)).subscribe(P=>{if(P.includes(":")){let se=document.getElementById(P.split(":")[0]);se&&setTimeout(()=>{let ce=se,he=-64;for(;ce!==document.body;)he+=ce.offsetTop,ce=ce.offsetParent;window.scrollTo({top:he})},1)}});let je=fe(M('a[href^="#__codelineno"]',f)).pipe(J(P=>h(P,"click").pipe(O(se=>se.preventDefault()),m(()=>P)))).pipe(D(i),te(qn),m(([P,se])=>{let he=+j(`[id="${P.hash.slice(1)}"]`).parentElement.id.split("-").pop();if(se===!1)return[he,he];{let Se=M(".hll",e).map(Ue=>+Ue.parentElement.id.split("-").pop());return[Math.min(he,...Se),Math.max(he,...Se)]}}));L(ee,je).subscribe(P=>{let se=`#__codelineno-${p}-`;P[0]===P[1]?se+=P[0]:se+=`${P[0]}:${P[1]}`,history.replaceState({},"",se),window.dispatchEvent(new HashChangeEvent("hashchange",{newURL:window.location.origin+window.location.pathname+se,oldURL:window.location.href}))})}if(Kn.default.isSupported()&&(e.closest(".copy")||V("content.code.copy")&&!e.closest(".no-copy"))){let d=Hn(a.id);s.push(d),V("content.tooltips")&&l.push(Xe(d,{viewport$}))}if(s.length){let d=Rn();d.append(...s),a.insertBefore(d,e)}return es(e).pipe(O(d=>n.next(d)),A(()=>n.complete()),m(d=>R({ref:e},d)),Ve(L(...l).pipe(D(i))))});return V("content.lazy")?mt(e).pipe(g(n=>n),Ee(1),b(()=>o)):o}function ts(e,{target$:t,print$:r}){let o=!0;return L(t.pipe(m(n=>n.closest("details:not([open])")),g(n=>e===n),m(()=>({action:"open",reveal:!0}))),r.pipe(g(n=>n||!o),O(()=>o=e.open),m(n=>({action:n?"open":"close"}))))}function Bn(e,t){return H(()=>{let r=new T;return r.subscribe(({action:o,reveal:n})=>{e.toggleAttribute("open",o==="open"),n&&e.scrollIntoView()}),ts(e,t).pipe(O(o=>r.next(o)),A(()=>r.complete()),m(o=>R({ref:e},o)))})}var Gn=0;function rs(e){let t=document.createElement("h3");t.innerHTML=e.innerHTML;let r=[t],o=e.nextElementSibling;for(;o&&!(o instanceof HTMLHeadingElement);)r.push(o),o=o.nextElementSibling;return r}function os(e,t){for(let r of M("[href], [src]",e))for(let o of["href","src"]){let n=r.getAttribute(o);if(n&&!/^(?:[a-z]+:)?\/\//i.test(n)){r[o]=new URL(r.getAttribute(o),t).toString();break}}for(let r of M("[name^=__], [for]",e))for(let o of["id","for","name"]){let n=r.getAttribute(o);n&&r.setAttribute(o,`${n}$preview_${Gn}`)}return Gn++,$(e)}function Jn(e,t){let{sitemap$:r}=t;if(!(e instanceof HTMLAnchorElement))return y;if(!(V("navigation.instant.preview")||e.hasAttribute("data-preview")))return y;let o=z([Ye(e),it(e)]).pipe(m(([i,s])=>i||s),Y(),g(i=>i));return rt([r,o]).pipe(b(([i])=>{let s=new URL(e.href);return s.search=s.hash="",i.has(`${s}`)?$(s):y}),b(i=>xr(i).pipe(b(s=>os(s,i)))),b(i=>{let s=e.hash?`article [id="${e.hash.slice(1)}"]`:"article h1",a=ue(s,i);return typeof a=="undefined"?y:$(rs(a))})).pipe(b(i=>{let s=new F(a=>{let c=wr(...i);return a.next(c),document.body.append(c),()=>c.remove()});return Nt(e,R({content$:s},t))}))}var Xn=".node circle,.node ellipse,.node path,.node polygon,.node rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}marker{fill:var(--md-mermaid-edge-color)!important}.edgeLabel .label rect{fill:#0000}.label{color:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.label foreignObject{line-height:normal;overflow:visible}.label div .edgeLabel{color:var(--md-mermaid-label-fg-color)}.edgeLabel,.edgeLabel p,.label div .edgeLabel{background-color:var(--md-mermaid-label-bg-color)}.edgeLabel,.edgeLabel p{fill:var(--md-mermaid-label-bg-color);color:var(--md-mermaid-edge-color)}.edgePath .path,.flowchart-link{stroke:var(--md-mermaid-edge-color);stroke-width:.05rem}.edgePath .arrowheadPath{fill:var(--md-mermaid-edge-color);stroke:none}.cluster rect{fill:var(--md-default-fg-color--lightest);stroke:var(--md-default-fg-color--lighter)}.cluster span{color:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}g #flowchart-circleEnd,g #flowchart-circleStart,g #flowchart-crossEnd,g #flowchart-crossStart,g #flowchart-pointEnd,g #flowchart-pointStart{stroke:none}g.classGroup line,g.classGroup rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}g.classGroup text{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.classLabel .box{fill:var(--md-mermaid-label-bg-color);background-color:var(--md-mermaid-label-bg-color);opacity:1}.classLabel .label{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.node .divider{stroke:var(--md-mermaid-node-fg-color)}.relation{stroke:var(--md-mermaid-edge-color)}.cardinality{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.cardinality text{fill:inherit!important}defs #classDiagram-compositionEnd,defs #classDiagram-compositionStart,defs #classDiagram-dependencyEnd,defs #classDiagram-dependencyStart,defs #classDiagram-extensionEnd,defs #classDiagram-extensionStart{fill:var(--md-mermaid-edge-color)!important;stroke:var(--md-mermaid-edge-color)!important}defs #classDiagram-aggregationEnd,defs #classDiagram-aggregationStart{fill:var(--md-mermaid-label-bg-color)!important;stroke:var(--md-mermaid-edge-color)!important}g.stateGroup rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}g.stateGroup .state-title{fill:var(--md-mermaid-label-fg-color)!important;font-family:var(--md-mermaid-font-family)}g.stateGroup .composit{fill:var(--md-mermaid-label-bg-color)}.nodeLabel,.nodeLabel p{color:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}a .nodeLabel{text-decoration:underline}.node circle.state-end,.node circle.state-start,.start-state{fill:var(--md-mermaid-edge-color);stroke:none}.end-state-inner,.end-state-outer{fill:var(--md-mermaid-edge-color)}.end-state-inner,.node circle.state-end{stroke:var(--md-mermaid-label-bg-color)}.transition{stroke:var(--md-mermaid-edge-color)}[id^=state-fork] rect,[id^=state-join] rect{fill:var(--md-mermaid-edge-color)!important;stroke:none!important}.statediagram-cluster.statediagram-cluster .inner{fill:var(--md-default-bg-color)}.statediagram-cluster rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}.statediagram-state rect.divider{fill:var(--md-default-fg-color--lightest);stroke:var(--md-default-fg-color--lighter)}defs #statediagram-barbEnd{stroke:var(--md-mermaid-edge-color)}.attributeBoxEven,.attributeBoxOdd{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}.entityBox{fill:var(--md-mermaid-label-bg-color);stroke:var(--md-mermaid-node-fg-color)}.entityLabel{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.relationshipLabelBox{fill:var(--md-mermaid-label-bg-color);fill-opacity:1;background-color:var(--md-mermaid-label-bg-color);opacity:1}.relationshipLabel{fill:var(--md-mermaid-label-fg-color)}.relationshipLine{stroke:var(--md-mermaid-edge-color)}defs #ONE_OR_MORE_END *,defs #ONE_OR_MORE_START *,defs #ONLY_ONE_END *,defs #ONLY_ONE_START *,defs #ZERO_OR_MORE_END *,defs #ZERO_OR_MORE_START *,defs #ZERO_OR_ONE_END *,defs #ZERO_OR_ONE_START *{stroke:var(--md-mermaid-edge-color)!important}defs #ZERO_OR_MORE_END circle,defs #ZERO_OR_MORE_START circle{fill:var(--md-mermaid-label-bg-color)}.actor{fill:var(--md-mermaid-sequence-actor-bg-color);stroke:var(--md-mermaid-sequence-actor-border-color)}text.actor>tspan{fill:var(--md-mermaid-sequence-actor-fg-color);font-family:var(--md-mermaid-font-family)}line{stroke:var(--md-mermaid-sequence-actor-line-color)}.actor-man circle,.actor-man line{fill:var(--md-mermaid-sequence-actorman-bg-color);stroke:var(--md-mermaid-sequence-actorman-line-color)}.messageLine0,.messageLine1{stroke:var(--md-mermaid-sequence-message-line-color)}.note{fill:var(--md-mermaid-sequence-note-bg-color);stroke:var(--md-mermaid-sequence-note-border-color)}.loopText,.loopText>tspan,.messageText,.noteText>tspan{stroke:none;font-family:var(--md-mermaid-font-family)!important}.messageText{fill:var(--md-mermaid-sequence-message-fg-color)}.loopText,.loopText>tspan{fill:var(--md-mermaid-sequence-loop-fg-color)}.noteText>tspan{fill:var(--md-mermaid-sequence-note-fg-color)}#arrowhead path{fill:var(--md-mermaid-sequence-message-line-color);stroke:none}.loopLine{fill:var(--md-mermaid-sequence-loop-bg-color);stroke:var(--md-mermaid-sequence-loop-border-color)}.labelBox{fill:var(--md-mermaid-sequence-label-bg-color);stroke:none}.labelText,.labelText>span{fill:var(--md-mermaid-sequence-label-fg-color);font-family:var(--md-mermaid-font-family)}.sequenceNumber{fill:var(--md-mermaid-sequence-number-fg-color)}rect.rect{fill:var(--md-mermaid-sequence-box-bg-color);stroke:none}rect.rect+text.text{fill:var(--md-mermaid-sequence-box-fg-color)}defs #sequencenumber{fill:var(--md-mermaid-sequence-number-bg-color)!important}";var so,is=0;function as(){return typeof mermaid=="undefined"||mermaid instanceof Element?At("https://unpkg.com/mermaid@11/dist/mermaid.min.js"):$(void 0)}function Zn(e){return e.classList.remove("mermaid"),so||(so=as().pipe(O(()=>mermaid.initialize({startOnLoad:!1,themeCSS:Xn,sequence:{actorFontSize:"16px",messageFontSize:"16px",noteFontSize:"16px"}})),m(()=>{}),Z(1))),so.subscribe(()=>go(this,null,function*(){e.classList.add("mermaid");let t=`__mermaid_${is++}`,r=x("div",{class:"mermaid"}),o=e.textContent,{svg:n,fn:i}=yield mermaid.render(t,o),s=r.attachShadow({mode:"closed"});s.innerHTML=n,e.replaceWith(r),i==null||i(s)})),so.pipe(m(()=>({ref:e})))}var ei=x("table");function ti(e){return e.replaceWith(ei),ei.replaceWith(Un(e)),$({ref:e})}function ss(e){let t=e.find(r=>r.checked)||e[0];return L(...e.map(r=>h(r,"change").pipe(m(()=>j(`label[for="${r.id}"]`))))).pipe(Q(j(`label[for="${t.id}"]`)),m(r=>({active:r})))}function ri(e,{viewport$:t,target$:r}){let o=j(".tabbed-labels",e),n=M(":scope > input",e),i=no("prev");e.append(i);let s=no("next");return e.append(s),H(()=>{let a=new T,c=a.pipe(oe(),ae(!0));z([a,Le(e),mt(e)]).pipe(D(c),$e(1,ye)).subscribe({next([{active:p},l]){let f=Be(p),{width:u}=de(p);e.style.setProperty("--md-indicator-x",`${f.x}px`),e.style.setProperty("--md-indicator-width",`${u}px`);let d=gr(o);(f.xd.x+l.width)&&o.scrollTo({left:Math.max(0,f.x-16),behavior:"smooth"})},complete(){e.style.removeProperty("--md-indicator-x"),e.style.removeProperty("--md-indicator-width")}}),z([Ge(o),Le(o)]).pipe(D(c)).subscribe(([p,l])=>{let f=Ct(o);i.hidden=p.x<16,s.hidden=p.x>f.width-l.width-16}),L(h(i,"click").pipe(m(()=>-1)),h(s,"click").pipe(m(()=>1))).pipe(D(c)).subscribe(p=>{let{width:l}=de(o);o.scrollBy({left:l*p,behavior:"smooth"})}),r.pipe(D(c),g(p=>n.includes(p))).subscribe(p=>p.click()),o.classList.add("tabbed-labels--linked");for(let p of n){let l=j(`label[for="${p.id}"]`);l.replaceChildren(x("a",{href:`#${l.htmlFor}`,tabIndex:-1},...Array.from(l.childNodes))),h(l.firstElementChild,"click").pipe(D(c),g(f=>!(f.metaKey||f.ctrlKey)),O(f=>{f.preventDefault(),f.stopPropagation()})).subscribe(()=>{history.replaceState({},"",`#${l.htmlFor}`),l.click()})}return V("content.tabs.link")&&a.pipe(Ie(1),te(t)).subscribe(([{active:p},{offset:l}])=>{let f=p.innerText.trim();if(p.hasAttribute("data-md-switching"))p.removeAttribute("data-md-switching");else{let u=e.offsetTop-l.y;for(let v of M("[data-tabs]"))for(let S of M(":scope > input",v)){let X=j(`label[for="${S.id}"]`);if(X!==p&&X.innerText.trim()===f){X.setAttribute("data-md-switching",""),S.click();break}}window.scrollTo({top:e.offsetTop-u});let d=__md_get("__tabs")||[];__md_set("__tabs",[...new Set([f,...d])])}}),a.pipe(D(c)).subscribe(()=>{for(let p of M("audio, video",e))p.pause()}),ss(n).pipe(O(p=>a.next(p)),A(()=>a.complete()),m(p=>R({ref:e},p)))}).pipe(et(pe))}function oi(e,t){let{viewport$:r,target$:o,print$:n}=t;return L(...M(".annotate:not(.highlight)",e).map(i=>zn(i,{target$:o,print$:n})),...M("pre:not(.mermaid) > code",e).map(i=>Yn(i,{target$:o,print$:n})),...M("a:not([title])",e).map(i=>Jn(i,t)),...M("pre.mermaid",e).map(i=>Zn(i)),...M("table:not([class])",e).map(i=>ti(i)),...M("details",e).map(i=>Bn(i,{target$:o,print$:n})),...M("[data-tabs]",e).map(i=>ri(i,{viewport$:r,target$:o})),...M("[title]",e).filter(()=>V("content.tooltips")).map(i=>Xe(i,{viewport$:r})),...M(".footnote-ref",e).filter(()=>V("content.footnote.tooltips")).map(i=>Nt(i,{content$:new F(s=>{let a=new URL(i.href).hash.slice(1),c=Array.from(document.getElementById(a).cloneNode(!0).children),p=wr(...c);return s.next(p),document.body.append(p),()=>p.remove()}),viewport$:r})))}function cs(e,{alert$:t}){return t.pipe(b(r=>L($(!0),$(!1).pipe(nt(2e3))).pipe(m(o=>({message:r,active:o})))))}function ni(e,t){let r=j(".md-typeset",e);return H(()=>{let o=new T;return o.subscribe(({message:n,active:i})=>{e.classList.toggle("md-dialog--active",i),r.textContent=n}),cs(e,t).pipe(O(n=>o.next(n)),A(()=>o.complete()),m(n=>R({ref:e},n)))})}var ps=0;function ls(e,t){document.body.append(e);let{width:r}=de(e);e.style.setProperty("--md-tooltip-width",`${r}px`),e.remove();let o=vr(t),n=typeof o!="undefined"?Ge(o):$({x:0,y:0}),i=L(Ye(t),it(t)).pipe(Y());return z([i,n]).pipe(m(([s,a])=>{let{x:c,y:p}=Be(t),l=de(t),f=t.closest("table");return f&&t.parentElement&&(c+=f.offsetLeft+t.parentElement.offsetLeft,p+=f.offsetTop+t.parentElement.offsetTop),{active:s,offset:{x:c-a.x+l.width/2-r/2,y:p-a.y+l.height+8}}}))}function ii(e){let t=e.title;if(!t.length)return y;let r=`__tooltip_${ps++}`,o=Vt(r,"inline"),n=j(".md-typeset",o);return n.innerHTML=t,H(()=>{let i=new T;return i.subscribe({next({offset:s}){o.style.setProperty("--md-tooltip-x",`${s.x}px`),o.style.setProperty("--md-tooltip-y",`${s.y}px`)},complete(){o.style.removeProperty("--md-tooltip-x"),o.style.removeProperty("--md-tooltip-y")}}),L(i.pipe(g(({active:s})=>s)),i.pipe(Ae(250),g(({active:s})=>!s))).subscribe({next({active:s}){s?(e.insertAdjacentElement("afterend",o),e.setAttribute("aria-describedby",r),e.removeAttribute("title")):(o.remove(),e.removeAttribute("aria-describedby"),e.setAttribute("title",t))},complete(){o.remove(),e.removeAttribute("aria-describedby"),e.setAttribute("title",t)}}),i.pipe($e(16,ye)).subscribe(({active:s})=>{o.classList.toggle("md-tooltip--active",s)}),i.pipe(gt(125,ye),g(()=>!!e.offsetParent),m(()=>e.offsetParent.getBoundingClientRect()),m(({x:s})=>s)).subscribe({next(s){s?o.style.setProperty("--md-tooltip-0",`${-s}px`):o.style.removeProperty("--md-tooltip-0")},complete(){o.style.removeProperty("--md-tooltip-0")}}),ls(o,e).pipe(O(s=>i.next(s)),A(()=>i.complete()),m(s=>R({ref:e},s)))}).pipe(et(pe))}function ms({viewport$:e}){if(!V("header.autohide"))return $(!1);let t=e.pipe(m(({offset:{y:n}})=>n),ot(2,1),m(([n,i])=>[nMath.abs(i-n.y)>100),m(([,[n]])=>n),Y()),o=Je("search");return z([e,o]).pipe(m(([{offset:n},i])=>n.y>400&&!i),Y(),b(n=>n?r:$(!1)),Q(!1))}function ai(e,t){return H(()=>z([Le(e),ms(t)])).pipe(m(([{height:r},o])=>({height:r,hidden:o})),Y((r,o)=>r.height===o.height&&r.hidden===o.hidden),Z(1))}function si(e,{header$:t,main$:r}){return H(()=>{let o=new T,n=o.pipe(oe(),ae(!0));o.pipe(ne("active"),Re(t)).subscribe(([{active:s},{hidden:a}])=>{e.classList.toggle("md-header--shadow",s&&!a),e.hidden=a});let i=fe(M("[title]",e)).pipe(g(()=>V("content.tooltips")),J(s=>ii(s)));return r.subscribe(o),t.pipe(D(n),m(s=>R({ref:e},s)),Ve(i.pipe(D(n))))})}function fs(e,{viewport$:t,header$:r}){return Er(e,{viewport$:t,header$:r}).pipe(m(({offset:{y:o}})=>{let{height:n}=de(e);return{active:o>=n}}),ne("active"))}function ci(e,t){return H(()=>{let r=new T;r.subscribe({next({active:n}){e.classList.toggle("md-header__title--active",n)},complete(){e.classList.remove("md-header__title--active")}});let o=ue(".md-content h1");return typeof o=="undefined"?y:fs(o,t).pipe(O(n=>r.next(n)),A(()=>r.complete()),m(n=>R({ref:e},n)))})}function pi(e,{viewport$:t,header$:r}){let o=r.pipe(m(({height:i})=>i),Y()),n=o.pipe(b(()=>Le(e).pipe(m(({height:i})=>({top:e.offsetTop,bottom:e.offsetTop+i})),ne("bottom"))));return z([o,n,t]).pipe(m(([i,{top:s,bottom:a},{offset:{y:c},size:{height:p}}])=>(p=Math.max(0,p-Math.max(0,s-c,i)-Math.max(0,p+c-a)),{offset:s-i,height:p,active:s-i<=c})),Y((i,s)=>i.offset===s.offset&&i.height===s.height&&i.active===s.active))}function us(e){let t=__md_get("__palette")||{index:e.findIndex(o=>matchMedia(o.getAttribute("data-md-color-media")).matches)},r=Math.max(0,Math.min(t.index,e.length-1));return $(...e).pipe(J(o=>h(o,"change").pipe(m(()=>o))),Q(e[r]),m(o=>({index:e.indexOf(o),color:{media:o.getAttribute("data-md-color-media"),scheme:o.getAttribute("data-md-color-scheme"),primary:o.getAttribute("data-md-color-primary"),accent:o.getAttribute("data-md-color-accent")}})),Z(1))}function li(e){let t=M("input",e),r=x("meta",{name:"theme-color"});document.head.appendChild(r);let o=x("meta",{name:"color-scheme"});document.head.appendChild(o);let n=Wt("(prefers-color-scheme: light)");return H(()=>{let i=new T;return i.subscribe(s=>{if(document.body.setAttribute("data-md-color-switching",""),s.color.media==="(prefers-color-scheme)"){let a=matchMedia("(prefers-color-scheme: light)"),c=document.querySelector(a.matches?"[data-md-color-media='(prefers-color-scheme: light)']":"[data-md-color-media='(prefers-color-scheme: dark)']");s.color.scheme=c.getAttribute("data-md-color-scheme"),s.color.primary=c.getAttribute("data-md-color-primary"),s.color.accent=c.getAttribute("data-md-color-accent")}for(let[a,c]of Object.entries(s.color))document.body.setAttribute(`data-md-color-${a}`,c);for(let a=0;as.key==="Enter"),te(i,(s,a)=>a)).subscribe(({index:s})=>{s=(s+1)%t.length,t[s].click(),t[s].focus()}),i.pipe(m(()=>{let s=Ce("header"),a=window.getComputedStyle(s);return o.content=a.colorScheme,a.backgroundColor.match(/\d+/g).map(c=>(+c).toString(16).padStart(2,"0")).join("")})).subscribe(s=>r.content=`#${s}`),i.pipe(xe(pe)).subscribe(()=>{document.body.removeAttribute("data-md-color-switching")}),us(t).pipe(D(n.pipe(Ie(1))),vt(),O(s=>i.next(s)),A(()=>i.complete()),m(s=>R({ref:e},s)))})}function mi(e,{progress$:t}){return H(()=>{let r=new T;return r.subscribe(({value:o})=>{e.style.setProperty("--md-progress-value",`${o}`)}),t.pipe(O(o=>r.next({value:o})),A(()=>r.complete()),m(o=>({ref:e,value:o})))})}function fi(e,t){return e.protocol=t.protocol,e.hostname=t.hostname,e}function ds(e,t){let r=new Map;for(let o of M("url",e)){let n=j("loc",o),i=[fi(new URL(n.textContent),t)];r.set(`${i[0]}`,i);for(let s of M("[rel=alternate]",o)){let a=s.getAttribute("href");a!=null&&i.push(fi(new URL(a),t))}}return r}function Ht(e){return En(new URL("sitemap.xml",e)).pipe(m(t=>ds(t,new URL(e))),ve(()=>$(new Map)),le())}function ui({document$:e}){let t=new Map;e.pipe(b(()=>M("link[rel=alternate]")),m(r=>new URL(r.href)),g(r=>!t.has(r.toString())),J(r=>Ht(r).pipe(m(o=>[r,o]),ve(()=>y)))).subscribe(([r,o])=>{t.set(r.toString().replace(/\/$/,""),o)}),h(document.body,"click").pipe(g(r=>!r.metaKey&&!r.ctrlKey),b(r=>{if(r.target instanceof Element){let o=r.target.closest("a");if(o&&!o.target){let n=[...t].find(([f])=>o.href.startsWith(`${f}/`));if(typeof n=="undefined")return y;let[i,s]=n,a=we();if(a.href.startsWith(i))return y;let c=Te(),p=a.href.replace(c.base,"");p=`${i}/${p}`;let l=s.has(p.split("#")[0])?new URL(p,c.base):new URL(i);return r.preventDefault(),$(l)}}return y})).subscribe(r=>st(r,!0))}var co=Rt(ao());function hs(e){e.setAttribute("data-md-copying","");let t=e.closest("[data-copy]"),r=t?t.getAttribute("data-copy"):e.innerText;return e.removeAttribute("data-md-copying"),r.trimEnd()}function di({alert$:e}){co.default.isSupported()&&new F(t=>{new co.default("[data-clipboard-target], [data-clipboard-text]",{text:r=>r.getAttribute("data-clipboard-text")||hs(j(r.getAttribute("data-clipboard-target")))}).on("success",r=>t.next(r))}).pipe(O(t=>{t.trigger.focus()}),m(()=>Me("clipboard.copied"))).subscribe(e)}function hi(e,t){if(!(e.target instanceof Element))return y;let r=e.target.closest("a");if(r===null)return y;if(r.target||e.metaKey||e.ctrlKey)return y;let o=new URL(r.href);return o.search=o.hash="",t.has(`${o}`)?(e.preventDefault(),$(r)):y}function bi(e){let t=new Map;for(let r of M(":scope > *",e.head))t.set(r.outerHTML,r);return t}function vi(e){for(let t of M("[href], [src]",e))for(let r of["href","src"]){let o=t.getAttribute(r);if(o&&!/^(?:[a-z]+:)?\/\//i.test(o)){t[r]=t[r];break}}return $(e)}function bs(e){for(let o of["[data-md-component=announce]","[data-md-component=container]","[data-md-component=header-topic]","[data-md-component=outdated]","[data-md-component=logo]","[data-md-component=skip]",...V("navigation.tabs.sticky")?["[data-md-component=tabs]"]:[]]){let n=ue(o),i=ue(o,e);typeof n!="undefined"&&typeof i!="undefined"&&n.replaceWith(i)}let t=bi(document);for(let[o,n]of bi(e))t.has(o)?t.delete(o):document.head.appendChild(n);for(let o of t.values()){let n=o.getAttribute("name");n!=="theme-color"&&n!=="color-scheme"&&o.remove()}let r=Ce("container");return Ke(M("script",r)).pipe(b(o=>{let n=e.createElement("script");if(o.src){for(let i of o.getAttributeNames())n.setAttribute(i,o.getAttribute(i));return o.replaceWith(n),new F(i=>{n.onload=()=>i.complete()})}else return n.textContent=o.textContent,o.replaceWith(n),y}),oe(),ae(document))}function gi({sitemap$:e,location$:t,viewport$:r,progress$:o}){if(location.protocol==="file:")return y;$(document).subscribe(vi);let n=h(document.body,"click").pipe(Re(e),b(([a,c])=>hi(a,c)),m(({href:a})=>new URL(a)),le()),i=h(window,"popstate").pipe(m(we),le());n.pipe(te(r)).subscribe(([a,{offset:c}])=>{history.replaceState(c,""),history.pushState(null,"",a)}),L(n,i).subscribe(t);let s=t.pipe(ne("pathname"),b(a=>xr(a,{progress$:o}).pipe(ve(()=>(st(a,!0),y)))),b(vi),b(bs),le());return L(s.pipe(te(t,(a,c)=>c)),s.pipe(b(()=>t),ne("pathname"),b(()=>t),ne("hash")),t.pipe(Y((a,c)=>a.pathname===c.pathname&&a.hash===c.hash),b(()=>n),O(()=>history.back()))).subscribe(a=>{var c,p;history.state!==null||!a.hash?window.scrollTo(0,(p=(c=history.state)==null?void 0:c.y)!=null?p:0):(history.scrollRestoration="auto",gn(a.hash),history.scrollRestoration="manual")}),t.subscribe(()=>{history.scrollRestoration="manual"}),h(window,"beforeunload").subscribe(()=>{history.scrollRestoration="auto"}),r.pipe(ne("offset"),Ae(100)).subscribe(({offset:a})=>{history.replaceState(a,"")}),V("navigation.instant.prefetch")&&L(h(document.body,"mousemove"),h(document.body,"focusin")).pipe(Re(e),b(([a,c])=>hi(a,c)),Ae(25),Qr(({href:a})=>a),hr(a=>{let c=document.createElement("link");return c.rel="prefetch",c.href=a.toString(),document.head.appendChild(c),h(c,"load").pipe(m(()=>c),Ee(1))})).subscribe(a=>a.remove()),s}var yi=Rt(ro());function xi(e){let t=e.separator.split("|").map(n=>n.replace(/(\(\?[!=<][^)]+\))/g,"").length===0?"\uFFFD":n).join("|"),r=new RegExp(t,"img"),o=(n,i,s)=>`${i}${s}`;return n=>{n=n.replace(/[\s*+\-:~^]+/g," ").trim();let i=new RegExp(`(^|${e.separator}|)(${n.replace(/[|\\{}()[\]^$+*?.-]/g,"\\$&").replace(r,"|")})`,"img");return s=>(0,yi.default)(s).replace(i,o).replace(/<\/mark>(\s+)]*>/img,"$1")}}function qt(e){return e.type===1}function Sr(e){return e.type===3}function Ei(e,t){let r=Mn(e);return L($(location.protocol!=="file:"),Je("search")).pipe(Pe(o=>o),b(()=>t)).subscribe(({config:o,docs:n})=>r.next({type:0,data:{config:o,docs:n,options:{suggest:V("search.suggest")}}})),r}function wi(e){var l;let{selectedVersionSitemap:t,selectedVersionBaseURL:r,currentLocation:o,currentBaseURL:n}=e,i=(l=po(n))==null?void 0:l.pathname;if(i===void 0)return;let s=ys(o.pathname,i);if(s===void 0)return;let a=Es(t.keys());if(!t.has(a))return;let c=po(s,a);if(!c||!t.has(c.href))return;let p=po(s,r);if(p)return p.hash=o.hash,p.search=o.search,p}function po(e,t){try{return new URL(e,t)}catch(r){return}}function ys(e,t){if(e.startsWith(t))return e.slice(t.length)}function xs(e,t){let r=Math.min(e.length,t.length),o;for(o=0;oy)),o=r.pipe(m(n=>{let[,i]=t.base.match(/([^/]+)\/?$/);return n.find(({version:s,aliases:a})=>s===i||a.includes(i))||n[0]}));r.pipe(m(n=>new Map(n.map(i=>[`${new URL(`../${i.version}/`,t.base)}`,i]))),b(n=>h(document.body,"click").pipe(g(i=>!i.metaKey&&!i.ctrlKey),te(o),b(([i,s])=>{if(i.target instanceof Element){let a=i.target.closest("a");if(a&&!a.target&&n.has(a.href)){let c=a.href;return!i.target.closest(".md-version")&&n.get(c)===s?y:(i.preventDefault(),$(new URL(c)))}}return y}),b(i=>Ht(i).pipe(m(s=>{var a;return(a=wi({selectedVersionSitemap:s,selectedVersionBaseURL:i,currentLocation:we(),currentBaseURL:t.base}))!=null?a:i})))))).subscribe(n=>st(n,!0)),z([r,o]).subscribe(([n,i])=>{j(".md-header__topic").appendChild(Dn(n,i))}),e.pipe(b(()=>o)).subscribe(n=>{var s;let i=__md_get("__outdated",sessionStorage);if(i===null){i=!0;let a=((s=t.version)==null?void 0:s.default)||"latest";Array.isArray(a)||(a=[a]);e:for(let c of a)for(let p of n.aliases.concat(n.version))if(new RegExp(c,"i").test(p)){i=!1;break e}__md_set("__outdated",i,sessionStorage)}if(i)for(let a of me("outdated"))a.hidden=!1})}function ws(e,{worker$:t}){let{searchParams:r}=we();r.has("q")&&(at("search",!0),e.value=r.get("q"),e.focus(),Je("search").pipe(Pe(i=>!i)).subscribe(()=>{let i=we();i.searchParams.delete("q"),history.replaceState({},"",`${i}`)}));let o=Ye(e),n=L(t.pipe(Pe(qt)),h(e,"keyup"),o).pipe(m(()=>e.value),Y());return z([n,o]).pipe(m(([i,s])=>({value:i,focus:s})),Z(1))}function Si(e,{worker$:t}){let r=new T,o=r.pipe(oe(),ae(!0));z([t.pipe(Pe(qt)),r],(i,s)=>s).pipe(ne("value")).subscribe(({value:i})=>t.next({type:2,data:i})),r.pipe(ne("focus")).subscribe(({focus:i})=>{i&&at("search",i)}),h(e.form,"reset").pipe(D(o)).subscribe(()=>e.focus());let n=j("header [for=__search]");return h(n,"click").subscribe(()=>e.focus()),ws(e,{worker$:t}).pipe(O(i=>r.next(i)),A(()=>r.complete()),m(i=>R({ref:e},i)),Z(1))}function Oi(e,{worker$:t,query$:r}){let o=new T,n=un(e.parentElement).pipe(g(Boolean)),i=e.parentElement,s=j(":scope > :first-child",e),a=j(":scope > :last-child",e);Je("search").subscribe(l=>a.setAttribute("role",l?"list":"presentation")),o.pipe(te(r),Gr(t.pipe(Pe(qt)))).subscribe(([{items:l},{value:f}])=>{switch(l.length){case 0:s.textContent=f.length?Me("search.result.none"):Me("search.result.placeholder");break;case 1:s.textContent=Me("search.result.one");break;default:let u=br(l.length);s.textContent=Me("search.result.other",u)}});let c=o.pipe(O(()=>a.innerHTML=""),b(({items:l})=>L($(...l.slice(0,10)),$(...l.slice(10)).pipe(ot(4),Xr(n),b(([f])=>f)))),m(Fn),le());return c.subscribe(l=>a.appendChild(l)),c.pipe(J(l=>{let f=ue("details",l);return typeof f=="undefined"?y:h(f,"toggle").pipe(D(o),m(()=>f))})).subscribe(l=>{l.open===!1&&l.offsetTop<=i.scrollTop&&i.scrollTo({top:l.offsetTop})}),t.pipe(g(Sr),m(({data:l})=>l)).pipe(O(l=>o.next(l)),A(()=>o.complete()),m(l=>R({ref:e},l)))}function Ts(e,{query$:t}){return t.pipe(m(({value:r})=>{let o=we();return o.hash="",r=r.replace(/\s+/g,"+").replace(/&/g,"%26").replace(/=/g,"%3D"),o.search=`q=${r}`,{url:o}}))}function Li(e,t){let r=new T,o=r.pipe(oe(),ae(!0));return r.subscribe(({url:n})=>{e.setAttribute("data-clipboard-text",e.href),e.href=`${n}`}),h(e,"click").pipe(D(o)).subscribe(n=>n.preventDefault()),Ts(e,t).pipe(O(n=>r.next(n)),A(()=>r.complete()),m(n=>R({ref:e},n)))}function Mi(e,{worker$:t,keyboard$:r}){let o=new T,n=Ce("search-query"),i=L(h(n,"keydown"),h(n,"focus")).pipe(xe(pe),m(()=>n.value),Y());return o.pipe(Re(i),m(([{suggest:a},c])=>{let p=c.split(/([\s-]+)/);if(a!=null&&a.length&&p[p.length-1]){let l=a[a.length-1];l.startsWith(p[p.length-1])&&(p[p.length-1]=l)}else p.length=0;return p})).subscribe(a=>e.innerHTML=a.join("").replace(/\s/g," ")),r.pipe(g(({mode:a})=>a==="search")).subscribe(a=>{switch(a.type){case"ArrowRight":e.innerText.length&&n.selectionStart===n.value.length&&(n.value=e.innerText);break}}),t.pipe(g(Sr),m(({data:a})=>a)).pipe(O(a=>o.next(a)),A(()=>o.complete()),m(()=>({ref:e})))}function _i(e,{index$:t,keyboard$:r}){let o=Te();try{let n=Ei(o.search,t),i=Ce("search-query",e),s=Ce("search-result",e);h(e,"click").pipe(g(({target:c})=>c instanceof Element&&!!c.closest("a"))).subscribe(()=>at("search",!1)),r.pipe(g(({mode:c})=>c==="search")).subscribe(c=>{let p=Ne();switch(c.type){case"Enter":if(p===i){let l=new Map;for(let f of M(":first-child [href]",s)){let u=f.firstElementChild;l.set(f,parseFloat(u.getAttribute("data-md-score")))}if(l.size){let[[f]]=[...l].sort(([,u],[,d])=>d-u);f.click()}c.claim()}break;case"Escape":case"Tab":at("search",!1),i.blur();break;case"ArrowUp":case"ArrowDown":if(typeof p=="undefined")i.focus();else{let l=[i,...M(":not(details) > [href], summary, details[open] [href]",s)],f=Math.max(0,(Math.max(0,l.indexOf(p))+l.length+(c.type==="ArrowUp"?-1:1))%l.length);l[f].focus()}c.claim();break;default:i!==Ne()&&i.focus()}}),r.pipe(g(({mode:c})=>c==="global")).subscribe(c=>{switch(c.type){case"f":case"s":case"/":i.focus(),i.select(),c.claim();break}});let a=Si(i,{worker$:n});return L(a,Oi(s,{worker$:n,query$:a})).pipe(Ve(...me("search-share",e).map(c=>Li(c,{query$:a})),...me("search-suggest",e).map(c=>Mi(c,{worker$:n,keyboard$:r}))))}catch(n){return e.hidden=!0,tt}}function Ai(e,{index$:t,location$:r}){return z([t,r.pipe(Q(we()),g(o=>!!o.searchParams.get("h")))]).pipe(m(([o,n])=>xi(o.config)(n.searchParams.get("h"))),m(o=>{var s;let n=new Map,i=document.createNodeIterator(e,NodeFilter.SHOW_TEXT);for(let a=i.nextNode();a;a=i.nextNode())if((s=a.parentElement)!=null&&s.offsetHeight){let c=a.textContent,p=o(c);p.length>c.length&&n.set(a,p)}for(let[a,c]of n){let{childNodes:p}=x("span",null,c);a.replaceWith(...Array.from(p))}return{ref:e,nodes:n}}))}function Ss(e,{viewport$:t,main$:r}){let o=e.closest(".md-grid"),n=o.offsetTop-o.parentElement.offsetTop;return z([r,t]).pipe(m(([{offset:i,height:s},{offset:{y:a}}])=>(s=s+Math.min(n,Math.max(0,a-i))-n,{height:s,locked:a>=i+n})),Y((i,s)=>i.height===s.height&&i.locked===s.locked))}function lo(e,o){var n=o,{header$:t}=n,r=vo(n,["header$"]);let i=j(".md-sidebar__scrollwrap",e),{y:s}=Be(i);return H(()=>{let a=new T,c=a.pipe(oe(),ae(!0)),p=a.pipe($e(0,ye));return p.pipe(te(t)).subscribe({next([{height:l},{height:f}]){i.style.height=`${l-2*s}px`,e.style.top=`${f}px`},complete(){i.style.height="",e.style.top=""}}),p.pipe(Pe()).subscribe(()=>{for(let l of M(".md-nav__link--active[href]",e)){if(!l.clientHeight)continue;let f=l.closest(".md-sidebar__scrollwrap");if(typeof f!="undefined"){let u=l.offsetTop-f.offsetTop,{height:d}=de(f);f.scrollTo({top:u-d/2})}}}),fe(M("label[tabindex]",e)).pipe(J(l=>h(l,"click").pipe(xe(pe),m(()=>l),D(c)))).subscribe(l=>{let f=j(`[id="${l.htmlFor}"]`);j(`[aria-labelledby="${l.id}"]`).setAttribute("aria-expanded",`${f.checked}`)}),V("content.tooltips")&&fe(M("abbr[title]",e)).pipe(J(l=>Xe(l,{viewport$})),D(c)).subscribe(),Ss(e,r).pipe(O(l=>a.next(l)),A(()=>a.complete()),m(l=>R({ref:e},l)))})}function Ci(e,t){if(typeof t!="undefined"){let r=`https://api.github.com/repos/${e}/${t}`;return rt(ze(`${r}/releases/latest`).pipe(ve(()=>y),m(o=>({version:o.tag_name})),Qe({})),ze(r).pipe(ve(()=>y),m(o=>({stars:o.stargazers_count,forks:o.forks_count})),Qe({}))).pipe(m(([o,n])=>R(R({},o),n)))}else{let r=`https://api.github.com/users/${e}`;return ze(r).pipe(m(o=>({repositories:o.public_repos})),Qe({}))}}function ki(e,t){let r=`https://${e}/api/v4/projects/${encodeURIComponent(t)}`;return rt(ze(`${r}/releases/permalink/latest`).pipe(ve(()=>y),m(({tag_name:o})=>({version:o})),Qe({})),ze(r).pipe(ve(()=>y),m(({star_count:o,forks_count:n})=>({stars:o,forks:n})),Qe({}))).pipe(m(([o,n])=>R(R({},o),n)))}function Hi(e){let t=e.match(/^.+github\.com\/([^/]+)\/?([^/]+)?/i);if(t){let[,r,o]=t;return Ci(r,o)}if(t=e.match(/^.+?([^/]*gitlab[^/]+)\/(.+?)\/?$/i),t){let[,r,o]=t;return ki(r,o)}return y}var Os;function Ls(e){return Os||(Os=H(()=>{let t=__md_get("__source",sessionStorage);if(t)return $(t);if(me("consent").length){let o=__md_get("__consent");if(!(o&&o.github))return y}return Hi(e.href).pipe(O(o=>__md_set("__source",o,sessionStorage)))}).pipe(ve(()=>y),g(t=>Object.keys(t).length>0),m(t=>({facts:t})),Z(1)))}function $i(e){let t=j(":scope > :last-child",e);return H(()=>{let r=new T;return r.subscribe(({facts:o})=>{t.appendChild(jn(o)),t.classList.add("md-source__repository--active")}),Ls(e).pipe(O(o=>r.next(o)),A(()=>r.complete()),m(o=>R({ref:e},o)))})}function Ms(e,{viewport$:t,header$:r}){return Le(document.body).pipe(b(()=>Er(e,{header$:r,viewport$:t})),m(({offset:{y:o}})=>({hidden:o>=10})),ne("hidden"))}function Ri(e,t){return H(()=>{let r=new T;return r.subscribe({next({hidden:o}){e.hidden=o},complete(){e.hidden=!1}}),(V("navigation.tabs.sticky")?$({hidden:!1}):Ms(e,t)).pipe(O(o=>r.next(o)),A(()=>r.complete()),m(o=>R({ref:e},o)))})}function _s(e,{viewport$:t,header$:r}){let o=new Map,n=M(".md-nav__link",e);for(let a of n){let c=decodeURIComponent(a.hash.substring(1)),p=ue(`[id="${c}"]`);typeof p!="undefined"&&o.set(a,p)}let i=r.pipe(ne("height"),m(({height:a})=>{let c=Ce("main"),p=j(":scope > :first-child",c);return a+.8*(p.offsetTop-c.offsetTop)}),le());return Le(document.body).pipe(ne("height"),b(a=>H(()=>{let c=[];return $([...o].reduce((p,[l,f])=>{for(;c.length&&o.get(c[c.length-1]).tagName>=f.tagName;)c.pop();let u=f.offsetTop;for(;!u&&f.parentElement;)f=f.parentElement,u=f.offsetTop;let d=f.offsetParent;for(;d;d=d.offsetParent)u+=d.offsetTop;return p.set([...c=[...c,l]].reverse(),u)},new Map))}).pipe(m(c=>new Map([...c].sort(([,p],[,l])=>p-l))),Re(i),b(([c,p])=>t.pipe(Dt(([l,f],{offset:{y:u},size:d})=>{let v=u+d.height>=Math.floor(a.height);for(;f.length;){let[,S]=f[0];if(S-p=u&&!v)f=[l.pop(),...f];else break}return[l,f]},[[],[...c]]),Y((l,f)=>l[0]===f[0]&&l[1]===f[1])))))).pipe(m(([a,c])=>({prev:a.map(([p])=>p),next:c.map(([p])=>p)})),Q({prev:[],next:[]}),ot(2,1),m(([a,c])=>a.prev.length{let i=new T,s=i.pipe(oe(),ae(!0));if(i.subscribe(({prev:a,next:c})=>{for(let[p]of c)p.classList.remove("md-nav__link--passed"),p.classList.remove("md-nav__link--active");for(let[p,[l]]of a.entries())l.classList.add("md-nav__link--passed"),l.classList.toggle("md-nav__link--active",p===a.length-1)}),V("toc.follow")){let a=L(t.pipe(Ae(1),m(()=>{})),t.pipe(Ae(250),m(()=>"smooth")));i.pipe(g(({prev:c})=>c.length>0),Re(o.pipe(xe(pe))),te(a)).subscribe(([[{prev:c}],p])=>{let[l]=c[c.length-1];if(l.offsetHeight){let f=vr(l);if(typeof f!="undefined"){let u=l.offsetTop-f.offsetTop,{height:d}=de(f);f.scrollTo({top:u-d/2,behavior:p})}}})}return V("navigation.tracking")&&t.pipe(D(s),ne("offset"),Ae(250),Ie(1),D(n.pipe(Ie(1))),vt({delay:250}),te(i)).subscribe(([,{prev:a}])=>{let c=we(),p=a[a.length-1];if(p&&p.length){let[l]=p,{hash:f}=new URL(l.href);c.hash!==f&&(c.hash=f,history.replaceState({},"",`${c}`))}else c.hash="",history.replaceState({},"",`${c}`)}),_s(e,{viewport$:t,header$:r}).pipe(O(a=>i.next(a)),A(()=>i.complete()),m(a=>R({ref:e},a)))})}function As(e,{viewport$:t,main$:r,target$:o}){let n=t.pipe(m(({offset:{y:s}})=>s),ot(2,1),m(([s,a])=>s>a&&a>0),Y()),i=r.pipe(m(({active:s})=>s));return z([i,n]).pipe(m(([s,a])=>!(s&&a)),Y(),D(o.pipe(Ie(1))),ae(!0),vt({delay:250}),m(s=>({hidden:s})))}function Ii(e,{viewport$:t,header$:r,main$:o,target$:n}){let i=new T,s=i.pipe(oe(),ae(!0));return i.subscribe({next({hidden:a}){e.hidden=a,a?(e.setAttribute("tabindex","-1"),e.blur()):e.removeAttribute("tabindex")},complete(){e.style.top="",e.hidden=!0,e.removeAttribute("tabindex")}}),r.pipe(D(s),ne("height")).subscribe(({height:a})=>{e.style.top=`${a+16}px`}),h(e,"click").subscribe(a=>{a.preventDefault(),window.scrollTo({top:0})}),As(e,{viewport$:t,main$:o,target$:n}).pipe(O(a=>i.next(a)),A(()=>i.complete()),m(a=>R({ref:e},a)))}function Fi({document$:e,viewport$:t}){e.pipe(b(()=>M(".md-ellipsis")),J(r=>mt(r).pipe(D(e.pipe(Ie(1))),g(o=>o),m(()=>r),Ee(1))),g(r=>r.offsetWidth{let o=r.innerText,n=r.closest("a")||r;return n.title=o,V("content.tooltips")?Xe(n,{viewport$:t}).pipe(D(e.pipe(Ie(1))),A(()=>n.removeAttribute("title"))):y})).subscribe(),V("content.tooltips")&&e.pipe(b(()=>M(".md-status")),J(r=>Xe(r,{viewport$:t}))).subscribe()}function ji({document$:e,tablet$:t}){e.pipe(b(()=>M(".md-toggle--indeterminate")),O(r=>{r.indeterminate=!0,r.checked=!1}),J(r=>h(r,"change").pipe(Jr(()=>r.classList.contains("md-toggle--indeterminate")),m(()=>r))),te(t)).subscribe(([r,o])=>{r.classList.remove("md-toggle--indeterminate"),o&&(r.checked=!1)})}function Cs(){return/(iPad|iPhone|iPod)/.test(navigator.userAgent)}function Ui({document$:e}){e.pipe(b(()=>M("[data-md-scrollfix]")),O(t=>t.removeAttribute("data-md-scrollfix")),g(Cs),J(t=>h(t,"touchstart").pipe(m(()=>t)))).subscribe(t=>{let r=t.scrollTop;r===0?t.scrollTop=1:r+t.offsetHeight===t.scrollHeight&&(t.scrollTop=r-1)})}function Di({viewport$:e,tablet$:t}){z([Je("search"),t]).pipe(m(([r,o])=>r&&!o),b(r=>$(r).pipe(nt(r?400:100))),te(e)).subscribe(([r,{offset:{y:o}}])=>{if(r)document.body.setAttribute("data-md-scrolllock",""),document.body.style.top=`-${o}px`;else{let n=-1*parseInt(document.body.style.top,10);document.body.removeAttribute("data-md-scrolllock"),document.body.style.top="",n&&window.scrollTo(0,n)}})}Object.entries||(Object.entries=function(e){let t=[];for(let r of Object.keys(e))t.push([r,e[r]]);return t});Object.values||(Object.values=function(e){let t=[];for(let r of Object.keys(e))t.push(e[r]);return t});typeof Element!="undefined"&&(Element.prototype.scrollTo||(Element.prototype.scrollTo=function(e,t){typeof e=="object"?(this.scrollLeft=e.left,this.scrollTop=e.top):(this.scrollLeft=e,this.scrollTop=t)}),Element.prototype.replaceWith||(Element.prototype.replaceWith=function(...e){let t=this.parentNode;if(t){e.length===0&&t.removeChild(this);for(let r=e.length-1;r>=0;r--){let o=e[r];typeof o=="string"?o=document.createTextNode(o):o.parentNode&&o.parentNode.removeChild(o),r?t.insertBefore(this.previousSibling,o):t.replaceChild(o,this)}}}));function ks(){return location.protocol==="file:"?At(`${new URL("search/search_index.js",Or.base)}`).pipe(m(()=>__index),Z(1)):ze(new URL("search/search_index.json",Or.base))}document.documentElement.classList.remove("no-js");document.documentElement.classList.add("js");var ct=an(),Qt=bn(),$t=yn(Qt),mo=hn(),ke=Ln(),Lr=Wt("(min-width: 960px)"),Vi=Wt("(min-width: 1220px)"),Ni=xn(),Or=Te(),zi=document.forms.namedItem("search")?ks():tt,fo=new T;di({alert$:fo});ui({document$:ct});var uo=new T,qi=Ht(Or.base);V("navigation.instant")&&gi({sitemap$:qi,location$:Qt,viewport$:ke,progress$:uo}).subscribe(ct);var Wi;((Wi=Or.version)==null?void 0:Wi.provider)==="mike"&&Ti({document$:ct});L(Qt,$t).pipe(nt(125)).subscribe(()=>{at("drawer",!1),at("search",!1)});mo.pipe(g(({mode:e})=>e==="global")).subscribe(e=>{switch(e.type){case"p":case",":let t=ue("link[rel=prev]");typeof t!="undefined"&&st(t);break;case"n":case".":let r=ue("link[rel=next]");typeof r!="undefined"&&st(r);break;case"Enter":let o=Ne();o instanceof HTMLLabelElement&&o.click()}});Fi({viewport$:ke,document$:ct});ji({document$:ct,tablet$:Lr});Ui({document$:ct});Di({viewport$:ke,tablet$:Lr});var ft=ai(Ce("header"),{viewport$:ke}),Kt=ct.pipe(m(()=>Ce("main")),b(e=>pi(e,{viewport$:ke,header$:ft})),Z(1)),Hs=L(...me("consent").map(e=>An(e,{target$:$t})),...me("dialog").map(e=>ni(e,{alert$:fo})),...me("palette").map(e=>li(e)),...me("progress").map(e=>mi(e,{progress$:uo})),...me("search").map(e=>_i(e,{index$:zi,keyboard$:mo})),...me("source").map(e=>$i(e))),$s=H(()=>L(...me("announce").map(e=>_n(e)),...me("content").map(e=>oi(e,{sitemap$:qi,viewport$:ke,target$:$t,print$:Ni})),...me("content").map(e=>V("search.highlight")?Ai(e,{index$:zi,location$:Qt}):y),...me("header").map(e=>si(e,{viewport$:ke,header$:ft,main$:Kt})),...me("header-title").map(e=>ci(e,{viewport$:ke,header$:ft})),...me("sidebar").map(e=>e.getAttribute("data-md-type")==="navigation"?eo(Vi,()=>lo(e,{viewport$:ke,header$:ft,main$:Kt})):eo(Lr,()=>lo(e,{viewport$:ke,header$:ft,main$:Kt}))),...me("tabs").map(e=>Ri(e,{viewport$:ke,header$:ft})),...me("toc").map(e=>Pi(e,{viewport$:ke,header$:ft,main$:Kt,target$:$t})),...me("top").map(e=>Ii(e,{viewport$:ke,header$:ft,main$:Kt,target$:$t})))),Ki=ct.pipe(b(()=>$s),Ve(Hs),Z(1));Ki.subscribe();window.document$=ct;window.location$=Qt;window.target$=$t;window.keyboard$=mo;window.viewport$=ke;window.tablet$=Lr;window.screen$=Vi;window.print$=Ni;window.alert$=fo;window.progress$=uo;window.component$=Ki;})(); diff --git a/assets/javascripts/lunr/min/lunr.ar.min.js b/assets/javascripts/lunr/min/lunr.ar.min.js new file mode 100644 index 0000000..9b06c26 --- /dev/null +++ b/assets/javascripts/lunr/min/lunr.ar.min.js @@ -0,0 +1 @@ +!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.ar=function(){this.pipeline.reset(),this.pipeline.add(e.ar.trimmer,e.ar.stopWordFilter,e.ar.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.ar.stemmer))},e.ar.wordCharacters="ء-ٛٱـ",e.ar.trimmer=e.trimmerSupport.generateTrimmer(e.ar.wordCharacters),e.Pipeline.registerFunction(e.ar.trimmer,"trimmer-ar"),e.ar.stemmer=function(){var e=this;return e.result=!1,e.preRemoved=!1,e.sufRemoved=!1,e.pre={pre1:"ف ك ب و س ل ن ا ي ت",pre2:"ال لل",pre3:"بال وال فال تال كال ولل",pre4:"فبال كبال وبال وكال"},e.suf={suf1:"ه ك ت ن ا ي",suf2:"نك نه ها وك يا اه ون ين تن تم نا وا ان كم كن ني نن ما هم هن تك ته ات يه",suf3:"تين كهم نيه نهم ونه وها يهم ونا ونك وني وهم تكم تنا تها تني تهم كما كها ناه نكم هنا تان يها",suf4:"كموه ناها ونني ونهم تكما تموه تكاه كماه ناكم ناهم نيها وننا"},e.patterns=JSON.parse('{"pt43":[{"pt":[{"c":"ا","l":1}]},{"pt":[{"c":"ا,ت,ن,ي","l":0}],"mPt":[{"c":"ف","l":0,"m":1},{"c":"ع","l":1,"m":2},{"c":"ل","l":2,"m":3}]},{"pt":[{"c":"و","l":2}],"mPt":[{"c":"ف","l":0,"m":0},{"c":"ع","l":1,"m":1},{"c":"ل","l":2,"m":3}]},{"pt":[{"c":"ا","l":2}]},{"pt":[{"c":"ي","l":2}],"mPt":[{"c":"ف","l":0,"m":0},{"c":"ع","l":1,"m":1},{"c":"ا","l":2},{"c":"ل","l":3,"m":3}]},{"pt":[{"c":"م","l":0}]}],"pt53":[{"pt":[{"c":"ت","l":0},{"c":"ا","l":2}]},{"pt":[{"c":"ا,ن,ت,ي","l":0},{"c":"ت","l":2}],"mPt":[{"c":"ا","l":0},{"c":"ف","l":1,"m":1},{"c":"ت","l":2},{"c":"ع","l":3,"m":3},{"c":"ا","l":4},{"c":"ل","l":5,"m":4}]},{"pt":[{"c":"ا","l":0},{"c":"ا","l":2}],"mPt":[{"c":"ا","l":0},{"c":"ف","l":1,"m":1},{"c":"ع","l":2,"m":3},{"c":"ل","l":3,"m":4},{"c":"ا","l":4},{"c":"ل","l":5,"m":4}]},{"pt":[{"c":"ا","l":0},{"c":"ا","l":3}],"mPt":[{"c":"ف","l":0,"m":1},{"c":"ع","l":1,"m":2},{"c":"ل","l":2,"m":4}]},{"pt":[{"c":"ا","l":3},{"c":"ن","l":4}]},{"pt":[{"c":"ت","l":0},{"c":"ي","l":3}]},{"pt":[{"c":"م","l":0},{"c":"و","l":3}]},{"pt":[{"c":"ا","l":1},{"c":"و","l":3}]},{"pt":[{"c":"و","l":1},{"c":"ا","l":2}]},{"pt":[{"c":"م","l":0},{"c":"ا","l":3}]},{"pt":[{"c":"م","l":0},{"c":"ي","l":3}]},{"pt":[{"c":"ا","l":2},{"c":"ن","l":3}]},{"pt":[{"c":"م","l":0},{"c":"ن","l":1}],"mPt":[{"c":"ا","l":0},{"c":"ن","l":1},{"c":"ف","l":2,"m":2},{"c":"ع","l":3,"m":3},{"c":"ا","l":4},{"c":"ل","l":5,"m":4}]},{"pt":[{"c":"م","l":0},{"c":"ت","l":2}],"mPt":[{"c":"ا","l":0},{"c":"ف","l":1,"m":1},{"c":"ت","l":2},{"c":"ع","l":3,"m":3},{"c":"ا","l":4},{"c":"ل","l":5,"m":4}]},{"pt":[{"c":"م","l":0},{"c":"ا","l":2}]},{"pt":[{"c":"م","l":1},{"c":"ا","l":3}]},{"pt":[{"c":"ي,ت,ا,ن","l":0},{"c":"ت","l":1}],"mPt":[{"c":"ف","l":0,"m":2},{"c":"ع","l":1,"m":3},{"c":"ا","l":2},{"c":"ل","l":3,"m":4}]},{"pt":[{"c":"ت,ي,ا,ن","l":0},{"c":"ت","l":2}],"mPt":[{"c":"ا","l":0},{"c":"ف","l":1,"m":1},{"c":"ت","l":2},{"c":"ع","l":3,"m":3},{"c":"ا","l":4},{"c":"ل","l":5,"m":4}]},{"pt":[{"c":"ا","l":2},{"c":"ي","l":3}]},{"pt":[{"c":"ا,ي,ت,ن","l":0},{"c":"ن","l":1}],"mPt":[{"c":"ا","l":0},{"c":"ن","l":1},{"c":"ف","l":2,"m":2},{"c":"ع","l":3,"m":3},{"c":"ا","l":4},{"c":"ل","l":5,"m":4}]},{"pt":[{"c":"ا","l":3},{"c":"ء","l":4}]}],"pt63":[{"pt":[{"c":"ا","l":0},{"c":"ت","l":2},{"c":"ا","l":4}]},{"pt":[{"c":"ا,ت,ن,ي","l":0},{"c":"س","l":1},{"c":"ت","l":2}],"mPt":[{"c":"ا","l":0},{"c":"س","l":1},{"c":"ت","l":2},{"c":"ف","l":3,"m":3},{"c":"ع","l":4,"m":4},{"c":"ا","l":5},{"c":"ل","l":6,"m":5}]},{"pt":[{"c":"ا,ن,ت,ي","l":0},{"c":"و","l":3}]},{"pt":[{"c":"م","l":0},{"c":"س","l":1},{"c":"ت","l":2}],"mPt":[{"c":"ا","l":0},{"c":"س","l":1},{"c":"ت","l":2},{"c":"ف","l":3,"m":3},{"c":"ع","l":4,"m":4},{"c":"ا","l":5},{"c":"ل","l":6,"m":5}]},{"pt":[{"c":"ي","l":1},{"c":"ي","l":3},{"c":"ا","l":4},{"c":"ء","l":5}]},{"pt":[{"c":"ا","l":0},{"c":"ن","l":1},{"c":"ا","l":4}]}],"pt54":[{"pt":[{"c":"ت","l":0}]},{"pt":[{"c":"ا,ي,ت,ن","l":0}],"mPt":[{"c":"ا","l":0},{"c":"ف","l":1,"m":1},{"c":"ع","l":2,"m":2},{"c":"ل","l":3,"m":3},{"c":"ر","l":4,"m":4},{"c":"ا","l":5},{"c":"ر","l":6,"m":4}]},{"pt":[{"c":"م","l":0}],"mPt":[{"c":"ا","l":0},{"c":"ف","l":1,"m":1},{"c":"ع","l":2,"m":2},{"c":"ل","l":3,"m":3},{"c":"ر","l":4,"m":4},{"c":"ا","l":5},{"c":"ر","l":6,"m":4}]},{"pt":[{"c":"ا","l":2}]},{"pt":[{"c":"ا","l":0},{"c":"ن","l":2}]}],"pt64":[{"pt":[{"c":"ا","l":0},{"c":"ا","l":4}]},{"pt":[{"c":"م","l":0},{"c":"ت","l":1}]}],"pt73":[{"pt":[{"c":"ا","l":0},{"c":"س","l":1},{"c":"ت","l":2},{"c":"ا","l":5}]}],"pt75":[{"pt":[{"c":"ا","l":0},{"c":"ا","l":5}]}]}'),e.execArray=["cleanWord","removeDiacritics","cleanAlef","removeStopWords","normalizeHamzaAndAlef","removeStartWaw","removePre432","removeEndTaa","wordCheck"],e.stem=function(){var r=0;for(e.result=!1,e.preRemoved=!1,e.sufRemoved=!1;r=0)return!0},e.normalizeHamzaAndAlef=function(){return e.word=e.word.replace("ؤ","ء"),e.word=e.word.replace("ئ","ء"),e.word=e.word.replace(/([\u0627])\1+/gi,"ا"),!1},e.removeEndTaa=function(){return!(e.word.length>2)||(e.word=e.word.replace(/[\u0627]$/,""),e.word=e.word.replace("ة",""),!1)},e.removeStartWaw=function(){return e.word.length>3&&"و"==e.word[0]&&"و"==e.word[1]&&(e.word=e.word.slice(1)),!1},e.removePre432=function(){var r=e.word;if(e.word.length>=7){var t=new RegExp("^("+e.pre.pre4.split(" ").join("|")+")");e.word=e.word.replace(t,"")}if(e.word==r&&e.word.length>=6){var c=new RegExp("^("+e.pre.pre3.split(" ").join("|")+")");e.word=e.word.replace(c,"")}if(e.word==r&&e.word.length>=5){var l=new RegExp("^("+e.pre.pre2.split(" ").join("|")+")");e.word=e.word.replace(l,"")}return r!=e.word&&(e.preRemoved=!0),!1},e.patternCheck=function(r){for(var t=0;t3){var t=new RegExp("^("+e.pre.pre1.split(" ").join("|")+")");e.word=e.word.replace(t,"")}return r!=e.word&&(e.preRemoved=!0),!1},e.removeSuf1=function(){var r=e.word;if(0==e.sufRemoved&&e.word.length>3){var t=new RegExp("("+e.suf.suf1.split(" ").join("|")+")$");e.word=e.word.replace(t,"")}return r!=e.word&&(e.sufRemoved=!0),!1},e.removeSuf432=function(){var r=e.word;if(e.word.length>=6){var t=new RegExp("("+e.suf.suf4.split(" ").join("|")+")$");e.word=e.word.replace(t,"")}if(e.word==r&&e.word.length>=5){var c=new RegExp("("+e.suf.suf3.split(" ").join("|")+")$");e.word=e.word.replace(c,"")}if(e.word==r&&e.word.length>=4){var l=new RegExp("("+e.suf.suf2.split(" ").join("|")+")$");e.word=e.word.replace(l,"")}return r!=e.word&&(e.sufRemoved=!0),!1},e.wordCheck=function(){for(var r=(e.word,[e.removeSuf432,e.removeSuf1,e.removePre1]),t=0,c=!1;e.word.length>=7&&!e.result&&t=f.limit)return;f.cursor++}for(;!f.out_grouping(w,97,248);){if(f.cursor>=f.limit)return;f.cursor++}d=f.cursor,d=d&&(r=f.limit_backward,f.limit_backward=d,f.ket=f.cursor,e=f.find_among_b(c,32),f.limit_backward=r,e))switch(f.bra=f.cursor,e){case 1:f.slice_del();break;case 2:f.in_grouping_b(p,97,229)&&f.slice_del()}}function t(){var e,r=f.limit-f.cursor;f.cursor>=d&&(e=f.limit_backward,f.limit_backward=d,f.ket=f.cursor,f.find_among_b(l,4)?(f.bra=f.cursor,f.limit_backward=e,f.cursor=f.limit-r,f.cursor>f.limit_backward&&(f.cursor--,f.bra=f.cursor,f.slice_del())):f.limit_backward=e)}function s(){var e,r,i,n=f.limit-f.cursor;if(f.ket=f.cursor,f.eq_s_b(2,"st")&&(f.bra=f.cursor,f.eq_s_b(2,"ig")&&f.slice_del()),f.cursor=f.limit-n,f.cursor>=d&&(r=f.limit_backward,f.limit_backward=d,f.ket=f.cursor,e=f.find_among_b(m,5),f.limit_backward=r,e))switch(f.bra=f.cursor,e){case 1:f.slice_del(),i=f.limit-f.cursor,t(),f.cursor=f.limit-i;break;case 2:f.slice_from("løs")}}function o(){var e;f.cursor>=d&&(e=f.limit_backward,f.limit_backward=d,f.ket=f.cursor,f.out_grouping_b(w,97,248)?(f.bra=f.cursor,u=f.slice_to(u),f.limit_backward=e,f.eq_v_b(u)&&f.slice_del()):f.limit_backward=e)}var a,d,u,c=[new r("hed",-1,1),new r("ethed",0,1),new r("ered",-1,1),new r("e",-1,1),new r("erede",3,1),new r("ende",3,1),new r("erende",5,1),new r("ene",3,1),new r("erne",3,1),new r("ere",3,1),new r("en",-1,1),new r("heden",10,1),new r("eren",10,1),new r("er",-1,1),new r("heder",13,1),new r("erer",13,1),new r("s",-1,2),new r("heds",16,1),new r("es",16,1),new r("endes",18,1),new r("erendes",19,1),new r("enes",18,1),new r("ernes",18,1),new r("eres",18,1),new r("ens",16,1),new r("hedens",24,1),new r("erens",24,1),new r("ers",16,1),new r("ets",16,1),new r("erets",28,1),new r("et",-1,1),new r("eret",30,1)],l=[new r("gd",-1,-1),new r("dt",-1,-1),new r("gt",-1,-1),new r("kt",-1,-1)],m=[new r("ig",-1,1),new r("lig",0,1),new r("elig",1,1),new r("els",-1,1),new r("løst",-1,2)],w=[17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,0,48,0,128],p=[239,254,42,3,0,0,0,0,0,0,0,0,0,0,0,0,16],f=new i;this.setCurrent=function(e){f.setCurrent(e)},this.getCurrent=function(){return f.getCurrent()},this.stem=function(){var r=f.cursor;return e(),f.limit_backward=r,f.cursor=f.limit,n(),f.cursor=f.limit,t(),f.cursor=f.limit,s(),f.cursor=f.limit,o(),!0}};return function(e){return"function"==typeof e.update?e.update(function(e){return n.setCurrent(e),n.stem(),n.getCurrent()}):(n.setCurrent(e),n.stem(),n.getCurrent())}}(),e.Pipeline.registerFunction(e.da.stemmer,"stemmer-da"),e.da.stopWordFilter=e.generateStopWordFilter("ad af alle alt anden at blev blive bliver da de dem den denne der deres det dette dig din disse dog du efter eller en end er et for fra ham han hans har havde have hende hendes her hos hun hvad hvis hvor i ikke ind jeg jer jo kunne man mange med meget men mig min mine mit mod ned noget nogle nu når og også om op os over på selv sig sin sine sit skal skulle som sådan thi til ud under var vi vil ville vor være været".split(" ")),e.Pipeline.registerFunction(e.da.stopWordFilter,"stopWordFilter-da")}}); \ No newline at end of file diff --git a/assets/javascripts/lunr/min/lunr.de.min.js b/assets/javascripts/lunr/min/lunr.de.min.js new file mode 100644 index 0000000..f3b5c10 --- /dev/null +++ b/assets/javascripts/lunr/min/lunr.de.min.js @@ -0,0 +1,18 @@ +/*! + * Lunr languages, `German` language + * https://github.com/MihaiValentin/lunr-languages + * + * Copyright 2014, Mihai Valentin + * http://www.mozilla.org/MPL/ + */ +/*! + * based on + * Snowball JavaScript Library v0.3 + * http://code.google.com/p/urim/ + * http://snowball.tartarus.org/ + * + * Copyright 2010, Oleg Mazko + * http://www.mozilla.org/MPL/ + */ + +!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.de=function(){this.pipeline.reset(),this.pipeline.add(e.de.trimmer,e.de.stopWordFilter,e.de.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.de.stemmer))},e.de.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",e.de.trimmer=e.trimmerSupport.generateTrimmer(e.de.wordCharacters),e.Pipeline.registerFunction(e.de.trimmer,"trimmer-de"),e.de.stemmer=function(){var r=e.stemmerSupport.Among,n=e.stemmerSupport.SnowballProgram,i=new function(){function e(e,r,n){return!(!v.eq_s(1,e)||(v.ket=v.cursor,!v.in_grouping(p,97,252)))&&(v.slice_from(r),v.cursor=n,!0)}function i(){for(var r,n,i,s,t=v.cursor;;)if(r=v.cursor,v.bra=r,v.eq_s(1,"ß"))v.ket=v.cursor,v.slice_from("ss");else{if(r>=v.limit)break;v.cursor=r+1}for(v.cursor=t;;)for(n=v.cursor;;){if(i=v.cursor,v.in_grouping(p,97,252)){if(s=v.cursor,v.bra=s,e("u","U",i))break;if(v.cursor=s,e("y","Y",i))break}if(i>=v.limit)return void(v.cursor=n);v.cursor=i+1}}function s(){for(;!v.in_grouping(p,97,252);){if(v.cursor>=v.limit)return!0;v.cursor++}for(;!v.out_grouping(p,97,252);){if(v.cursor>=v.limit)return!0;v.cursor++}return!1}function t(){m=v.limit,l=m;var e=v.cursor+3;0<=e&&e<=v.limit&&(d=e,s()||(m=v.cursor,m=v.limit)return;v.cursor++}}}function c(){return m<=v.cursor}function u(){return l<=v.cursor}function a(){var e,r,n,i,s=v.limit-v.cursor;if(v.ket=v.cursor,(e=v.find_among_b(w,7))&&(v.bra=v.cursor,c()))switch(e){case 1:v.slice_del();break;case 2:v.slice_del(),v.ket=v.cursor,v.eq_s_b(1,"s")&&(v.bra=v.cursor,v.eq_s_b(3,"nis")&&v.slice_del());break;case 3:v.in_grouping_b(g,98,116)&&v.slice_del()}if(v.cursor=v.limit-s,v.ket=v.cursor,(e=v.find_among_b(f,4))&&(v.bra=v.cursor,c()))switch(e){case 1:v.slice_del();break;case 2:if(v.in_grouping_b(k,98,116)){var t=v.cursor-3;v.limit_backward<=t&&t<=v.limit&&(v.cursor=t,v.slice_del())}}if(v.cursor=v.limit-s,v.ket=v.cursor,(e=v.find_among_b(_,8))&&(v.bra=v.cursor,u()))switch(e){case 1:v.slice_del(),v.ket=v.cursor,v.eq_s_b(2,"ig")&&(v.bra=v.cursor,r=v.limit-v.cursor,v.eq_s_b(1,"e")||(v.cursor=v.limit-r,u()&&v.slice_del()));break;case 2:n=v.limit-v.cursor,v.eq_s_b(1,"e")||(v.cursor=v.limit-n,v.slice_del());break;case 3:if(v.slice_del(),v.ket=v.cursor,i=v.limit-v.cursor,!v.eq_s_b(2,"er")&&(v.cursor=v.limit-i,!v.eq_s_b(2,"en")))break;v.bra=v.cursor,c()&&v.slice_del();break;case 4:v.slice_del(),v.ket=v.cursor,e=v.find_among_b(b,2),e&&(v.bra=v.cursor,u()&&1==e&&v.slice_del())}}var d,l,m,h=[new r("",-1,6),new r("U",0,2),new r("Y",0,1),new r("ä",0,3),new r("ö",0,4),new r("ü",0,5)],w=[new r("e",-1,2),new r("em",-1,1),new r("en",-1,2),new r("ern",-1,1),new r("er",-1,1),new r("s",-1,3),new r("es",5,2)],f=[new r("en",-1,1),new r("er",-1,1),new r("st",-1,2),new r("est",2,1)],b=[new r("ig",-1,1),new r("lich",-1,1)],_=[new r("end",-1,1),new r("ig",-1,2),new r("ung",-1,1),new r("lich",-1,3),new r("isch",-1,2),new r("ik",-1,2),new r("heit",-1,3),new r("keit",-1,4)],p=[17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,0,8,0,32,8],g=[117,30,5],k=[117,30,4],v=new n;this.setCurrent=function(e){v.setCurrent(e)},this.getCurrent=function(){return v.getCurrent()},this.stem=function(){var e=v.cursor;return i(),v.cursor=e,t(),v.limit_backward=e,v.cursor=v.limit,a(),v.cursor=v.limit_backward,o(),!0}};return function(e){return"function"==typeof e.update?e.update(function(e){return i.setCurrent(e),i.stem(),i.getCurrent()}):(i.setCurrent(e),i.stem(),i.getCurrent())}}(),e.Pipeline.registerFunction(e.de.stemmer,"stemmer-de"),e.de.stopWordFilter=e.generateStopWordFilter("aber alle allem allen aller alles als also am an ander andere anderem anderen anderer anderes anderm andern anderr anders auch auf aus bei bin bis bist da damit dann das dasselbe dazu daß dein deine deinem deinen deiner deines dem demselben den denn denselben der derer derselbe derselben des desselben dessen dich die dies diese dieselbe dieselben diesem diesen dieser dieses dir doch dort du durch ein eine einem einen einer eines einig einige einigem einigen einiger einiges einmal er es etwas euch euer eure eurem euren eurer eures für gegen gewesen hab habe haben hat hatte hatten hier hin hinter ich ihm ihn ihnen ihr ihre ihrem ihren ihrer ihres im in indem ins ist jede jedem jeden jeder jedes jene jenem jenen jener jenes jetzt kann kein keine keinem keinen keiner keines können könnte machen man manche manchem manchen mancher manches mein meine meinem meinen meiner meines mich mir mit muss musste nach nicht nichts noch nun nur ob oder ohne sehr sein seine seinem seinen seiner seines selbst sich sie sind so solche solchem solchen solcher solches soll sollte sondern sonst um und uns unse unsem unsen unser unses unter viel vom von vor war waren warst was weg weil weiter welche welchem welchen welcher welches wenn werde werden wie wieder will wir wird wirst wo wollen wollte während würde würden zu zum zur zwar zwischen über".split(" ")),e.Pipeline.registerFunction(e.de.stopWordFilter,"stopWordFilter-de")}}); \ No newline at end of file diff --git a/assets/javascripts/lunr/min/lunr.du.min.js b/assets/javascripts/lunr/min/lunr.du.min.js new file mode 100644 index 0000000..49a0f3f --- /dev/null +++ b/assets/javascripts/lunr/min/lunr.du.min.js @@ -0,0 +1,18 @@ +/*! + * Lunr languages, `Dutch` language + * https://github.com/MihaiValentin/lunr-languages + * + * Copyright 2014, Mihai Valentin + * http://www.mozilla.org/MPL/ + */ +/*! + * based on + * Snowball JavaScript Library v0.3 + * http://code.google.com/p/urim/ + * http://snowball.tartarus.org/ + * + * Copyright 2010, Oleg Mazko + * http://www.mozilla.org/MPL/ + */ + +!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");console.warn('[Lunr Languages] Please use the "nl" instead of the "du". The "nl" code is the standard code for Dutch language, and "du" will be removed in the next major versions.'),e.du=function(){this.pipeline.reset(),this.pipeline.add(e.du.trimmer,e.du.stopWordFilter,e.du.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.du.stemmer))},e.du.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",e.du.trimmer=e.trimmerSupport.generateTrimmer(e.du.wordCharacters),e.Pipeline.registerFunction(e.du.trimmer,"trimmer-du"),e.du.stemmer=function(){var r=e.stemmerSupport.Among,i=e.stemmerSupport.SnowballProgram,n=new function(){function e(){for(var e,r,i,o=C.cursor;;){if(C.bra=C.cursor,e=C.find_among(b,11))switch(C.ket=C.cursor,e){case 1:C.slice_from("a");continue;case 2:C.slice_from("e");continue;case 3:C.slice_from("i");continue;case 4:C.slice_from("o");continue;case 5:C.slice_from("u");continue;case 6:if(C.cursor>=C.limit)break;C.cursor++;continue}break}for(C.cursor=o,C.bra=o,C.eq_s(1,"y")?(C.ket=C.cursor,C.slice_from("Y")):C.cursor=o;;)if(r=C.cursor,C.in_grouping(q,97,232)){if(i=C.cursor,C.bra=i,C.eq_s(1,"i"))C.ket=C.cursor,C.in_grouping(q,97,232)&&(C.slice_from("I"),C.cursor=r);else if(C.cursor=i,C.eq_s(1,"y"))C.ket=C.cursor,C.slice_from("Y"),C.cursor=r;else if(n(r))break}else if(n(r))break}function n(e){return C.cursor=e,e>=C.limit||(C.cursor++,!1)}function o(){_=C.limit,f=_,t()||(_=C.cursor,_<3&&(_=3),t()||(f=C.cursor))}function t(){for(;!C.in_grouping(q,97,232);){if(C.cursor>=C.limit)return!0;C.cursor++}for(;!C.out_grouping(q,97,232);){if(C.cursor>=C.limit)return!0;C.cursor++}return!1}function s(){for(var e;;)if(C.bra=C.cursor,e=C.find_among(p,3))switch(C.ket=C.cursor,e){case 1:C.slice_from("y");break;case 2:C.slice_from("i");break;case 3:if(C.cursor>=C.limit)return;C.cursor++}}function u(){return _<=C.cursor}function c(){return f<=C.cursor}function a(){var e=C.limit-C.cursor;C.find_among_b(g,3)&&(C.cursor=C.limit-e,C.ket=C.cursor,C.cursor>C.limit_backward&&(C.cursor--,C.bra=C.cursor,C.slice_del()))}function l(){var e;w=!1,C.ket=C.cursor,C.eq_s_b(1,"e")&&(C.bra=C.cursor,u()&&(e=C.limit-C.cursor,C.out_grouping_b(q,97,232)&&(C.cursor=C.limit-e,C.slice_del(),w=!0,a())))}function m(){var e;u()&&(e=C.limit-C.cursor,C.out_grouping_b(q,97,232)&&(C.cursor=C.limit-e,C.eq_s_b(3,"gem")||(C.cursor=C.limit-e,C.slice_del(),a())))}function d(){var e,r,i,n,o,t,s=C.limit-C.cursor;if(C.ket=C.cursor,e=C.find_among_b(h,5))switch(C.bra=C.cursor,e){case 1:u()&&C.slice_from("heid");break;case 2:m();break;case 3:u()&&C.out_grouping_b(z,97,232)&&C.slice_del()}if(C.cursor=C.limit-s,l(),C.cursor=C.limit-s,C.ket=C.cursor,C.eq_s_b(4,"heid")&&(C.bra=C.cursor,c()&&(r=C.limit-C.cursor,C.eq_s_b(1,"c")||(C.cursor=C.limit-r,C.slice_del(),C.ket=C.cursor,C.eq_s_b(2,"en")&&(C.bra=C.cursor,m())))),C.cursor=C.limit-s,C.ket=C.cursor,e=C.find_among_b(k,6))switch(C.bra=C.cursor,e){case 1:if(c()){if(C.slice_del(),i=C.limit-C.cursor,C.ket=C.cursor,C.eq_s_b(2,"ig")&&(C.bra=C.cursor,c()&&(n=C.limit-C.cursor,!C.eq_s_b(1,"e")))){C.cursor=C.limit-n,C.slice_del();break}C.cursor=C.limit-i,a()}break;case 2:c()&&(o=C.limit-C.cursor,C.eq_s_b(1,"e")||(C.cursor=C.limit-o,C.slice_del()));break;case 3:c()&&(C.slice_del(),l());break;case 4:c()&&C.slice_del();break;case 5:c()&&w&&C.slice_del()}C.cursor=C.limit-s,C.out_grouping_b(j,73,232)&&(t=C.limit-C.cursor,C.find_among_b(v,4)&&C.out_grouping_b(q,97,232)&&(C.cursor=C.limit-t,C.ket=C.cursor,C.cursor>C.limit_backward&&(C.cursor--,C.bra=C.cursor,C.slice_del())))}var f,_,w,b=[new r("",-1,6),new r("á",0,1),new r("ä",0,1),new r("é",0,2),new r("ë",0,2),new r("í",0,3),new r("ï",0,3),new r("ó",0,4),new r("ö",0,4),new r("ú",0,5),new r("ü",0,5)],p=[new r("",-1,3),new r("I",0,2),new r("Y",0,1)],g=[new r("dd",-1,-1),new r("kk",-1,-1),new r("tt",-1,-1)],h=[new r("ene",-1,2),new r("se",-1,3),new r("en",-1,2),new r("heden",2,1),new r("s",-1,3)],k=[new r("end",-1,1),new r("ig",-1,2),new r("ing",-1,1),new r("lijk",-1,3),new r("baar",-1,4),new r("bar",-1,5)],v=[new r("aa",-1,-1),new r("ee",-1,-1),new r("oo",-1,-1),new r("uu",-1,-1)],q=[17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,0,128],j=[1,0,0,17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,0,128],z=[17,67,16,1,0,0,0,0,0,0,0,0,0,0,0,0,128],C=new i;this.setCurrent=function(e){C.setCurrent(e)},this.getCurrent=function(){return C.getCurrent()},this.stem=function(){var r=C.cursor;return e(),C.cursor=r,o(),C.limit_backward=r,C.cursor=C.limit,d(),C.cursor=C.limit_backward,s(),!0}};return function(e){return"function"==typeof e.update?e.update(function(e){return n.setCurrent(e),n.stem(),n.getCurrent()}):(n.setCurrent(e),n.stem(),n.getCurrent())}}(),e.Pipeline.registerFunction(e.du.stemmer,"stemmer-du"),e.du.stopWordFilter=e.generateStopWordFilter(" aan al alles als altijd andere ben bij daar dan dat de der deze die dit doch doen door dus een eens en er ge geen geweest haar had heb hebben heeft hem het hier hij hoe hun iemand iets ik in is ja je kan kon kunnen maar me meer men met mij mijn moet na naar niet niets nog nu of om omdat onder ons ook op over reeds te tegen toch toen tot u uit uw van veel voor want waren was wat werd wezen wie wil worden wordt zal ze zelf zich zij zijn zo zonder zou".split(" ")),e.Pipeline.registerFunction(e.du.stopWordFilter,"stopWordFilter-du")}}); \ No newline at end of file diff --git a/assets/javascripts/lunr/min/lunr.el.min.js b/assets/javascripts/lunr/min/lunr.el.min.js new file mode 100644 index 0000000..ace017b --- /dev/null +++ b/assets/javascripts/lunr/min/lunr.el.min.js @@ -0,0 +1 @@ +!function(e,t){"function"==typeof define&&define.amd?define(t):"object"==typeof exports?module.exports=t():t()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.el=function(){this.pipeline.reset(),void 0===this.searchPipeline&&this.pipeline.add(e.el.trimmer,e.el.normilizer),this.pipeline.add(e.el.stopWordFilter,e.el.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.el.stemmer))},e.el.wordCharacters="A-Za-zΑαΒβΓγΔδΕεΖζΗηΘθΙιΚκΛλΜμΝνΞξΟοΠπΡρΣσςΤτΥυΦφΧχΨψΩωΆάΈέΉήΊίΌόΎύΏώΪΐΫΰΐΰ",e.el.trimmer=e.trimmerSupport.generateTrimmer(e.el.wordCharacters),e.Pipeline.registerFunction(e.el.trimmer,"trimmer-el"),e.el.stemmer=function(){function e(e){return s.test(e)}function t(e){return/[ΑΕΗΙΟΥΩ]$/.test(e)}function r(e){return/[ΑΕΗΙΟΩ]$/.test(e)}function n(n){var s=n;if(n.length<3)return s;if(!e(n))return s;if(i.indexOf(n)>=0)return s;var u=new RegExp("(.*)("+Object.keys(l).join("|")+")$"),o=u.exec(s);return null!==o&&(s=o[1]+l[o[2]]),null!==(o=/^(.+?)(ΑΔΕΣ|ΑΔΩΝ)$/.exec(s))&&(s=o[1],/(ΟΚ|ΜΑΜ|ΜΑΝ|ΜΠΑΜΠ|ΠΑΤΕΡ|ΓΙΑΓΙ|ΝΤΑΝΤ|ΚΥΡ|ΘΕΙ|ΠΕΘΕΡ|ΜΟΥΣΑΜ|ΚΑΠΛΑΜ|ΠΑΡ|ΨΑΡ|ΤΖΟΥΡ|ΤΑΜΠΟΥΡ|ΓΑΛΑΤ|ΦΑΦΛΑΤ)$/.test(o[1])||(s+="ΑΔ")),null!==(o=/^(.+?)(ΕΔΕΣ|ΕΔΩΝ)$/.exec(s))&&(s=o[1],/(ΟΠ|ΙΠ|ΕΜΠ|ΥΠ|ΓΗΠ|ΔΑΠ|ΚΡΑΣΠ|ΜΙΛ)$/.test(o[1])&&(s+="ΕΔ")),null!==(o=/^(.+?)(ΟΥΔΕΣ|ΟΥΔΩΝ)$/.exec(s))&&(s=o[1],/(ΑΡΚ|ΚΑΛΙΑΚ|ΠΕΤΑΛ|ΛΙΧ|ΠΛΕΞ|ΣΚ|Σ|ΦΛ|ΦΡ|ΒΕΛ|ΛΟΥΛ|ΧΝ|ΣΠ|ΤΡΑΓ|ΦΕ)$/.test(o[1])&&(s+="ΟΥΔ")),null!==(o=/^(.+?)(ΕΩΣ|ΕΩΝ|ΕΑΣ|ΕΑ)$/.exec(s))&&(s=o[1],/^(Θ|Δ|ΕΛ|ΓΑΛ|Ν|Π|ΙΔ|ΠΑΡ|ΣΤΕΡ|ΟΡΦ|ΑΝΔΡ|ΑΝΤΡ)$/.test(o[1])&&(s+="Ε")),null!==(o=/^(.+?)(ΕΙΟ|ΕΙΟΣ|ΕΙΟΙ|ΕΙΑ|ΕΙΑΣ|ΕΙΕΣ|ΕΙΟΥ|ΕΙΟΥΣ|ΕΙΩΝ)$/.exec(s))&&o[1].length>4&&(s=o[1]),null!==(o=/^(.+?)(ΙΟΥΣ|ΙΑΣ|ΙΕΣ|ΙΟΣ|ΙΟΥ|ΙΟΙ|ΙΩΝ|ΙΟΝ|ΙΑ|ΙΟ)$/.exec(s))&&(s=o[1],(t(s)||s.length<2||/^(ΑΓ|ΑΓΓΕΛ|ΑΓΡ|ΑΕΡ|ΑΘΛ|ΑΚΟΥΣ|ΑΞ|ΑΣ|Β|ΒΙΒΛ|ΒΥΤ|Γ|ΓΙΑΓ|ΓΩΝ|Δ|ΔΑΝ|ΔΗΛ|ΔΗΜ|ΔΟΚΙΜ|ΕΛ|ΖΑΧΑΡ|ΗΛ|ΗΠ|ΙΔ|ΙΣΚ|ΙΣΤ|ΙΟΝ|ΙΩΝ|ΚΙΜΩΛ|ΚΟΛΟΝ|ΚΟΡ|ΚΤΗΡ|ΚΥΡ|ΛΑΓ|ΛΟΓ|ΜΑΓ|ΜΠΑΝ|ΜΠΡ|ΝΑΥΤ|ΝΟΤ|ΟΠΑΛ|ΟΞ|ΟΡ|ΟΣ|ΠΑΝΑΓ|ΠΑΤΡ|ΠΗΛ|ΠΗΝ|ΠΛΑΙΣ|ΠΟΝΤ|ΡΑΔ|ΡΟΔ|ΣΚ|ΣΚΟΡΠ|ΣΟΥΝ|ΣΠΑΝ|ΣΤΑΔ|ΣΥΡ|ΤΗΛ|ΤΙΜ|ΤΟΚ|ΤΟΠ|ΤΡΟΧ|ΦΙΛ|ΦΩΤ|Χ|ΧΙΛ|ΧΡΩΜ|ΧΩΡ)$/.test(o[1]))&&(s+="Ι"),/^(ΠΑΛ)$/.test(o[1])&&(s+="ΑΙ")),null!==(o=/^(.+?)(ΙΚΟΣ|ΙΚΟΝ|ΙΚΕΙΣ|ΙΚΟΙ|ΙΚΕΣ|ΙΚΟΥΣ|ΙΚΗ|ΙΚΗΣ|ΙΚΟ|ΙΚΑ|ΙΚΟΥ|ΙΚΩΝ|ΙΚΩΣ)$/.exec(s))&&(s=o[1],(t(s)||/^(ΑΔ|ΑΛ|ΑΜΑΝ|ΑΜΕΡ|ΑΜΜΟΧΑΛ|ΑΝΗΘ|ΑΝΤΙΔ|ΑΠΛ|ΑΤΤ|ΑΦΡ|ΒΑΣ|ΒΡΩΜ|ΓΕΝ|ΓΕΡ|Δ|ΔΙΚΑΝ|ΔΥΤ|ΕΙΔ|ΕΝΔ|ΕΞΩΔ|ΗΘ|ΘΕΤ|ΚΑΛΛΙΝ|ΚΑΛΠ|ΚΑΤΑΔ|ΚΟΥΖΙΝ|ΚΡ|ΚΩΔ|ΛΟΓ|Μ|ΜΕΡ|ΜΟΝΑΔ|ΜΟΥΛ|ΜΟΥΣ|ΜΠΑΓΙΑΤ|ΜΠΑΝ|ΜΠΟΛ|ΜΠΟΣ|ΜΥΣΤ|Ν|ΝΙΤ|ΞΙΚ|ΟΠΤ|ΠΑΝ|ΠΕΤΣ|ΠΙΚΑΝΤ|ΠΙΤΣ|ΠΛΑΣΤ|ΠΛΙΑΤΣ|ΠΟΝΤ|ΠΟΣΤΕΛΝ|ΠΡΩΤΟΔ|ΣΕΡΤ|ΣΗΜΑΝΤ|ΣΤΑΤ|ΣΥΝΑΔ|ΣΥΝΟΜΗΛ|ΤΕΛ|ΤΕΧΝ|ΤΡΟΠ|ΤΣΑΜ|ΥΠΟΔ|Φ|ΦΙΛΟΝ|ΦΥΛΟΔ|ΦΥΣ|ΧΑΣ)$/.test(o[1])||/(ΦΟΙΝ)$/.test(o[1]))&&(s+="ΙΚ")),"ΑΓΑΜΕ"===s&&(s="ΑΓΑΜ"),null!==(o=/^(.+?)(ΑΓΑΜΕ|ΗΣΑΜΕ|ΟΥΣΑΜΕ|ΗΚΑΜΕ|ΗΘΗΚΑΜΕ)$/.exec(s))&&(s=o[1]),null!==(o=/^(.+?)(ΑΜΕ)$/.exec(s))&&(s=o[1],/^(ΑΝΑΠ|ΑΠΟΘ|ΑΠΟΚ|ΑΠΟΣΤ|ΒΟΥΒ|ΞΕΘ|ΟΥΛ|ΠΕΘ|ΠΙΚΡ|ΠΟΤ|ΣΙΧ|Χ)$/.test(o[1])&&(s+="ΑΜ")),null!==(o=/^(.+?)(ΑΓΑΝΕ|ΗΣΑΝΕ|ΟΥΣΑΝΕ|ΙΟΝΤΑΝΕ|ΙΟΤΑΝΕ|ΙΟΥΝΤΑΝΕ|ΟΝΤΑΝΕ|ΟΤΑΝΕ|ΟΥΝΤΑΝΕ|ΗΚΑΝΕ|ΗΘΗΚΑΝΕ)$/.exec(s))&&(s=o[1],/^(ΤΡ|ΤΣ)$/.test(o[1])&&(s+="ΑΓΑΝ")),null!==(o=/^(.+?)(ΑΝΕ)$/.exec(s))&&(s=o[1],(r(s)||/^(ΒΕΤΕΡ|ΒΟΥΛΚ|ΒΡΑΧΜ|Γ|ΔΡΑΔΟΥΜ|Θ|ΚΑΛΠΟΥΖ|ΚΑΣΤΕΛ|ΚΟΡΜΟΡ|ΛΑΟΠΛ|ΜΩΑΜΕΘ|Μ|ΜΟΥΣΟΥΛΜΑΝ|ΟΥΛ|Π|ΠΕΛΕΚ|ΠΛ|ΠΟΛΙΣ|ΠΟΡΤΟΛ|ΣΑΡΑΚΑΤΣ|ΣΟΥΛΤ|ΤΣΑΡΛΑΤ|ΟΡΦ|ΤΣΙΓΓ|ΤΣΟΠ|ΦΩΤΟΣΤΕΦ|Χ|ΨΥΧΟΠΛ|ΑΓ|ΟΡΦ|ΓΑΛ|ΓΕΡ|ΔΕΚ|ΔΙΠΛ|ΑΜΕΡΙΚΑΝ|ΟΥΡ|ΠΙΘ|ΠΟΥΡΙΤ|Σ|ΖΩΝΤ|ΙΚ|ΚΑΣΤ|ΚΟΠ|ΛΙΧ|ΛΟΥΘΗΡ|ΜΑΙΝΤ|ΜΕΛ|ΣΙΓ|ΣΠ|ΣΤΕΓ|ΤΡΑΓ|ΤΣΑΓ|Φ|ΕΡ|ΑΔΑΠ|ΑΘΙΓΓ|ΑΜΗΧ|ΑΝΙΚ|ΑΝΟΡΓ|ΑΠΗΓ|ΑΠΙΘ|ΑΤΣΙΓΓ|ΒΑΣ|ΒΑΣΚ|ΒΑΘΥΓΑΛ|ΒΙΟΜΗΧ|ΒΡΑΧΥΚ|ΔΙΑΤ|ΔΙΑΦ|ΕΝΟΡΓ|ΘΥΣ|ΚΑΠΝΟΒΙΟΜΗΧ|ΚΑΤΑΓΑΛ|ΚΛΙΒ|ΚΟΙΛΑΡΦ|ΛΙΒ|ΜΕΓΛΟΒΙΟΜΗΧ|ΜΙΚΡΟΒΙΟΜΗΧ|ΝΤΑΒ|ΞΗΡΟΚΛΙΒ|ΟΛΙΓΟΔΑΜ|ΟΛΟΓΑΛ|ΠΕΝΤΑΡΦ|ΠΕΡΗΦ|ΠΕΡΙΤΡ|ΠΛΑΤ|ΠΟΛΥΔΑΠ|ΠΟΛΥΜΗΧ|ΣΤΕΦ|ΤΑΒ|ΤΕΤ|ΥΠΕΡΗΦ|ΥΠΟΚΟΠ|ΧΑΜΗΛΟΔΑΠ|ΨΗΛΟΤΑΒ)$/.test(o[1]))&&(s+="ΑΝ")),null!==(o=/^(.+?)(ΗΣΕΤΕ)$/.exec(s))&&(s=o[1]),null!==(o=/^(.+?)(ΕΤΕ)$/.exec(s))&&(s=o[1],(r(s)||/(ΟΔ|ΑΙΡ|ΦΟΡ|ΤΑΘ|ΔΙΑΘ|ΣΧ|ΕΝΔ|ΕΥΡ|ΤΙΘ|ΥΠΕΡΘ|ΡΑΘ|ΕΝΘ|ΡΟΘ|ΣΘ|ΠΥΡ|ΑΙΝ|ΣΥΝΔ|ΣΥΝ|ΣΥΝΘ|ΧΩΡ|ΠΟΝ|ΒΡ|ΚΑΘ|ΕΥΘ|ΕΚΘ|ΝΕΤ|ΡΟΝ|ΑΡΚ|ΒΑΡ|ΒΟΛ|ΩΦΕΛ)$/.test(o[1])||/^(ΑΒΑΡ|ΒΕΝ|ΕΝΑΡ|ΑΒΡ|ΑΔ|ΑΘ|ΑΝ|ΑΠΛ|ΒΑΡΟΝ|ΝΤΡ|ΣΚ|ΚΟΠ|ΜΠΟΡ|ΝΙΦ|ΠΑΓ|ΠΑΡΑΚΑΛ|ΣΕΡΠ|ΣΚΕΛ|ΣΥΡΦ|ΤΟΚ|Υ|Δ|ΕΜ|ΘΑΡΡ|Θ)$/.test(o[1]))&&(s+="ΕΤ")),null!==(o=/^(.+?)(ΟΝΤΑΣ|ΩΝΤΑΣ)$/.exec(s))&&(s=o[1],/^ΑΡΧ$/.test(o[1])&&(s+="ΟΝΤ"),/ΚΡΕ$/.test(o[1])&&(s+="ΩΝΤ")),null!==(o=/^(.+?)(ΟΜΑΣΤΕ|ΙΟΜΑΣΤΕ)$/.exec(s))&&(s=o[1],/^ΟΝ$/.test(o[1])&&(s+="ΟΜΑΣΤ")),null!==(o=/^(.+?)(ΙΕΣΤΕ)$/.exec(s))&&(s=o[1],/^(Π|ΑΠ|ΣΥΜΠ|ΑΣΥΜΠ|ΑΚΑΤΑΠ|ΑΜΕΤΑΜΦ)$/.test(o[1])&&(s+="ΙΕΣΤ")),null!==(o=/^(.+?)(ΕΣΤΕ)$/.exec(s))&&(s=o[1],/^(ΑΛ|ΑΡ|ΕΚΤΕΛ|Ζ|Μ|Ξ|ΠΑΡΑΚΑΛ|ΠΡΟ|ΝΙΣ)$/.test(o[1])&&(s+="ΕΣΤ")),null!==(o=/^(.+?)(ΗΘΗΚΑ|ΗΘΗΚΕΣ|ΗΘΗΚΕ)$/.exec(s))&&(s=o[1]),null!==(o=/^(.+?)(ΗΚΑ|ΗΚΕΣ|ΗΚΕ)$/.exec(s))&&(s=o[1],(/(ΣΚΩΛ|ΣΚΟΥΛ|ΝΑΡΘ|ΣΦ|ΟΘ|ΠΙΘ)$/.test(o[1])||/^(ΔΙΑΘ|Θ|ΠΑΡΑΚΑΤΑΘ|ΠΡΟΣΘ|ΣΥΝΘ)$/.test(o[1]))&&(s+="ΗΚ")),null!==(o=/^(.+?)(ΟΥΣΑ|ΟΥΣΕΣ|ΟΥΣΕ)$/.exec(s))&&(s=o[1],(t(s)||/^(ΦΑΡΜΑΚ|ΧΑΔ|ΑΓΚ|ΑΝΑΡΡ|ΒΡΟΜ|ΕΚΛΙΠ|ΛΑΜΠΙΔ|ΛΕΧ|Μ|ΠΑΤ|Ρ|Λ|ΜΕΔ|ΜΕΣΑΖ|ΥΠΟΤΕΙΝ|ΑΜ|ΑΙΘ|ΑΝΗΚ|ΔΕΣΠΟΖ|ΕΝΔΙΑΦΕΡ)$/.test(o[1])||/(ΠΟΔΑΡ|ΒΛΕΠ|ΠΑΝΤΑΧ|ΦΡΥΔ|ΜΑΝΤΙΛ|ΜΑΛΛ|ΚΥΜΑΤ|ΛΑΧ|ΛΗΓ|ΦΑΓ|ΟΜ|ΠΡΩΤ)$/.test(o[1]))&&(s+="ΟΥΣ")),null!==(o=/^(.+?)(ΑΓΑ|ΑΓΕΣ|ΑΓΕ)$/.exec(s))&&(s=o[1],(/^(ΑΒΑΣΤ|ΠΟΛΥΦ|ΑΔΗΦ|ΠΑΜΦ|Ρ|ΑΣΠ|ΑΦ|ΑΜΑΛ|ΑΜΑΛΛΙ|ΑΝΥΣΤ|ΑΠΕΡ|ΑΣΠΑΡ|ΑΧΑΡ|ΔΕΡΒΕΝ|ΔΡΟΣΟΠ|ΞΕΦ|ΝΕΟΠ|ΝΟΜΟΤ|ΟΛΟΠ|ΟΜΟΤ|ΠΡΟΣΤ|ΠΡΟΣΩΠΟΠ|ΣΥΜΠ|ΣΥΝΤ|Τ|ΥΠΟΤ|ΧΑΡ|ΑΕΙΠ|ΑΙΜΟΣΤ|ΑΝΥΠ|ΑΠΟΤ|ΑΡΤΙΠ|ΔΙΑΤ|ΕΝ|ΕΠΙΤ|ΚΡΟΚΑΛΟΠ|ΣΙΔΗΡΟΠ|Λ|ΝΑΥ|ΟΥΛΑΜ|ΟΥΡ|Π|ΤΡ|Μ)$/.test(o[1])||/(ΟΦ|ΠΕΛ|ΧΟΡΤ|ΛΛ|ΣΦ|ΡΠ|ΦΡ|ΠΡ|ΛΟΧ|ΣΜΗΝ)$/.test(o[1])&&!/^(ΨΟΦ|ΝΑΥΛΟΧ)$/.test(o[1])||/(ΚΟΛΛ)$/.test(o[1]))&&(s+="ΑΓ")),null!==(o=/^(.+?)(ΗΣΕ|ΗΣΟΥ|ΗΣΑ)$/.exec(s))&&(s=o[1],/^(Ν|ΧΕΡΣΟΝ|ΔΩΔΕΚΑΝ|ΕΡΗΜΟΝ|ΜΕΓΑΛΟΝ|ΕΠΤΑΝ|Ι)$/.test(o[1])&&(s+="ΗΣ")),null!==(o=/^(.+?)(ΗΣΤΕ)$/.exec(s))&&(s=o[1],/^(ΑΣΒ|ΣΒ|ΑΧΡ|ΧΡ|ΑΠΛ|ΑΕΙΜΝ|ΔΥΣΧΡ|ΕΥΧΡ|ΚΟΙΝΟΧΡ|ΠΑΛΙΜΨ)$/.test(o[1])&&(s+="ΗΣΤ")),null!==(o=/^(.+?)(ΟΥΝΕ|ΗΣΟΥΝΕ|ΗΘΟΥΝΕ)$/.exec(s))&&(s=o[1],/^(Ν|Ρ|ΣΠΙ|ΣΤΡΑΒΟΜΟΥΤΣ|ΚΑΚΟΜΟΥΤΣ|ΕΞΩΝ)$/.test(o[1])&&(s+="ΟΥΝ")),null!==(o=/^(.+?)(ΟΥΜΕ|ΗΣΟΥΜΕ|ΗΘΟΥΜΕ)$/.exec(s))&&(s=o[1],/^(ΠΑΡΑΣΟΥΣ|Φ|Χ|ΩΡΙΟΠΛ|ΑΖ|ΑΛΛΟΣΟΥΣ|ΑΣΟΥΣ)$/.test(o[1])&&(s+="ΟΥΜ")),null!=(o=/^(.+?)(ΜΑΤΟΙ|ΜΑΤΟΥΣ|ΜΑΤΟ|ΜΑΤΑ|ΜΑΤΩΣ|ΜΑΤΩΝ|ΜΑΤΟΣ|ΜΑΤΕΣ|ΜΑΤΗ|ΜΑΤΗΣ|ΜΑΤΟΥ)$/.exec(s))&&(s=o[1]+"Μ",/^(ΓΡΑΜ)$/.test(o[1])?s+="Α":/^(ΓΕ|ΣΤΑ)$/.test(o[1])&&(s+="ΑΤ")),null!==(o=/^(.+?)(ΟΥΑ)$/.exec(s))&&(s=o[1]+"ΟΥ"),n.length===s.length&&null!==(o=/^(.+?)(Α|ΑΓΑΤΕ|ΑΓΑΝ|ΑΕΙ|ΑΜΑΙ|ΑΝ|ΑΣ|ΑΣΑΙ|ΑΤΑΙ|ΑΩ|Ε|ΕΙ|ΕΙΣ|ΕΙΤΕ|ΕΣΑΙ|ΕΣ|ΕΤΑΙ|Ι|ΙΕΜΑΙ|ΙΕΜΑΣΤΕ|ΙΕΤΑΙ|ΙΕΣΑΙ|ΙΕΣΑΣΤΕ|ΙΟΜΑΣΤΑΝ|ΙΟΜΟΥΝ|ΙΟΜΟΥΝΑ|ΙΟΝΤΑΝ|ΙΟΝΤΟΥΣΑΝ|ΙΟΣΑΣΤΑΝ|ΙΟΣΑΣΤΕ|ΙΟΣΟΥΝ|ΙΟΣΟΥΝΑ|ΙΟΤΑΝ|ΙΟΥΜΑ|ΙΟΥΜΑΣΤΕ|ΙΟΥΝΤΑΙ|ΙΟΥΝΤΑΝ|Η|ΗΔΕΣ|ΗΔΩΝ|ΗΘΕΙ|ΗΘΕΙΣ|ΗΘΕΙΤΕ|ΗΘΗΚΑΤΕ|ΗΘΗΚΑΝ|ΗΘΟΥΝ|ΗΘΩ|ΗΚΑΤΕ|ΗΚΑΝ|ΗΣ|ΗΣΑΝ|ΗΣΑΤΕ|ΗΣΕΙ|ΗΣΕΣ|ΗΣΟΥΝ|ΗΣΩ|Ο|ΟΙ|ΟΜΑΙ|ΟΜΑΣΤΑΝ|ΟΜΟΥΝ|ΟΜΟΥΝΑ|ΟΝΤΑΙ|ΟΝΤΑΝ|ΟΝΤΟΥΣΑΝ|ΟΣ|ΟΣΑΣΤΑΝ|ΟΣΑΣΤΕ|ΟΣΟΥΝ|ΟΣΟΥΝΑ|ΟΤΑΝ|ΟΥ|ΟΥΜΑΙ|ΟΥΜΑΣΤΕ|ΟΥΝ|ΟΥΝΤΑΙ|ΟΥΝΤΑΝ|ΟΥΣ|ΟΥΣΑΝ|ΟΥΣΑΤΕ|Υ||ΥΑ|ΥΣ|Ω|ΩΝ|ΟΙΣ)$/.exec(s))&&(s=o[1]),null!=(o=/^(.+?)(ΕΣΤΕΡ|ΕΣΤΑΤ|ΟΤΕΡ|ΟΤΑΤ|ΥΤΕΡ|ΥΤΑΤ|ΩΤΕΡ|ΩΤΑΤ)$/.exec(s))&&(/^(ΕΞ|ΕΣ|ΑΝ|ΚΑΤ|Κ|ΠΡ)$/.test(o[1])||(s=o[1]),/^(ΚΑ|Μ|ΕΛΕ|ΛΕ|ΔΕ)$/.test(o[1])&&(s+="ΥΤ")),s}var l={"ΦΑΓΙΑ":"ΦΑ","ΦΑΓΙΟΥ":"ΦΑ","ΦΑΓΙΩΝ":"ΦΑ","ΣΚΑΓΙΑ":"ΣΚΑ","ΣΚΑΓΙΟΥ":"ΣΚΑ","ΣΚΑΓΙΩΝ":"ΣΚΑ","ΣΟΓΙΟΥ":"ΣΟ","ΣΟΓΙΑ":"ΣΟ","ΣΟΓΙΩΝ":"ΣΟ","ΤΑΤΟΓΙΑ":"ΤΑΤΟ","ΤΑΤΟΓΙΟΥ":"ΤΑΤΟ","ΤΑΤΟΓΙΩΝ":"ΤΑΤΟ","ΚΡΕΑΣ":"ΚΡΕ","ΚΡΕΑΤΟΣ":"ΚΡΕ","ΚΡΕΑΤΑ":"ΚΡΕ","ΚΡΕΑΤΩΝ":"ΚΡΕ","ΠΕΡΑΣ":"ΠΕΡ","ΠΕΡΑΤΟΣ":"ΠΕΡ","ΠΕΡΑΤΑ":"ΠΕΡ","ΠΕΡΑΤΩΝ":"ΠΕΡ","ΤΕΡΑΣ":"ΤΕΡ","ΤΕΡΑΤΟΣ":"ΤΕΡ","ΤΕΡΑΤΑ":"ΤΕΡ","ΤΕΡΑΤΩΝ":"ΤΕΡ","ΦΩΣ":"ΦΩ","ΦΩΤΟΣ":"ΦΩ","ΦΩΤΑ":"ΦΩ","ΦΩΤΩΝ":"ΦΩ","ΚΑΘΕΣΤΩΣ":"ΚΑΘΕΣΤ","ΚΑΘΕΣΤΩΤΟΣ":"ΚΑΘΕΣΤ","ΚΑΘΕΣΤΩΤΑ":"ΚΑΘΕΣΤ","ΚΑΘΕΣΤΩΤΩΝ":"ΚΑΘΕΣΤ","ΓΕΓΟΝΟΣ":"ΓΕΓΟΝ","ΓΕΓΟΝΟΤΟΣ":"ΓΕΓΟΝ","ΓΕΓΟΝΟΤΑ":"ΓΕΓΟΝ","ΓΕΓΟΝΟΤΩΝ":"ΓΕΓΟΝ","ΕΥΑ":"ΕΥ"},i=["ΑΚΡΙΒΩΣ","ΑΛΑ","ΑΛΛΑ","ΑΛΛΙΩΣ","ΑΛΛΟΤΕ","ΑΜΑ","ΑΝΩ","ΑΝΑ","ΑΝΑΜΕΣΑ","ΑΝΑΜΕΤΑΞΥ","ΑΝΕΥ","ΑΝΤΙ","ΑΝΤΙΠΕΡΑ","ΑΝΤΙΟ","ΑΞΑΦΝΑ","ΑΠΟ","ΑΠΟΨΕ","ΑΡΑ","ΑΡΑΓΕ","ΑΥΡΙΟ","ΑΦΟΙ","ΑΦΟΥ","ΑΦΟΤΟΥ","ΒΡΕ","ΓΕΙΑ","ΓΙΑ","ΓΙΑΤΙ","ΓΡΑΜΜΑ","ΔΕΗ","ΔΕΝ","ΔΗΛΑΔΗ","ΔΙΧΩΣ","ΔΥΟ","ΕΑΝ","ΕΓΩ","ΕΔΩ","ΕΔΑ","ΕΙΘΕ","ΕΙΜΑΙ","ΕΙΜΑΣΤΕ","ΕΙΣΑΙ","ΕΙΣΑΣΤΕ","ΕΙΝΑΙ","ΕΙΣΤΕ","ΕΙΤΕ","ΕΚΕΙ","ΕΚΟ","ΕΛΑ","ΕΜΑΣ","ΕΜΕΙΣ","ΕΝΤΕΛΩΣ","ΕΝΤΟΣ","ΕΝΤΩΜΕΤΑΞΥ","ΕΝΩ","ΕΞΙ","ΕΞΙΣΟΥ","ΕΞΗΣ","ΕΞΩ","ΕΟΚ","ΕΠΑΝΩ","ΕΠΕΙΔΗ","ΕΠΕΙΤΑ","ΕΠΙ","ΕΠΙΣΗΣ","ΕΠΟΜΕΝΩΣ","ΕΠΤΑ","ΕΣΑΣ","ΕΣΕΙΣ","ΕΣΤΩ","ΕΣΥ","ΕΣΩ","ΕΤΣΙ","ΕΥΓΕ","ΕΦΕ","ΕΦΕΞΗΣ","ΕΧΤΕΣ","ΕΩΣ","ΗΔΗ","ΗΜΙ","ΗΠΑ","ΗΤΟΙ","ΘΕΣ","ΙΔΙΩΣ","ΙΔΗ","ΙΚΑ","ΙΣΩΣ","ΚΑΘΕ","ΚΑΘΕΤΙ","ΚΑΘΟΛΟΥ","ΚΑΘΩΣ","ΚΑΙ","ΚΑΝ","ΚΑΠΟΤΕ","ΚΑΠΟΥ","ΚΑΤΑ","ΚΑΤΙ","ΚΑΤΟΠΙΝ","ΚΑΤΩ","ΚΕΙ","ΚΙΧ","ΚΚΕ","ΚΟΛΑΝ","ΚΥΡΙΩΣ","ΚΩΣ","ΜΑΚΑΡΙ","ΜΑΛΙΣΤΑ","ΜΑΛΛΟΝ","ΜΑΙ","ΜΑΟ","ΜΑΟΥΣ","ΜΑΣ","ΜΕΘΑΥΡΙΟ","ΜΕΣ","ΜΕΣΑ","ΜΕΤΑ","ΜΕΤΑΞΥ","ΜΕΧΡΙ","ΜΗΔΕ","ΜΗΝ","ΜΗΠΩΣ","ΜΗΤΕ","ΜΙΑ","ΜΙΑΣ","ΜΙΣ","ΜΜΕ","ΜΟΛΟΝΟΤΙ","ΜΟΥ","ΜΠΑ","ΜΠΑΣ","ΜΠΟΥΦΑΝ","ΜΠΡΟΣ","ΝΑΙ","ΝΕΣ","ΝΤΑ","ΝΤΕ","ΞΑΝΑ","ΟΗΕ","ΟΚΤΩ","ΟΜΩΣ","ΟΝΕ","ΟΠΑ","ΟΠΟΥ","ΟΠΩΣ","ΟΣΟ","ΟΤΑΝ","ΟΤΕ","ΟΤΙ","ΟΥΤΕ","ΟΧΙ","ΠΑΛΙ","ΠΑΝ","ΠΑΝΟ","ΠΑΝΤΟΤΕ","ΠΑΝΤΟΥ","ΠΑΝΤΩΣ","ΠΑΝΩ","ΠΑΡΑ","ΠΕΡΑ","ΠΕΡΙ","ΠΕΡΙΠΟΥ","ΠΙΑ","ΠΙΟ","ΠΙΣΩ","ΠΛΑΙ","ΠΛΕΟΝ","ΠΛΗΝ","ΠΟΤΕ","ΠΟΥ","ΠΡΟ","ΠΡΟΣ","ΠΡΟΧΤΕΣ","ΠΡΟΧΘΕΣ","ΡΟΔΙ","ΠΩΣ","ΣΑΙ","ΣΑΣ","ΣΑΝ","ΣΕΙΣ","ΣΙΑ","ΣΚΙ","ΣΟΙ","ΣΟΥ","ΣΡΙ","ΣΥΝ","ΣΥΝΑΜΑ","ΣΧΕΔΟΝ","ΤΑΔΕ","ΤΑΞΙ","ΤΑΧΑ","ΤΕΙ","ΤΗΝ","ΤΗΣ","ΤΙΠΟΤΑ","ΤΙΠΟΤΕ","ΤΙΣ","ΤΟΝ","ΤΟΤΕ","ΤΟΥ","ΤΟΥΣ","ΤΣΑ","ΤΣΕ","ΤΣΙ","ΤΣΟΥ","ΤΩΝ","ΥΠΟ","ΥΠΟΨΗ","ΥΠΟΨΙΝ","ΥΣΤΕΡΑ","ΦΕΤΟΣ","ΦΙΣ","ΦΠΑ","ΧΑΦ","ΧΘΕΣ","ΧΤΕΣ","ΧΩΡΙΣ","ΩΣ","ΩΣΑΝ","ΩΣΟΤΟΥ","ΩΣΠΟΥ","ΩΣΤΕ","ΩΣΤΟΣΟ"],s=new RegExp("^[ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩ]+$");return function(e){return"function"==typeof e.update?e.update(function(e){return n(e.toUpperCase()).toLowerCase()}):n(e.toUpperCase()).toLowerCase()}}(),e.Pipeline.registerFunction(e.el.stemmer,"stemmer-el"),e.el.stopWordFilter=e.generateStopWordFilter("αλλα αν αντι απο αυτα αυτεσ αυτη αυτο αυτοι αυτοσ αυτουσ αυτων για δε δεν εαν ειμαι ειμαστε ειναι εισαι ειστε εκεινα εκεινεσ εκεινη εκεινο εκεινοι εκεινοσ εκεινουσ εκεινων ενω επι η θα ισωσ κ και κατα κι μα με μετα μη μην να ο οι ομωσ οπωσ οσο οτι παρα ποια ποιεσ ποιο ποιοι ποιοσ ποιουσ ποιων που προσ πωσ σε στη στην στο στον τα την τησ το τον τοτε του των ωσ".split(" ")),e.Pipeline.registerFunction(e.el.stopWordFilter,"stopWordFilter-el"),e.el.normilizer=function(){var e={"Ά":"Α","ά":"α","Έ":"Ε","έ":"ε","Ή":"Η","ή":"η","Ί":"Ι","ί":"ι","Ό":"Ο","ο":"ο","Ύ":"Υ","ύ":"υ","Ώ":"Ω","ώ":"ω","Ϊ":"Ι","ϊ":"ι","Ϋ":"Υ","ϋ":"υ","ΐ":"ι","ΰ":"υ"};return function(t){if("function"==typeof t.update)return t.update(function(t){for(var r="",n=0;n=A.limit)return!0;A.cursor++}return!1}return!0}function n(){if(A.in_grouping(x,97,252)){var s=A.cursor;if(e()){if(A.cursor=s,!A.in_grouping(x,97,252))return!0;for(;!A.out_grouping(x,97,252);){if(A.cursor>=A.limit)return!0;A.cursor++}}return!1}return!0}function i(){var s,r=A.cursor;if(n()){if(A.cursor=r,!A.out_grouping(x,97,252))return;if(s=A.cursor,e()){if(A.cursor=s,!A.in_grouping(x,97,252)||A.cursor>=A.limit)return;A.cursor++}}g=A.cursor}function a(){for(;!A.in_grouping(x,97,252);){if(A.cursor>=A.limit)return!1;A.cursor++}for(;!A.out_grouping(x,97,252);){if(A.cursor>=A.limit)return!1;A.cursor++}return!0}function t(){var e=A.cursor;g=A.limit,p=g,v=g,i(),A.cursor=e,a()&&(p=A.cursor,a()&&(v=A.cursor))}function o(){for(var e;;){if(A.bra=A.cursor,e=A.find_among(k,6))switch(A.ket=A.cursor,e){case 1:A.slice_from("a");continue;case 2:A.slice_from("e");continue;case 3:A.slice_from("i");continue;case 4:A.slice_from("o");continue;case 5:A.slice_from("u");continue;case 6:if(A.cursor>=A.limit)break;A.cursor++;continue}break}}function u(){return g<=A.cursor}function w(){return p<=A.cursor}function c(){return v<=A.cursor}function m(){var e;if(A.ket=A.cursor,A.find_among_b(y,13)&&(A.bra=A.cursor,(e=A.find_among_b(q,11))&&u()))switch(e){case 1:A.bra=A.cursor,A.slice_from("iendo");break;case 2:A.bra=A.cursor,A.slice_from("ando");break;case 3:A.bra=A.cursor,A.slice_from("ar");break;case 4:A.bra=A.cursor,A.slice_from("er");break;case 5:A.bra=A.cursor,A.slice_from("ir");break;case 6:A.slice_del();break;case 7:A.eq_s_b(1,"u")&&A.slice_del()}}function l(e,s){if(!c())return!0;A.slice_del(),A.ket=A.cursor;var r=A.find_among_b(e,s);return r&&(A.bra=A.cursor,1==r&&c()&&A.slice_del()),!1}function d(e){return!c()||(A.slice_del(),A.ket=A.cursor,A.eq_s_b(2,e)&&(A.bra=A.cursor,c()&&A.slice_del()),!1)}function b(){var e;if(A.ket=A.cursor,e=A.find_among_b(S,46)){switch(A.bra=A.cursor,e){case 1:if(!c())return!1;A.slice_del();break;case 2:if(d("ic"))return!1;break;case 3:if(!c())return!1;A.slice_from("log");break;case 4:if(!c())return!1;A.slice_from("u");break;case 5:if(!c())return!1;A.slice_from("ente");break;case 6:if(!w())return!1;A.slice_del(),A.ket=A.cursor,e=A.find_among_b(C,4),e&&(A.bra=A.cursor,c()&&(A.slice_del(),1==e&&(A.ket=A.cursor,A.eq_s_b(2,"at")&&(A.bra=A.cursor,c()&&A.slice_del()))));break;case 7:if(l(P,3))return!1;break;case 8:if(l(F,3))return!1;break;case 9:if(d("at"))return!1}return!0}return!1}function f(){var e,s;if(A.cursor>=g&&(s=A.limit_backward,A.limit_backward=g,A.ket=A.cursor,e=A.find_among_b(W,12),A.limit_backward=s,e)){if(A.bra=A.cursor,1==e){if(!A.eq_s_b(1,"u"))return!1;A.slice_del()}return!0}return!1}function _(){var e,s,r,n;if(A.cursor>=g&&(s=A.limit_backward,A.limit_backward=g,A.ket=A.cursor,e=A.find_among_b(L,96),A.limit_backward=s,e))switch(A.bra=A.cursor,e){case 1:r=A.limit-A.cursor,A.eq_s_b(1,"u")?(n=A.limit-A.cursor,A.eq_s_b(1,"g")?A.cursor=A.limit-n:A.cursor=A.limit-r):A.cursor=A.limit-r,A.bra=A.cursor;case 2:A.slice_del()}}function h(){var e,s;if(A.ket=A.cursor,e=A.find_among_b(z,8))switch(A.bra=A.cursor,e){case 1:u()&&A.slice_del();break;case 2:u()&&(A.slice_del(),A.ket=A.cursor,A.eq_s_b(1,"u")&&(A.bra=A.cursor,s=A.limit-A.cursor,A.eq_s_b(1,"g")&&(A.cursor=A.limit-s,u()&&A.slice_del())))}}var v,p,g,k=[new s("",-1,6),new s("á",0,1),new s("é",0,2),new s("í",0,3),new s("ó",0,4),new s("ú",0,5)],y=[new s("la",-1,-1),new s("sela",0,-1),new s("le",-1,-1),new s("me",-1,-1),new s("se",-1,-1),new s("lo",-1,-1),new s("selo",5,-1),new s("las",-1,-1),new s("selas",7,-1),new s("les",-1,-1),new s("los",-1,-1),new s("selos",10,-1),new s("nos",-1,-1)],q=[new s("ando",-1,6),new s("iendo",-1,6),new s("yendo",-1,7),new s("ándo",-1,2),new s("iéndo",-1,1),new s("ar",-1,6),new s("er",-1,6),new s("ir",-1,6),new s("ár",-1,3),new s("ér",-1,4),new s("ír",-1,5)],C=[new s("ic",-1,-1),new s("ad",-1,-1),new s("os",-1,-1),new s("iv",-1,1)],P=[new s("able",-1,1),new s("ible",-1,1),new s("ante",-1,1)],F=[new s("ic",-1,1),new s("abil",-1,1),new s("iv",-1,1)],S=[new s("ica",-1,1),new s("ancia",-1,2),new s("encia",-1,5),new s("adora",-1,2),new s("osa",-1,1),new s("ista",-1,1),new s("iva",-1,9),new s("anza",-1,1),new s("logía",-1,3),new s("idad",-1,8),new s("able",-1,1),new s("ible",-1,1),new s("ante",-1,2),new s("mente",-1,7),new s("amente",13,6),new s("ación",-1,2),new s("ución",-1,4),new s("ico",-1,1),new s("ismo",-1,1),new s("oso",-1,1),new s("amiento",-1,1),new s("imiento",-1,1),new s("ivo",-1,9),new s("ador",-1,2),new s("icas",-1,1),new s("ancias",-1,2),new s("encias",-1,5),new s("adoras",-1,2),new s("osas",-1,1),new s("istas",-1,1),new s("ivas",-1,9),new s("anzas",-1,1),new s("logías",-1,3),new s("idades",-1,8),new s("ables",-1,1),new s("ibles",-1,1),new s("aciones",-1,2),new s("uciones",-1,4),new s("adores",-1,2),new s("antes",-1,2),new s("icos",-1,1),new s("ismos",-1,1),new s("osos",-1,1),new s("amientos",-1,1),new s("imientos",-1,1),new s("ivos",-1,9)],W=[new s("ya",-1,1),new s("ye",-1,1),new s("yan",-1,1),new s("yen",-1,1),new s("yeron",-1,1),new s("yendo",-1,1),new s("yo",-1,1),new s("yas",-1,1),new s("yes",-1,1),new s("yais",-1,1),new s("yamos",-1,1),new s("yó",-1,1)],L=[new s("aba",-1,2),new s("ada",-1,2),new s("ida",-1,2),new s("ara",-1,2),new s("iera",-1,2),new s("ía",-1,2),new s("aría",5,2),new s("ería",5,2),new s("iría",5,2),new s("ad",-1,2),new s("ed",-1,2),new s("id",-1,2),new s("ase",-1,2),new s("iese",-1,2),new s("aste",-1,2),new s("iste",-1,2),new s("an",-1,2),new s("aban",16,2),new s("aran",16,2),new s("ieran",16,2),new s("ían",16,2),new s("arían",20,2),new s("erían",20,2),new s("irían",20,2),new s("en",-1,1),new s("asen",24,2),new s("iesen",24,2),new s("aron",-1,2),new s("ieron",-1,2),new s("arán",-1,2),new s("erán",-1,2),new s("irán",-1,2),new s("ado",-1,2),new s("ido",-1,2),new s("ando",-1,2),new s("iendo",-1,2),new s("ar",-1,2),new s("er",-1,2),new s("ir",-1,2),new s("as",-1,2),new s("abas",39,2),new s("adas",39,2),new s("idas",39,2),new s("aras",39,2),new s("ieras",39,2),new s("ías",39,2),new s("arías",45,2),new s("erías",45,2),new s("irías",45,2),new s("es",-1,1),new s("ases",49,2),new s("ieses",49,2),new s("abais",-1,2),new s("arais",-1,2),new s("ierais",-1,2),new s("íais",-1,2),new s("aríais",55,2),new s("eríais",55,2),new s("iríais",55,2),new s("aseis",-1,2),new s("ieseis",-1,2),new s("asteis",-1,2),new s("isteis",-1,2),new s("áis",-1,2),new s("éis",-1,1),new s("aréis",64,2),new s("eréis",64,2),new s("iréis",64,2),new s("ados",-1,2),new s("idos",-1,2),new s("amos",-1,2),new s("ábamos",70,2),new s("áramos",70,2),new s("iéramos",70,2),new s("íamos",70,2),new s("aríamos",74,2),new s("eríamos",74,2),new s("iríamos",74,2),new s("emos",-1,1),new s("aremos",78,2),new s("eremos",78,2),new s("iremos",78,2),new s("ásemos",78,2),new s("iésemos",78,2),new s("imos",-1,2),new s("arás",-1,2),new s("erás",-1,2),new s("irás",-1,2),new s("ís",-1,2),new s("ará",-1,2),new s("erá",-1,2),new s("irá",-1,2),new s("aré",-1,2),new s("eré",-1,2),new s("iré",-1,2),new s("ió",-1,2)],z=[new s("a",-1,1),new s("e",-1,2),new s("o",-1,1),new s("os",-1,1),new s("á",-1,1),new s("é",-1,2),new s("í",-1,1),new s("ó",-1,1)],x=[17,65,16,0,0,0,0,0,0,0,0,0,0,0,0,0,1,17,4,10],A=new r;this.setCurrent=function(e){A.setCurrent(e)},this.getCurrent=function(){return A.getCurrent()},this.stem=function(){var e=A.cursor;return t(),A.limit_backward=e,A.cursor=A.limit,m(),A.cursor=A.limit,b()||(A.cursor=A.limit,f()||(A.cursor=A.limit,_())),A.cursor=A.limit,h(),A.cursor=A.limit_backward,o(),!0}};return function(e){return"function"==typeof e.update?e.update(function(e){return n.setCurrent(e),n.stem(),n.getCurrent()}):(n.setCurrent(e),n.stem(),n.getCurrent())}}(),e.Pipeline.registerFunction(e.es.stemmer,"stemmer-es"),e.es.stopWordFilter=e.generateStopWordFilter("a al algo algunas algunos ante antes como con contra cual cuando de del desde donde durante e el ella ellas ellos en entre era erais eran eras eres es esa esas ese eso esos esta estaba estabais estaban estabas estad estada estadas estado estados estamos estando estar estaremos estará estarán estarás estaré estaréis estaría estaríais estaríamos estarían estarías estas este estemos esto estos estoy estuve estuviera estuvierais estuvieran estuvieras estuvieron estuviese estuvieseis estuviesen estuvieses estuvimos estuviste estuvisteis estuviéramos estuviésemos estuvo está estábamos estáis están estás esté estéis estén estés fue fuera fuerais fueran fueras fueron fuese fueseis fuesen fueses fui fuimos fuiste fuisteis fuéramos fuésemos ha habida habidas habido habidos habiendo habremos habrá habrán habrás habré habréis habría habríais habríamos habrían habrías habéis había habíais habíamos habían habías han has hasta hay haya hayamos hayan hayas hayáis he hemos hube hubiera hubierais hubieran hubieras hubieron hubiese hubieseis hubiesen hubieses hubimos hubiste hubisteis hubiéramos hubiésemos hubo la las le les lo los me mi mis mucho muchos muy más mí mía mías mío míos nada ni no nos nosotras nosotros nuestra nuestras nuestro nuestros o os otra otras otro otros para pero poco por porque que quien quienes qué se sea seamos sean seas seremos será serán serás seré seréis sería seríais seríamos serían serías seáis sido siendo sin sobre sois somos son soy su sus suya suyas suyo suyos sí también tanto te tendremos tendrá tendrán tendrás tendré tendréis tendría tendríais tendríamos tendrían tendrías tened tenemos tenga tengamos tengan tengas tengo tengáis tenida tenidas tenido tenidos teniendo tenéis tenía teníais teníamos tenían tenías ti tiene tienen tienes todo todos tu tus tuve tuviera tuvierais tuvieran tuvieras tuvieron tuviese tuvieseis tuviesen tuvieses tuvimos tuviste tuvisteis tuviéramos tuviésemos tuvo tuya tuyas tuyo tuyos tú un una uno unos vosotras vosotros vuestra vuestras vuestro vuestros y ya yo él éramos".split(" ")),e.Pipeline.registerFunction(e.es.stopWordFilter,"stopWordFilter-es")}}); \ No newline at end of file diff --git a/assets/javascripts/lunr/min/lunr.fi.min.js b/assets/javascripts/lunr/min/lunr.fi.min.js new file mode 100644 index 0000000..29f5dfc --- /dev/null +++ b/assets/javascripts/lunr/min/lunr.fi.min.js @@ -0,0 +1,18 @@ +/*! + * Lunr languages, `Finnish` language + * https://github.com/MihaiValentin/lunr-languages + * + * Copyright 2014, Mihai Valentin + * http://www.mozilla.org/MPL/ + */ +/*! + * based on + * Snowball JavaScript Library v0.3 + * http://code.google.com/p/urim/ + * http://snowball.tartarus.org/ + * + * Copyright 2010, Oleg Mazko + * http://www.mozilla.org/MPL/ + */ + +!function(i,e){"function"==typeof define&&define.amd?define(e):"object"==typeof exports?module.exports=e():e()(i.lunr)}(this,function(){return function(i){if(void 0===i)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===i.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");i.fi=function(){this.pipeline.reset(),this.pipeline.add(i.fi.trimmer,i.fi.stopWordFilter,i.fi.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(i.fi.stemmer))},i.fi.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",i.fi.trimmer=i.trimmerSupport.generateTrimmer(i.fi.wordCharacters),i.Pipeline.registerFunction(i.fi.trimmer,"trimmer-fi"),i.fi.stemmer=function(){var e=i.stemmerSupport.Among,r=i.stemmerSupport.SnowballProgram,n=new function(){function i(){f=A.limit,d=f,n()||(f=A.cursor,n()||(d=A.cursor))}function n(){for(var i;;){if(i=A.cursor,A.in_grouping(W,97,246))break;if(A.cursor=i,i>=A.limit)return!0;A.cursor++}for(A.cursor=i;!A.out_grouping(W,97,246);){if(A.cursor>=A.limit)return!0;A.cursor++}return!1}function t(){return d<=A.cursor}function s(){var i,e;if(A.cursor>=f)if(e=A.limit_backward,A.limit_backward=f,A.ket=A.cursor,i=A.find_among_b(h,10)){switch(A.bra=A.cursor,A.limit_backward=e,i){case 1:if(!A.in_grouping_b(x,97,246))return;break;case 2:if(!t())return}A.slice_del()}else A.limit_backward=e}function o(){var i,e,r;if(A.cursor>=f)if(e=A.limit_backward,A.limit_backward=f,A.ket=A.cursor,i=A.find_among_b(v,9))switch(A.bra=A.cursor,A.limit_backward=e,i){case 1:r=A.limit-A.cursor,A.eq_s_b(1,"k")||(A.cursor=A.limit-r,A.slice_del());break;case 2:A.slice_del(),A.ket=A.cursor,A.eq_s_b(3,"kse")&&(A.bra=A.cursor,A.slice_from("ksi"));break;case 3:A.slice_del();break;case 4:A.find_among_b(p,6)&&A.slice_del();break;case 5:A.find_among_b(g,6)&&A.slice_del();break;case 6:A.find_among_b(j,2)&&A.slice_del()}else A.limit_backward=e}function l(){return A.find_among_b(q,7)}function a(){return A.eq_s_b(1,"i")&&A.in_grouping_b(L,97,246)}function u(){var i,e,r;if(A.cursor>=f)if(e=A.limit_backward,A.limit_backward=f,A.ket=A.cursor,i=A.find_among_b(C,30)){switch(A.bra=A.cursor,A.limit_backward=e,i){case 1:if(!A.eq_s_b(1,"a"))return;break;case 2:case 9:if(!A.eq_s_b(1,"e"))return;break;case 3:if(!A.eq_s_b(1,"i"))return;break;case 4:if(!A.eq_s_b(1,"o"))return;break;case 5:if(!A.eq_s_b(1,"ä"))return;break;case 6:if(!A.eq_s_b(1,"ö"))return;break;case 7:if(r=A.limit-A.cursor,!l()&&(A.cursor=A.limit-r,!A.eq_s_b(2,"ie"))){A.cursor=A.limit-r;break}if(A.cursor=A.limit-r,A.cursor<=A.limit_backward){A.cursor=A.limit-r;break}A.cursor--,A.bra=A.cursor;break;case 8:if(!A.in_grouping_b(W,97,246)||!A.out_grouping_b(W,97,246))return}A.slice_del(),k=!0}else A.limit_backward=e}function c(){var i,e,r;if(A.cursor>=d)if(e=A.limit_backward,A.limit_backward=d,A.ket=A.cursor,i=A.find_among_b(P,14)){if(A.bra=A.cursor,A.limit_backward=e,1==i){if(r=A.limit-A.cursor,A.eq_s_b(2,"po"))return;A.cursor=A.limit-r}A.slice_del()}else A.limit_backward=e}function m(){var i;A.cursor>=f&&(i=A.limit_backward,A.limit_backward=f,A.ket=A.cursor,A.find_among_b(F,2)?(A.bra=A.cursor,A.limit_backward=i,A.slice_del()):A.limit_backward=i)}function w(){var i,e,r,n,t,s;if(A.cursor>=f){if(e=A.limit_backward,A.limit_backward=f,A.ket=A.cursor,A.eq_s_b(1,"t")&&(A.bra=A.cursor,r=A.limit-A.cursor,A.in_grouping_b(W,97,246)&&(A.cursor=A.limit-r,A.slice_del(),A.limit_backward=e,n=A.limit-A.cursor,A.cursor>=d&&(A.cursor=d,t=A.limit_backward,A.limit_backward=A.cursor,A.cursor=A.limit-n,A.ket=A.cursor,i=A.find_among_b(S,2))))){if(A.bra=A.cursor,A.limit_backward=t,1==i){if(s=A.limit-A.cursor,A.eq_s_b(2,"po"))return;A.cursor=A.limit-s}return void A.slice_del()}A.limit_backward=e}}function _(){var i,e,r,n;if(A.cursor>=f){for(i=A.limit_backward,A.limit_backward=f,e=A.limit-A.cursor,l()&&(A.cursor=A.limit-e,A.ket=A.cursor,A.cursor>A.limit_backward&&(A.cursor--,A.bra=A.cursor,A.slice_del())),A.cursor=A.limit-e,A.ket=A.cursor,A.in_grouping_b(y,97,228)&&(A.bra=A.cursor,A.out_grouping_b(W,97,246)&&A.slice_del()),A.cursor=A.limit-e,A.ket=A.cursor,A.eq_s_b(1,"j")&&(A.bra=A.cursor,r=A.limit-A.cursor,A.eq_s_b(1,"o")?A.slice_del():(A.cursor=A.limit-r,A.eq_s_b(1,"u")&&A.slice_del())),A.cursor=A.limit-e,A.ket=A.cursor,A.eq_s_b(1,"o")&&(A.bra=A.cursor,A.eq_s_b(1,"j")&&A.slice_del()),A.cursor=A.limit-e,A.limit_backward=i;;){if(n=A.limit-A.cursor,A.out_grouping_b(W,97,246)){A.cursor=A.limit-n;break}if(A.cursor=A.limit-n,A.cursor<=A.limit_backward)return;A.cursor--}A.ket=A.cursor,A.cursor>A.limit_backward&&(A.cursor--,A.bra=A.cursor,b=A.slice_to(),A.eq_v_b(b)&&A.slice_del())}}var k,b,d,f,h=[new e("pa",-1,1),new e("sti",-1,2),new e("kaan",-1,1),new e("han",-1,1),new e("kin",-1,1),new e("hän",-1,1),new e("kään",-1,1),new e("ko",-1,1),new e("pä",-1,1),new e("kö",-1,1)],p=[new e("lla",-1,-1),new e("na",-1,-1),new e("ssa",-1,-1),new e("ta",-1,-1),new e("lta",3,-1),new e("sta",3,-1)],g=[new e("llä",-1,-1),new e("nä",-1,-1),new e("ssä",-1,-1),new e("tä",-1,-1),new e("ltä",3,-1),new e("stä",3,-1)],j=[new e("lle",-1,-1),new e("ine",-1,-1)],v=[new e("nsa",-1,3),new e("mme",-1,3),new e("nne",-1,3),new e("ni",-1,2),new e("si",-1,1),new e("an",-1,4),new e("en",-1,6),new e("än",-1,5),new e("nsä",-1,3)],q=[new e("aa",-1,-1),new e("ee",-1,-1),new e("ii",-1,-1),new e("oo",-1,-1),new e("uu",-1,-1),new e("ää",-1,-1),new e("öö",-1,-1)],C=[new e("a",-1,8),new e("lla",0,-1),new e("na",0,-1),new e("ssa",0,-1),new e("ta",0,-1),new e("lta",4,-1),new e("sta",4,-1),new e("tta",4,9),new e("lle",-1,-1),new e("ine",-1,-1),new e("ksi",-1,-1),new e("n",-1,7),new e("han",11,1),new e("den",11,-1,a),new e("seen",11,-1,l),new e("hen",11,2),new e("tten",11,-1,a),new e("hin",11,3),new e("siin",11,-1,a),new e("hon",11,4),new e("hän",11,5),new e("hön",11,6),new e("ä",-1,8),new e("llä",22,-1),new e("nä",22,-1),new e("ssä",22,-1),new e("tä",22,-1),new e("ltä",26,-1),new e("stä",26,-1),new e("ttä",26,9)],P=[new e("eja",-1,-1),new e("mma",-1,1),new e("imma",1,-1),new e("mpa",-1,1),new e("impa",3,-1),new e("mmi",-1,1),new e("immi",5,-1),new e("mpi",-1,1),new e("impi",7,-1),new e("ejä",-1,-1),new e("mmä",-1,1),new e("immä",10,-1),new e("mpä",-1,1),new e("impä",12,-1)],F=[new e("i",-1,-1),new e("j",-1,-1)],S=[new e("mma",-1,1),new e("imma",0,-1)],y=[17,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8],W=[17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,0,8,0,32],L=[17,65,16,0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,32],x=[17,97,24,1,0,0,0,0,0,0,0,0,0,0,0,0,8,0,32],A=new r;this.setCurrent=function(i){A.setCurrent(i)},this.getCurrent=function(){return A.getCurrent()},this.stem=function(){var e=A.cursor;return i(),k=!1,A.limit_backward=e,A.cursor=A.limit,s(),A.cursor=A.limit,o(),A.cursor=A.limit,u(),A.cursor=A.limit,c(),A.cursor=A.limit,k?(m(),A.cursor=A.limit):(A.cursor=A.limit,w(),A.cursor=A.limit),_(),!0}};return function(i){return"function"==typeof i.update?i.update(function(i){return n.setCurrent(i),n.stem(),n.getCurrent()}):(n.setCurrent(i),n.stem(),n.getCurrent())}}(),i.Pipeline.registerFunction(i.fi.stemmer,"stemmer-fi"),i.fi.stopWordFilter=i.generateStopWordFilter("ei eivät emme en et ette että he heidän heidät heihin heille heillä heiltä heissä heistä heitä hän häneen hänelle hänellä häneltä hänen hänessä hänestä hänet häntä itse ja johon joiden joihin joiksi joilla joille joilta joina joissa joista joita joka joksi jolla jolle jolta jona jonka jos jossa josta jota jotka kanssa keiden keihin keiksi keille keillä keiltä keinä keissä keistä keitä keneen keneksi kenelle kenellä keneltä kenen kenenä kenessä kenestä kenet ketkä ketkä ketä koska kuin kuka kun me meidän meidät meihin meille meillä meiltä meissä meistä meitä mihin miksi mikä mille millä miltä minkä minkä minua minulla minulle minulta minun minussa minusta minut minuun minä minä missä mistä mitkä mitä mukaan mutta ne niiden niihin niiksi niille niillä niiltä niin niin niinä niissä niistä niitä noiden noihin noiksi noilla noille noilta noin noina noissa noista noita nuo nyt näiden näihin näiksi näille näillä näiltä näinä näissä näistä näitä nämä ole olemme olen olet olette oli olimme olin olisi olisimme olisin olisit olisitte olisivat olit olitte olivat olla olleet ollut on ovat poikki se sekä sen siihen siinä siitä siksi sille sillä sillä siltä sinua sinulla sinulle sinulta sinun sinussa sinusta sinut sinuun sinä sinä sitä tai te teidän teidät teihin teille teillä teiltä teissä teistä teitä tuo tuohon tuoksi tuolla tuolle tuolta tuon tuona tuossa tuosta tuota tähän täksi tälle tällä tältä tämä tämän tänä tässä tästä tätä vaan vai vaikka yli".split(" ")),i.Pipeline.registerFunction(i.fi.stopWordFilter,"stopWordFilter-fi")}}); \ No newline at end of file diff --git a/assets/javascripts/lunr/min/lunr.fr.min.js b/assets/javascripts/lunr/min/lunr.fr.min.js new file mode 100644 index 0000000..68cd009 --- /dev/null +++ b/assets/javascripts/lunr/min/lunr.fr.min.js @@ -0,0 +1,18 @@ +/*! + * Lunr languages, `French` language + * https://github.com/MihaiValentin/lunr-languages + * + * Copyright 2014, Mihai Valentin + * http://www.mozilla.org/MPL/ + */ +/*! + * based on + * Snowball JavaScript Library v0.3 + * http://code.google.com/p/urim/ + * http://snowball.tartarus.org/ + * + * Copyright 2010, Oleg Mazko + * http://www.mozilla.org/MPL/ + */ + +!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.fr=function(){this.pipeline.reset(),this.pipeline.add(e.fr.trimmer,e.fr.stopWordFilter,e.fr.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.fr.stemmer))},e.fr.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",e.fr.trimmer=e.trimmerSupport.generateTrimmer(e.fr.wordCharacters),e.Pipeline.registerFunction(e.fr.trimmer,"trimmer-fr"),e.fr.stemmer=function(){var r=e.stemmerSupport.Among,s=e.stemmerSupport.SnowballProgram,i=new function(){function e(e,r,s){return!(!W.eq_s(1,e)||(W.ket=W.cursor,!W.in_grouping(F,97,251)))&&(W.slice_from(r),W.cursor=s,!0)}function i(e,r,s){return!!W.eq_s(1,e)&&(W.ket=W.cursor,W.slice_from(r),W.cursor=s,!0)}function n(){for(var r,s;;){if(r=W.cursor,W.in_grouping(F,97,251)){if(W.bra=W.cursor,s=W.cursor,e("u","U",r))continue;if(W.cursor=s,e("i","I",r))continue;if(W.cursor=s,i("y","Y",r))continue}if(W.cursor=r,W.bra=r,!e("y","Y",r)){if(W.cursor=r,W.eq_s(1,"q")&&(W.bra=W.cursor,i("u","U",r)))continue;if(W.cursor=r,r>=W.limit)return;W.cursor++}}}function t(){for(;!W.in_grouping(F,97,251);){if(W.cursor>=W.limit)return!0;W.cursor++}for(;!W.out_grouping(F,97,251);){if(W.cursor>=W.limit)return!0;W.cursor++}return!1}function u(){var e=W.cursor;if(q=W.limit,g=q,p=q,W.in_grouping(F,97,251)&&W.in_grouping(F,97,251)&&W.cursor=W.limit){W.cursor=q;break}W.cursor++}while(!W.in_grouping(F,97,251))}q=W.cursor,W.cursor=e,t()||(g=W.cursor,t()||(p=W.cursor))}function o(){for(var e,r;;){if(r=W.cursor,W.bra=r,!(e=W.find_among(h,4)))break;switch(W.ket=W.cursor,e){case 1:W.slice_from("i");break;case 2:W.slice_from("u");break;case 3:W.slice_from("y");break;case 4:if(W.cursor>=W.limit)return;W.cursor++}}}function c(){return q<=W.cursor}function a(){return g<=W.cursor}function l(){return p<=W.cursor}function w(){var e,r;if(W.ket=W.cursor,e=W.find_among_b(C,43)){switch(W.bra=W.cursor,e){case 1:if(!l())return!1;W.slice_del();break;case 2:if(!l())return!1;W.slice_del(),W.ket=W.cursor,W.eq_s_b(2,"ic")&&(W.bra=W.cursor,l()?W.slice_del():W.slice_from("iqU"));break;case 3:if(!l())return!1;W.slice_from("log");break;case 4:if(!l())return!1;W.slice_from("u");break;case 5:if(!l())return!1;W.slice_from("ent");break;case 6:if(!c())return!1;if(W.slice_del(),W.ket=W.cursor,e=W.find_among_b(z,6))switch(W.bra=W.cursor,e){case 1:l()&&(W.slice_del(),W.ket=W.cursor,W.eq_s_b(2,"at")&&(W.bra=W.cursor,l()&&W.slice_del()));break;case 2:l()?W.slice_del():a()&&W.slice_from("eux");break;case 3:l()&&W.slice_del();break;case 4:c()&&W.slice_from("i")}break;case 7:if(!l())return!1;if(W.slice_del(),W.ket=W.cursor,e=W.find_among_b(y,3))switch(W.bra=W.cursor,e){case 1:l()?W.slice_del():W.slice_from("abl");break;case 2:l()?W.slice_del():W.slice_from("iqU");break;case 3:l()&&W.slice_del()}break;case 8:if(!l())return!1;if(W.slice_del(),W.ket=W.cursor,W.eq_s_b(2,"at")&&(W.bra=W.cursor,l()&&(W.slice_del(),W.ket=W.cursor,W.eq_s_b(2,"ic")))){W.bra=W.cursor,l()?W.slice_del():W.slice_from("iqU");break}break;case 9:W.slice_from("eau");break;case 10:if(!a())return!1;W.slice_from("al");break;case 11:if(l())W.slice_del();else{if(!a())return!1;W.slice_from("eux")}break;case 12:if(!a()||!W.out_grouping_b(F,97,251))return!1;W.slice_del();break;case 13:return c()&&W.slice_from("ant"),!1;case 14:return c()&&W.slice_from("ent"),!1;case 15:return r=W.limit-W.cursor,W.in_grouping_b(F,97,251)&&c()&&(W.cursor=W.limit-r,W.slice_del()),!1}return!0}return!1}function f(){var e,r;if(W.cursor=q){if(s=W.limit_backward,W.limit_backward=q,W.ket=W.cursor,e=W.find_among_b(P,7))switch(W.bra=W.cursor,e){case 1:if(l()){if(i=W.limit-W.cursor,!W.eq_s_b(1,"s")&&(W.cursor=W.limit-i,!W.eq_s_b(1,"t")))break;W.slice_del()}break;case 2:W.slice_from("i");break;case 3:W.slice_del();break;case 4:W.eq_s_b(2,"gu")&&W.slice_del()}W.limit_backward=s}}function b(){var e=W.limit-W.cursor;W.find_among_b(U,5)&&(W.cursor=W.limit-e,W.ket=W.cursor,W.cursor>W.limit_backward&&(W.cursor--,W.bra=W.cursor,W.slice_del()))}function d(){for(var e,r=1;W.out_grouping_b(F,97,251);)r--;if(r<=0){if(W.ket=W.cursor,e=W.limit-W.cursor,!W.eq_s_b(1,"é")&&(W.cursor=W.limit-e,!W.eq_s_b(1,"è")))return;W.bra=W.cursor,W.slice_from("e")}}function k(){if(!w()&&(W.cursor=W.limit,!f()&&(W.cursor=W.limit,!m())))return W.cursor=W.limit,void _();W.cursor=W.limit,W.ket=W.cursor,W.eq_s_b(1,"Y")?(W.bra=W.cursor,W.slice_from("i")):(W.cursor=W.limit,W.eq_s_b(1,"ç")&&(W.bra=W.cursor,W.slice_from("c")))}var p,g,q,v=[new r("col",-1,-1),new r("par",-1,-1),new r("tap",-1,-1)],h=[new r("",-1,4),new r("I",0,1),new r("U",0,2),new r("Y",0,3)],z=[new r("iqU",-1,3),new r("abl",-1,3),new r("Ièr",-1,4),new r("ièr",-1,4),new r("eus",-1,2),new r("iv",-1,1)],y=[new r("ic",-1,2),new r("abil",-1,1),new r("iv",-1,3)],C=[new r("iqUe",-1,1),new r("atrice",-1,2),new r("ance",-1,1),new r("ence",-1,5),new r("logie",-1,3),new r("able",-1,1),new r("isme",-1,1),new r("euse",-1,11),new r("iste",-1,1),new r("ive",-1,8),new r("if",-1,8),new r("usion",-1,4),new r("ation",-1,2),new r("ution",-1,4),new r("ateur",-1,2),new r("iqUes",-1,1),new r("atrices",-1,2),new r("ances",-1,1),new r("ences",-1,5),new r("logies",-1,3),new r("ables",-1,1),new r("ismes",-1,1),new r("euses",-1,11),new r("istes",-1,1),new r("ives",-1,8),new r("ifs",-1,8),new r("usions",-1,4),new r("ations",-1,2),new r("utions",-1,4),new r("ateurs",-1,2),new r("ments",-1,15),new r("ements",30,6),new r("issements",31,12),new r("ités",-1,7),new r("ment",-1,15),new r("ement",34,6),new r("issement",35,12),new r("amment",34,13),new r("emment",34,14),new r("aux",-1,10),new r("eaux",39,9),new r("eux",-1,1),new r("ité",-1,7)],x=[new r("ira",-1,1),new r("ie",-1,1),new r("isse",-1,1),new r("issante",-1,1),new r("i",-1,1),new r("irai",4,1),new r("ir",-1,1),new r("iras",-1,1),new r("ies",-1,1),new r("îmes",-1,1),new r("isses",-1,1),new r("issantes",-1,1),new r("îtes",-1,1),new r("is",-1,1),new r("irais",13,1),new r("issais",13,1),new r("irions",-1,1),new r("issions",-1,1),new r("irons",-1,1),new r("issons",-1,1),new r("issants",-1,1),new r("it",-1,1),new r("irait",21,1),new r("issait",21,1),new r("issant",-1,1),new r("iraIent",-1,1),new r("issaIent",-1,1),new r("irent",-1,1),new r("issent",-1,1),new r("iront",-1,1),new r("ît",-1,1),new r("iriez",-1,1),new r("issiez",-1,1),new r("irez",-1,1),new r("issez",-1,1)],I=[new r("a",-1,3),new r("era",0,2),new r("asse",-1,3),new r("ante",-1,3),new r("ée",-1,2),new r("ai",-1,3),new r("erai",5,2),new r("er",-1,2),new r("as",-1,3),new r("eras",8,2),new r("âmes",-1,3),new r("asses",-1,3),new r("antes",-1,3),new r("âtes",-1,3),new r("ées",-1,2),new r("ais",-1,3),new r("erais",15,2),new r("ions",-1,1),new r("erions",17,2),new r("assions",17,3),new r("erons",-1,2),new r("ants",-1,3),new r("és",-1,2),new r("ait",-1,3),new r("erait",23,2),new r("ant",-1,3),new r("aIent",-1,3),new r("eraIent",26,2),new r("èrent",-1,2),new r("assent",-1,3),new r("eront",-1,2),new r("ât",-1,3),new r("ez",-1,2),new r("iez",32,2),new r("eriez",33,2),new r("assiez",33,3),new r("erez",32,2),new r("é",-1,2)],P=[new r("e",-1,3),new r("Ière",0,2),new r("ière",0,2),new r("ion",-1,1),new r("Ier",-1,2),new r("ier",-1,2),new r("ë",-1,4)],U=[new r("ell",-1,-1),new r("eill",-1,-1),new r("enn",-1,-1),new r("onn",-1,-1),new r("ett",-1,-1)],F=[17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,128,130,103,8,5],S=[1,65,20,0,0,0,0,0,0,0,0,0,0,0,0,0,128],W=new s;this.setCurrent=function(e){W.setCurrent(e)},this.getCurrent=function(){return W.getCurrent()},this.stem=function(){var e=W.cursor;return n(),W.cursor=e,u(),W.limit_backward=e,W.cursor=W.limit,k(),W.cursor=W.limit,b(),W.cursor=W.limit,d(),W.cursor=W.limit_backward,o(),!0}};return function(e){return"function"==typeof e.update?e.update(function(e){return i.setCurrent(e),i.stem(),i.getCurrent()}):(i.setCurrent(e),i.stem(),i.getCurrent())}}(),e.Pipeline.registerFunction(e.fr.stemmer,"stemmer-fr"),e.fr.stopWordFilter=e.generateStopWordFilter("ai aie aient aies ait as au aura aurai auraient aurais aurait auras aurez auriez aurions aurons auront aux avaient avais avait avec avez aviez avions avons ayant ayez ayons c ce ceci celà ces cet cette d dans de des du elle en es est et eu eue eues eurent eus eusse eussent eusses eussiez eussions eut eux eûmes eût eûtes furent fus fusse fussent fusses fussiez fussions fut fûmes fût fûtes ici il ils j je l la le les leur leurs lui m ma mais me mes moi mon même n ne nos notre nous on ont ou par pas pour qu que quel quelle quelles quels qui s sa sans se sera serai seraient serais serait seras serez seriez serions serons seront ses soi soient sois soit sommes son sont soyez soyons suis sur t ta te tes toi ton tu un une vos votre vous y à étaient étais était étant étiez étions été étée étées étés êtes".split(" ")),e.Pipeline.registerFunction(e.fr.stopWordFilter,"stopWordFilter-fr")}}); \ No newline at end of file diff --git a/assets/javascripts/lunr/min/lunr.he.min.js b/assets/javascripts/lunr/min/lunr.he.min.js new file mode 100644 index 0000000..b863d3e --- /dev/null +++ b/assets/javascripts/lunr/min/lunr.he.min.js @@ -0,0 +1 @@ +!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.he=function(){this.pipeline.reset(),this.pipeline.add(e.he.trimmer,e.he.stopWordFilter,e.he.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.he.stemmer))},e.he.wordCharacters="֑-״א-תa-zA-Za-zA-Z0-90-9",e.he.trimmer=e.trimmerSupport.generateTrimmer(e.he.wordCharacters),e.Pipeline.registerFunction(e.he.trimmer,"trimmer-he"),e.he.stemmer=function(){var e=this;return e.result=!1,e.preRemoved=!1,e.sufRemoved=!1,e.pre={pre1:"ה ו י ת",pre2:"ב כ ל מ ש כש",pre3:"הב הכ הל המ הש בש לכ",pre4:"וב וכ ול ומ וש",pre5:"מה שה כל",pre6:"מב מכ מל ממ מש",pre7:"בה בו בי בת כה כו כי כת לה לו לי לת",pre8:"ובה ובו ובי ובת וכה וכו וכי וכת ולה ולו ולי ולת"},e.suf={suf1:"ך כ ם ן נ",suf2:"ים ות וך וכ ום ון ונ הם הן יכ יך ינ ים",suf3:"תי תך תכ תם תן תנ",suf4:"ותי ותך ותכ ותם ותן ותנ",suf5:"נו כם כן הם הן",suf6:"ונו וכם וכן והם והן",suf7:"תכם תכן תנו תהם תהן",suf8:"הוא היא הם הן אני אתה את אנו אתם אתן",suf9:"ני נו כי כו כם כן תי תך תכ תם תן",suf10:"י ך כ ם ן נ ת"},e.patterns=JSON.parse('{"hebrewPatterns": [{"pt1": [{"c": "ה", "l": 0}]}, {"pt2": [{"c": "ו", "l": 0}]}, {"pt3": [{"c": "י", "l": 0}]}, {"pt4": [{"c": "ת", "l": 0}]}, {"pt5": [{"c": "מ", "l": 0}]}, {"pt6": [{"c": "ל", "l": 0}]}, {"pt7": [{"c": "ב", "l": 0}]}, {"pt8": [{"c": "כ", "l": 0}]}, {"pt9": [{"c": "ש", "l": 0}]}, {"pt10": [{"c": "כש", "l": 0}]}, {"pt11": [{"c": "בה", "l": 0}]}, {"pt12": [{"c": "וב", "l": 0}]}, {"pt13": [{"c": "וכ", "l": 0}]}, {"pt14": [{"c": "ול", "l": 0}]}, {"pt15": [{"c": "ומ", "l": 0}]}, {"pt16": [{"c": "וש", "l": 0}]}, {"pt17": [{"c": "הב", "l": 0}]}, {"pt18": [{"c": "הכ", "l": 0}]}, {"pt19": [{"c": "הל", "l": 0}]}, {"pt20": [{"c": "המ", "l": 0}]}, {"pt21": [{"c": "הש", "l": 0}]}, {"pt22": [{"c": "מה", "l": 0}]}, {"pt23": [{"c": "שה", "l": 0}]}, {"pt24": [{"c": "כל", "l": 0}]}]}'),e.execArray=["cleanWord","removeDiacritics","removeStopWords","normalizeHebrewCharacters"],e.stem=function(){var r=0;for(e.result=!1,e.preRemoved=!1,e.sufRemoved=!1;r=0)return!0},e.normalizeHebrewCharacters=function(){return e.word=e.word.replace("ך","כ"),e.word=e.word.replace("ם","מ"),e.word=e.word.replace("ן","נ"),e.word=e.word.replace("ף","פ"),e.word=e.word.replace("ץ","צ"),!1},function(r){return"function"==typeof r.update?r.update(function(r){return e.setCurrent(r),e.stem(),e.getCurrent()}):(e.setCurrent(r),e.stem(),e.getCurrent())}}(),e.Pipeline.registerFunction(e.he.stemmer,"stemmer-he"),e.he.stopWordFilter=e.generateStopWordFilter("אבל או אולי אותו אותי אותך אותם אותן אותנו אז אחר אחרות אחרי אחריכן אחרים אחרת אי איזה איך אין איפה אל אלה אלו אם אנחנו אני אף אפשר את אתה אתכם אתכן אתם אתן באיזה באיזו בגלל בין בלבד בעבור בעזרת בכל בכן בלי במידה במקום שבו ברוב בשביל בשעה ש בתוך גם דרך הוא היא היה היי היכן היתה היתי הם הן הנה הסיבה שבגללה הרי ואילו ואת זאת זה זות יהיה יוכל יוכלו יותר מדי יכול יכולה יכולות יכולים יכל יכלה יכלו יש כאן כאשר כולם כולן כזה כי כיצד כך כל כלל כמו כן כפי כש לא לאו לאיזותך לאן לבין לה להיות להם להן לו לזה לזות לי לך לכם לכן למה למעלה למעלה מ למטה למטה מ למעט למקום שבו למרות לנו לעבר לעיכן לפיכך לפני מאד מאחורי מאיזו סיבה מאין מאיפה מבלי מבעד מדוע מה מהיכן מול מחוץ מי מידע מכאן מכל מכן מלבד מן מנין מסוגל מעט מעטים מעל מצד מקום בו מתחת מתי נגד נגר נו עד עז על עלי עליו עליה עליהם עליך עלינו עם עצמה עצמהם עצמהן עצמו עצמי עצמם עצמן עצמנו פה רק שוב של שלה שלהם שלהן שלו שלי שלך שלכה שלכם שלכן שלנו שם תהיה תחת".split(" ")),e.Pipeline.registerFunction(e.he.stopWordFilter,"stopWordFilter-he")}}); \ No newline at end of file diff --git a/assets/javascripts/lunr/min/lunr.hi.min.js b/assets/javascripts/lunr/min/lunr.hi.min.js new file mode 100644 index 0000000..7dbc414 --- /dev/null +++ b/assets/javascripts/lunr/min/lunr.hi.min.js @@ -0,0 +1 @@ +!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.hi=function(){this.pipeline.reset(),this.pipeline.add(e.hi.trimmer,e.hi.stopWordFilter,e.hi.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.hi.stemmer))},e.hi.wordCharacters="ऀ-ःऄ-एऐ-टठ-यर-िी-ॏॐ-य़ॠ-९॰-ॿa-zA-Za-zA-Z0-90-9",e.hi.trimmer=e.trimmerSupport.generateTrimmer(e.hi.wordCharacters),e.Pipeline.registerFunction(e.hi.trimmer,"trimmer-hi"),e.hi.stopWordFilter=e.generateStopWordFilter("अत अपना अपनी अपने अभी अंदर आदि आप इत्यादि इन इनका इन्हीं इन्हें इन्हों इस इसका इसकी इसके इसमें इसी इसे उन उनका उनकी उनके उनको उन्हीं उन्हें उन्हों उस उसके उसी उसे एक एवं एस ऐसे और कई कर करता करते करना करने करें कहते कहा का काफ़ी कि कितना किन्हें किन्हों किया किर किस किसी किसे की कुछ कुल के को कोई कौन कौनसा गया घर जब जहाँ जा जितना जिन जिन्हें जिन्हों जिस जिसे जीधर जैसा जैसे जो तक तब तरह तिन तिन्हें तिन्हों तिस तिसे तो था थी थे दबारा दिया दुसरा दूसरे दो द्वारा न नके नहीं ना निहायत नीचे ने पर पहले पूरा पे फिर बनी बही बहुत बाद बाला बिलकुल भी भीतर मगर मानो मे में यदि यह यहाँ यही या यिह ये रखें रहा रहे ऱ्वासा लिए लिये लेकिन व वग़ैरह वर्ग वह वहाँ वहीं वाले वुह वे वो सकता सकते सबसे सभी साथ साबुत साभ सारा से सो संग ही हुआ हुई हुए है हैं हो होता होती होते होना होने".split(" ")),e.hi.stemmer=function(){return function(e){return"function"==typeof e.update?e.update(function(e){return e}):e}}();var r=e.wordcut;r.init(),e.hi.tokenizer=function(i){if(!arguments.length||null==i||void 0==i)return[];if(Array.isArray(i))return i.map(function(r){return isLunr2?new e.Token(r.toLowerCase()):r.toLowerCase()});var t=i.toString().toLowerCase().replace(/^\s+/,"");return r.cut(t).split("|")},e.Pipeline.registerFunction(e.hi.stemmer,"stemmer-hi"),e.Pipeline.registerFunction(e.hi.stopWordFilter,"stopWordFilter-hi")}}); \ No newline at end of file diff --git a/assets/javascripts/lunr/min/lunr.hu.min.js b/assets/javascripts/lunr/min/lunr.hu.min.js new file mode 100644 index 0000000..ed9d909 --- /dev/null +++ b/assets/javascripts/lunr/min/lunr.hu.min.js @@ -0,0 +1,18 @@ +/*! + * Lunr languages, `Hungarian` language + * https://github.com/MihaiValentin/lunr-languages + * + * Copyright 2014, Mihai Valentin + * http://www.mozilla.org/MPL/ + */ +/*! + * based on + * Snowball JavaScript Library v0.3 + * http://code.google.com/p/urim/ + * http://snowball.tartarus.org/ + * + * Copyright 2010, Oleg Mazko + * http://www.mozilla.org/MPL/ + */ + +!function(e,n){"function"==typeof define&&define.amd?define(n):"object"==typeof exports?module.exports=n():n()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.hu=function(){this.pipeline.reset(),this.pipeline.add(e.hu.trimmer,e.hu.stopWordFilter,e.hu.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.hu.stemmer))},e.hu.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",e.hu.trimmer=e.trimmerSupport.generateTrimmer(e.hu.wordCharacters),e.Pipeline.registerFunction(e.hu.trimmer,"trimmer-hu"),e.hu.stemmer=function(){var n=e.stemmerSupport.Among,r=e.stemmerSupport.SnowballProgram,i=new function(){function e(){var e,n=L.cursor;if(d=L.limit,L.in_grouping(W,97,252))for(;;){if(e=L.cursor,L.out_grouping(W,97,252))return L.cursor=e,L.find_among(g,8)||(L.cursor=e,e=L.limit)return void(d=e);L.cursor++}if(L.cursor=n,L.out_grouping(W,97,252)){for(;!L.in_grouping(W,97,252);){if(L.cursor>=L.limit)return;L.cursor++}d=L.cursor}}function i(){return d<=L.cursor}function a(){var e;if(L.ket=L.cursor,(e=L.find_among_b(h,2))&&(L.bra=L.cursor,i()))switch(e){case 1:L.slice_from("a");break;case 2:L.slice_from("e")}}function t(){var e=L.limit-L.cursor;return!!L.find_among_b(p,23)&&(L.cursor=L.limit-e,!0)}function s(){if(L.cursor>L.limit_backward){L.cursor--,L.ket=L.cursor;var e=L.cursor-1;L.limit_backward<=e&&e<=L.limit&&(L.cursor=e,L.bra=e,L.slice_del())}}function c(){var e;if(L.ket=L.cursor,(e=L.find_among_b(_,2))&&(L.bra=L.cursor,i())){if((1==e||2==e)&&!t())return;L.slice_del(),s()}}function o(){L.ket=L.cursor,L.find_among_b(v,44)&&(L.bra=L.cursor,i()&&(L.slice_del(),a()))}function w(){var e;if(L.ket=L.cursor,(e=L.find_among_b(z,3))&&(L.bra=L.cursor,i()))switch(e){case 1:L.slice_from("e");break;case 2:case 3:L.slice_from("a")}}function l(){var e;if(L.ket=L.cursor,(e=L.find_among_b(y,6))&&(L.bra=L.cursor,i()))switch(e){case 1:case 2:L.slice_del();break;case 3:L.slice_from("a");break;case 4:L.slice_from("e")}}function u(){var e;if(L.ket=L.cursor,(e=L.find_among_b(j,2))&&(L.bra=L.cursor,i())){if((1==e||2==e)&&!t())return;L.slice_del(),s()}}function m(){var e;if(L.ket=L.cursor,(e=L.find_among_b(C,7))&&(L.bra=L.cursor,i()))switch(e){case 1:L.slice_from("a");break;case 2:L.slice_from("e");break;case 3:case 4:case 5:case 6:case 7:L.slice_del()}}function k(){var e;if(L.ket=L.cursor,(e=L.find_among_b(P,12))&&(L.bra=L.cursor,i()))switch(e){case 1:case 4:case 7:case 9:L.slice_del();break;case 2:case 5:case 8:L.slice_from("e");break;case 3:case 6:L.slice_from("a")}}function f(){var e;if(L.ket=L.cursor,(e=L.find_among_b(F,31))&&(L.bra=L.cursor,i()))switch(e){case 1:case 4:case 7:case 8:case 9:case 12:case 13:case 16:case 17:case 18:L.slice_del();break;case 2:case 5:case 10:case 14:case 19:L.slice_from("a");break;case 3:case 6:case 11:case 15:case 20:L.slice_from("e")}}function b(){var e;if(L.ket=L.cursor,(e=L.find_among_b(S,42))&&(L.bra=L.cursor,i()))switch(e){case 1:case 4:case 5:case 6:case 9:case 10:case 11:case 14:case 15:case 16:case 17:case 20:case 21:case 24:case 25:case 26:case 29:L.slice_del();break;case 2:case 7:case 12:case 18:case 22:case 27:L.slice_from("a");break;case 3:case 8:case 13:case 19:case 23:case 28:L.slice_from("e")}}var d,g=[new n("cs",-1,-1),new n("dzs",-1,-1),new n("gy",-1,-1),new n("ly",-1,-1),new n("ny",-1,-1),new n("sz",-1,-1),new n("ty",-1,-1),new n("zs",-1,-1)],h=[new n("á",-1,1),new n("é",-1,2)],p=[new n("bb",-1,-1),new n("cc",-1,-1),new n("dd",-1,-1),new n("ff",-1,-1),new n("gg",-1,-1),new n("jj",-1,-1),new n("kk",-1,-1),new n("ll",-1,-1),new n("mm",-1,-1),new n("nn",-1,-1),new n("pp",-1,-1),new n("rr",-1,-1),new n("ccs",-1,-1),new n("ss",-1,-1),new n("zzs",-1,-1),new n("tt",-1,-1),new n("vv",-1,-1),new n("ggy",-1,-1),new n("lly",-1,-1),new n("nny",-1,-1),new n("tty",-1,-1),new n("ssz",-1,-1),new n("zz",-1,-1)],_=[new n("al",-1,1),new n("el",-1,2)],v=[new n("ba",-1,-1),new n("ra",-1,-1),new n("be",-1,-1),new n("re",-1,-1),new n("ig",-1,-1),new n("nak",-1,-1),new n("nek",-1,-1),new n("val",-1,-1),new n("vel",-1,-1),new n("ul",-1,-1),new n("nál",-1,-1),new n("nél",-1,-1),new n("ból",-1,-1),new n("ról",-1,-1),new n("tól",-1,-1),new n("bõl",-1,-1),new n("rõl",-1,-1),new n("tõl",-1,-1),new n("ül",-1,-1),new n("n",-1,-1),new n("an",19,-1),new n("ban",20,-1),new n("en",19,-1),new n("ben",22,-1),new n("képpen",22,-1),new n("on",19,-1),new n("ön",19,-1),new n("képp",-1,-1),new n("kor",-1,-1),new n("t",-1,-1),new n("at",29,-1),new n("et",29,-1),new n("ként",29,-1),new n("anként",32,-1),new n("enként",32,-1),new n("onként",32,-1),new n("ot",29,-1),new n("ért",29,-1),new n("öt",29,-1),new n("hez",-1,-1),new n("hoz",-1,-1),new n("höz",-1,-1),new n("vá",-1,-1),new n("vé",-1,-1)],z=[new n("án",-1,2),new n("én",-1,1),new n("ánként",-1,3)],y=[new n("stul",-1,2),new n("astul",0,1),new n("ástul",0,3),new n("stül",-1,2),new n("estül",3,1),new n("éstül",3,4)],j=[new n("á",-1,1),new n("é",-1,2)],C=[new n("k",-1,7),new n("ak",0,4),new n("ek",0,6),new n("ok",0,5),new n("ák",0,1),new n("ék",0,2),new n("ök",0,3)],P=[new n("éi",-1,7),new n("áéi",0,6),new n("ééi",0,5),new n("é",-1,9),new n("ké",3,4),new n("aké",4,1),new n("eké",4,1),new n("oké",4,1),new n("áké",4,3),new n("éké",4,2),new n("öké",4,1),new n("éé",3,8)],F=[new n("a",-1,18),new n("ja",0,17),new n("d",-1,16),new n("ad",2,13),new n("ed",2,13),new n("od",2,13),new n("ád",2,14),new n("éd",2,15),new n("öd",2,13),new n("e",-1,18),new n("je",9,17),new n("nk",-1,4),new n("unk",11,1),new n("ánk",11,2),new n("énk",11,3),new n("ünk",11,1),new n("uk",-1,8),new n("juk",16,7),new n("ájuk",17,5),new n("ük",-1,8),new n("jük",19,7),new n("éjük",20,6),new n("m",-1,12),new n("am",22,9),new n("em",22,9),new n("om",22,9),new n("ám",22,10),new n("ém",22,11),new n("o",-1,18),new n("á",-1,19),new n("é",-1,20)],S=[new n("id",-1,10),new n("aid",0,9),new n("jaid",1,6),new n("eid",0,9),new n("jeid",3,6),new n("áid",0,7),new n("éid",0,8),new n("i",-1,15),new n("ai",7,14),new n("jai",8,11),new n("ei",7,14),new n("jei",10,11),new n("ái",7,12),new n("éi",7,13),new n("itek",-1,24),new n("eitek",14,21),new n("jeitek",15,20),new n("éitek",14,23),new n("ik",-1,29),new n("aik",18,26),new n("jaik",19,25),new n("eik",18,26),new n("jeik",21,25),new n("áik",18,27),new n("éik",18,28),new n("ink",-1,20),new n("aink",25,17),new n("jaink",26,16),new n("eink",25,17),new n("jeink",28,16),new n("áink",25,18),new n("éink",25,19),new n("aitok",-1,21),new n("jaitok",32,20),new n("áitok",-1,22),new n("im",-1,5),new n("aim",35,4),new n("jaim",36,1),new n("eim",35,4),new n("jeim",38,1),new n("áim",35,2),new n("éim",35,3)],W=[17,65,16,0,0,0,0,0,0,0,0,0,0,0,0,0,1,17,52,14],L=new r;this.setCurrent=function(e){L.setCurrent(e)},this.getCurrent=function(){return L.getCurrent()},this.stem=function(){var n=L.cursor;return e(),L.limit_backward=n,L.cursor=L.limit,c(),L.cursor=L.limit,o(),L.cursor=L.limit,w(),L.cursor=L.limit,l(),L.cursor=L.limit,u(),L.cursor=L.limit,k(),L.cursor=L.limit,f(),L.cursor=L.limit,b(),L.cursor=L.limit,m(),!0}};return function(e){return"function"==typeof e.update?e.update(function(e){return i.setCurrent(e),i.stem(),i.getCurrent()}):(i.setCurrent(e),i.stem(),i.getCurrent())}}(),e.Pipeline.registerFunction(e.hu.stemmer,"stemmer-hu"),e.hu.stopWordFilter=e.generateStopWordFilter("a abban ahhoz ahogy ahol aki akik akkor alatt amely amelyek amelyekben amelyeket amelyet amelynek ami amikor amit amolyan amíg annak arra arról az azok azon azonban azt aztán azután azzal azért be belül benne bár cikk cikkek cikkeket csak de e ebben eddig egy egyes egyetlen egyik egyre egyéb egész ehhez ekkor el ellen elsõ elég elõ elõször elõtt emilyen ennek erre ez ezek ezen ezt ezzel ezért fel felé hanem hiszen hogy hogyan igen ill ill. illetve ilyen ilyenkor ismét ison itt jobban jó jól kell kellett keressünk keresztül ki kívül között közül legalább legyen lehet lehetett lenne lenni lesz lett maga magát majd majd meg mellett mely melyek mert mi mikor milyen minden mindenki mindent mindig mint mintha mit mivel miért most már más másik még míg nagy nagyobb nagyon ne nekem neki nem nincs néha néhány nélkül olyan ott pedig persze rá s saját sem semmi sok sokat sokkal szemben szerint szinte számára talán tehát teljes tovább továbbá több ugyanis utolsó után utána vagy vagyis vagyok valaki valami valamint való van vannak vele vissza viszont volna volt voltak voltam voltunk által általában át én éppen és így õ õk õket össze úgy új újabb újra".split(" ")),e.Pipeline.registerFunction(e.hu.stopWordFilter,"stopWordFilter-hu")}}); \ No newline at end of file diff --git a/assets/javascripts/lunr/min/lunr.hy.min.js b/assets/javascripts/lunr/min/lunr.hy.min.js new file mode 100644 index 0000000..b37f792 --- /dev/null +++ b/assets/javascripts/lunr/min/lunr.hy.min.js @@ -0,0 +1 @@ +!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.hy=function(){this.pipeline.reset(),this.pipeline.add(e.hy.trimmer,e.hy.stopWordFilter)},e.hy.wordCharacters="[A-Za-z԰-֏ff-ﭏ]",e.hy.trimmer=e.trimmerSupport.generateTrimmer(e.hy.wordCharacters),e.Pipeline.registerFunction(e.hy.trimmer,"trimmer-hy"),e.hy.stopWordFilter=e.generateStopWordFilter("դու և եք էիր էիք հետո նաև նրանք որը վրա է որ պիտի են այս մեջ ն իր ու ի այդ որոնք այն կամ էր մի ես համար այլ իսկ էին ենք հետ ին թ էինք մենք նրա նա դուք եմ էի ըստ որպես ում".split(" ")),e.Pipeline.registerFunction(e.hy.stopWordFilter,"stopWordFilter-hy"),e.hy.stemmer=function(){return function(e){return"function"==typeof e.update?e.update(function(e){return e}):e}}(),e.Pipeline.registerFunction(e.hy.stemmer,"stemmer-hy")}}); \ No newline at end of file diff --git a/assets/javascripts/lunr/min/lunr.it.min.js b/assets/javascripts/lunr/min/lunr.it.min.js new file mode 100644 index 0000000..344b6a3 --- /dev/null +++ b/assets/javascripts/lunr/min/lunr.it.min.js @@ -0,0 +1,18 @@ +/*! + * Lunr languages, `Italian` language + * https://github.com/MihaiValentin/lunr-languages + * + * Copyright 2014, Mihai Valentin + * http://www.mozilla.org/MPL/ + */ +/*! + * based on + * Snowball JavaScript Library v0.3 + * http://code.google.com/p/urim/ + * http://snowball.tartarus.org/ + * + * Copyright 2010, Oleg Mazko + * http://www.mozilla.org/MPL/ + */ + +!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.it=function(){this.pipeline.reset(),this.pipeline.add(e.it.trimmer,e.it.stopWordFilter,e.it.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.it.stemmer))},e.it.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",e.it.trimmer=e.trimmerSupport.generateTrimmer(e.it.wordCharacters),e.Pipeline.registerFunction(e.it.trimmer,"trimmer-it"),e.it.stemmer=function(){var r=e.stemmerSupport.Among,n=e.stemmerSupport.SnowballProgram,i=new function(){function e(e,r,n){return!(!x.eq_s(1,e)||(x.ket=x.cursor,!x.in_grouping(L,97,249)))&&(x.slice_from(r),x.cursor=n,!0)}function i(){for(var r,n,i,o,t=x.cursor;;){if(x.bra=x.cursor,r=x.find_among(h,7))switch(x.ket=x.cursor,r){case 1:x.slice_from("à");continue;case 2:x.slice_from("è");continue;case 3:x.slice_from("ì");continue;case 4:x.slice_from("ò");continue;case 5:x.slice_from("ù");continue;case 6:x.slice_from("qU");continue;case 7:if(x.cursor>=x.limit)break;x.cursor++;continue}break}for(x.cursor=t;;)for(n=x.cursor;;){if(i=x.cursor,x.in_grouping(L,97,249)){if(x.bra=x.cursor,o=x.cursor,e("u","U",i))break;if(x.cursor=o,e("i","I",i))break}if(x.cursor=i,x.cursor>=x.limit)return void(x.cursor=n);x.cursor++}}function o(e){if(x.cursor=e,!x.in_grouping(L,97,249))return!1;for(;!x.out_grouping(L,97,249);){if(x.cursor>=x.limit)return!1;x.cursor++}return!0}function t(){if(x.in_grouping(L,97,249)){var e=x.cursor;if(x.out_grouping(L,97,249)){for(;!x.in_grouping(L,97,249);){if(x.cursor>=x.limit)return o(e);x.cursor++}return!0}return o(e)}return!1}function s(){var e,r=x.cursor;if(!t()){if(x.cursor=r,!x.out_grouping(L,97,249))return;if(e=x.cursor,x.out_grouping(L,97,249)){for(;!x.in_grouping(L,97,249);){if(x.cursor>=x.limit)return x.cursor=e,void(x.in_grouping(L,97,249)&&x.cursor=x.limit)return;x.cursor++}k=x.cursor}function a(){for(;!x.in_grouping(L,97,249);){if(x.cursor>=x.limit)return!1;x.cursor++}for(;!x.out_grouping(L,97,249);){if(x.cursor>=x.limit)return!1;x.cursor++}return!0}function u(){var e=x.cursor;k=x.limit,p=k,g=k,s(),x.cursor=e,a()&&(p=x.cursor,a()&&(g=x.cursor))}function c(){for(var e;;){if(x.bra=x.cursor,!(e=x.find_among(q,3)))break;switch(x.ket=x.cursor,e){case 1:x.slice_from("i");break;case 2:x.slice_from("u");break;case 3:if(x.cursor>=x.limit)return;x.cursor++}}}function w(){return k<=x.cursor}function l(){return p<=x.cursor}function m(){return g<=x.cursor}function f(){var e;if(x.ket=x.cursor,x.find_among_b(C,37)&&(x.bra=x.cursor,(e=x.find_among_b(z,5))&&w()))switch(e){case 1:x.slice_del();break;case 2:x.slice_from("e")}}function v(){var e;if(x.ket=x.cursor,!(e=x.find_among_b(S,51)))return!1;switch(x.bra=x.cursor,e){case 1:if(!m())return!1;x.slice_del();break;case 2:if(!m())return!1;x.slice_del(),x.ket=x.cursor,x.eq_s_b(2,"ic")&&(x.bra=x.cursor,m()&&x.slice_del());break;case 3:if(!m())return!1;x.slice_from("log");break;case 4:if(!m())return!1;x.slice_from("u");break;case 5:if(!m())return!1;x.slice_from("ente");break;case 6:if(!w())return!1;x.slice_del();break;case 7:if(!l())return!1;x.slice_del(),x.ket=x.cursor,e=x.find_among_b(P,4),e&&(x.bra=x.cursor,m()&&(x.slice_del(),1==e&&(x.ket=x.cursor,x.eq_s_b(2,"at")&&(x.bra=x.cursor,m()&&x.slice_del()))));break;case 8:if(!m())return!1;x.slice_del(),x.ket=x.cursor,e=x.find_among_b(F,3),e&&(x.bra=x.cursor,1==e&&m()&&x.slice_del());break;case 9:if(!m())return!1;x.slice_del(),x.ket=x.cursor,x.eq_s_b(2,"at")&&(x.bra=x.cursor,m()&&(x.slice_del(),x.ket=x.cursor,x.eq_s_b(2,"ic")&&(x.bra=x.cursor,m()&&x.slice_del())))}return!0}function b(){var e,r;x.cursor>=k&&(r=x.limit_backward,x.limit_backward=k,x.ket=x.cursor,e=x.find_among_b(W,87),e&&(x.bra=x.cursor,1==e&&x.slice_del()),x.limit_backward=r)}function d(){var e=x.limit-x.cursor;if(x.ket=x.cursor,x.in_grouping_b(y,97,242)&&(x.bra=x.cursor,w()&&(x.slice_del(),x.ket=x.cursor,x.eq_s_b(1,"i")&&(x.bra=x.cursor,w()))))return void x.slice_del();x.cursor=x.limit-e}function _(){d(),x.ket=x.cursor,x.eq_s_b(1,"h")&&(x.bra=x.cursor,x.in_grouping_b(U,99,103)&&w()&&x.slice_del())}var g,p,k,h=[new r("",-1,7),new r("qu",0,6),new r("á",0,1),new r("é",0,2),new r("í",0,3),new r("ó",0,4),new r("ú",0,5)],q=[new r("",-1,3),new r("I",0,1),new r("U",0,2)],C=[new r("la",-1,-1),new r("cela",0,-1),new r("gliela",0,-1),new r("mela",0,-1),new r("tela",0,-1),new r("vela",0,-1),new r("le",-1,-1),new r("cele",6,-1),new r("gliele",6,-1),new r("mele",6,-1),new r("tele",6,-1),new r("vele",6,-1),new r("ne",-1,-1),new r("cene",12,-1),new r("gliene",12,-1),new r("mene",12,-1),new r("sene",12,-1),new r("tene",12,-1),new r("vene",12,-1),new r("ci",-1,-1),new r("li",-1,-1),new r("celi",20,-1),new r("glieli",20,-1),new r("meli",20,-1),new r("teli",20,-1),new r("veli",20,-1),new r("gli",20,-1),new r("mi",-1,-1),new r("si",-1,-1),new r("ti",-1,-1),new r("vi",-1,-1),new r("lo",-1,-1),new r("celo",31,-1),new r("glielo",31,-1),new r("melo",31,-1),new r("telo",31,-1),new r("velo",31,-1)],z=[new r("ando",-1,1),new r("endo",-1,1),new r("ar",-1,2),new r("er",-1,2),new r("ir",-1,2)],P=[new r("ic",-1,-1),new r("abil",-1,-1),new r("os",-1,-1),new r("iv",-1,1)],F=[new r("ic",-1,1),new r("abil",-1,1),new r("iv",-1,1)],S=[new r("ica",-1,1),new r("logia",-1,3),new r("osa",-1,1),new r("ista",-1,1),new r("iva",-1,9),new r("anza",-1,1),new r("enza",-1,5),new r("ice",-1,1),new r("atrice",7,1),new r("iche",-1,1),new r("logie",-1,3),new r("abile",-1,1),new r("ibile",-1,1),new r("usione",-1,4),new r("azione",-1,2),new r("uzione",-1,4),new r("atore",-1,2),new r("ose",-1,1),new r("ante",-1,1),new r("mente",-1,1),new r("amente",19,7),new r("iste",-1,1),new r("ive",-1,9),new r("anze",-1,1),new r("enze",-1,5),new r("ici",-1,1),new r("atrici",25,1),new r("ichi",-1,1),new r("abili",-1,1),new r("ibili",-1,1),new r("ismi",-1,1),new r("usioni",-1,4),new r("azioni",-1,2),new r("uzioni",-1,4),new r("atori",-1,2),new r("osi",-1,1),new r("anti",-1,1),new r("amenti",-1,6),new r("imenti",-1,6),new r("isti",-1,1),new r("ivi",-1,9),new r("ico",-1,1),new r("ismo",-1,1),new r("oso",-1,1),new r("amento",-1,6),new r("imento",-1,6),new r("ivo",-1,9),new r("ità",-1,8),new r("istà",-1,1),new r("istè",-1,1),new r("istì",-1,1)],W=[new r("isca",-1,1),new r("enda",-1,1),new r("ata",-1,1),new r("ita",-1,1),new r("uta",-1,1),new r("ava",-1,1),new r("eva",-1,1),new r("iva",-1,1),new r("erebbe",-1,1),new r("irebbe",-1,1),new r("isce",-1,1),new r("ende",-1,1),new r("are",-1,1),new r("ere",-1,1),new r("ire",-1,1),new r("asse",-1,1),new r("ate",-1,1),new r("avate",16,1),new r("evate",16,1),new r("ivate",16,1),new r("ete",-1,1),new r("erete",20,1),new r("irete",20,1),new r("ite",-1,1),new r("ereste",-1,1),new r("ireste",-1,1),new r("ute",-1,1),new r("erai",-1,1),new r("irai",-1,1),new r("isci",-1,1),new r("endi",-1,1),new r("erei",-1,1),new r("irei",-1,1),new r("assi",-1,1),new r("ati",-1,1),new r("iti",-1,1),new r("eresti",-1,1),new r("iresti",-1,1),new r("uti",-1,1),new r("avi",-1,1),new r("evi",-1,1),new r("ivi",-1,1),new r("isco",-1,1),new r("ando",-1,1),new r("endo",-1,1),new r("Yamo",-1,1),new r("iamo",-1,1),new r("avamo",-1,1),new r("evamo",-1,1),new r("ivamo",-1,1),new r("eremo",-1,1),new r("iremo",-1,1),new r("assimo",-1,1),new r("ammo",-1,1),new r("emmo",-1,1),new r("eremmo",54,1),new r("iremmo",54,1),new r("immo",-1,1),new r("ano",-1,1),new r("iscano",58,1),new r("avano",58,1),new r("evano",58,1),new r("ivano",58,1),new r("eranno",-1,1),new r("iranno",-1,1),new r("ono",-1,1),new r("iscono",65,1),new r("arono",65,1),new r("erono",65,1),new r("irono",65,1),new r("erebbero",-1,1),new r("irebbero",-1,1),new r("assero",-1,1),new r("essero",-1,1),new r("issero",-1,1),new r("ato",-1,1),new r("ito",-1,1),new r("uto",-1,1),new r("avo",-1,1),new r("evo",-1,1),new r("ivo",-1,1),new r("ar",-1,1),new r("ir",-1,1),new r("erà",-1,1),new r("irà",-1,1),new r("erò",-1,1),new r("irò",-1,1)],L=[17,65,16,0,0,0,0,0,0,0,0,0,0,0,0,128,128,8,2,1],y=[17,65,0,0,0,0,0,0,0,0,0,0,0,0,0,128,128,8,2],U=[17],x=new n;this.setCurrent=function(e){x.setCurrent(e)},this.getCurrent=function(){return x.getCurrent()},this.stem=function(){var e=x.cursor;return i(),x.cursor=e,u(),x.limit_backward=e,x.cursor=x.limit,f(),x.cursor=x.limit,v()||(x.cursor=x.limit,b()),x.cursor=x.limit,_(),x.cursor=x.limit_backward,c(),!0}};return function(e){return"function"==typeof e.update?e.update(function(e){return i.setCurrent(e),i.stem(),i.getCurrent()}):(i.setCurrent(e),i.stem(),i.getCurrent())}}(),e.Pipeline.registerFunction(e.it.stemmer,"stemmer-it"),e.it.stopWordFilter=e.generateStopWordFilter("a abbia abbiamo abbiano abbiate ad agl agli ai al all alla alle allo anche avemmo avendo avesse avessero avessi avessimo aveste avesti avete aveva avevamo avevano avevate avevi avevo avrai avranno avrebbe avrebbero avrei avremmo avremo avreste avresti avrete avrà avrò avuta avute avuti avuto c che chi ci coi col come con contro cui da dagl dagli dai dal dall dalla dalle dallo degl degli dei del dell della delle dello di dov dove e ebbe ebbero ebbi ed era erano eravamo eravate eri ero essendo faccia facciamo facciano facciate faccio facemmo facendo facesse facessero facessi facessimo faceste facesti faceva facevamo facevano facevate facevi facevo fai fanno farai faranno farebbe farebbero farei faremmo faremo fareste faresti farete farà farò fece fecero feci fosse fossero fossi fossimo foste fosti fu fui fummo furono gli ha hai hanno ho i il in io l la le lei li lo loro lui ma mi mia mie miei mio ne negl negli nei nel nell nella nelle nello noi non nostra nostre nostri nostro o per perché più quale quanta quante quanti quanto quella quelle quelli quello questa queste questi questo sarai saranno sarebbe sarebbero sarei saremmo saremo sareste saresti sarete sarà sarò se sei si sia siamo siano siate siete sono sta stai stando stanno starai staranno starebbe starebbero starei staremmo staremo stareste staresti starete starà starò stava stavamo stavano stavate stavi stavo stemmo stesse stessero stessi stessimo steste stesti stette stettero stetti stia stiamo stiano stiate sto su sua sue sugl sugli sui sul sull sulla sulle sullo suo suoi ti tra tu tua tue tuo tuoi tutti tutto un una uno vi voi vostra vostre vostri vostro è".split(" ")),e.Pipeline.registerFunction(e.it.stopWordFilter,"stopWordFilter-it")}}); \ No newline at end of file diff --git a/assets/javascripts/lunr/min/lunr.ja.min.js b/assets/javascripts/lunr/min/lunr.ja.min.js new file mode 100644 index 0000000..5f254eb --- /dev/null +++ b/assets/javascripts/lunr/min/lunr.ja.min.js @@ -0,0 +1 @@ +!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");var r="2"==e.version[0];e.ja=function(){this.pipeline.reset(),this.pipeline.add(e.ja.trimmer,e.ja.stopWordFilter,e.ja.stemmer),r?this.tokenizer=e.ja.tokenizer:(e.tokenizer&&(e.tokenizer=e.ja.tokenizer),this.tokenizerFn&&(this.tokenizerFn=e.ja.tokenizer))};var t=new e.TinySegmenter;e.ja.tokenizer=function(i){var n,o,s,p,a,u,m,l,c,f;if(!arguments.length||null==i||void 0==i)return[];if(Array.isArray(i))return i.map(function(t){return r?new e.Token(t.toLowerCase()):t.toLowerCase()});for(o=i.toString().toLowerCase().replace(/^\s+/,""),n=o.length-1;n>=0;n--)if(/\S/.test(o.charAt(n))){o=o.substring(0,n+1);break}for(a=[],s=o.length,c=0,l=0;c<=s;c++)if(u=o.charAt(c),m=c-l,u.match(/\s/)||c==s){if(m>0)for(p=t.segment(o.slice(l,c)).filter(function(e){return!!e}),f=l,n=0;n=C.limit)break;C.cursor++;continue}break}for(C.cursor=o,C.bra=o,C.eq_s(1,"y")?(C.ket=C.cursor,C.slice_from("Y")):C.cursor=o;;)if(e=C.cursor,C.in_grouping(q,97,232)){if(i=C.cursor,C.bra=i,C.eq_s(1,"i"))C.ket=C.cursor,C.in_grouping(q,97,232)&&(C.slice_from("I"),C.cursor=e);else if(C.cursor=i,C.eq_s(1,"y"))C.ket=C.cursor,C.slice_from("Y"),C.cursor=e;else if(n(e))break}else if(n(e))break}function n(r){return C.cursor=r,r>=C.limit||(C.cursor++,!1)}function o(){_=C.limit,d=_,t()||(_=C.cursor,_<3&&(_=3),t()||(d=C.cursor))}function t(){for(;!C.in_grouping(q,97,232);){if(C.cursor>=C.limit)return!0;C.cursor++}for(;!C.out_grouping(q,97,232);){if(C.cursor>=C.limit)return!0;C.cursor++}return!1}function s(){for(var r;;)if(C.bra=C.cursor,r=C.find_among(p,3))switch(C.ket=C.cursor,r){case 1:C.slice_from("y");break;case 2:C.slice_from("i");break;case 3:if(C.cursor>=C.limit)return;C.cursor++}}function u(){return _<=C.cursor}function c(){return d<=C.cursor}function a(){var r=C.limit-C.cursor;C.find_among_b(g,3)&&(C.cursor=C.limit-r,C.ket=C.cursor,C.cursor>C.limit_backward&&(C.cursor--,C.bra=C.cursor,C.slice_del()))}function l(){var r;w=!1,C.ket=C.cursor,C.eq_s_b(1,"e")&&(C.bra=C.cursor,u()&&(r=C.limit-C.cursor,C.out_grouping_b(q,97,232)&&(C.cursor=C.limit-r,C.slice_del(),w=!0,a())))}function m(){var r;u()&&(r=C.limit-C.cursor,C.out_grouping_b(q,97,232)&&(C.cursor=C.limit-r,C.eq_s_b(3,"gem")||(C.cursor=C.limit-r,C.slice_del(),a())))}function f(){var r,e,i,n,o,t,s=C.limit-C.cursor;if(C.ket=C.cursor,r=C.find_among_b(h,5))switch(C.bra=C.cursor,r){case 1:u()&&C.slice_from("heid");break;case 2:m();break;case 3:u()&&C.out_grouping_b(j,97,232)&&C.slice_del()}if(C.cursor=C.limit-s,l(),C.cursor=C.limit-s,C.ket=C.cursor,C.eq_s_b(4,"heid")&&(C.bra=C.cursor,c()&&(e=C.limit-C.cursor,C.eq_s_b(1,"c")||(C.cursor=C.limit-e,C.slice_del(),C.ket=C.cursor,C.eq_s_b(2,"en")&&(C.bra=C.cursor,m())))),C.cursor=C.limit-s,C.ket=C.cursor,r=C.find_among_b(k,6))switch(C.bra=C.cursor,r){case 1:if(c()){if(C.slice_del(),i=C.limit-C.cursor,C.ket=C.cursor,C.eq_s_b(2,"ig")&&(C.bra=C.cursor,c()&&(n=C.limit-C.cursor,!C.eq_s_b(1,"e")))){C.cursor=C.limit-n,C.slice_del();break}C.cursor=C.limit-i,a()}break;case 2:c()&&(o=C.limit-C.cursor,C.eq_s_b(1,"e")||(C.cursor=C.limit-o,C.slice_del()));break;case 3:c()&&(C.slice_del(),l());break;case 4:c()&&C.slice_del();break;case 5:c()&&w&&C.slice_del()}C.cursor=C.limit-s,C.out_grouping_b(z,73,232)&&(t=C.limit-C.cursor,C.find_among_b(v,4)&&C.out_grouping_b(q,97,232)&&(C.cursor=C.limit-t,C.ket=C.cursor,C.cursor>C.limit_backward&&(C.cursor--,C.bra=C.cursor,C.slice_del())))}var d,_,w,b=[new e("",-1,6),new e("á",0,1),new e("ä",0,1),new e("é",0,2),new e("ë",0,2),new e("í",0,3),new e("ï",0,3),new e("ó",0,4),new e("ö",0,4),new e("ú",0,5),new e("ü",0,5)],p=[new e("",-1,3),new e("I",0,2),new e("Y",0,1)],g=[new e("dd",-1,-1),new e("kk",-1,-1),new e("tt",-1,-1)],h=[new e("ene",-1,2),new e("se",-1,3),new e("en",-1,2),new e("heden",2,1),new e("s",-1,3)],k=[new e("end",-1,1),new e("ig",-1,2),new e("ing",-1,1),new e("lijk",-1,3),new e("baar",-1,4),new e("bar",-1,5)],v=[new e("aa",-1,-1),new e("ee",-1,-1),new e("oo",-1,-1),new e("uu",-1,-1)],q=[17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,0,128],z=[1,0,0,17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,0,128],j=[17,67,16,1,0,0,0,0,0,0,0,0,0,0,0,0,128],C=new i;this.setCurrent=function(r){C.setCurrent(r)},this.getCurrent=function(){return C.getCurrent()},this.stem=function(){var e=C.cursor;return r(),C.cursor=e,o(),C.limit_backward=e,C.cursor=C.limit,f(),C.cursor=C.limit_backward,s(),!0}};return function(r){return"function"==typeof r.update?r.update(function(r){return n.setCurrent(r),n.stem(),n.getCurrent()}):(n.setCurrent(r),n.stem(),n.getCurrent())}}(),r.Pipeline.registerFunction(r.nl.stemmer,"stemmer-nl"),r.nl.stopWordFilter=r.generateStopWordFilter(" aan al alles als altijd andere ben bij daar dan dat de der deze die dit doch doen door dus een eens en er ge geen geweest haar had heb hebben heeft hem het hier hij hoe hun iemand iets ik in is ja je kan kon kunnen maar me meer men met mij mijn moet na naar niet niets nog nu of om omdat onder ons ook op over reeds te tegen toch toen tot u uit uw van veel voor want waren was wat werd wezen wie wil worden wordt zal ze zelf zich zij zijn zo zonder zou".split(" ")),r.Pipeline.registerFunction(r.nl.stopWordFilter,"stopWordFilter-nl")}}); \ No newline at end of file diff --git a/assets/javascripts/lunr/min/lunr.no.min.js b/assets/javascripts/lunr/min/lunr.no.min.js new file mode 100644 index 0000000..92bc7e4 --- /dev/null +++ b/assets/javascripts/lunr/min/lunr.no.min.js @@ -0,0 +1,18 @@ +/*! + * Lunr languages, `Norwegian` language + * https://github.com/MihaiValentin/lunr-languages + * + * Copyright 2014, Mihai Valentin + * http://www.mozilla.org/MPL/ + */ +/*! + * based on + * Snowball JavaScript Library v0.3 + * http://code.google.com/p/urim/ + * http://snowball.tartarus.org/ + * + * Copyright 2010, Oleg Mazko + * http://www.mozilla.org/MPL/ + */ + +!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.no=function(){this.pipeline.reset(),this.pipeline.add(e.no.trimmer,e.no.stopWordFilter,e.no.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.no.stemmer))},e.no.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",e.no.trimmer=e.trimmerSupport.generateTrimmer(e.no.wordCharacters),e.Pipeline.registerFunction(e.no.trimmer,"trimmer-no"),e.no.stemmer=function(){var r=e.stemmerSupport.Among,n=e.stemmerSupport.SnowballProgram,i=new function(){function e(){var e,r=w.cursor+3;if(a=w.limit,0<=r||r<=w.limit){for(s=r;;){if(e=w.cursor,w.in_grouping(d,97,248)){w.cursor=e;break}if(e>=w.limit)return;w.cursor=e+1}for(;!w.out_grouping(d,97,248);){if(w.cursor>=w.limit)return;w.cursor++}a=w.cursor,a=a&&(r=w.limit_backward,w.limit_backward=a,w.ket=w.cursor,e=w.find_among_b(m,29),w.limit_backward=r,e))switch(w.bra=w.cursor,e){case 1:w.slice_del();break;case 2:n=w.limit-w.cursor,w.in_grouping_b(c,98,122)?w.slice_del():(w.cursor=w.limit-n,w.eq_s_b(1,"k")&&w.out_grouping_b(d,97,248)&&w.slice_del());break;case 3:w.slice_from("er")}}function t(){var e,r=w.limit-w.cursor;w.cursor>=a&&(e=w.limit_backward,w.limit_backward=a,w.ket=w.cursor,w.find_among_b(u,2)?(w.bra=w.cursor,w.limit_backward=e,w.cursor=w.limit-r,w.cursor>w.limit_backward&&(w.cursor--,w.bra=w.cursor,w.slice_del())):w.limit_backward=e)}function o(){var e,r;w.cursor>=a&&(r=w.limit_backward,w.limit_backward=a,w.ket=w.cursor,e=w.find_among_b(l,11),e?(w.bra=w.cursor,w.limit_backward=r,1==e&&w.slice_del()):w.limit_backward=r)}var s,a,m=[new r("a",-1,1),new r("e",-1,1),new r("ede",1,1),new r("ande",1,1),new r("ende",1,1),new r("ane",1,1),new r("ene",1,1),new r("hetene",6,1),new r("erte",1,3),new r("en",-1,1),new r("heten",9,1),new r("ar",-1,1),new r("er",-1,1),new r("heter",12,1),new r("s",-1,2),new r("as",14,1),new r("es",14,1),new r("edes",16,1),new r("endes",16,1),new r("enes",16,1),new r("hetenes",19,1),new r("ens",14,1),new r("hetens",21,1),new r("ers",14,1),new r("ets",14,1),new r("et",-1,1),new r("het",25,1),new r("ert",-1,3),new r("ast",-1,1)],u=[new r("dt",-1,-1),new r("vt",-1,-1)],l=[new r("leg",-1,1),new r("eleg",0,1),new r("ig",-1,1),new r("eig",2,1),new r("lig",2,1),new r("elig",4,1),new r("els",-1,1),new r("lov",-1,1),new r("elov",7,1),new r("slov",7,1),new r("hetslov",9,1)],d=[17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,0,48,0,128],c=[119,125,149,1],w=new n;this.setCurrent=function(e){w.setCurrent(e)},this.getCurrent=function(){return w.getCurrent()},this.stem=function(){var r=w.cursor;return e(),w.limit_backward=r,w.cursor=w.limit,i(),w.cursor=w.limit,t(),w.cursor=w.limit,o(),!0}};return function(e){return"function"==typeof e.update?e.update(function(e){return i.setCurrent(e),i.stem(),i.getCurrent()}):(i.setCurrent(e),i.stem(),i.getCurrent())}}(),e.Pipeline.registerFunction(e.no.stemmer,"stemmer-no"),e.no.stopWordFilter=e.generateStopWordFilter("alle at av bare begge ble blei bli blir blitt både båe da de deg dei deim deira deires dem den denne der dere deres det dette di din disse ditt du dykk dykkar då eg ein eit eitt eller elles en enn er et ett etter for fordi fra før ha hadde han hans har hennar henne hennes her hjå ho hoe honom hoss hossen hun hva hvem hver hvilke hvilken hvis hvor hvordan hvorfor i ikke ikkje ikkje ingen ingi inkje inn inni ja jeg kan kom korleis korso kun kunne kva kvar kvarhelst kven kvi kvifor man mange me med medan meg meget mellom men mi min mine mitt mot mykje ned no noe noen noka noko nokon nokor nokre nå når og også om opp oss over på samme seg selv si si sia sidan siden sin sine sitt sjøl skal skulle slik so som som somme somt så sånn til um upp ut uten var vart varte ved vere verte vi vil ville vore vors vort vår være være vært å".split(" ")),e.Pipeline.registerFunction(e.no.stopWordFilter,"stopWordFilter-no")}}); \ No newline at end of file diff --git a/assets/javascripts/lunr/min/lunr.pt.min.js b/assets/javascripts/lunr/min/lunr.pt.min.js new file mode 100644 index 0000000..6c16996 --- /dev/null +++ b/assets/javascripts/lunr/min/lunr.pt.min.js @@ -0,0 +1,18 @@ +/*! + * Lunr languages, `Portuguese` language + * https://github.com/MihaiValentin/lunr-languages + * + * Copyright 2014, Mihai Valentin + * http://www.mozilla.org/MPL/ + */ +/*! + * based on + * Snowball JavaScript Library v0.3 + * http://code.google.com/p/urim/ + * http://snowball.tartarus.org/ + * + * Copyright 2010, Oleg Mazko + * http://www.mozilla.org/MPL/ + */ + +!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.pt=function(){this.pipeline.reset(),this.pipeline.add(e.pt.trimmer,e.pt.stopWordFilter,e.pt.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.pt.stemmer))},e.pt.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",e.pt.trimmer=e.trimmerSupport.generateTrimmer(e.pt.wordCharacters),e.Pipeline.registerFunction(e.pt.trimmer,"trimmer-pt"),e.pt.stemmer=function(){var r=e.stemmerSupport.Among,s=e.stemmerSupport.SnowballProgram,n=new function(){function e(){for(var e;;){if(z.bra=z.cursor,e=z.find_among(k,3))switch(z.ket=z.cursor,e){case 1:z.slice_from("a~");continue;case 2:z.slice_from("o~");continue;case 3:if(z.cursor>=z.limit)break;z.cursor++;continue}break}}function n(){if(z.out_grouping(y,97,250)){for(;!z.in_grouping(y,97,250);){if(z.cursor>=z.limit)return!0;z.cursor++}return!1}return!0}function i(){if(z.in_grouping(y,97,250))for(;!z.out_grouping(y,97,250);){if(z.cursor>=z.limit)return!1;z.cursor++}return g=z.cursor,!0}function o(){var e,r,s=z.cursor;if(z.in_grouping(y,97,250))if(e=z.cursor,n()){if(z.cursor=e,i())return}else g=z.cursor;if(z.cursor=s,z.out_grouping(y,97,250)){if(r=z.cursor,n()){if(z.cursor=r,!z.in_grouping(y,97,250)||z.cursor>=z.limit)return;z.cursor++}g=z.cursor}}function t(){for(;!z.in_grouping(y,97,250);){if(z.cursor>=z.limit)return!1;z.cursor++}for(;!z.out_grouping(y,97,250);){if(z.cursor>=z.limit)return!1;z.cursor++}return!0}function a(){var e=z.cursor;g=z.limit,b=g,h=g,o(),z.cursor=e,t()&&(b=z.cursor,t()&&(h=z.cursor))}function u(){for(var e;;){if(z.bra=z.cursor,e=z.find_among(q,3))switch(z.ket=z.cursor,e){case 1:z.slice_from("ã");continue;case 2:z.slice_from("õ");continue;case 3:if(z.cursor>=z.limit)break;z.cursor++;continue}break}}function w(){return g<=z.cursor}function m(){return b<=z.cursor}function c(){return h<=z.cursor}function l(){var e;if(z.ket=z.cursor,!(e=z.find_among_b(F,45)))return!1;switch(z.bra=z.cursor,e){case 1:if(!c())return!1;z.slice_del();break;case 2:if(!c())return!1;z.slice_from("log");break;case 3:if(!c())return!1;z.slice_from("u");break;case 4:if(!c())return!1;z.slice_from("ente");break;case 5:if(!m())return!1;z.slice_del(),z.ket=z.cursor,e=z.find_among_b(j,4),e&&(z.bra=z.cursor,c()&&(z.slice_del(),1==e&&(z.ket=z.cursor,z.eq_s_b(2,"at")&&(z.bra=z.cursor,c()&&z.slice_del()))));break;case 6:if(!c())return!1;z.slice_del(),z.ket=z.cursor,e=z.find_among_b(C,3),e&&(z.bra=z.cursor,1==e&&c()&&z.slice_del());break;case 7:if(!c())return!1;z.slice_del(),z.ket=z.cursor,e=z.find_among_b(P,3),e&&(z.bra=z.cursor,1==e&&c()&&z.slice_del());break;case 8:if(!c())return!1;z.slice_del(),z.ket=z.cursor,z.eq_s_b(2,"at")&&(z.bra=z.cursor,c()&&z.slice_del());break;case 9:if(!w()||!z.eq_s_b(1,"e"))return!1;z.slice_from("ir")}return!0}function f(){var e,r;if(z.cursor>=g){if(r=z.limit_backward,z.limit_backward=g,z.ket=z.cursor,e=z.find_among_b(S,120))return z.bra=z.cursor,1==e&&z.slice_del(),z.limit_backward=r,!0;z.limit_backward=r}return!1}function d(){var e;z.ket=z.cursor,(e=z.find_among_b(W,7))&&(z.bra=z.cursor,1==e&&w()&&z.slice_del())}function v(e,r){if(z.eq_s_b(1,e)){z.bra=z.cursor;var s=z.limit-z.cursor;if(z.eq_s_b(1,r))return z.cursor=z.limit-s,w()&&z.slice_del(),!1}return!0}function p(){var e;if(z.ket=z.cursor,e=z.find_among_b(L,4))switch(z.bra=z.cursor,e){case 1:w()&&(z.slice_del(),z.ket=z.cursor,z.limit-z.cursor,v("u","g")&&v("i","c"));break;case 2:z.slice_from("c")}}function _(){if(!l()&&(z.cursor=z.limit,!f()))return z.cursor=z.limit,void d();z.cursor=z.limit,z.ket=z.cursor,z.eq_s_b(1,"i")&&(z.bra=z.cursor,z.eq_s_b(1,"c")&&(z.cursor=z.limit,w()&&z.slice_del()))}var h,b,g,k=[new r("",-1,3),new r("ã",0,1),new r("õ",0,2)],q=[new r("",-1,3),new r("a~",0,1),new r("o~",0,2)],j=[new r("ic",-1,-1),new r("ad",-1,-1),new r("os",-1,-1),new r("iv",-1,1)],C=[new r("ante",-1,1),new r("avel",-1,1),new r("ível",-1,1)],P=[new r("ic",-1,1),new r("abil",-1,1),new r("iv",-1,1)],F=[new r("ica",-1,1),new r("ância",-1,1),new r("ência",-1,4),new r("ira",-1,9),new r("adora",-1,1),new r("osa",-1,1),new r("ista",-1,1),new r("iva",-1,8),new r("eza",-1,1),new r("logía",-1,2),new r("idade",-1,7),new r("ante",-1,1),new r("mente",-1,6),new r("amente",12,5),new r("ável",-1,1),new r("ível",-1,1),new r("ución",-1,3),new r("ico",-1,1),new r("ismo",-1,1),new r("oso",-1,1),new r("amento",-1,1),new r("imento",-1,1),new r("ivo",-1,8),new r("aça~o",-1,1),new r("ador",-1,1),new r("icas",-1,1),new r("ências",-1,4),new r("iras",-1,9),new r("adoras",-1,1),new r("osas",-1,1),new r("istas",-1,1),new r("ivas",-1,8),new r("ezas",-1,1),new r("logías",-1,2),new r("idades",-1,7),new r("uciones",-1,3),new r("adores",-1,1),new r("antes",-1,1),new r("aço~es",-1,1),new r("icos",-1,1),new r("ismos",-1,1),new r("osos",-1,1),new r("amentos",-1,1),new r("imentos",-1,1),new r("ivos",-1,8)],S=[new r("ada",-1,1),new r("ida",-1,1),new r("ia",-1,1),new r("aria",2,1),new r("eria",2,1),new r("iria",2,1),new r("ara",-1,1),new r("era",-1,1),new r("ira",-1,1),new r("ava",-1,1),new r("asse",-1,1),new r("esse",-1,1),new r("isse",-1,1),new r("aste",-1,1),new r("este",-1,1),new r("iste",-1,1),new r("ei",-1,1),new r("arei",16,1),new r("erei",16,1),new r("irei",16,1),new r("am",-1,1),new r("iam",20,1),new r("ariam",21,1),new r("eriam",21,1),new r("iriam",21,1),new r("aram",20,1),new r("eram",20,1),new r("iram",20,1),new r("avam",20,1),new r("em",-1,1),new r("arem",29,1),new r("erem",29,1),new r("irem",29,1),new r("assem",29,1),new r("essem",29,1),new r("issem",29,1),new r("ado",-1,1),new r("ido",-1,1),new r("ando",-1,1),new r("endo",-1,1),new r("indo",-1,1),new r("ara~o",-1,1),new r("era~o",-1,1),new r("ira~o",-1,1),new r("ar",-1,1),new r("er",-1,1),new r("ir",-1,1),new r("as",-1,1),new r("adas",47,1),new r("idas",47,1),new r("ias",47,1),new r("arias",50,1),new r("erias",50,1),new r("irias",50,1),new r("aras",47,1),new r("eras",47,1),new r("iras",47,1),new r("avas",47,1),new r("es",-1,1),new r("ardes",58,1),new r("erdes",58,1),new r("irdes",58,1),new r("ares",58,1),new r("eres",58,1),new r("ires",58,1),new r("asses",58,1),new r("esses",58,1),new r("isses",58,1),new r("astes",58,1),new r("estes",58,1),new r("istes",58,1),new r("is",-1,1),new r("ais",71,1),new r("eis",71,1),new r("areis",73,1),new r("ereis",73,1),new r("ireis",73,1),new r("áreis",73,1),new r("éreis",73,1),new r("íreis",73,1),new r("ásseis",73,1),new r("ésseis",73,1),new r("ísseis",73,1),new r("áveis",73,1),new r("íeis",73,1),new r("aríeis",84,1),new r("eríeis",84,1),new r("iríeis",84,1),new r("ados",-1,1),new r("idos",-1,1),new r("amos",-1,1),new r("áramos",90,1),new r("éramos",90,1),new r("íramos",90,1),new r("ávamos",90,1),new r("íamos",90,1),new r("aríamos",95,1),new r("eríamos",95,1),new r("iríamos",95,1),new r("emos",-1,1),new r("aremos",99,1),new r("eremos",99,1),new r("iremos",99,1),new r("ássemos",99,1),new r("êssemos",99,1),new r("íssemos",99,1),new r("imos",-1,1),new r("armos",-1,1),new r("ermos",-1,1),new r("irmos",-1,1),new r("ámos",-1,1),new r("arás",-1,1),new r("erás",-1,1),new r("irás",-1,1),new r("eu",-1,1),new r("iu",-1,1),new r("ou",-1,1),new r("ará",-1,1),new r("erá",-1,1),new r("irá",-1,1)],W=[new r("a",-1,1),new r("i",-1,1),new r("o",-1,1),new r("os",-1,1),new r("á",-1,1),new r("í",-1,1),new r("ó",-1,1)],L=[new r("e",-1,1),new r("ç",-1,2),new r("é",-1,1),new r("ê",-1,1)],y=[17,65,16,0,0,0,0,0,0,0,0,0,0,0,0,0,3,19,12,2],z=new s;this.setCurrent=function(e){z.setCurrent(e)},this.getCurrent=function(){return z.getCurrent()},this.stem=function(){var r=z.cursor;return e(),z.cursor=r,a(),z.limit_backward=r,z.cursor=z.limit,_(),z.cursor=z.limit,p(),z.cursor=z.limit_backward,u(),!0}};return function(e){return"function"==typeof e.update?e.update(function(e){return n.setCurrent(e),n.stem(),n.getCurrent()}):(n.setCurrent(e),n.stem(),n.getCurrent())}}(),e.Pipeline.registerFunction(e.pt.stemmer,"stemmer-pt"),e.pt.stopWordFilter=e.generateStopWordFilter("a ao aos aquela aquelas aquele aqueles aquilo as até com como da das de dela delas dele deles depois do dos e ela elas ele eles em entre era eram essa essas esse esses esta estamos estas estava estavam este esteja estejam estejamos estes esteve estive estivemos estiver estivera estiveram estiverem estivermos estivesse estivessem estivéramos estivéssemos estou está estávamos estão eu foi fomos for fora foram forem formos fosse fossem fui fôramos fôssemos haja hajam hajamos havemos hei houve houvemos houver houvera houveram houverei houverem houveremos houveria houveriam houvermos houverá houverão houveríamos houvesse houvessem houvéramos houvéssemos há hão isso isto já lhe lhes mais mas me mesmo meu meus minha minhas muito na nas nem no nos nossa nossas nosso nossos num numa não nós o os ou para pela pelas pelo pelos por qual quando que quem se seja sejam sejamos sem serei seremos seria seriam será serão seríamos seu seus somos sou sua suas são só também te tem temos tenha tenham tenhamos tenho terei teremos teria teriam terá terão teríamos teu teus teve tinha tinham tive tivemos tiver tivera tiveram tiverem tivermos tivesse tivessem tivéramos tivéssemos tu tua tuas tém tínhamos um uma você vocês vos à às éramos".split(" ")),e.Pipeline.registerFunction(e.pt.stopWordFilter,"stopWordFilter-pt")}}); \ No newline at end of file diff --git a/assets/javascripts/lunr/min/lunr.ro.min.js b/assets/javascripts/lunr/min/lunr.ro.min.js new file mode 100644 index 0000000..7277140 --- /dev/null +++ b/assets/javascripts/lunr/min/lunr.ro.min.js @@ -0,0 +1,18 @@ +/*! + * Lunr languages, `Romanian` language + * https://github.com/MihaiValentin/lunr-languages + * + * Copyright 2014, Mihai Valentin + * http://www.mozilla.org/MPL/ + */ +/*! + * based on + * Snowball JavaScript Library v0.3 + * http://code.google.com/p/urim/ + * http://snowball.tartarus.org/ + * + * Copyright 2010, Oleg Mazko + * http://www.mozilla.org/MPL/ + */ + +!function(e,i){"function"==typeof define&&define.amd?define(i):"object"==typeof exports?module.exports=i():i()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.ro=function(){this.pipeline.reset(),this.pipeline.add(e.ro.trimmer,e.ro.stopWordFilter,e.ro.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.ro.stemmer))},e.ro.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",e.ro.trimmer=e.trimmerSupport.generateTrimmer(e.ro.wordCharacters),e.Pipeline.registerFunction(e.ro.trimmer,"trimmer-ro"),e.ro.stemmer=function(){var i=e.stemmerSupport.Among,r=e.stemmerSupport.SnowballProgram,n=new function(){function e(e,i){L.eq_s(1,e)&&(L.ket=L.cursor,L.in_grouping(W,97,259)&&L.slice_from(i))}function n(){for(var i,r;;){if(i=L.cursor,L.in_grouping(W,97,259)&&(r=L.cursor,L.bra=r,e("u","U"),L.cursor=r,e("i","I")),L.cursor=i,L.cursor>=L.limit)break;L.cursor++}}function t(){if(L.out_grouping(W,97,259)){for(;!L.in_grouping(W,97,259);){if(L.cursor>=L.limit)return!0;L.cursor++}return!1}return!0}function a(){if(L.in_grouping(W,97,259))for(;!L.out_grouping(W,97,259);){if(L.cursor>=L.limit)return!0;L.cursor++}return!1}function o(){var e,i,r=L.cursor;if(L.in_grouping(W,97,259)){if(e=L.cursor,!t())return void(h=L.cursor);if(L.cursor=e,!a())return void(h=L.cursor)}L.cursor=r,L.out_grouping(W,97,259)&&(i=L.cursor,t()&&(L.cursor=i,L.in_grouping(W,97,259)&&L.cursor=L.limit)return!1;L.cursor++}for(;!L.out_grouping(W,97,259);){if(L.cursor>=L.limit)return!1;L.cursor++}return!0}function c(){var e=L.cursor;h=L.limit,k=h,g=h,o(),L.cursor=e,u()&&(k=L.cursor,u()&&(g=L.cursor))}function s(){for(var e;;){if(L.bra=L.cursor,e=L.find_among(z,3))switch(L.ket=L.cursor,e){case 1:L.slice_from("i");continue;case 2:L.slice_from("u");continue;case 3:if(L.cursor>=L.limit)break;L.cursor++;continue}break}}function w(){return h<=L.cursor}function m(){return k<=L.cursor}function l(){return g<=L.cursor}function f(){var e,i;if(L.ket=L.cursor,(e=L.find_among_b(C,16))&&(L.bra=L.cursor,m()))switch(e){case 1:L.slice_del();break;case 2:L.slice_from("a");break;case 3:L.slice_from("e");break;case 4:L.slice_from("i");break;case 5:i=L.limit-L.cursor,L.eq_s_b(2,"ab")||(L.cursor=L.limit-i,L.slice_from("i"));break;case 6:L.slice_from("at");break;case 7:L.slice_from("aţi")}}function p(){var e,i=L.limit-L.cursor;if(L.ket=L.cursor,(e=L.find_among_b(P,46))&&(L.bra=L.cursor,m())){switch(e){case 1:L.slice_from("abil");break;case 2:L.slice_from("ibil");break;case 3:L.slice_from("iv");break;case 4:L.slice_from("ic");break;case 5:L.slice_from("at");break;case 6:L.slice_from("it")}return _=!0,L.cursor=L.limit-i,!0}return!1}function d(){var e,i;for(_=!1;;)if(i=L.limit-L.cursor,!p()){L.cursor=L.limit-i;break}if(L.ket=L.cursor,(e=L.find_among_b(F,62))&&(L.bra=L.cursor,l())){switch(e){case 1:L.slice_del();break;case 2:L.eq_s_b(1,"ţ")&&(L.bra=L.cursor,L.slice_from("t"));break;case 3:L.slice_from("ist")}_=!0}}function b(){var e,i,r;if(L.cursor>=h){if(i=L.limit_backward,L.limit_backward=h,L.ket=L.cursor,e=L.find_among_b(q,94))switch(L.bra=L.cursor,e){case 1:if(r=L.limit-L.cursor,!L.out_grouping_b(W,97,259)&&(L.cursor=L.limit-r,!L.eq_s_b(1,"u")))break;case 2:L.slice_del()}L.limit_backward=i}}function v(){var e;L.ket=L.cursor,(e=L.find_among_b(S,5))&&(L.bra=L.cursor,w()&&1==e&&L.slice_del())}var _,g,k,h,z=[new i("",-1,3),new i("I",0,1),new i("U",0,2)],C=[new i("ea",-1,3),new i("aţia",-1,7),new i("aua",-1,2),new i("iua",-1,4),new i("aţie",-1,7),new i("ele",-1,3),new i("ile",-1,5),new i("iile",6,4),new i("iei",-1,4),new i("atei",-1,6),new i("ii",-1,4),new i("ului",-1,1),new i("ul",-1,1),new i("elor",-1,3),new i("ilor",-1,4),new i("iilor",14,4)],P=[new i("icala",-1,4),new i("iciva",-1,4),new i("ativa",-1,5),new i("itiva",-1,6),new i("icale",-1,4),new i("aţiune",-1,5),new i("iţiune",-1,6),new i("atoare",-1,5),new i("itoare",-1,6),new i("ătoare",-1,5),new i("icitate",-1,4),new i("abilitate",-1,1),new i("ibilitate",-1,2),new i("ivitate",-1,3),new i("icive",-1,4),new i("ative",-1,5),new i("itive",-1,6),new i("icali",-1,4),new i("atori",-1,5),new i("icatori",18,4),new i("itori",-1,6),new i("ători",-1,5),new i("icitati",-1,4),new i("abilitati",-1,1),new i("ivitati",-1,3),new i("icivi",-1,4),new i("ativi",-1,5),new i("itivi",-1,6),new i("icităi",-1,4),new i("abilităi",-1,1),new i("ivităi",-1,3),new i("icităţi",-1,4),new i("abilităţi",-1,1),new i("ivităţi",-1,3),new i("ical",-1,4),new i("ator",-1,5),new i("icator",35,4),new i("itor",-1,6),new i("ător",-1,5),new i("iciv",-1,4),new i("ativ",-1,5),new i("itiv",-1,6),new i("icală",-1,4),new i("icivă",-1,4),new i("ativă",-1,5),new i("itivă",-1,6)],F=[new i("ica",-1,1),new i("abila",-1,1),new i("ibila",-1,1),new i("oasa",-1,1),new i("ata",-1,1),new i("ita",-1,1),new i("anta",-1,1),new i("ista",-1,3),new i("uta",-1,1),new i("iva",-1,1),new i("ic",-1,1),new i("ice",-1,1),new i("abile",-1,1),new i("ibile",-1,1),new i("isme",-1,3),new i("iune",-1,2),new i("oase",-1,1),new i("ate",-1,1),new i("itate",17,1),new i("ite",-1,1),new i("ante",-1,1),new i("iste",-1,3),new i("ute",-1,1),new i("ive",-1,1),new i("ici",-1,1),new i("abili",-1,1),new i("ibili",-1,1),new i("iuni",-1,2),new i("atori",-1,1),new i("osi",-1,1),new i("ati",-1,1),new i("itati",30,1),new i("iti",-1,1),new i("anti",-1,1),new i("isti",-1,3),new i("uti",-1,1),new i("işti",-1,3),new i("ivi",-1,1),new i("ităi",-1,1),new i("oşi",-1,1),new i("ităţi",-1,1),new i("abil",-1,1),new i("ibil",-1,1),new i("ism",-1,3),new i("ator",-1,1),new i("os",-1,1),new i("at",-1,1),new i("it",-1,1),new i("ant",-1,1),new i("ist",-1,3),new i("ut",-1,1),new i("iv",-1,1),new i("ică",-1,1),new i("abilă",-1,1),new i("ibilă",-1,1),new i("oasă",-1,1),new i("ată",-1,1),new i("ită",-1,1),new i("antă",-1,1),new i("istă",-1,3),new i("ută",-1,1),new i("ivă",-1,1)],q=[new i("ea",-1,1),new i("ia",-1,1),new i("esc",-1,1),new i("ăsc",-1,1),new i("ind",-1,1),new i("ând",-1,1),new i("are",-1,1),new i("ere",-1,1),new i("ire",-1,1),new i("âre",-1,1),new i("se",-1,2),new i("ase",10,1),new i("sese",10,2),new i("ise",10,1),new i("use",10,1),new i("âse",10,1),new i("eşte",-1,1),new i("ăşte",-1,1),new i("eze",-1,1),new i("ai",-1,1),new i("eai",19,1),new i("iai",19,1),new i("sei",-1,2),new i("eşti",-1,1),new i("ăşti",-1,1),new i("ui",-1,1),new i("ezi",-1,1),new i("âi",-1,1),new i("aşi",-1,1),new i("seşi",-1,2),new i("aseşi",29,1),new i("seseşi",29,2),new i("iseşi",29,1),new i("useşi",29,1),new i("âseşi",29,1),new i("işi",-1,1),new i("uşi",-1,1),new i("âşi",-1,1),new i("aţi",-1,2),new i("eaţi",38,1),new i("iaţi",38,1),new i("eţi",-1,2),new i("iţi",-1,2),new i("âţi",-1,2),new i("arăţi",-1,1),new i("serăţi",-1,2),new i("aserăţi",45,1),new i("seserăţi",45,2),new i("iserăţi",45,1),new i("userăţi",45,1),new i("âserăţi",45,1),new i("irăţi",-1,1),new i("urăţi",-1,1),new i("ârăţi",-1,1),new i("am",-1,1),new i("eam",54,1),new i("iam",54,1),new i("em",-1,2),new i("asem",57,1),new i("sesem",57,2),new i("isem",57,1),new i("usem",57,1),new i("âsem",57,1),new i("im",-1,2),new i("âm",-1,2),new i("ăm",-1,2),new i("arăm",65,1),new i("serăm",65,2),new i("aserăm",67,1),new i("seserăm",67,2),new i("iserăm",67,1),new i("userăm",67,1),new i("âserăm",67,1),new i("irăm",65,1),new i("urăm",65,1),new i("ârăm",65,1),new i("au",-1,1),new i("eau",76,1),new i("iau",76,1),new i("indu",-1,1),new i("ându",-1,1),new i("ez",-1,1),new i("ească",-1,1),new i("ară",-1,1),new i("seră",-1,2),new i("aseră",84,1),new i("seseră",84,2),new i("iseră",84,1),new i("useră",84,1),new i("âseră",84,1),new i("iră",-1,1),new i("ură",-1,1),new i("âră",-1,1),new i("ează",-1,1)],S=[new i("a",-1,1),new i("e",-1,1),new i("ie",1,1),new i("i",-1,1),new i("ă",-1,1)],W=[17,65,16,0,0,0,0,0,0,0,0,0,0,0,0,0,2,32,0,0,4],L=new r;this.setCurrent=function(e){L.setCurrent(e)},this.getCurrent=function(){return L.getCurrent()},this.stem=function(){var e=L.cursor;return n(),L.cursor=e,c(),L.limit_backward=e,L.cursor=L.limit,f(),L.cursor=L.limit,d(),L.cursor=L.limit,_||(L.cursor=L.limit,b(),L.cursor=L.limit),v(),L.cursor=L.limit_backward,s(),!0}};return function(e){return"function"==typeof e.update?e.update(function(e){return n.setCurrent(e),n.stem(),n.getCurrent()}):(n.setCurrent(e),n.stem(),n.getCurrent())}}(),e.Pipeline.registerFunction(e.ro.stemmer,"stemmer-ro"),e.ro.stopWordFilter=e.generateStopWordFilter("acea aceasta această aceea acei aceia acel acela acele acelea acest acesta aceste acestea aceşti aceştia acolo acord acum ai aia aibă aici al ale alea altceva altcineva am ar are asemenea asta astea astăzi asupra au avea avem aveţi azi aş aşadar aţi bine bucur bună ca care caut ce cel ceva chiar cinci cine cineva contra cu cum cumva curând curînd când cât câte câtva câţi cînd cît cîte cîtva cîţi că căci cărei căror cărui către da dacă dar datorită dată dau de deci deja deoarece departe deşi din dinaintea dintr- dintre doi doilea două drept după dă ea ei el ele eram este eu eşti face fata fi fie fiecare fii fim fiu fiţi frumos fără graţie halbă iar ieri la le li lor lui lângă lîngă mai mea mei mele mereu meu mi mie mine mult multă mulţi mulţumesc mâine mîine mă ne nevoie nici nicăieri nimeni nimeri nimic nişte noastre noastră noi noroc nostru nouă noştri nu opt ori oricare orice oricine oricum oricând oricât oricînd oricît oriunde patra patru patrulea pe pentru peste pic poate pot prea prima primul prin puţin puţina puţină până pînă rog sa sale sau se spate spre sub sunt suntem sunteţi sută sînt sîntem sînteţi să săi său ta tale te timp tine toate toată tot totuşi toţi trei treia treilea tu tăi tău un una unde undeva unei uneia unele uneori unii unor unora unu unui unuia unul vi voastre voastră voi vostru vouă voştri vreme vreo vreun vă zece zero zi zice îi îl îmi împotriva în înainte înaintea încotro încât încît între întrucât întrucît îţi ăla ălea ăsta ăstea ăştia şapte şase şi ştiu ţi ţie".split(" ")),e.Pipeline.registerFunction(e.ro.stopWordFilter,"stopWordFilter-ro")}}); \ No newline at end of file diff --git a/assets/javascripts/lunr/min/lunr.ru.min.js b/assets/javascripts/lunr/min/lunr.ru.min.js new file mode 100644 index 0000000..186cc48 --- /dev/null +++ b/assets/javascripts/lunr/min/lunr.ru.min.js @@ -0,0 +1,18 @@ +/*! + * Lunr languages, `Russian` language + * https://github.com/MihaiValentin/lunr-languages + * + * Copyright 2014, Mihai Valentin + * http://www.mozilla.org/MPL/ + */ +/*! + * based on + * Snowball JavaScript Library v0.3 + * http://code.google.com/p/urim/ + * http://snowball.tartarus.org/ + * + * Copyright 2010, Oleg Mazko + * http://www.mozilla.org/MPL/ + */ + +!function(e,n){"function"==typeof define&&define.amd?define(n):"object"==typeof exports?module.exports=n():n()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.ru=function(){this.pipeline.reset(),this.pipeline.add(e.ru.trimmer,e.ru.stopWordFilter,e.ru.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.ru.stemmer))},e.ru.wordCharacters="Ѐ-҄҇-ԯᴫᵸⷠ-ⷿꙀ-ꚟ︮︯",e.ru.trimmer=e.trimmerSupport.generateTrimmer(e.ru.wordCharacters),e.Pipeline.registerFunction(e.ru.trimmer,"trimmer-ru"),e.ru.stemmer=function(){var n=e.stemmerSupport.Among,r=e.stemmerSupport.SnowballProgram,t=new function(){function e(){for(;!W.in_grouping(S,1072,1103);){if(W.cursor>=W.limit)return!1;W.cursor++}return!0}function t(){for(;!W.out_grouping(S,1072,1103);){if(W.cursor>=W.limit)return!1;W.cursor++}return!0}function w(){b=W.limit,_=b,e()&&(b=W.cursor,t()&&e()&&t()&&(_=W.cursor))}function i(){return _<=W.cursor}function u(e,n){var r,t;if(W.ket=W.cursor,r=W.find_among_b(e,n)){switch(W.bra=W.cursor,r){case 1:if(t=W.limit-W.cursor,!W.eq_s_b(1,"а")&&(W.cursor=W.limit-t,!W.eq_s_b(1,"я")))return!1;case 2:W.slice_del()}return!0}return!1}function o(){return u(h,9)}function s(e,n){var r;return W.ket=W.cursor,!!(r=W.find_among_b(e,n))&&(W.bra=W.cursor,1==r&&W.slice_del(),!0)}function c(){return s(g,26)}function m(){return!!c()&&(u(C,8),!0)}function f(){return s(k,2)}function l(){return u(P,46)}function a(){s(v,36)}function p(){var e;W.ket=W.cursor,(e=W.find_among_b(F,2))&&(W.bra=W.cursor,i()&&1==e&&W.slice_del())}function d(){var e;if(W.ket=W.cursor,e=W.find_among_b(q,4))switch(W.bra=W.cursor,e){case 1:if(W.slice_del(),W.ket=W.cursor,!W.eq_s_b(1,"н"))break;W.bra=W.cursor;case 2:if(!W.eq_s_b(1,"н"))break;case 3:W.slice_del()}}var _,b,h=[new n("в",-1,1),new n("ив",0,2),new n("ыв",0,2),new n("вши",-1,1),new n("ивши",3,2),new n("ывши",3,2),new n("вшись",-1,1),new n("ившись",6,2),new n("ывшись",6,2)],g=[new n("ее",-1,1),new n("ие",-1,1),new n("ое",-1,1),new n("ые",-1,1),new n("ими",-1,1),new n("ыми",-1,1),new n("ей",-1,1),new n("ий",-1,1),new n("ой",-1,1),new n("ый",-1,1),new n("ем",-1,1),new n("им",-1,1),new n("ом",-1,1),new n("ым",-1,1),new n("его",-1,1),new n("ого",-1,1),new n("ему",-1,1),new n("ому",-1,1),new n("их",-1,1),new n("ых",-1,1),new n("ею",-1,1),new n("ою",-1,1),new n("ую",-1,1),new n("юю",-1,1),new n("ая",-1,1),new n("яя",-1,1)],C=[new n("ем",-1,1),new n("нн",-1,1),new n("вш",-1,1),new n("ивш",2,2),new n("ывш",2,2),new n("щ",-1,1),new n("ющ",5,1),new n("ующ",6,2)],k=[new n("сь",-1,1),new n("ся",-1,1)],P=[new n("ла",-1,1),new n("ила",0,2),new n("ыла",0,2),new n("на",-1,1),new n("ена",3,2),new n("ете",-1,1),new n("ите",-1,2),new n("йте",-1,1),new n("ейте",7,2),new n("уйте",7,2),new n("ли",-1,1),new n("или",10,2),new n("ыли",10,2),new n("й",-1,1),new n("ей",13,2),new n("уй",13,2),new n("л",-1,1),new n("ил",16,2),new n("ыл",16,2),new n("ем",-1,1),new n("им",-1,2),new n("ым",-1,2),new n("н",-1,1),new n("ен",22,2),new n("ло",-1,1),new n("ило",24,2),new n("ыло",24,2),new n("но",-1,1),new n("ено",27,2),new n("нно",27,1),new n("ет",-1,1),new n("ует",30,2),new n("ит",-1,2),new n("ыт",-1,2),new n("ют",-1,1),new n("уют",34,2),new n("ят",-1,2),new n("ны",-1,1),new n("ены",37,2),new n("ть",-1,1),new n("ить",39,2),new n("ыть",39,2),new n("ешь",-1,1),new n("ишь",-1,2),new n("ю",-1,2),new n("ую",44,2)],v=[new n("а",-1,1),new n("ев",-1,1),new n("ов",-1,1),new n("е",-1,1),new n("ие",3,1),new n("ье",3,1),new n("и",-1,1),new n("еи",6,1),new n("ии",6,1),new n("ами",6,1),new n("ями",6,1),new n("иями",10,1),new n("й",-1,1),new n("ей",12,1),new n("ией",13,1),new n("ий",12,1),new n("ой",12,1),new n("ам",-1,1),new n("ем",-1,1),new n("ием",18,1),new n("ом",-1,1),new n("ям",-1,1),new n("иям",21,1),new n("о",-1,1),new n("у",-1,1),new n("ах",-1,1),new n("ях",-1,1),new n("иях",26,1),new n("ы",-1,1),new n("ь",-1,1),new n("ю",-1,1),new n("ию",30,1),new n("ью",30,1),new n("я",-1,1),new n("ия",33,1),new n("ья",33,1)],F=[new n("ост",-1,1),new n("ость",-1,1)],q=[new n("ейше",-1,1),new n("н",-1,2),new n("ейш",-1,1),new n("ь",-1,3)],S=[33,65,8,232],W=new r;this.setCurrent=function(e){W.setCurrent(e)},this.getCurrent=function(){return W.getCurrent()},this.stem=function(){return w(),W.cursor=W.limit,!(W.cursor=i&&(e-=i,t[e>>3]&1<<(7&e)))return this.cursor++,!0}return!1},in_grouping_b:function(t,i,s){if(this.cursor>this.limit_backward){var e=r.charCodeAt(this.cursor-1);if(e<=s&&e>=i&&(e-=i,t[e>>3]&1<<(7&e)))return this.cursor--,!0}return!1},out_grouping:function(t,i,s){if(this.cursors||e>3]&1<<(7&e)))return this.cursor++,!0}return!1},out_grouping_b:function(t,i,s){if(this.cursor>this.limit_backward){var e=r.charCodeAt(this.cursor-1);if(e>s||e>3]&1<<(7&e)))return this.cursor--,!0}return!1},eq_s:function(t,i){if(this.limit-this.cursor>1),f=0,l=o0||e==s||c)break;c=!0}}for(;;){var _=t[s];if(o>=_.s_size){if(this.cursor=n+_.s_size,!_.method)return _.result;var b=_.method();if(this.cursor=n+_.s_size,b)return _.result}if((s=_.substring_i)<0)return 0}},find_among_b:function(t,i){for(var s=0,e=i,n=this.cursor,u=this.limit_backward,o=0,h=0,c=!1;;){for(var a=s+(e-s>>1),f=0,l=o=0;m--){if(n-l==u){f=-1;break}if(f=r.charCodeAt(n-1-l)-_.s[m])break;l++}if(f<0?(e=a,h=l):(s=a,o=l),e-s<=1){if(s>0||e==s||c)break;c=!0}}for(;;){var _=t[s];if(o>=_.s_size){if(this.cursor=n-_.s_size,!_.method)return _.result;var b=_.method();if(this.cursor=n-_.s_size,b)return _.result}if((s=_.substring_i)<0)return 0}},replace_s:function(t,i,s){var e=s.length-(i-t),n=r.substring(0,t),u=r.substring(i);return r=n+s+u,this.limit+=e,this.cursor>=i?this.cursor+=e:this.cursor>t&&(this.cursor=t),e},slice_check:function(){if(this.bra<0||this.bra>this.ket||this.ket>this.limit||this.limit>r.length)throw"faulty slice operation"},slice_from:function(r){this.slice_check(),this.replace_s(this.bra,this.ket,r)},slice_del:function(){this.slice_from("")},insert:function(r,t,i){var s=this.replace_s(r,t,i);r<=this.bra&&(this.bra+=s),r<=this.ket&&(this.ket+=s)},slice_to:function(){return this.slice_check(),r.substring(this.bra,this.ket)},eq_v_b:function(r){return this.eq_s_b(r.length,r)}}}},r.trimmerSupport={generateTrimmer:function(r){var t=new RegExp("^[^"+r+"]+"),i=new RegExp("[^"+r+"]+$");return function(r){return"function"==typeof r.update?r.update(function(r){return r.replace(t,"").replace(i,"")}):r.replace(t,"").replace(i,"")}}}}}); \ No newline at end of file diff --git a/assets/javascripts/lunr/min/lunr.sv.min.js b/assets/javascripts/lunr/min/lunr.sv.min.js new file mode 100644 index 0000000..3e5eb64 --- /dev/null +++ b/assets/javascripts/lunr/min/lunr.sv.min.js @@ -0,0 +1,18 @@ +/*! + * Lunr languages, `Swedish` language + * https://github.com/MihaiValentin/lunr-languages + * + * Copyright 2014, Mihai Valentin + * http://www.mozilla.org/MPL/ + */ +/*! + * based on + * Snowball JavaScript Library v0.3 + * http://code.google.com/p/urim/ + * http://snowball.tartarus.org/ + * + * Copyright 2010, Oleg Mazko + * http://www.mozilla.org/MPL/ + */ + +!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.sv=function(){this.pipeline.reset(),this.pipeline.add(e.sv.trimmer,e.sv.stopWordFilter,e.sv.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.sv.stemmer))},e.sv.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",e.sv.trimmer=e.trimmerSupport.generateTrimmer(e.sv.wordCharacters),e.Pipeline.registerFunction(e.sv.trimmer,"trimmer-sv"),e.sv.stemmer=function(){var r=e.stemmerSupport.Among,n=e.stemmerSupport.SnowballProgram,t=new function(){function e(){var e,r=w.cursor+3;if(o=w.limit,0<=r||r<=w.limit){for(a=r;;){if(e=w.cursor,w.in_grouping(l,97,246)){w.cursor=e;break}if(w.cursor=e,w.cursor>=w.limit)return;w.cursor++}for(;!w.out_grouping(l,97,246);){if(w.cursor>=w.limit)return;w.cursor++}o=w.cursor,o=o&&(w.limit_backward=o,w.cursor=w.limit,w.ket=w.cursor,e=w.find_among_b(u,37),w.limit_backward=r,e))switch(w.bra=w.cursor,e){case 1:w.slice_del();break;case 2:w.in_grouping_b(d,98,121)&&w.slice_del()}}function i(){var e=w.limit_backward;w.cursor>=o&&(w.limit_backward=o,w.cursor=w.limit,w.find_among_b(c,7)&&(w.cursor=w.limit,w.ket=w.cursor,w.cursor>w.limit_backward&&(w.bra=--w.cursor,w.slice_del())),w.limit_backward=e)}function s(){var e,r;if(w.cursor>=o){if(r=w.limit_backward,w.limit_backward=o,w.cursor=w.limit,w.ket=w.cursor,e=w.find_among_b(m,5))switch(w.bra=w.cursor,e){case 1:w.slice_del();break;case 2:w.slice_from("lös");break;case 3:w.slice_from("full")}w.limit_backward=r}}var a,o,u=[new r("a",-1,1),new r("arna",0,1),new r("erna",0,1),new r("heterna",2,1),new r("orna",0,1),new r("ad",-1,1),new r("e",-1,1),new r("ade",6,1),new r("ande",6,1),new r("arne",6,1),new r("are",6,1),new r("aste",6,1),new r("en",-1,1),new r("anden",12,1),new r("aren",12,1),new r("heten",12,1),new r("ern",-1,1),new r("ar",-1,1),new r("er",-1,1),new r("heter",18,1),new r("or",-1,1),new r("s",-1,2),new r("as",21,1),new r("arnas",22,1),new r("ernas",22,1),new r("ornas",22,1),new r("es",21,1),new r("ades",26,1),new r("andes",26,1),new r("ens",21,1),new r("arens",29,1),new r("hetens",29,1),new r("erns",21,1),new r("at",-1,1),new r("andet",-1,1),new r("het",-1,1),new r("ast",-1,1)],c=[new r("dd",-1,-1),new r("gd",-1,-1),new r("nn",-1,-1),new r("dt",-1,-1),new r("gt",-1,-1),new r("kt",-1,-1),new r("tt",-1,-1)],m=[new r("ig",-1,1),new r("lig",0,1),new r("els",-1,1),new r("fullt",-1,3),new r("löst",-1,2)],l=[17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,0,24,0,32],d=[119,127,149],w=new n;this.setCurrent=function(e){w.setCurrent(e)},this.getCurrent=function(){return w.getCurrent()},this.stem=function(){var r=w.cursor;return e(),w.limit_backward=r,w.cursor=w.limit,t(),w.cursor=w.limit,i(),w.cursor=w.limit,s(),!0}};return function(e){return"function"==typeof e.update?e.update(function(e){return t.setCurrent(e),t.stem(),t.getCurrent()}):(t.setCurrent(e),t.stem(),t.getCurrent())}}(),e.Pipeline.registerFunction(e.sv.stemmer,"stemmer-sv"),e.sv.stopWordFilter=e.generateStopWordFilter("alla allt att av blev bli blir blivit de dem den denna deras dess dessa det detta dig din dina ditt du där då efter ej eller en er era ert ett från för ha hade han hans har henne hennes hon honom hur här i icke ingen inom inte jag ju kan kunde man med mellan men mig min mina mitt mot mycket ni nu när någon något några och om oss på samma sedan sig sin sina sitta själv skulle som så sådan sådana sådant till under upp ut utan vad var vara varför varit varje vars vart vem vi vid vilka vilkas vilken vilket vår våra vårt än är åt över".split(" ")),e.Pipeline.registerFunction(e.sv.stopWordFilter,"stopWordFilter-sv")}}); \ No newline at end of file diff --git a/assets/javascripts/lunr/min/lunr.ta.min.js b/assets/javascripts/lunr/min/lunr.ta.min.js new file mode 100644 index 0000000..a644bed --- /dev/null +++ b/assets/javascripts/lunr/min/lunr.ta.min.js @@ -0,0 +1 @@ +!function(e,t){"function"==typeof define&&define.amd?define(t):"object"==typeof exports?module.exports=t():t()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.ta=function(){this.pipeline.reset(),this.pipeline.add(e.ta.trimmer,e.ta.stopWordFilter,e.ta.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.ta.stemmer))},e.ta.wordCharacters="஀-உஊ-ஏஐ-ஙச-ட஠-னப-யர-ஹ஺-ிீ-௉ொ-௏ௐ-௙௚-௟௠-௩௪-௯௰-௹௺-௿a-zA-Za-zA-Z0-90-9",e.ta.trimmer=e.trimmerSupport.generateTrimmer(e.ta.wordCharacters),e.Pipeline.registerFunction(e.ta.trimmer,"trimmer-ta"),e.ta.stopWordFilter=e.generateStopWordFilter("அங்கு அங்கே அது அதை அந்த அவர் அவர்கள் அவள் அவன் அவை ஆக ஆகவே ஆகையால் ஆதலால் ஆதலினால் ஆனாலும் ஆனால் இங்கு இங்கே இது இதை இந்த இப்படி இவர் இவர்கள் இவள் இவன் இவை இவ்வளவு உனக்கு உனது உன் உன்னால் எங்கு எங்கே எது எதை எந்த எப்படி எவர் எவர்கள் எவள் எவன் எவை எவ்வளவு எனக்கு எனது எனவே என் என்ன என்னால் ஏது ஏன் தனது தன்னால் தானே தான் நாங்கள் நாம் நான் நீ நீங்கள்".split(" ")),e.ta.stemmer=function(){return function(e){return"function"==typeof e.update?e.update(function(e){return e}):e}}();var t=e.wordcut;t.init(),e.ta.tokenizer=function(r){if(!arguments.length||null==r||void 0==r)return[];if(Array.isArray(r))return r.map(function(t){return isLunr2?new e.Token(t.toLowerCase()):t.toLowerCase()});var i=r.toString().toLowerCase().replace(/^\s+/,"");return t.cut(i).split("|")},e.Pipeline.registerFunction(e.ta.stemmer,"stemmer-ta"),e.Pipeline.registerFunction(e.ta.stopWordFilter,"stopWordFilter-ta")}}); \ No newline at end of file diff --git a/assets/javascripts/lunr/min/lunr.te.min.js b/assets/javascripts/lunr/min/lunr.te.min.js new file mode 100644 index 0000000..9fa7a93 --- /dev/null +++ b/assets/javascripts/lunr/min/lunr.te.min.js @@ -0,0 +1 @@ +!function(e,t){"function"==typeof define&&define.amd?define(t):"object"==typeof exports?module.exports=t():t()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.te=function(){this.pipeline.reset(),this.pipeline.add(e.te.trimmer,e.te.stopWordFilter,e.te.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.te.stemmer))},e.te.wordCharacters="ఀ-ఄఅ-ఔక-హా-ౌౕ-ౖౘ-ౚౠ-ౡౢ-ౣ౦-౯౸-౿఼ఽ్ౝ౷౤౥",e.te.trimmer=e.trimmerSupport.generateTrimmer(e.te.wordCharacters),e.Pipeline.registerFunction(e.te.trimmer,"trimmer-te"),e.te.stopWordFilter=e.generateStopWordFilter("అందరూ అందుబాటులో అడగండి అడగడం అడ్డంగా అనుగుణంగా అనుమతించు అనుమతిస్తుంది అయితే ఇప్పటికే ఉన్నారు ఎక్కడైనా ఎప్పుడు ఎవరైనా ఎవరో ఏ ఏదైనా ఏమైనప్పటికి ఒక ఒకరు కనిపిస్తాయి కాదు కూడా గా గురించి చుట్టూ చేయగలిగింది తగిన తర్వాత దాదాపు దూరంగా నిజంగా పై ప్రకారం ప్రక్కన మధ్య మరియు మరొక మళ్ళీ మాత్రమే మెచ్చుకో వద్ద వెంట వేరుగా వ్యతిరేకంగా సంబంధం".split(" ")),e.te.stemmer=function(){return function(e){return"function"==typeof e.update?e.update(function(e){return e}):e}}();var t=e.wordcut;t.init(),e.te.tokenizer=function(r){if(!arguments.length||null==r||void 0==r)return[];if(Array.isArray(r))return r.map(function(t){return isLunr2?new e.Token(t.toLowerCase()):t.toLowerCase()});var i=r.toString().toLowerCase().replace(/^\s+/,"");return t.cut(i).split("|")},e.Pipeline.registerFunction(e.te.stemmer,"stemmer-te"),e.Pipeline.registerFunction(e.te.stopWordFilter,"stopWordFilter-te")}}); \ No newline at end of file diff --git a/assets/javascripts/lunr/min/lunr.th.min.js b/assets/javascripts/lunr/min/lunr.th.min.js new file mode 100644 index 0000000..dee3aac --- /dev/null +++ b/assets/javascripts/lunr/min/lunr.th.min.js @@ -0,0 +1 @@ +!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");var r="2"==e.version[0];e.th=function(){this.pipeline.reset(),this.pipeline.add(e.th.trimmer),r?this.tokenizer=e.th.tokenizer:(e.tokenizer&&(e.tokenizer=e.th.tokenizer),this.tokenizerFn&&(this.tokenizerFn=e.th.tokenizer))},e.th.wordCharacters="[฀-๿]",e.th.trimmer=e.trimmerSupport.generateTrimmer(e.th.wordCharacters),e.Pipeline.registerFunction(e.th.trimmer,"trimmer-th");var t=e.wordcut;t.init(),e.th.tokenizer=function(i){if(!arguments.length||null==i||void 0==i)return[];if(Array.isArray(i))return i.map(function(t){return r?new e.Token(t):t});var n=i.toString().replace(/^\s+/,"");return t.cut(n).split("|")}}}); \ No newline at end of file diff --git a/assets/javascripts/lunr/min/lunr.tr.min.js b/assets/javascripts/lunr/min/lunr.tr.min.js new file mode 100644 index 0000000..563f6ec --- /dev/null +++ b/assets/javascripts/lunr/min/lunr.tr.min.js @@ -0,0 +1,18 @@ +/*! + * Lunr languages, `Turkish` language + * https://github.com/MihaiValentin/lunr-languages + * + * Copyright 2014, Mihai Valentin + * http://www.mozilla.org/MPL/ + */ +/*! + * based on + * Snowball JavaScript Library v0.3 + * http://code.google.com/p/urim/ + * http://snowball.tartarus.org/ + * + * Copyright 2010, Oleg Mazko + * http://www.mozilla.org/MPL/ + */ + +!function(r,i){"function"==typeof define&&define.amd?define(i):"object"==typeof exports?module.exports=i():i()(r.lunr)}(this,function(){return function(r){if(void 0===r)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===r.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");r.tr=function(){this.pipeline.reset(),this.pipeline.add(r.tr.trimmer,r.tr.stopWordFilter,r.tr.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(r.tr.stemmer))},r.tr.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",r.tr.trimmer=r.trimmerSupport.generateTrimmer(r.tr.wordCharacters),r.Pipeline.registerFunction(r.tr.trimmer,"trimmer-tr"),r.tr.stemmer=function(){var i=r.stemmerSupport.Among,e=r.stemmerSupport.SnowballProgram,n=new function(){function r(r,i,e){for(;;){var n=Dr.limit-Dr.cursor;if(Dr.in_grouping_b(r,i,e)){Dr.cursor=Dr.limit-n;break}if(Dr.cursor=Dr.limit-n,Dr.cursor<=Dr.limit_backward)return!1;Dr.cursor--}return!0}function n(){var i,e;i=Dr.limit-Dr.cursor,r(Wr,97,305);for(var n=0;nDr.limit_backward&&(Dr.cursor--,e=Dr.limit-Dr.cursor,i()))?(Dr.cursor=Dr.limit-e,!0):(Dr.cursor=Dr.limit-n,r()?(Dr.cursor=Dr.limit-n,!1):(Dr.cursor=Dr.limit-n,!(Dr.cursor<=Dr.limit_backward)&&(Dr.cursor--,!!i()&&(Dr.cursor=Dr.limit-n,!0))))}function u(r){return t(r,function(){return Dr.in_grouping_b(Wr,97,305)})}function o(){return u(function(){return Dr.eq_s_b(1,"n")})}function s(){return u(function(){return Dr.eq_s_b(1,"s")})}function c(){return u(function(){return Dr.eq_s_b(1,"y")})}function l(){return t(function(){return Dr.in_grouping_b(Lr,105,305)},function(){return Dr.out_grouping_b(Wr,97,305)})}function a(){return Dr.find_among_b(ur,10)&&l()}function m(){return n()&&Dr.in_grouping_b(Lr,105,305)&&s()}function d(){return Dr.find_among_b(or,2)}function f(){return n()&&Dr.in_grouping_b(Lr,105,305)&&c()}function b(){return n()&&Dr.find_among_b(sr,4)}function w(){return n()&&Dr.find_among_b(cr,4)&&o()}function _(){return n()&&Dr.find_among_b(lr,2)&&c()}function k(){return n()&&Dr.find_among_b(ar,2)}function p(){return n()&&Dr.find_among_b(mr,4)}function g(){return n()&&Dr.find_among_b(dr,2)}function y(){return n()&&Dr.find_among_b(fr,4)}function z(){return n()&&Dr.find_among_b(br,2)}function v(){return n()&&Dr.find_among_b(wr,2)&&c()}function h(){return Dr.eq_s_b(2,"ki")}function q(){return n()&&Dr.find_among_b(_r,2)&&o()}function C(){return n()&&Dr.find_among_b(kr,4)&&c()}function P(){return n()&&Dr.find_among_b(pr,4)}function F(){return n()&&Dr.find_among_b(gr,4)&&c()}function S(){return Dr.find_among_b(yr,4)}function W(){return n()&&Dr.find_among_b(zr,2)}function L(){return n()&&Dr.find_among_b(vr,4)}function x(){return n()&&Dr.find_among_b(hr,8)}function A(){return Dr.find_among_b(qr,2)}function E(){return n()&&Dr.find_among_b(Cr,32)&&c()}function j(){return Dr.find_among_b(Pr,8)&&c()}function T(){return n()&&Dr.find_among_b(Fr,4)&&c()}function Z(){return Dr.eq_s_b(3,"ken")&&c()}function B(){var r=Dr.limit-Dr.cursor;return!(T()||(Dr.cursor=Dr.limit-r,E()||(Dr.cursor=Dr.limit-r,j()||(Dr.cursor=Dr.limit-r,Z()))))}function D(){if(A()){var r=Dr.limit-Dr.cursor;if(S()||(Dr.cursor=Dr.limit-r,W()||(Dr.cursor=Dr.limit-r,C()||(Dr.cursor=Dr.limit-r,P()||(Dr.cursor=Dr.limit-r,F()||(Dr.cursor=Dr.limit-r))))),T())return!1}return!0}function G(){if(W()){Dr.bra=Dr.cursor,Dr.slice_del();var r=Dr.limit-Dr.cursor;return Dr.ket=Dr.cursor,x()||(Dr.cursor=Dr.limit-r,E()||(Dr.cursor=Dr.limit-r,j()||(Dr.cursor=Dr.limit-r,T()||(Dr.cursor=Dr.limit-r)))),nr=!1,!1}return!0}function H(){if(!L())return!0;var r=Dr.limit-Dr.cursor;return!E()&&(Dr.cursor=Dr.limit-r,!j())}function I(){var r,i=Dr.limit-Dr.cursor;return!(S()||(Dr.cursor=Dr.limit-i,F()||(Dr.cursor=Dr.limit-i,P()||(Dr.cursor=Dr.limit-i,C()))))||(Dr.bra=Dr.cursor,Dr.slice_del(),r=Dr.limit-Dr.cursor,Dr.ket=Dr.cursor,T()||(Dr.cursor=Dr.limit-r),!1)}function J(){var r,i=Dr.limit-Dr.cursor;if(Dr.ket=Dr.cursor,nr=!0,B()&&(Dr.cursor=Dr.limit-i,D()&&(Dr.cursor=Dr.limit-i,G()&&(Dr.cursor=Dr.limit-i,H()&&(Dr.cursor=Dr.limit-i,I()))))){if(Dr.cursor=Dr.limit-i,!x())return;Dr.bra=Dr.cursor,Dr.slice_del(),Dr.ket=Dr.cursor,r=Dr.limit-Dr.cursor,S()||(Dr.cursor=Dr.limit-r,W()||(Dr.cursor=Dr.limit-r,C()||(Dr.cursor=Dr.limit-r,P()||(Dr.cursor=Dr.limit-r,F()||(Dr.cursor=Dr.limit-r))))),T()||(Dr.cursor=Dr.limit-r)}Dr.bra=Dr.cursor,Dr.slice_del()}function K(){var r,i,e,n;if(Dr.ket=Dr.cursor,h()){if(r=Dr.limit-Dr.cursor,p())return Dr.bra=Dr.cursor,Dr.slice_del(),i=Dr.limit-Dr.cursor,Dr.ket=Dr.cursor,W()?(Dr.bra=Dr.cursor,Dr.slice_del(),K()):(Dr.cursor=Dr.limit-i,a()&&(Dr.bra=Dr.cursor,Dr.slice_del(),Dr.ket=Dr.cursor,W()&&(Dr.bra=Dr.cursor,Dr.slice_del(),K()))),!0;if(Dr.cursor=Dr.limit-r,w()){if(Dr.bra=Dr.cursor,Dr.slice_del(),Dr.ket=Dr.cursor,e=Dr.limit-Dr.cursor,d())Dr.bra=Dr.cursor,Dr.slice_del();else{if(Dr.cursor=Dr.limit-e,Dr.ket=Dr.cursor,!a()&&(Dr.cursor=Dr.limit-e,!m()&&(Dr.cursor=Dr.limit-e,!K())))return!0;Dr.bra=Dr.cursor,Dr.slice_del(),Dr.ket=Dr.cursor,W()&&(Dr.bra=Dr.cursor,Dr.slice_del(),K())}return!0}if(Dr.cursor=Dr.limit-r,g()){if(n=Dr.limit-Dr.cursor,d())Dr.bra=Dr.cursor,Dr.slice_del();else if(Dr.cursor=Dr.limit-n,m())Dr.bra=Dr.cursor,Dr.slice_del(),Dr.ket=Dr.cursor,W()&&(Dr.bra=Dr.cursor,Dr.slice_del(),K());else if(Dr.cursor=Dr.limit-n,!K())return!1;return!0}}return!1}function M(r){if(Dr.ket=Dr.cursor,!g()&&(Dr.cursor=Dr.limit-r,!k()))return!1;var i=Dr.limit-Dr.cursor;if(d())Dr.bra=Dr.cursor,Dr.slice_del();else if(Dr.cursor=Dr.limit-i,m())Dr.bra=Dr.cursor,Dr.slice_del(),Dr.ket=Dr.cursor,W()&&(Dr.bra=Dr.cursor,Dr.slice_del(),K());else if(Dr.cursor=Dr.limit-i,!K())return!1;return!0}function N(r){if(Dr.ket=Dr.cursor,!z()&&(Dr.cursor=Dr.limit-r,!b()))return!1;var i=Dr.limit-Dr.cursor;return!(!m()&&(Dr.cursor=Dr.limit-i,!d()))&&(Dr.bra=Dr.cursor,Dr.slice_del(),Dr.ket=Dr.cursor,W()&&(Dr.bra=Dr.cursor,Dr.slice_del(),K()),!0)}function O(){var r,i=Dr.limit-Dr.cursor;return Dr.ket=Dr.cursor,!(!w()&&(Dr.cursor=Dr.limit-i,!v()))&&(Dr.bra=Dr.cursor,Dr.slice_del(),r=Dr.limit-Dr.cursor,Dr.ket=Dr.cursor,!(!W()||(Dr.bra=Dr.cursor,Dr.slice_del(),!K()))||(Dr.cursor=Dr.limit-r,Dr.ket=Dr.cursor,!(a()||(Dr.cursor=Dr.limit-r,m()||(Dr.cursor=Dr.limit-r,K())))||(Dr.bra=Dr.cursor,Dr.slice_del(),Dr.ket=Dr.cursor,W()&&(Dr.bra=Dr.cursor,Dr.slice_del(),K()),!0)))}function Q(){var r,i,e=Dr.limit-Dr.cursor;if(Dr.ket=Dr.cursor,!p()&&(Dr.cursor=Dr.limit-e,!f()&&(Dr.cursor=Dr.limit-e,!_())))return!1;if(Dr.bra=Dr.cursor,Dr.slice_del(),Dr.ket=Dr.cursor,r=Dr.limit-Dr.cursor,a())Dr.bra=Dr.cursor,Dr.slice_del(),i=Dr.limit-Dr.cursor,Dr.ket=Dr.cursor,W()||(Dr.cursor=Dr.limit-i);else if(Dr.cursor=Dr.limit-r,!W())return!0;return Dr.bra=Dr.cursor,Dr.slice_del(),Dr.ket=Dr.cursor,K(),!0}function R(){var r,i,e=Dr.limit-Dr.cursor;if(Dr.ket=Dr.cursor,W())return Dr.bra=Dr.cursor,Dr.slice_del(),void K();if(Dr.cursor=Dr.limit-e,Dr.ket=Dr.cursor,q())if(Dr.bra=Dr.cursor,Dr.slice_del(),r=Dr.limit-Dr.cursor,Dr.ket=Dr.cursor,d())Dr.bra=Dr.cursor,Dr.slice_del();else{if(Dr.cursor=Dr.limit-r,Dr.ket=Dr.cursor,!a()&&(Dr.cursor=Dr.limit-r,!m())){if(Dr.cursor=Dr.limit-r,Dr.ket=Dr.cursor,!W())return;if(Dr.bra=Dr.cursor,Dr.slice_del(),!K())return}Dr.bra=Dr.cursor,Dr.slice_del(),Dr.ket=Dr.cursor,W()&&(Dr.bra=Dr.cursor,Dr.slice_del(),K())}else if(Dr.cursor=Dr.limit-e,!M(e)&&(Dr.cursor=Dr.limit-e,!N(e))){if(Dr.cursor=Dr.limit-e,Dr.ket=Dr.cursor,y())return Dr.bra=Dr.cursor,Dr.slice_del(),Dr.ket=Dr.cursor,i=Dr.limit-Dr.cursor,void(a()?(Dr.bra=Dr.cursor,Dr.slice_del(),Dr.ket=Dr.cursor,W()&&(Dr.bra=Dr.cursor,Dr.slice_del(),K())):(Dr.cursor=Dr.limit-i,W()?(Dr.bra=Dr.cursor,Dr.slice_del(),K()):(Dr.cursor=Dr.limit-i,K())));if(Dr.cursor=Dr.limit-e,!O()){if(Dr.cursor=Dr.limit-e,d())return Dr.bra=Dr.cursor,void Dr.slice_del();Dr.cursor=Dr.limit-e,K()||(Dr.cursor=Dr.limit-e,Q()||(Dr.cursor=Dr.limit-e,Dr.ket=Dr.cursor,(a()||(Dr.cursor=Dr.limit-e,m()))&&(Dr.bra=Dr.cursor,Dr.slice_del(),Dr.ket=Dr.cursor,W()&&(Dr.bra=Dr.cursor,Dr.slice_del(),K()))))}}}function U(){var r;if(Dr.ket=Dr.cursor,r=Dr.find_among_b(Sr,4))switch(Dr.bra=Dr.cursor,r){case 1:Dr.slice_from("p");break;case 2:Dr.slice_from("ç");break;case 3:Dr.slice_from("t");break;case 4:Dr.slice_from("k")}}function V(){for(;;){var r=Dr.limit-Dr.cursor;if(Dr.in_grouping_b(Wr,97,305)){Dr.cursor=Dr.limit-r;break}if(Dr.cursor=Dr.limit-r,Dr.cursor<=Dr.limit_backward)return!1;Dr.cursor--}return!0}function X(r,i,e){if(Dr.cursor=Dr.limit-r,V()){var n=Dr.limit-Dr.cursor;if(!Dr.eq_s_b(1,i)&&(Dr.cursor=Dr.limit-n,!Dr.eq_s_b(1,e)))return!0;Dr.cursor=Dr.limit-r;var t=Dr.cursor;return Dr.insert(Dr.cursor,Dr.cursor,e),Dr.cursor=t,!1}return!0}function Y(){var r=Dr.limit-Dr.cursor;(Dr.eq_s_b(1,"d")||(Dr.cursor=Dr.limit-r,Dr.eq_s_b(1,"g")))&&X(r,"a","ı")&&X(r,"e","i")&&X(r,"o","u")&&X(r,"ö","ü")}function $(){for(var r,i=Dr.cursor,e=2;;){for(r=Dr.cursor;!Dr.in_grouping(Wr,97,305);){if(Dr.cursor>=Dr.limit)return Dr.cursor=r,!(e>0)&&(Dr.cursor=i,!0);Dr.cursor++}e--}}function rr(r,i,e){for(;!Dr.eq_s(i,e);){if(Dr.cursor>=Dr.limit)return!0;Dr.cursor++}return(tr=i)!=Dr.limit||(Dr.cursor=r,!1)}function ir(){var r=Dr.cursor;return!rr(r,2,"ad")||(Dr.cursor=r,!rr(r,5,"soyad"))}function er(){var r=Dr.cursor;return!ir()&&(Dr.limit_backward=r,Dr.cursor=Dr.limit,Y(),Dr.cursor=Dr.limit,U(),!0)}var nr,tr,ur=[new i("m",-1,-1),new i("n",-1,-1),new i("miz",-1,-1),new i("niz",-1,-1),new i("muz",-1,-1),new i("nuz",-1,-1),new i("müz",-1,-1),new i("nüz",-1,-1),new i("mız",-1,-1),new i("nız",-1,-1)],or=[new i("leri",-1,-1),new i("ları",-1,-1)],sr=[new i("ni",-1,-1),new i("nu",-1,-1),new i("nü",-1,-1),new i("nı",-1,-1)],cr=[new i("in",-1,-1),new i("un",-1,-1),new i("ün",-1,-1),new i("ın",-1,-1)],lr=[new i("a",-1,-1),new i("e",-1,-1)],ar=[new i("na",-1,-1),new i("ne",-1,-1)],mr=[new i("da",-1,-1),new i("ta",-1,-1),new i("de",-1,-1),new i("te",-1,-1)],dr=[new i("nda",-1,-1),new i("nde",-1,-1)],fr=[new i("dan",-1,-1),new i("tan",-1,-1),new i("den",-1,-1),new i("ten",-1,-1)],br=[new i("ndan",-1,-1),new i("nden",-1,-1)],wr=[new i("la",-1,-1),new i("le",-1,-1)],_r=[new i("ca",-1,-1),new i("ce",-1,-1)],kr=[new i("im",-1,-1),new i("um",-1,-1),new i("üm",-1,-1),new i("ım",-1,-1)],pr=[new i("sin",-1,-1),new i("sun",-1,-1),new i("sün",-1,-1),new i("sın",-1,-1)],gr=[new i("iz",-1,-1),new i("uz",-1,-1),new i("üz",-1,-1),new i("ız",-1,-1)],yr=[new i("siniz",-1,-1),new i("sunuz",-1,-1),new i("sünüz",-1,-1),new i("sınız",-1,-1)],zr=[new i("lar",-1,-1),new i("ler",-1,-1)],vr=[new i("niz",-1,-1),new i("nuz",-1,-1),new i("nüz",-1,-1),new i("nız",-1,-1)],hr=[new i("dir",-1,-1),new i("tir",-1,-1),new i("dur",-1,-1),new i("tur",-1,-1),new i("dür",-1,-1),new i("tür",-1,-1),new i("dır",-1,-1),new i("tır",-1,-1)],qr=[new i("casına",-1,-1),new i("cesine",-1,-1)],Cr=[new i("di",-1,-1),new i("ti",-1,-1),new i("dik",-1,-1),new i("tik",-1,-1),new i("duk",-1,-1),new i("tuk",-1,-1),new i("dük",-1,-1),new i("tük",-1,-1),new i("dık",-1,-1),new i("tık",-1,-1),new i("dim",-1,-1),new i("tim",-1,-1),new i("dum",-1,-1),new i("tum",-1,-1),new i("düm",-1,-1),new i("tüm",-1,-1),new i("dım",-1,-1),new i("tım",-1,-1),new i("din",-1,-1),new i("tin",-1,-1),new i("dun",-1,-1),new i("tun",-1,-1),new i("dün",-1,-1),new i("tün",-1,-1),new i("dın",-1,-1),new i("tın",-1,-1),new i("du",-1,-1),new i("tu",-1,-1),new i("dü",-1,-1),new i("tü",-1,-1),new i("dı",-1,-1),new i("tı",-1,-1)],Pr=[new i("sa",-1,-1),new i("se",-1,-1),new i("sak",-1,-1),new i("sek",-1,-1),new i("sam",-1,-1),new i("sem",-1,-1),new i("san",-1,-1),new i("sen",-1,-1)],Fr=[new i("miş",-1,-1),new i("muş",-1,-1),new i("müş",-1,-1),new i("mış",-1,-1)],Sr=[new i("b",-1,1),new i("c",-1,2),new i("d",-1,3),new i("ğ",-1,4)],Wr=[17,65,16,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,8,0,0,0,0,0,0,1],Lr=[1,16,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,0,0,0,0,0,1],xr=[1,64,16,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],Ar=[17,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,130],Er=[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],jr=[17],Tr=[65],Zr=[65],Br=[["a",xr,97,305],["e",Ar,101,252],["ı",Er,97,305],["i",jr,101,105],["o",Tr,111,117],["ö",Zr,246,252],["u",Tr,111,117]],Dr=new e;this.setCurrent=function(r){Dr.setCurrent(r)},this.getCurrent=function(){return Dr.getCurrent()},this.stem=function(){return!!($()&&(Dr.limit_backward=Dr.cursor,Dr.cursor=Dr.limit,J(),Dr.cursor=Dr.limit,nr&&(R(),Dr.cursor=Dr.limit_backward,er())))}};return function(r){return"function"==typeof r.update?r.update(function(r){return n.setCurrent(r),n.stem(),n.getCurrent()}):(n.setCurrent(r),n.stem(),n.getCurrent())}}(),r.Pipeline.registerFunction(r.tr.stemmer,"stemmer-tr"),r.tr.stopWordFilter=r.generateStopWordFilter("acaba altmış altı ama ancak arada aslında ayrıca bana bazı belki ben benden beni benim beri beş bile bin bir biri birkaç birkez birçok birşey birşeyi biz bizden bize bizi bizim bu buna bunda bundan bunlar bunları bunların bunu bunun burada böyle böylece da daha dahi de defa değil diye diğer doksan dokuz dolayı dolayısıyla dört edecek eden ederek edilecek ediliyor edilmesi ediyor elli en etmesi etti ettiği ettiğini eğer gibi göre halen hangi hatta hem henüz hep hepsi her herhangi herkesin hiç hiçbir iki ile ilgili ise itibaren itibariyle için işte kadar karşın katrilyon kendi kendilerine kendini kendisi kendisine kendisini kez ki kim kimden kime kimi kimse kırk milyar milyon mu mü mı nasıl ne neden nedenle nerde nerede nereye niye niçin o olan olarak oldu olduklarını olduğu olduğunu olmadı olmadığı olmak olması olmayan olmaz olsa olsun olup olur olursa oluyor on ona ondan onlar onlardan onları onların onu onun otuz oysa pek rağmen sadece sanki sekiz seksen sen senden seni senin siz sizden sizi sizin tarafından trilyon tüm var vardı ve veya ya yani yapacak yapmak yaptı yaptıkları yaptığı yaptığını yapılan yapılması yapıyor yedi yerine yetmiş yine yirmi yoksa yüz zaten çok çünkü öyle üzere üç şey şeyden şeyi şeyler şu şuna şunda şundan şunları şunu şöyle".split(" ")),r.Pipeline.registerFunction(r.tr.stopWordFilter,"stopWordFilter-tr")}}); \ No newline at end of file diff --git a/assets/javascripts/lunr/min/lunr.vi.min.js b/assets/javascripts/lunr/min/lunr.vi.min.js new file mode 100644 index 0000000..22aed28 --- /dev/null +++ b/assets/javascripts/lunr/min/lunr.vi.min.js @@ -0,0 +1 @@ +!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.vi=function(){this.pipeline.reset(),this.pipeline.add(e.vi.stopWordFilter,e.vi.trimmer)},e.vi.wordCharacters="[A-Za-ẓ̀͐́͑̉̃̓ÂâÊêÔôĂ-ăĐ-đƠ-ơƯ-ư]",e.vi.trimmer=e.trimmerSupport.generateTrimmer(e.vi.wordCharacters),e.Pipeline.registerFunction(e.vi.trimmer,"trimmer-vi"),e.vi.stopWordFilter=e.generateStopWordFilter("là cái nhưng mà".split(" "))}}); \ No newline at end of file diff --git a/assets/javascripts/lunr/min/lunr.zh.min.js b/assets/javascripts/lunr/min/lunr.zh.min.js new file mode 100644 index 0000000..fda66e9 --- /dev/null +++ b/assets/javascripts/lunr/min/lunr.zh.min.js @@ -0,0 +1 @@ +!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r(require("@node-rs/jieba")):r()(e.lunr)}(this,function(e){return function(r,t){if(void 0===r)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===r.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");var i="2"==r.version[0];r.zh=function(){this.pipeline.reset(),this.pipeline.add(r.zh.trimmer,r.zh.stopWordFilter,r.zh.stemmer),i?this.tokenizer=r.zh.tokenizer:(r.tokenizer&&(r.tokenizer=r.zh.tokenizer),this.tokenizerFn&&(this.tokenizerFn=r.zh.tokenizer))},r.zh.tokenizer=function(n){if(!arguments.length||null==n||void 0==n)return[];if(Array.isArray(n))return n.map(function(e){return i?new r.Token(e.toLowerCase()):e.toLowerCase()});t&&e.load(t);var o=n.toString().trim().toLowerCase(),s=[];e.cut(o,!0).forEach(function(e){s=s.concat(e.split(" "))}),s=s.filter(function(e){return!!e});var u=0;return s.map(function(e,t){if(i){var n=o.indexOf(e,u),s={};return s.position=[n,e.length],s.index=t,u=n,new r.Token(e,s)}return e})},r.zh.wordCharacters="\\w一-龥",r.zh.trimmer=r.trimmerSupport.generateTrimmer(r.zh.wordCharacters),r.Pipeline.registerFunction(r.zh.trimmer,"trimmer-zh"),r.zh.stemmer=function(){return function(e){return e}}(),r.Pipeline.registerFunction(r.zh.stemmer,"stemmer-zh"),r.zh.stopWordFilter=r.generateStopWordFilter("的 一 不 在 人 有 是 为 為 以 于 於 上 他 而 后 後 之 来 來 及 了 因 下 可 到 由 这 這 与 與 也 此 但 并 並 个 個 其 已 无 無 小 我 们 們 起 最 再 今 去 好 只 又 或 很 亦 某 把 那 你 乃 它 吧 被 比 别 趁 当 當 从 從 得 打 凡 儿 兒 尔 爾 该 該 各 给 給 跟 和 何 还 還 即 几 幾 既 看 据 據 距 靠 啦 另 么 麽 每 嘛 拿 哪 您 凭 憑 且 却 卻 让 讓 仍 啥 如 若 使 谁 誰 虽 雖 随 隨 同 所 她 哇 嗡 往 些 向 沿 哟 喲 用 咱 则 則 怎 曾 至 致 着 著 诸 諸 自".split(" ")),r.Pipeline.registerFunction(r.zh.stopWordFilter,"stopWordFilter-zh")}}); \ No newline at end of file diff --git a/assets/javascripts/lunr/tinyseg.js b/assets/javascripts/lunr/tinyseg.js new file mode 100644 index 0000000..167fa6d --- /dev/null +++ b/assets/javascripts/lunr/tinyseg.js @@ -0,0 +1,206 @@ +/** + * export the module via AMD, CommonJS or as a browser global + * Export code from https://github.com/umdjs/umd/blob/master/returnExports.js + */ +;(function (root, factory) { + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define(factory) + } else if (typeof exports === 'object') { + /** + * Node. Does not work with strict CommonJS, but + * only CommonJS-like environments that support module.exports, + * like Node. + */ + module.exports = factory() + } else { + // Browser globals (root is window) + factory()(root.lunr); + } +}(this, function () { + /** + * Just return a value to define the module export. + * This example returns an object, but the module + * can return a function as the exported value. + */ + + return function(lunr) { + // TinySegmenter 0.1 -- Super compact Japanese tokenizer in Javascript + // (c) 2008 Taku Kudo + // TinySegmenter is freely distributable under the terms of a new BSD licence. + // For details, see http://chasen.org/~taku/software/TinySegmenter/LICENCE.txt + + function TinySegmenter() { + var patterns = { + "[一二三四五六七八九十百千万億兆]":"M", + "[一-龠々〆ヵヶ]":"H", + "[ぁ-ん]":"I", + "[ァ-ヴーア-ン゙ー]":"K", + "[a-zA-Za-zA-Z]":"A", + "[0-90-9]":"N" + } + this.chartype_ = []; + for (var i in patterns) { + var regexp = new RegExp(i); + this.chartype_.push([regexp, patterns[i]]); + } + + this.BIAS__ = -332 + this.BC1__ = {"HH":6,"II":2461,"KH":406,"OH":-1378}; + this.BC2__ = {"AA":-3267,"AI":2744,"AN":-878,"HH":-4070,"HM":-1711,"HN":4012,"HO":3761,"IA":1327,"IH":-1184,"II":-1332,"IK":1721,"IO":5492,"KI":3831,"KK":-8741,"MH":-3132,"MK":3334,"OO":-2920}; + this.BC3__ = {"HH":996,"HI":626,"HK":-721,"HN":-1307,"HO":-836,"IH":-301,"KK":2762,"MK":1079,"MM":4034,"OA":-1652,"OH":266}; + this.BP1__ = {"BB":295,"OB":304,"OO":-125,"UB":352}; + this.BP2__ = {"BO":60,"OO":-1762}; + this.BQ1__ = {"BHH":1150,"BHM":1521,"BII":-1158,"BIM":886,"BMH":1208,"BNH":449,"BOH":-91,"BOO":-2597,"OHI":451,"OIH":-296,"OKA":1851,"OKH":-1020,"OKK":904,"OOO":2965}; + this.BQ2__ = {"BHH":118,"BHI":-1159,"BHM":466,"BIH":-919,"BKK":-1720,"BKO":864,"OHH":-1139,"OHM":-181,"OIH":153,"UHI":-1146}; + this.BQ3__ = {"BHH":-792,"BHI":2664,"BII":-299,"BKI":419,"BMH":937,"BMM":8335,"BNN":998,"BOH":775,"OHH":2174,"OHM":439,"OII":280,"OKH":1798,"OKI":-793,"OKO":-2242,"OMH":-2402,"OOO":11699}; + this.BQ4__ = {"BHH":-3895,"BIH":3761,"BII":-4654,"BIK":1348,"BKK":-1806,"BMI":-3385,"BOO":-12396,"OAH":926,"OHH":266,"OHK":-2036,"ONN":-973}; + this.BW1__ = {",と":660,",同":727,"B1あ":1404,"B1同":542,"、と":660,"、同":727,"」と":1682,"あっ":1505,"いう":1743,"いっ":-2055,"いる":672,"うし":-4817,"うん":665,"から":3472,"がら":600,"こう":-790,"こと":2083,"こん":-1262,"さら":-4143,"さん":4573,"した":2641,"して":1104,"すで":-3399,"そこ":1977,"それ":-871,"たち":1122,"ため":601,"った":3463,"つい":-802,"てい":805,"てき":1249,"でき":1127,"です":3445,"では":844,"とい":-4915,"とみ":1922,"どこ":3887,"ない":5713,"なっ":3015,"など":7379,"なん":-1113,"にし":2468,"には":1498,"にも":1671,"に対":-912,"の一":-501,"の中":741,"ませ":2448,"まで":1711,"まま":2600,"まる":-2155,"やむ":-1947,"よっ":-2565,"れた":2369,"れで":-913,"をし":1860,"を見":731,"亡く":-1886,"京都":2558,"取り":-2784,"大き":-2604,"大阪":1497,"平方":-2314,"引き":-1336,"日本":-195,"本当":-2423,"毎日":-2113,"目指":-724,"B1あ":1404,"B1同":542,"」と":1682}; + this.BW2__ = {"..":-11822,"11":-669,"――":-5730,"−−":-13175,"いう":-1609,"うか":2490,"かし":-1350,"かも":-602,"から":-7194,"かれ":4612,"がい":853,"がら":-3198,"きた":1941,"くな":-1597,"こと":-8392,"この":-4193,"させ":4533,"され":13168,"さん":-3977,"しい":-1819,"しか":-545,"した":5078,"して":972,"しな":939,"その":-3744,"たい":-1253,"たた":-662,"ただ":-3857,"たち":-786,"たと":1224,"たは":-939,"った":4589,"って":1647,"っと":-2094,"てい":6144,"てき":3640,"てく":2551,"ては":-3110,"ても":-3065,"でい":2666,"でき":-1528,"でし":-3828,"です":-4761,"でも":-4203,"とい":1890,"とこ":-1746,"とと":-2279,"との":720,"とみ":5168,"とも":-3941,"ない":-2488,"なが":-1313,"など":-6509,"なの":2614,"なん":3099,"にお":-1615,"にし":2748,"にな":2454,"によ":-7236,"に対":-14943,"に従":-4688,"に関":-11388,"のか":2093,"ので":-7059,"のに":-6041,"のの":-6125,"はい":1073,"はが":-1033,"はず":-2532,"ばれ":1813,"まし":-1316,"まで":-6621,"まれ":5409,"めて":-3153,"もい":2230,"もの":-10713,"らか":-944,"らし":-1611,"らに":-1897,"りし":651,"りま":1620,"れた":4270,"れて":849,"れば":4114,"ろう":6067,"われ":7901,"を通":-11877,"んだ":728,"んな":-4115,"一人":602,"一方":-1375,"一日":970,"一部":-1051,"上が":-4479,"会社":-1116,"出て":2163,"分の":-7758,"同党":970,"同日":-913,"大阪":-2471,"委員":-1250,"少な":-1050,"年度":-8669,"年間":-1626,"府県":-2363,"手権":-1982,"新聞":-4066,"日新":-722,"日本":-7068,"日米":3372,"曜日":-601,"朝鮮":-2355,"本人":-2697,"東京":-1543,"然と":-1384,"社会":-1276,"立て":-990,"第に":-1612,"米国":-4268,"11":-669}; + this.BW3__ = {"あた":-2194,"あり":719,"ある":3846,"い.":-1185,"い。":-1185,"いい":5308,"いえ":2079,"いく":3029,"いた":2056,"いっ":1883,"いる":5600,"いわ":1527,"うち":1117,"うと":4798,"えと":1454,"か.":2857,"か。":2857,"かけ":-743,"かっ":-4098,"かに":-669,"から":6520,"かり":-2670,"が,":1816,"が、":1816,"がき":-4855,"がけ":-1127,"がっ":-913,"がら":-4977,"がり":-2064,"きた":1645,"けど":1374,"こと":7397,"この":1542,"ころ":-2757,"さい":-714,"さを":976,"し,":1557,"し、":1557,"しい":-3714,"した":3562,"して":1449,"しな":2608,"しま":1200,"す.":-1310,"す。":-1310,"する":6521,"ず,":3426,"ず、":3426,"ずに":841,"そう":428,"た.":8875,"た。":8875,"たい":-594,"たの":812,"たり":-1183,"たる":-853,"だ.":4098,"だ。":4098,"だっ":1004,"った":-4748,"って":300,"てい":6240,"てお":855,"ても":302,"です":1437,"でに":-1482,"では":2295,"とう":-1387,"とし":2266,"との":541,"とも":-3543,"どう":4664,"ない":1796,"なく":-903,"など":2135,"に,":-1021,"に、":-1021,"にし":1771,"にな":1906,"には":2644,"の,":-724,"の、":-724,"の子":-1000,"は,":1337,"は、":1337,"べき":2181,"まし":1113,"ます":6943,"まっ":-1549,"まで":6154,"まれ":-793,"らし":1479,"られ":6820,"るる":3818,"れ,":854,"れ、":854,"れた":1850,"れて":1375,"れば":-3246,"れる":1091,"われ":-605,"んだ":606,"んで":798,"カ月":990,"会議":860,"入り":1232,"大会":2217,"始め":1681,"市":965,"新聞":-5055,"日,":974,"日、":974,"社会":2024,"カ月":990}; + this.TC1__ = {"AAA":1093,"HHH":1029,"HHM":580,"HII":998,"HOH":-390,"HOM":-331,"IHI":1169,"IOH":-142,"IOI":-1015,"IOM":467,"MMH":187,"OOI":-1832}; + this.TC2__ = {"HHO":2088,"HII":-1023,"HMM":-1154,"IHI":-1965,"KKH":703,"OII":-2649}; + this.TC3__ = {"AAA":-294,"HHH":346,"HHI":-341,"HII":-1088,"HIK":731,"HOH":-1486,"IHH":128,"IHI":-3041,"IHO":-1935,"IIH":-825,"IIM":-1035,"IOI":-542,"KHH":-1216,"KKA":491,"KKH":-1217,"KOK":-1009,"MHH":-2694,"MHM":-457,"MHO":123,"MMH":-471,"NNH":-1689,"NNO":662,"OHO":-3393}; + this.TC4__ = {"HHH":-203,"HHI":1344,"HHK":365,"HHM":-122,"HHN":182,"HHO":669,"HIH":804,"HII":679,"HOH":446,"IHH":695,"IHO":-2324,"IIH":321,"III":1497,"IIO":656,"IOO":54,"KAK":4845,"KKA":3386,"KKK":3065,"MHH":-405,"MHI":201,"MMH":-241,"MMM":661,"MOM":841}; + this.TQ1__ = {"BHHH":-227,"BHHI":316,"BHIH":-132,"BIHH":60,"BIII":1595,"BNHH":-744,"BOHH":225,"BOOO":-908,"OAKK":482,"OHHH":281,"OHIH":249,"OIHI":200,"OIIH":-68}; + this.TQ2__ = {"BIHH":-1401,"BIII":-1033,"BKAK":-543,"BOOO":-5591}; + this.TQ3__ = {"BHHH":478,"BHHM":-1073,"BHIH":222,"BHII":-504,"BIIH":-116,"BIII":-105,"BMHI":-863,"BMHM":-464,"BOMH":620,"OHHH":346,"OHHI":1729,"OHII":997,"OHMH":481,"OIHH":623,"OIIH":1344,"OKAK":2792,"OKHH":587,"OKKA":679,"OOHH":110,"OOII":-685}; + this.TQ4__ = {"BHHH":-721,"BHHM":-3604,"BHII":-966,"BIIH":-607,"BIII":-2181,"OAAA":-2763,"OAKK":180,"OHHH":-294,"OHHI":2446,"OHHO":480,"OHIH":-1573,"OIHH":1935,"OIHI":-493,"OIIH":626,"OIII":-4007,"OKAK":-8156}; + this.TW1__ = {"につい":-4681,"東京都":2026}; + this.TW2__ = {"ある程":-2049,"いった":-1256,"ころが":-2434,"しょう":3873,"その後":-4430,"だって":-1049,"ていた":1833,"として":-4657,"ともに":-4517,"もので":1882,"一気に":-792,"初めて":-1512,"同時に":-8097,"大きな":-1255,"対して":-2721,"社会党":-3216}; + this.TW3__ = {"いただ":-1734,"してい":1314,"として":-4314,"につい":-5483,"にとっ":-5989,"に当た":-6247,"ので,":-727,"ので、":-727,"のもの":-600,"れから":-3752,"十二月":-2287}; + this.TW4__ = {"いう.":8576,"いう。":8576,"からな":-2348,"してい":2958,"たが,":1516,"たが、":1516,"ている":1538,"という":1349,"ました":5543,"ません":1097,"ようと":-4258,"よると":5865}; + this.UC1__ = {"A":484,"K":93,"M":645,"O":-505}; + this.UC2__ = {"A":819,"H":1059,"I":409,"M":3987,"N":5775,"O":646}; + this.UC3__ = {"A":-1370,"I":2311}; + this.UC4__ = {"A":-2643,"H":1809,"I":-1032,"K":-3450,"M":3565,"N":3876,"O":6646}; + this.UC5__ = {"H":313,"I":-1238,"K":-799,"M":539,"O":-831}; + this.UC6__ = {"H":-506,"I":-253,"K":87,"M":247,"O":-387}; + this.UP1__ = {"O":-214}; + this.UP2__ = {"B":69,"O":935}; + this.UP3__ = {"B":189}; + this.UQ1__ = {"BH":21,"BI":-12,"BK":-99,"BN":142,"BO":-56,"OH":-95,"OI":477,"OK":410,"OO":-2422}; + this.UQ2__ = {"BH":216,"BI":113,"OK":1759}; + this.UQ3__ = {"BA":-479,"BH":42,"BI":1913,"BK":-7198,"BM":3160,"BN":6427,"BO":14761,"OI":-827,"ON":-3212}; + this.UW1__ = {",":156,"、":156,"「":-463,"あ":-941,"う":-127,"が":-553,"き":121,"こ":505,"で":-201,"と":-547,"ど":-123,"に":-789,"の":-185,"は":-847,"も":-466,"や":-470,"よ":182,"ら":-292,"り":208,"れ":169,"を":-446,"ん":-137,"・":-135,"主":-402,"京":-268,"区":-912,"午":871,"国":-460,"大":561,"委":729,"市":-411,"日":-141,"理":361,"生":-408,"県":-386,"都":-718,"「":-463,"・":-135}; + this.UW2__ = {",":-829,"、":-829,"〇":892,"「":-645,"」":3145,"あ":-538,"い":505,"う":134,"お":-502,"か":1454,"が":-856,"く":-412,"こ":1141,"さ":878,"ざ":540,"し":1529,"す":-675,"せ":300,"そ":-1011,"た":188,"だ":1837,"つ":-949,"て":-291,"で":-268,"と":-981,"ど":1273,"な":1063,"に":-1764,"の":130,"は":-409,"ひ":-1273,"べ":1261,"ま":600,"も":-1263,"や":-402,"よ":1639,"り":-579,"る":-694,"れ":571,"を":-2516,"ん":2095,"ア":-587,"カ":306,"キ":568,"ッ":831,"三":-758,"不":-2150,"世":-302,"中":-968,"主":-861,"事":492,"人":-123,"会":978,"保":362,"入":548,"初":-3025,"副":-1566,"北":-3414,"区":-422,"大":-1769,"天":-865,"太":-483,"子":-1519,"学":760,"実":1023,"小":-2009,"市":-813,"年":-1060,"強":1067,"手":-1519,"揺":-1033,"政":1522,"文":-1355,"新":-1682,"日":-1815,"明":-1462,"最":-630,"朝":-1843,"本":-1650,"東":-931,"果":-665,"次":-2378,"民":-180,"気":-1740,"理":752,"発":529,"目":-1584,"相":-242,"県":-1165,"立":-763,"第":810,"米":509,"自":-1353,"行":838,"西":-744,"見":-3874,"調":1010,"議":1198,"込":3041,"開":1758,"間":-1257,"「":-645,"」":3145,"ッ":831,"ア":-587,"カ":306,"キ":568}; + this.UW3__ = {",":4889,"1":-800,"−":-1723,"、":4889,"々":-2311,"〇":5827,"」":2670,"〓":-3573,"あ":-2696,"い":1006,"う":2342,"え":1983,"お":-4864,"か":-1163,"が":3271,"く":1004,"け":388,"げ":401,"こ":-3552,"ご":-3116,"さ":-1058,"し":-395,"す":584,"せ":3685,"そ":-5228,"た":842,"ち":-521,"っ":-1444,"つ":-1081,"て":6167,"で":2318,"と":1691,"ど":-899,"な":-2788,"に":2745,"の":4056,"は":4555,"ひ":-2171,"ふ":-1798,"へ":1199,"ほ":-5516,"ま":-4384,"み":-120,"め":1205,"も":2323,"や":-788,"よ":-202,"ら":727,"り":649,"る":5905,"れ":2773,"わ":-1207,"を":6620,"ん":-518,"ア":551,"グ":1319,"ス":874,"ッ":-1350,"ト":521,"ム":1109,"ル":1591,"ロ":2201,"ン":278,"・":-3794,"一":-1619,"下":-1759,"世":-2087,"両":3815,"中":653,"主":-758,"予":-1193,"二":974,"人":2742,"今":792,"他":1889,"以":-1368,"低":811,"何":4265,"作":-361,"保":-2439,"元":4858,"党":3593,"全":1574,"公":-3030,"六":755,"共":-1880,"円":5807,"再":3095,"分":457,"初":2475,"別":1129,"前":2286,"副":4437,"力":365,"動":-949,"務":-1872,"化":1327,"北":-1038,"区":4646,"千":-2309,"午":-783,"協":-1006,"口":483,"右":1233,"各":3588,"合":-241,"同":3906,"和":-837,"員":4513,"国":642,"型":1389,"場":1219,"外":-241,"妻":2016,"学":-1356,"安":-423,"実":-1008,"家":1078,"小":-513,"少":-3102,"州":1155,"市":3197,"平":-1804,"年":2416,"広":-1030,"府":1605,"度":1452,"建":-2352,"当":-3885,"得":1905,"思":-1291,"性":1822,"戸":-488,"指":-3973,"政":-2013,"教":-1479,"数":3222,"文":-1489,"新":1764,"日":2099,"旧":5792,"昨":-661,"時":-1248,"曜":-951,"最":-937,"月":4125,"期":360,"李":3094,"村":364,"東":-805,"核":5156,"森":2438,"業":484,"氏":2613,"民":-1694,"決":-1073,"法":1868,"海":-495,"無":979,"物":461,"特":-3850,"生":-273,"用":914,"町":1215,"的":7313,"直":-1835,"省":792,"県":6293,"知":-1528,"私":4231,"税":401,"立":-960,"第":1201,"米":7767,"系":3066,"約":3663,"級":1384,"統":-4229,"総":1163,"線":1255,"者":6457,"能":725,"自":-2869,"英":785,"見":1044,"調":-562,"財":-733,"費":1777,"車":1835,"軍":1375,"込":-1504,"通":-1136,"選":-681,"郎":1026,"郡":4404,"部":1200,"金":2163,"長":421,"開":-1432,"間":1302,"関":-1282,"雨":2009,"電":-1045,"非":2066,"駅":1620,"1":-800,"」":2670,"・":-3794,"ッ":-1350,"ア":551,"グ":1319,"ス":874,"ト":521,"ム":1109,"ル":1591,"ロ":2201,"ン":278}; + this.UW4__ = {",":3930,".":3508,"―":-4841,"、":3930,"。":3508,"〇":4999,"「":1895,"」":3798,"〓":-5156,"あ":4752,"い":-3435,"う":-640,"え":-2514,"お":2405,"か":530,"が":6006,"き":-4482,"ぎ":-3821,"く":-3788,"け":-4376,"げ":-4734,"こ":2255,"ご":1979,"さ":2864,"し":-843,"じ":-2506,"す":-731,"ず":1251,"せ":181,"そ":4091,"た":5034,"だ":5408,"ち":-3654,"っ":-5882,"つ":-1659,"て":3994,"で":7410,"と":4547,"な":5433,"に":6499,"ぬ":1853,"ね":1413,"の":7396,"は":8578,"ば":1940,"ひ":4249,"び":-4134,"ふ":1345,"へ":6665,"べ":-744,"ほ":1464,"ま":1051,"み":-2082,"む":-882,"め":-5046,"も":4169,"ゃ":-2666,"や":2795,"ょ":-1544,"よ":3351,"ら":-2922,"り":-9726,"る":-14896,"れ":-2613,"ろ":-4570,"わ":-1783,"を":13150,"ん":-2352,"カ":2145,"コ":1789,"セ":1287,"ッ":-724,"ト":-403,"メ":-1635,"ラ":-881,"リ":-541,"ル":-856,"ン":-3637,"・":-4371,"ー":-11870,"一":-2069,"中":2210,"予":782,"事":-190,"井":-1768,"人":1036,"以":544,"会":950,"体":-1286,"作":530,"側":4292,"先":601,"党":-2006,"共":-1212,"内":584,"円":788,"初":1347,"前":1623,"副":3879,"力":-302,"動":-740,"務":-2715,"化":776,"区":4517,"協":1013,"参":1555,"合":-1834,"和":-681,"員":-910,"器":-851,"回":1500,"国":-619,"園":-1200,"地":866,"場":-1410,"塁":-2094,"士":-1413,"多":1067,"大":571,"子":-4802,"学":-1397,"定":-1057,"寺":-809,"小":1910,"屋":-1328,"山":-1500,"島":-2056,"川":-2667,"市":2771,"年":374,"庁":-4556,"後":456,"性":553,"感":916,"所":-1566,"支":856,"改":787,"政":2182,"教":704,"文":522,"方":-856,"日":1798,"時":1829,"最":845,"月":-9066,"木":-485,"来":-442,"校":-360,"業":-1043,"氏":5388,"民":-2716,"気":-910,"沢":-939,"済":-543,"物":-735,"率":672,"球":-1267,"生":-1286,"産":-1101,"田":-2900,"町":1826,"的":2586,"目":922,"省":-3485,"県":2997,"空":-867,"立":-2112,"第":788,"米":2937,"系":786,"約":2171,"経":1146,"統":-1169,"総":940,"線":-994,"署":749,"者":2145,"能":-730,"般":-852,"行":-792,"規":792,"警":-1184,"議":-244,"谷":-1000,"賞":730,"車":-1481,"軍":1158,"輪":-1433,"込":-3370,"近":929,"道":-1291,"選":2596,"郎":-4866,"都":1192,"野":-1100,"銀":-2213,"長":357,"間":-2344,"院":-2297,"際":-2604,"電":-878,"領":-1659,"題":-792,"館":-1984,"首":1749,"高":2120,"「":1895,"」":3798,"・":-4371,"ッ":-724,"ー":-11870,"カ":2145,"コ":1789,"セ":1287,"ト":-403,"メ":-1635,"ラ":-881,"リ":-541,"ル":-856,"ン":-3637}; + this.UW5__ = {",":465,".":-299,"1":-514,"E2":-32768,"]":-2762,"、":465,"。":-299,"「":363,"あ":1655,"い":331,"う":-503,"え":1199,"お":527,"か":647,"が":-421,"き":1624,"ぎ":1971,"く":312,"げ":-983,"さ":-1537,"し":-1371,"す":-852,"だ":-1186,"ち":1093,"っ":52,"つ":921,"て":-18,"で":-850,"と":-127,"ど":1682,"な":-787,"に":-1224,"の":-635,"は":-578,"べ":1001,"み":502,"め":865,"ゃ":3350,"ょ":854,"り":-208,"る":429,"れ":504,"わ":419,"を":-1264,"ん":327,"イ":241,"ル":451,"ン":-343,"中":-871,"京":722,"会":-1153,"党":-654,"務":3519,"区":-901,"告":848,"員":2104,"大":-1296,"学":-548,"定":1785,"嵐":-1304,"市":-2991,"席":921,"年":1763,"思":872,"所":-814,"挙":1618,"新":-1682,"日":218,"月":-4353,"査":932,"格":1356,"機":-1508,"氏":-1347,"田":240,"町":-3912,"的":-3149,"相":1319,"省":-1052,"県":-4003,"研":-997,"社":-278,"空":-813,"統":1955,"者":-2233,"表":663,"語":-1073,"議":1219,"選":-1018,"郎":-368,"長":786,"間":1191,"題":2368,"館":-689,"1":-514,"E2":-32768,"「":363,"イ":241,"ル":451,"ン":-343}; + this.UW6__ = {",":227,".":808,"1":-270,"E1":306,"、":227,"。":808,"あ":-307,"う":189,"か":241,"が":-73,"く":-121,"こ":-200,"じ":1782,"す":383,"た":-428,"っ":573,"て":-1014,"で":101,"と":-105,"な":-253,"に":-149,"の":-417,"は":-236,"も":-206,"り":187,"る":-135,"を":195,"ル":-673,"ン":-496,"一":-277,"中":201,"件":-800,"会":624,"前":302,"区":1792,"員":-1212,"委":798,"学":-960,"市":887,"広":-695,"後":535,"業":-697,"相":753,"社":-507,"福":974,"空":-822,"者":1811,"連":463,"郎":1082,"1":-270,"E1":306,"ル":-673,"ン":-496}; + + return this; + } + TinySegmenter.prototype.ctype_ = function(str) { + for (var i in this.chartype_) { + if (str.match(this.chartype_[i][0])) { + return this.chartype_[i][1]; + } + } + return "O"; + } + + TinySegmenter.prototype.ts_ = function(v) { + if (v) { return v; } + return 0; + } + + TinySegmenter.prototype.segment = function(input) { + if (input == null || input == undefined || input == "") { + return []; + } + var result = []; + var seg = ["B3","B2","B1"]; + var ctype = ["O","O","O"]; + var o = input.split(""); + for (i = 0; i < o.length; ++i) { + seg.push(o[i]); + ctype.push(this.ctype_(o[i])) + } + seg.push("E1"); + seg.push("E2"); + seg.push("E3"); + ctype.push("O"); + ctype.push("O"); + ctype.push("O"); + var word = seg[3]; + var p1 = "U"; + var p2 = "U"; + var p3 = "U"; + for (var i = 4; i < seg.length - 3; ++i) { + var score = this.BIAS__; + var w1 = seg[i-3]; + var w2 = seg[i-2]; + var w3 = seg[i-1]; + var w4 = seg[i]; + var w5 = seg[i+1]; + var w6 = seg[i+2]; + var c1 = ctype[i-3]; + var c2 = ctype[i-2]; + var c3 = ctype[i-1]; + var c4 = ctype[i]; + var c5 = ctype[i+1]; + var c6 = ctype[i+2]; + score += this.ts_(this.UP1__[p1]); + score += this.ts_(this.UP2__[p2]); + score += this.ts_(this.UP3__[p3]); + score += this.ts_(this.BP1__[p1 + p2]); + score += this.ts_(this.BP2__[p2 + p3]); + score += this.ts_(this.UW1__[w1]); + score += this.ts_(this.UW2__[w2]); + score += this.ts_(this.UW3__[w3]); + score += this.ts_(this.UW4__[w4]); + score += this.ts_(this.UW5__[w5]); + score += this.ts_(this.UW6__[w6]); + score += this.ts_(this.BW1__[w2 + w3]); + score += this.ts_(this.BW2__[w3 + w4]); + score += this.ts_(this.BW3__[w4 + w5]); + score += this.ts_(this.TW1__[w1 + w2 + w3]); + score += this.ts_(this.TW2__[w2 + w3 + w4]); + score += this.ts_(this.TW3__[w3 + w4 + w5]); + score += this.ts_(this.TW4__[w4 + w5 + w6]); + score += this.ts_(this.UC1__[c1]); + score += this.ts_(this.UC2__[c2]); + score += this.ts_(this.UC3__[c3]); + score += this.ts_(this.UC4__[c4]); + score += this.ts_(this.UC5__[c5]); + score += this.ts_(this.UC6__[c6]); + score += this.ts_(this.BC1__[c2 + c3]); + score += this.ts_(this.BC2__[c3 + c4]); + score += this.ts_(this.BC3__[c4 + c5]); + score += this.ts_(this.TC1__[c1 + c2 + c3]); + score += this.ts_(this.TC2__[c2 + c3 + c4]); + score += this.ts_(this.TC3__[c3 + c4 + c5]); + score += this.ts_(this.TC4__[c4 + c5 + c6]); + // score += this.ts_(this.TC5__[c4 + c5 + c6]); + score += this.ts_(this.UQ1__[p1 + c1]); + score += this.ts_(this.UQ2__[p2 + c2]); + score += this.ts_(this.UQ3__[p3 + c3]); + score += this.ts_(this.BQ1__[p2 + c2 + c3]); + score += this.ts_(this.BQ2__[p2 + c3 + c4]); + score += this.ts_(this.BQ3__[p3 + c2 + c3]); + score += this.ts_(this.BQ4__[p3 + c3 + c4]); + score += this.ts_(this.TQ1__[p2 + c1 + c2 + c3]); + score += this.ts_(this.TQ2__[p2 + c2 + c3 + c4]); + score += this.ts_(this.TQ3__[p3 + c1 + c2 + c3]); + score += this.ts_(this.TQ4__[p3 + c2 + c3 + c4]); + var p = "O"; + if (score > 0) { + result.push(word); + word = ""; + p = "B"; + } + p1 = p2; + p2 = p3; + p3 = p; + word += seg[i]; + } + result.push(word); + + return result; + } + + lunr.TinySegmenter = TinySegmenter; + }; + +})); \ No newline at end of file diff --git a/assets/javascripts/lunr/wordcut.js b/assets/javascripts/lunr/wordcut.js new file mode 100644 index 0000000..0d898c9 --- /dev/null +++ b/assets/javascripts/lunr/wordcut.js @@ -0,0 +1,6708 @@ +(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}(g.lunr || (g.lunr = {})).wordcut = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o 1; + }) + this.addWords(words, false) + } + if(finalize){ + this.finalizeDict(); + } + }, + + dictSeek: function (l, r, ch, strOffset, pos) { + var ans = null; + while (l <= r) { + var m = Math.floor((l + r) / 2), + dict_item = this.dict[m], + len = dict_item.length; + if (len <= strOffset) { + l = m + 1; + } else { + var ch_ = dict_item[strOffset]; + if (ch_ < ch) { + l = m + 1; + } else if (ch_ > ch) { + r = m - 1; + } else { + ans = m; + if (pos == LEFT) { + r = m - 1; + } else { + l = m + 1; + } + } + } + } + return ans; + }, + + isFinal: function (acceptor) { + return this.dict[acceptor.l].length == acceptor.strOffset; + }, + + createAcceptor: function () { + return { + l: 0, + r: this.dict.length - 1, + strOffset: 0, + isFinal: false, + dict: this, + transit: function (ch) { + return this.dict.transit(this, ch); + }, + isError: false, + tag: "DICT", + w: 1, + type: "DICT" + }; + }, + + transit: function (acceptor, ch) { + var l = this.dictSeek(acceptor.l, + acceptor.r, + ch, + acceptor.strOffset, + LEFT); + if (l !== null) { + var r = this.dictSeek(l, + acceptor.r, + ch, + acceptor.strOffset, + RIGHT); + acceptor.l = l; + acceptor.r = r; + acceptor.strOffset++; + acceptor.isFinal = this.isFinal(acceptor); + } else { + acceptor.isError = true; + } + return acceptor; + }, + + sortuniq: function(a){ + return a.sort().filter(function(item, pos, arr){ + return !pos || item != arr[pos - 1]; + }) + }, + + flatten: function(a){ + //[[1,2],[3]] -> [1,2,3] + return [].concat.apply([], a); + } +}; +module.exports = WordcutDict; + +}).call(this,"/dist/tmp") +},{"glob":16,"path":22}],3:[function(require,module,exports){ +var WordRule = { + createAcceptor: function(tag) { + if (tag["WORD_RULE"]) + return null; + + return {strOffset: 0, + isFinal: false, + transit: function(ch) { + var lch = ch.toLowerCase(); + if (lch >= "a" && lch <= "z") { + this.isFinal = true; + this.strOffset++; + } else { + this.isError = true; + } + return this; + }, + isError: false, + tag: "WORD_RULE", + type: "WORD_RULE", + w: 1}; + } +}; + +var NumberRule = { + createAcceptor: function(tag) { + if (tag["NUMBER_RULE"]) + return null; + + return {strOffset: 0, + isFinal: false, + transit: function(ch) { + if (ch >= "0" && ch <= "9") { + this.isFinal = true; + this.strOffset++; + } else { + this.isError = true; + } + return this; + }, + isError: false, + tag: "NUMBER_RULE", + type: "NUMBER_RULE", + w: 1}; + } +}; + +var SpaceRule = { + tag: "SPACE_RULE", + createAcceptor: function(tag) { + + if (tag["SPACE_RULE"]) + return null; + + return {strOffset: 0, + isFinal: false, + transit: function(ch) { + if (ch == " " || ch == "\t" || ch == "\r" || ch == "\n" || + ch == "\u00A0" || ch=="\u2003"//nbsp and emsp + ) { + this.isFinal = true; + this.strOffset++; + } else { + this.isError = true; + } + return this; + }, + isError: false, + tag: SpaceRule.tag, + w: 1, + type: "SPACE_RULE"}; + } +} + +var SingleSymbolRule = { + tag: "SINSYM", + createAcceptor: function(tag) { + return {strOffset: 0, + isFinal: false, + transit: function(ch) { + if (this.strOffset == 0 && ch.match(/^[\@\(\)\/\,\-\."`]$/)) { + this.isFinal = true; + this.strOffset++; + } else { + this.isError = true; + } + return this; + }, + isError: false, + tag: "SINSYM", + w: 1, + type: "SINSYM"}; + } +} + + +var LatinRules = [WordRule, SpaceRule, SingleSymbolRule, NumberRule]; + +module.exports = LatinRules; + +},{}],4:[function(require,module,exports){ +var _ = require("underscore") + , WordcutCore = require("./wordcut_core"); +var PathInfoBuilder = { + + /* + buildByPartAcceptors: function(path, acceptors, i) { + var + var genInfos = partAcceptors.reduce(function(genInfos, acceptor) { + + }, []); + + return genInfos; + } + */ + + buildByAcceptors: function(path, finalAcceptors, i) { + var self = this; + var infos = finalAcceptors.map(function(acceptor) { + var p = i - acceptor.strOffset + 1 + , _info = path[p]; + + var info = {p: p, + mw: _info.mw + (acceptor.mw === undefined ? 0 : acceptor.mw), + w: acceptor.w + _info.w, + unk: (acceptor.unk ? acceptor.unk : 0) + _info.unk, + type: acceptor.type}; + + if (acceptor.type == "PART") { + for(var j = p + 1; j <= i; j++) { + path[j].merge = p; + } + info.merge = p; + } + + return info; + }); + return infos.filter(function(info) { return info; }); + }, + + fallback: function(path, leftBoundary, text, i) { + var _info = path[leftBoundary]; + if (text[i].match(/[\u0E48-\u0E4E]/)) { + if (leftBoundary != 0) + leftBoundary = path[leftBoundary].p; + return {p: leftBoundary, + mw: 0, + w: 1 + _info.w, + unk: 1 + _info.unk, + type: "UNK"}; +/* } else if(leftBoundary > 0 && path[leftBoundary].type !== "UNK") { + leftBoundary = path[leftBoundary].p; + return {p: leftBoundary, + w: 1 + _info.w, + unk: 1 + _info.unk, + type: "UNK"}; */ + } else { + return {p: leftBoundary, + mw: _info.mw, + w: 1 + _info.w, + unk: 1 + _info.unk, + type: "UNK"}; + } + }, + + build: function(path, finalAcceptors, i, leftBoundary, text) { + var basicPathInfos = this.buildByAcceptors(path, finalAcceptors, i); + if (basicPathInfos.length > 0) { + return basicPathInfos; + } else { + return [this.fallback(path, leftBoundary, text, i)]; + } + } +}; + +module.exports = function() { + return _.clone(PathInfoBuilder); +} + +},{"./wordcut_core":8,"underscore":25}],5:[function(require,module,exports){ +var _ = require("underscore"); + + +var PathSelector = { + selectPath: function(paths) { + var path = paths.reduce(function(selectedPath, path) { + if (selectedPath == null) { + return path; + } else { + if (path.unk < selectedPath.unk) + return path; + if (path.unk == selectedPath.unk) { + if (path.mw < selectedPath.mw) + return path + if (path.mw == selectedPath.mw) { + if (path.w < selectedPath.w) + return path; + } + } + return selectedPath; + } + }, null); + return path; + }, + + createPath: function() { + return [{p:null, w:0, unk:0, type: "INIT", mw:0}]; + } +}; + +module.exports = function() { + return _.clone(PathSelector); +}; + +},{"underscore":25}],6:[function(require,module,exports){ +function isMatch(pat, offset, ch) { + if (pat.length <= offset) + return false; + var _ch = pat[offset]; + return _ch == ch || + (_ch.match(/[กข]/) && ch.match(/[ก-ฮ]/)) || + (_ch.match(/[มบ]/) && ch.match(/[ก-ฮ]/)) || + (_ch.match(/\u0E49/) && ch.match(/[\u0E48-\u0E4B]/)); +} + +var Rule0 = { + pat: "เหก็ม", + createAcceptor: function(tag) { + return {strOffset: 0, + isFinal: false, + transit: function(ch) { + if (isMatch(Rule0.pat, this.strOffset,ch)) { + this.isFinal = (this.strOffset + 1 == Rule0.pat.length); + this.strOffset++; + } else { + this.isError = true; + } + return this; + }, + isError: false, + tag: "THAI_RULE", + type: "THAI_RULE", + w: 1}; + } +}; + +var PartRule = { + createAcceptor: function(tag) { + return {strOffset: 0, + patterns: [ + "แก", "เก", "ก้", "กก์", "กา", "กี", "กิ", "กืก" + ], + isFinal: false, + transit: function(ch) { + var offset = this.strOffset; + this.patterns = this.patterns.filter(function(pat) { + return isMatch(pat, offset, ch); + }); + + if (this.patterns.length > 0) { + var len = 1 + offset; + this.isFinal = this.patterns.some(function(pat) { + return pat.length == len; + }); + this.strOffset++; + } else { + this.isError = true; + } + return this; + }, + isError: false, + tag: "PART", + type: "PART", + unk: 1, + w: 1}; + } +}; + +var ThaiRules = [Rule0, PartRule]; + +module.exports = ThaiRules; + +},{}],7:[function(require,module,exports){ +var sys = require("sys") + , WordcutDict = require("./dict") + , WordcutCore = require("./wordcut_core") + , PathInfoBuilder = require("./path_info_builder") + , PathSelector = require("./path_selector") + , Acceptors = require("./acceptors") + , latinRules = require("./latin_rules") + , thaiRules = require("./thai_rules") + , _ = require("underscore"); + + +var Wordcut = Object.create(WordcutCore); +Wordcut.defaultPathInfoBuilder = PathInfoBuilder; +Wordcut.defaultPathSelector = PathSelector; +Wordcut.defaultAcceptors = Acceptors; +Wordcut.defaultLatinRules = latinRules; +Wordcut.defaultThaiRules = thaiRules; +Wordcut.defaultDict = WordcutDict; + + +Wordcut.initNoDict = function(dict_path) { + var self = this; + self.pathInfoBuilder = new self.defaultPathInfoBuilder; + self.pathSelector = new self.defaultPathSelector; + self.acceptors = new self.defaultAcceptors; + self.defaultLatinRules.forEach(function(rule) { + self.acceptors.creators.push(rule); + }); + self.defaultThaiRules.forEach(function(rule) { + self.acceptors.creators.push(rule); + }); +}; + +Wordcut.init = function(dict_path, withDefault, additionalWords) { + withDefault = withDefault || false; + this.initNoDict(); + var dict = _.clone(this.defaultDict); + dict.init(dict_path, withDefault, additionalWords); + this.acceptors.creators.push(dict); +}; + +module.exports = Wordcut; + +},{"./acceptors":1,"./dict":2,"./latin_rules":3,"./path_info_builder":4,"./path_selector":5,"./thai_rules":6,"./wordcut_core":8,"sys":28,"underscore":25}],8:[function(require,module,exports){ +var WordcutCore = { + + buildPath: function(text) { + var self = this + , path = self.pathSelector.createPath() + , leftBoundary = 0; + self.acceptors.reset(); + for (var i = 0; i < text.length; i++) { + var ch = text[i]; + self.acceptors.transit(ch); + + var possiblePathInfos = self + .pathInfoBuilder + .build(path, + self.acceptors.getFinalAcceptors(), + i, + leftBoundary, + text); + var selectedPath = self.pathSelector.selectPath(possiblePathInfos) + + path.push(selectedPath); + if (selectedPath.type !== "UNK") { + leftBoundary = i; + } + } + return path; + }, + + pathToRanges: function(path) { + var e = path.length - 1 + , ranges = []; + + while (e > 0) { + var info = path[e] + , s = info.p; + + if (info.merge !== undefined && ranges.length > 0) { + var r = ranges[ranges.length - 1]; + r.s = info.merge; + s = r.s; + } else { + ranges.push({s:s, e:e}); + } + e = s; + } + return ranges.reverse(); + }, + + rangesToText: function(text, ranges, delimiter) { + return ranges.map(function(r) { + return text.substring(r.s, r.e); + }).join(delimiter); + }, + + cut: function(text, delimiter) { + var path = this.buildPath(text) + , ranges = this.pathToRanges(path); + return this + .rangesToText(text, ranges, + (delimiter === undefined ? "|" : delimiter)); + }, + + cutIntoRanges: function(text, noText) { + var path = this.buildPath(text) + , ranges = this.pathToRanges(path); + + if (!noText) { + ranges.forEach(function(r) { + r.text = text.substring(r.s, r.e); + }); + } + return ranges; + }, + + cutIntoArray: function(text) { + var path = this.buildPath(text) + , ranges = this.pathToRanges(path); + + return ranges.map(function(r) { + return text.substring(r.s, r.e) + }); + } +}; + +module.exports = WordcutCore; + +},{}],9:[function(require,module,exports){ +// http://wiki.commonjs.org/wiki/Unit_Testing/1.0 +// +// THIS IS NOT TESTED NOR LIKELY TO WORK OUTSIDE V8! +// +// Originally from narwhal.js (http://narwhaljs.org) +// Copyright (c) 2009 Thomas Robinson <280north.com> +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the 'Software'), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +// when used in node, this will actually load the util module we depend on +// versus loading the builtin util module as happens otherwise +// this is a bug in node module loading as far as I am concerned +var util = require('util/'); + +var pSlice = Array.prototype.slice; +var hasOwn = Object.prototype.hasOwnProperty; + +// 1. The assert module provides functions that throw +// AssertionError's when particular conditions are not met. The +// assert module must conform to the following interface. + +var assert = module.exports = ok; + +// 2. The AssertionError is defined in assert. +// new assert.AssertionError({ message: message, +// actual: actual, +// expected: expected }) + +assert.AssertionError = function AssertionError(options) { + this.name = 'AssertionError'; + this.actual = options.actual; + this.expected = options.expected; + this.operator = options.operator; + if (options.message) { + this.message = options.message; + this.generatedMessage = false; + } else { + this.message = getMessage(this); + this.generatedMessage = true; + } + var stackStartFunction = options.stackStartFunction || fail; + + if (Error.captureStackTrace) { + Error.captureStackTrace(this, stackStartFunction); + } + else { + // non v8 browsers so we can have a stacktrace + var err = new Error(); + if (err.stack) { + var out = err.stack; + + // try to strip useless frames + var fn_name = stackStartFunction.name; + var idx = out.indexOf('\n' + fn_name); + if (idx >= 0) { + // once we have located the function frame + // we need to strip out everything before it (and its line) + var next_line = out.indexOf('\n', idx + 1); + out = out.substring(next_line + 1); + } + + this.stack = out; + } + } +}; + +// assert.AssertionError instanceof Error +util.inherits(assert.AssertionError, Error); + +function replacer(key, value) { + if (util.isUndefined(value)) { + return '' + value; + } + if (util.isNumber(value) && !isFinite(value)) { + return value.toString(); + } + if (util.isFunction(value) || util.isRegExp(value)) { + return value.toString(); + } + return value; +} + +function truncate(s, n) { + if (util.isString(s)) { + return s.length < n ? s : s.slice(0, n); + } else { + return s; + } +} + +function getMessage(self) { + return truncate(JSON.stringify(self.actual, replacer), 128) + ' ' + + self.operator + ' ' + + truncate(JSON.stringify(self.expected, replacer), 128); +} + +// At present only the three keys mentioned above are used and +// understood by the spec. Implementations or sub modules can pass +// other keys to the AssertionError's constructor - they will be +// ignored. + +// 3. All of the following functions must throw an AssertionError +// when a corresponding condition is not met, with a message that +// may be undefined if not provided. All assertion methods provide +// both the actual and expected values to the assertion error for +// display purposes. + +function fail(actual, expected, message, operator, stackStartFunction) { + throw new assert.AssertionError({ + message: message, + actual: actual, + expected: expected, + operator: operator, + stackStartFunction: stackStartFunction + }); +} + +// EXTENSION! allows for well behaved errors defined elsewhere. +assert.fail = fail; + +// 4. Pure assertion tests whether a value is truthy, as determined +// by !!guard. +// assert.ok(guard, message_opt); +// This statement is equivalent to assert.equal(true, !!guard, +// message_opt);. To test strictly for the value true, use +// assert.strictEqual(true, guard, message_opt);. + +function ok(value, message) { + if (!value) fail(value, true, message, '==', assert.ok); +} +assert.ok = ok; + +// 5. The equality assertion tests shallow, coercive equality with +// ==. +// assert.equal(actual, expected, message_opt); + +assert.equal = function equal(actual, expected, message) { + if (actual != expected) fail(actual, expected, message, '==', assert.equal); +}; + +// 6. The non-equality assertion tests for whether two objects are not equal +// with != assert.notEqual(actual, expected, message_opt); + +assert.notEqual = function notEqual(actual, expected, message) { + if (actual == expected) { + fail(actual, expected, message, '!=', assert.notEqual); + } +}; + +// 7. The equivalence assertion tests a deep equality relation. +// assert.deepEqual(actual, expected, message_opt); + +assert.deepEqual = function deepEqual(actual, expected, message) { + if (!_deepEqual(actual, expected)) { + fail(actual, expected, message, 'deepEqual', assert.deepEqual); + } +}; + +function _deepEqual(actual, expected) { + // 7.1. All identical values are equivalent, as determined by ===. + if (actual === expected) { + return true; + + } else if (util.isBuffer(actual) && util.isBuffer(expected)) { + if (actual.length != expected.length) return false; + + for (var i = 0; i < actual.length; i++) { + if (actual[i] !== expected[i]) return false; + } + + return true; + + // 7.2. If the expected value is a Date object, the actual value is + // equivalent if it is also a Date object that refers to the same time. + } else if (util.isDate(actual) && util.isDate(expected)) { + return actual.getTime() === expected.getTime(); + + // 7.3 If the expected value is a RegExp object, the actual value is + // equivalent if it is also a RegExp object with the same source and + // properties (`global`, `multiline`, `lastIndex`, `ignoreCase`). + } else if (util.isRegExp(actual) && util.isRegExp(expected)) { + return actual.source === expected.source && + actual.global === expected.global && + actual.multiline === expected.multiline && + actual.lastIndex === expected.lastIndex && + actual.ignoreCase === expected.ignoreCase; + + // 7.4. Other pairs that do not both pass typeof value == 'object', + // equivalence is determined by ==. + } else if (!util.isObject(actual) && !util.isObject(expected)) { + return actual == expected; + + // 7.5 For all other Object pairs, including Array objects, equivalence is + // determined by having the same number of owned properties (as verified + // with Object.prototype.hasOwnProperty.call), the same set of keys + // (although not necessarily the same order), equivalent values for every + // corresponding key, and an identical 'prototype' property. Note: this + // accounts for both named and indexed properties on Arrays. + } else { + return objEquiv(actual, expected); + } +} + +function isArguments(object) { + return Object.prototype.toString.call(object) == '[object Arguments]'; +} + +function objEquiv(a, b) { + if (util.isNullOrUndefined(a) || util.isNullOrUndefined(b)) + return false; + // an identical 'prototype' property. + if (a.prototype !== b.prototype) return false; + // if one is a primitive, the other must be same + if (util.isPrimitive(a) || util.isPrimitive(b)) { + return a === b; + } + var aIsArgs = isArguments(a), + bIsArgs = isArguments(b); + if ((aIsArgs && !bIsArgs) || (!aIsArgs && bIsArgs)) + return false; + if (aIsArgs) { + a = pSlice.call(a); + b = pSlice.call(b); + return _deepEqual(a, b); + } + var ka = objectKeys(a), + kb = objectKeys(b), + key, i; + // having the same number of owned properties (keys incorporates + // hasOwnProperty) + if (ka.length != kb.length) + return false; + //the same set of keys (although not necessarily the same order), + ka.sort(); + kb.sort(); + //~~~cheap key test + for (i = ka.length - 1; i >= 0; i--) { + if (ka[i] != kb[i]) + return false; + } + //equivalent values for every corresponding key, and + //~~~possibly expensive deep test + for (i = ka.length - 1; i >= 0; i--) { + key = ka[i]; + if (!_deepEqual(a[key], b[key])) return false; + } + return true; +} + +// 8. The non-equivalence assertion tests for any deep inequality. +// assert.notDeepEqual(actual, expected, message_opt); + +assert.notDeepEqual = function notDeepEqual(actual, expected, message) { + if (_deepEqual(actual, expected)) { + fail(actual, expected, message, 'notDeepEqual', assert.notDeepEqual); + } +}; + +// 9. The strict equality assertion tests strict equality, as determined by ===. +// assert.strictEqual(actual, expected, message_opt); + +assert.strictEqual = function strictEqual(actual, expected, message) { + if (actual !== expected) { + fail(actual, expected, message, '===', assert.strictEqual); + } +}; + +// 10. The strict non-equality assertion tests for strict inequality, as +// determined by !==. assert.notStrictEqual(actual, expected, message_opt); + +assert.notStrictEqual = function notStrictEqual(actual, expected, message) { + if (actual === expected) { + fail(actual, expected, message, '!==', assert.notStrictEqual); + } +}; + +function expectedException(actual, expected) { + if (!actual || !expected) { + return false; + } + + if (Object.prototype.toString.call(expected) == '[object RegExp]') { + return expected.test(actual); + } else if (actual instanceof expected) { + return true; + } else if (expected.call({}, actual) === true) { + return true; + } + + return false; +} + +function _throws(shouldThrow, block, expected, message) { + var actual; + + if (util.isString(expected)) { + message = expected; + expected = null; + } + + try { + block(); + } catch (e) { + actual = e; + } + + message = (expected && expected.name ? ' (' + expected.name + ').' : '.') + + (message ? ' ' + message : '.'); + + if (shouldThrow && !actual) { + fail(actual, expected, 'Missing expected exception' + message); + } + + if (!shouldThrow && expectedException(actual, expected)) { + fail(actual, expected, 'Got unwanted exception' + message); + } + + if ((shouldThrow && actual && expected && + !expectedException(actual, expected)) || (!shouldThrow && actual)) { + throw actual; + } +} + +// 11. Expected to throw an error: +// assert.throws(block, Error_opt, message_opt); + +assert.throws = function(block, /*optional*/error, /*optional*/message) { + _throws.apply(this, [true].concat(pSlice.call(arguments))); +}; + +// EXTENSION! This is annoying to write outside this module. +assert.doesNotThrow = function(block, /*optional*/message) { + _throws.apply(this, [false].concat(pSlice.call(arguments))); +}; + +assert.ifError = function(err) { if (err) {throw err;}}; + +var objectKeys = Object.keys || function (obj) { + var keys = []; + for (var key in obj) { + if (hasOwn.call(obj, key)) keys.push(key); + } + return keys; +}; + +},{"util/":28}],10:[function(require,module,exports){ +'use strict'; +module.exports = balanced; +function balanced(a, b, str) { + if (a instanceof RegExp) a = maybeMatch(a, str); + if (b instanceof RegExp) b = maybeMatch(b, str); + + var r = range(a, b, str); + + return r && { + start: r[0], + end: r[1], + pre: str.slice(0, r[0]), + body: str.slice(r[0] + a.length, r[1]), + post: str.slice(r[1] + b.length) + }; +} + +function maybeMatch(reg, str) { + var m = str.match(reg); + return m ? m[0] : null; +} + +balanced.range = range; +function range(a, b, str) { + var begs, beg, left, right, result; + var ai = str.indexOf(a); + var bi = str.indexOf(b, ai + 1); + var i = ai; + + if (ai >= 0 && bi > 0) { + begs = []; + left = str.length; + + while (i >= 0 && !result) { + if (i == ai) { + begs.push(i); + ai = str.indexOf(a, i + 1); + } else if (begs.length == 1) { + result = [ begs.pop(), bi ]; + } else { + beg = begs.pop(); + if (beg < left) { + left = beg; + right = bi; + } + + bi = str.indexOf(b, i + 1); + } + + i = ai < bi && ai >= 0 ? ai : bi; + } + + if (begs.length) { + result = [ left, right ]; + } + } + + return result; +} + +},{}],11:[function(require,module,exports){ +var concatMap = require('concat-map'); +var balanced = require('balanced-match'); + +module.exports = expandTop; + +var escSlash = '\0SLASH'+Math.random()+'\0'; +var escOpen = '\0OPEN'+Math.random()+'\0'; +var escClose = '\0CLOSE'+Math.random()+'\0'; +var escComma = '\0COMMA'+Math.random()+'\0'; +var escPeriod = '\0PERIOD'+Math.random()+'\0'; + +function numeric(str) { + return parseInt(str, 10) == str + ? parseInt(str, 10) + : str.charCodeAt(0); +} + +function escapeBraces(str) { + return str.split('\\\\').join(escSlash) + .split('\\{').join(escOpen) + .split('\\}').join(escClose) + .split('\\,').join(escComma) + .split('\\.').join(escPeriod); +} + +function unescapeBraces(str) { + return str.split(escSlash).join('\\') + .split(escOpen).join('{') + .split(escClose).join('}') + .split(escComma).join(',') + .split(escPeriod).join('.'); +} + + +// Basically just str.split(","), but handling cases +// where we have nested braced sections, which should be +// treated as individual members, like {a,{b,c},d} +function parseCommaParts(str) { + if (!str) + return ['']; + + var parts = []; + var m = balanced('{', '}', str); + + if (!m) + return str.split(','); + + var pre = m.pre; + var body = m.body; + var post = m.post; + var p = pre.split(','); + + p[p.length-1] += '{' + body + '}'; + var postParts = parseCommaParts(post); + if (post.length) { + p[p.length-1] += postParts.shift(); + p.push.apply(p, postParts); + } + + parts.push.apply(parts, p); + + return parts; +} + +function expandTop(str) { + if (!str) + return []; + + // I don't know why Bash 4.3 does this, but it does. + // Anything starting with {} will have the first two bytes preserved + // but *only* at the top level, so {},a}b will not expand to anything, + // but a{},b}c will be expanded to [a}c,abc]. + // One could argue that this is a bug in Bash, but since the goal of + // this module is to match Bash's rules, we escape a leading {} + if (str.substr(0, 2) === '{}') { + str = '\\{\\}' + str.substr(2); + } + + return expand(escapeBraces(str), true).map(unescapeBraces); +} + +function identity(e) { + return e; +} + +function embrace(str) { + return '{' + str + '}'; +} +function isPadded(el) { + return /^-?0\d/.test(el); +} + +function lte(i, y) { + return i <= y; +} +function gte(i, y) { + return i >= y; +} + +function expand(str, isTop) { + var expansions = []; + + var m = balanced('{', '}', str); + if (!m || /\$$/.test(m.pre)) return [str]; + + var isNumericSequence = /^-?\d+\.\.-?\d+(?:\.\.-?\d+)?$/.test(m.body); + var isAlphaSequence = /^[a-zA-Z]\.\.[a-zA-Z](?:\.\.-?\d+)?$/.test(m.body); + var isSequence = isNumericSequence || isAlphaSequence; + var isOptions = m.body.indexOf(',') >= 0; + if (!isSequence && !isOptions) { + // {a},b} + if (m.post.match(/,.*\}/)) { + str = m.pre + '{' + m.body + escClose + m.post; + return expand(str); + } + return [str]; + } + + var n; + if (isSequence) { + n = m.body.split(/\.\./); + } else { + n = parseCommaParts(m.body); + if (n.length === 1) { + // x{{a,b}}y ==> x{a}y x{b}y + n = expand(n[0], false).map(embrace); + if (n.length === 1) { + var post = m.post.length + ? expand(m.post, false) + : ['']; + return post.map(function(p) { + return m.pre + n[0] + p; + }); + } + } + } + + // at this point, n is the parts, and we know it's not a comma set + // with a single entry. + + // no need to expand pre, since it is guaranteed to be free of brace-sets + var pre = m.pre; + var post = m.post.length + ? expand(m.post, false) + : ['']; + + var N; + + if (isSequence) { + var x = numeric(n[0]); + var y = numeric(n[1]); + var width = Math.max(n[0].length, n[1].length) + var incr = n.length == 3 + ? Math.abs(numeric(n[2])) + : 1; + var test = lte; + var reverse = y < x; + if (reverse) { + incr *= -1; + test = gte; + } + var pad = n.some(isPadded); + + N = []; + + for (var i = x; test(i, y); i += incr) { + var c; + if (isAlphaSequence) { + c = String.fromCharCode(i); + if (c === '\\') + c = ''; + } else { + c = String(i); + if (pad) { + var need = width - c.length; + if (need > 0) { + var z = new Array(need + 1).join('0'); + if (i < 0) + c = '-' + z + c.slice(1); + else + c = z + c; + } + } + } + N.push(c); + } + } else { + N = concatMap(n, function(el) { return expand(el, false) }); + } + + for (var j = 0; j < N.length; j++) { + for (var k = 0; k < post.length; k++) { + var expansion = pre + N[j] + post[k]; + if (!isTop || isSequence || expansion) + expansions.push(expansion); + } + } + + return expansions; +} + + +},{"balanced-match":10,"concat-map":13}],12:[function(require,module,exports){ + +},{}],13:[function(require,module,exports){ +module.exports = function (xs, fn) { + var res = []; + for (var i = 0; i < xs.length; i++) { + var x = fn(xs[i], i); + if (isArray(x)) res.push.apply(res, x); + else res.push(x); + } + return res; +}; + +var isArray = Array.isArray || function (xs) { + return Object.prototype.toString.call(xs) === '[object Array]'; +}; + +},{}],14:[function(require,module,exports){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +function EventEmitter() { + this._events = this._events || {}; + this._maxListeners = this._maxListeners || undefined; +} +module.exports = EventEmitter; + +// Backwards-compat with node 0.10.x +EventEmitter.EventEmitter = EventEmitter; + +EventEmitter.prototype._events = undefined; +EventEmitter.prototype._maxListeners = undefined; + +// By default EventEmitters will print a warning if more than 10 listeners are +// added to it. This is a useful default which helps finding memory leaks. +EventEmitter.defaultMaxListeners = 10; + +// Obviously not all Emitters should be limited to 10. This function allows +// that to be increased. Set to zero for unlimited. +EventEmitter.prototype.setMaxListeners = function(n) { + if (!isNumber(n) || n < 0 || isNaN(n)) + throw TypeError('n must be a positive number'); + this._maxListeners = n; + return this; +}; + +EventEmitter.prototype.emit = function(type) { + var er, handler, len, args, i, listeners; + + if (!this._events) + this._events = {}; + + // If there is no 'error' event listener then throw. + if (type === 'error') { + if (!this._events.error || + (isObject(this._events.error) && !this._events.error.length)) { + er = arguments[1]; + if (er instanceof Error) { + throw er; // Unhandled 'error' event + } + throw TypeError('Uncaught, unspecified "error" event.'); + } + } + + handler = this._events[type]; + + if (isUndefined(handler)) + return false; + + if (isFunction(handler)) { + switch (arguments.length) { + // fast cases + case 1: + handler.call(this); + break; + case 2: + handler.call(this, arguments[1]); + break; + case 3: + handler.call(this, arguments[1], arguments[2]); + break; + // slower + default: + len = arguments.length; + args = new Array(len - 1); + for (i = 1; i < len; i++) + args[i - 1] = arguments[i]; + handler.apply(this, args); + } + } else if (isObject(handler)) { + len = arguments.length; + args = new Array(len - 1); + for (i = 1; i < len; i++) + args[i - 1] = arguments[i]; + + listeners = handler.slice(); + len = listeners.length; + for (i = 0; i < len; i++) + listeners[i].apply(this, args); + } + + return true; +}; + +EventEmitter.prototype.addListener = function(type, listener) { + var m; + + if (!isFunction(listener)) + throw TypeError('listener must be a function'); + + if (!this._events) + this._events = {}; + + // To avoid recursion in the case that type === "newListener"! Before + // adding it to the listeners, first emit "newListener". + if (this._events.newListener) + this.emit('newListener', type, + isFunction(listener.listener) ? + listener.listener : listener); + + if (!this._events[type]) + // Optimize the case of one listener. Don't need the extra array object. + this._events[type] = listener; + else if (isObject(this._events[type])) + // If we've already got an array, just append. + this._events[type].push(listener); + else + // Adding the second element, need to change to array. + this._events[type] = [this._events[type], listener]; + + // Check for listener leak + if (isObject(this._events[type]) && !this._events[type].warned) { + var m; + if (!isUndefined(this._maxListeners)) { + m = this._maxListeners; + } else { + m = EventEmitter.defaultMaxListeners; + } + + if (m && m > 0 && this._events[type].length > m) { + this._events[type].warned = true; + console.error('(node) warning: possible EventEmitter memory ' + + 'leak detected. %d listeners added. ' + + 'Use emitter.setMaxListeners() to increase limit.', + this._events[type].length); + if (typeof console.trace === 'function') { + // not supported in IE 10 + console.trace(); + } + } + } + + return this; +}; + +EventEmitter.prototype.on = EventEmitter.prototype.addListener; + +EventEmitter.prototype.once = function(type, listener) { + if (!isFunction(listener)) + throw TypeError('listener must be a function'); + + var fired = false; + + function g() { + this.removeListener(type, g); + + if (!fired) { + fired = true; + listener.apply(this, arguments); + } + } + + g.listener = listener; + this.on(type, g); + + return this; +}; + +// emits a 'removeListener' event iff the listener was removed +EventEmitter.prototype.removeListener = function(type, listener) { + var list, position, length, i; + + if (!isFunction(listener)) + throw TypeError('listener must be a function'); + + if (!this._events || !this._events[type]) + return this; + + list = this._events[type]; + length = list.length; + position = -1; + + if (list === listener || + (isFunction(list.listener) && list.listener === listener)) { + delete this._events[type]; + if (this._events.removeListener) + this.emit('removeListener', type, listener); + + } else if (isObject(list)) { + for (i = length; i-- > 0;) { + if (list[i] === listener || + (list[i].listener && list[i].listener === listener)) { + position = i; + break; + } + } + + if (position < 0) + return this; + + if (list.length === 1) { + list.length = 0; + delete this._events[type]; + } else { + list.splice(position, 1); + } + + if (this._events.removeListener) + this.emit('removeListener', type, listener); + } + + return this; +}; + +EventEmitter.prototype.removeAllListeners = function(type) { + var key, listeners; + + if (!this._events) + return this; + + // not listening for removeListener, no need to emit + if (!this._events.removeListener) { + if (arguments.length === 0) + this._events = {}; + else if (this._events[type]) + delete this._events[type]; + return this; + } + + // emit removeListener for all listeners on all events + if (arguments.length === 0) { + for (key in this._events) { + if (key === 'removeListener') continue; + this.removeAllListeners(key); + } + this.removeAllListeners('removeListener'); + this._events = {}; + return this; + } + + listeners = this._events[type]; + + if (isFunction(listeners)) { + this.removeListener(type, listeners); + } else { + // LIFO order + while (listeners.length) + this.removeListener(type, listeners[listeners.length - 1]); + } + delete this._events[type]; + + return this; +}; + +EventEmitter.prototype.listeners = function(type) { + var ret; + if (!this._events || !this._events[type]) + ret = []; + else if (isFunction(this._events[type])) + ret = [this._events[type]]; + else + ret = this._events[type].slice(); + return ret; +}; + +EventEmitter.listenerCount = function(emitter, type) { + var ret; + if (!emitter._events || !emitter._events[type]) + ret = 0; + else if (isFunction(emitter._events[type])) + ret = 1; + else + ret = emitter._events[type].length; + return ret; +}; + +function isFunction(arg) { + return typeof arg === 'function'; +} + +function isNumber(arg) { + return typeof arg === 'number'; +} + +function isObject(arg) { + return typeof arg === 'object' && arg !== null; +} + +function isUndefined(arg) { + return arg === void 0; +} + +},{}],15:[function(require,module,exports){ +(function (process){ +exports.alphasort = alphasort +exports.alphasorti = alphasorti +exports.setopts = setopts +exports.ownProp = ownProp +exports.makeAbs = makeAbs +exports.finish = finish +exports.mark = mark +exports.isIgnored = isIgnored +exports.childrenIgnored = childrenIgnored + +function ownProp (obj, field) { + return Object.prototype.hasOwnProperty.call(obj, field) +} + +var path = require("path") +var minimatch = require("minimatch") +var isAbsolute = require("path-is-absolute") +var Minimatch = minimatch.Minimatch + +function alphasorti (a, b) { + return a.toLowerCase().localeCompare(b.toLowerCase()) +} + +function alphasort (a, b) { + return a.localeCompare(b) +} + +function setupIgnores (self, options) { + self.ignore = options.ignore || [] + + if (!Array.isArray(self.ignore)) + self.ignore = [self.ignore] + + if (self.ignore.length) { + self.ignore = self.ignore.map(ignoreMap) + } +} + +function ignoreMap (pattern) { + var gmatcher = null + if (pattern.slice(-3) === '/**') { + var gpattern = pattern.replace(/(\/\*\*)+$/, '') + gmatcher = new Minimatch(gpattern) + } + + return { + matcher: new Minimatch(pattern), + gmatcher: gmatcher + } +} + +function setopts (self, pattern, options) { + if (!options) + options = {} + + // base-matching: just use globstar for that. + if (options.matchBase && -1 === pattern.indexOf("/")) { + if (options.noglobstar) { + throw new Error("base matching requires globstar") + } + pattern = "**/" + pattern + } + + self.silent = !!options.silent + self.pattern = pattern + self.strict = options.strict !== false + self.realpath = !!options.realpath + self.realpathCache = options.realpathCache || Object.create(null) + self.follow = !!options.follow + self.dot = !!options.dot + self.mark = !!options.mark + self.nodir = !!options.nodir + if (self.nodir) + self.mark = true + self.sync = !!options.sync + self.nounique = !!options.nounique + self.nonull = !!options.nonull + self.nosort = !!options.nosort + self.nocase = !!options.nocase + self.stat = !!options.stat + self.noprocess = !!options.noprocess + + self.maxLength = options.maxLength || Infinity + self.cache = options.cache || Object.create(null) + self.statCache = options.statCache || Object.create(null) + self.symlinks = options.symlinks || Object.create(null) + + setupIgnores(self, options) + + self.changedCwd = false + var cwd = process.cwd() + if (!ownProp(options, "cwd")) + self.cwd = cwd + else { + self.cwd = options.cwd + self.changedCwd = path.resolve(options.cwd) !== cwd + } + + self.root = options.root || path.resolve(self.cwd, "/") + self.root = path.resolve(self.root) + if (process.platform === "win32") + self.root = self.root.replace(/\\/g, "/") + + self.nomount = !!options.nomount + + // disable comments and negation unless the user explicitly + // passes in false as the option. + options.nonegate = options.nonegate === false ? false : true + options.nocomment = options.nocomment === false ? false : true + deprecationWarning(options) + + self.minimatch = new Minimatch(pattern, options) + self.options = self.minimatch.options +} + +// TODO(isaacs): remove entirely in v6 +// exported to reset in tests +exports.deprecationWarned +function deprecationWarning(options) { + if (!options.nonegate || !options.nocomment) { + if (process.noDeprecation !== true && !exports.deprecationWarned) { + var msg = 'glob WARNING: comments and negation will be disabled in v6' + if (process.throwDeprecation) + throw new Error(msg) + else if (process.traceDeprecation) + console.trace(msg) + else + console.error(msg) + + exports.deprecationWarned = true + } + } +} + +function finish (self) { + var nou = self.nounique + var all = nou ? [] : Object.create(null) + + for (var i = 0, l = self.matches.length; i < l; i ++) { + var matches = self.matches[i] + if (!matches || Object.keys(matches).length === 0) { + if (self.nonull) { + // do like the shell, and spit out the literal glob + var literal = self.minimatch.globSet[i] + if (nou) + all.push(literal) + else + all[literal] = true + } + } else { + // had matches + var m = Object.keys(matches) + if (nou) + all.push.apply(all, m) + else + m.forEach(function (m) { + all[m] = true + }) + } + } + + if (!nou) + all = Object.keys(all) + + if (!self.nosort) + all = all.sort(self.nocase ? alphasorti : alphasort) + + // at *some* point we statted all of these + if (self.mark) { + for (var i = 0; i < all.length; i++) { + all[i] = self._mark(all[i]) + } + if (self.nodir) { + all = all.filter(function (e) { + return !(/\/$/.test(e)) + }) + } + } + + if (self.ignore.length) + all = all.filter(function(m) { + return !isIgnored(self, m) + }) + + self.found = all +} + +function mark (self, p) { + var abs = makeAbs(self, p) + var c = self.cache[abs] + var m = p + if (c) { + var isDir = c === 'DIR' || Array.isArray(c) + var slash = p.slice(-1) === '/' + + if (isDir && !slash) + m += '/' + else if (!isDir && slash) + m = m.slice(0, -1) + + if (m !== p) { + var mabs = makeAbs(self, m) + self.statCache[mabs] = self.statCache[abs] + self.cache[mabs] = self.cache[abs] + } + } + + return m +} + +// lotta situps... +function makeAbs (self, f) { + var abs = f + if (f.charAt(0) === '/') { + abs = path.join(self.root, f) + } else if (isAbsolute(f) || f === '') { + abs = f + } else if (self.changedCwd) { + abs = path.resolve(self.cwd, f) + } else { + abs = path.resolve(f) + } + return abs +} + + +// Return true, if pattern ends with globstar '**', for the accompanying parent directory. +// Ex:- If node_modules/** is the pattern, add 'node_modules' to ignore list along with it's contents +function isIgnored (self, path) { + if (!self.ignore.length) + return false + + return self.ignore.some(function(item) { + return item.matcher.match(path) || !!(item.gmatcher && item.gmatcher.match(path)) + }) +} + +function childrenIgnored (self, path) { + if (!self.ignore.length) + return false + + return self.ignore.some(function(item) { + return !!(item.gmatcher && item.gmatcher.match(path)) + }) +} + +}).call(this,require('_process')) +},{"_process":24,"minimatch":20,"path":22,"path-is-absolute":23}],16:[function(require,module,exports){ +(function (process){ +// Approach: +// +// 1. Get the minimatch set +// 2. For each pattern in the set, PROCESS(pattern, false) +// 3. Store matches per-set, then uniq them +// +// PROCESS(pattern, inGlobStar) +// Get the first [n] items from pattern that are all strings +// Join these together. This is PREFIX. +// If there is no more remaining, then stat(PREFIX) and +// add to matches if it succeeds. END. +// +// If inGlobStar and PREFIX is symlink and points to dir +// set ENTRIES = [] +// else readdir(PREFIX) as ENTRIES +// If fail, END +// +// with ENTRIES +// If pattern[n] is GLOBSTAR +// // handle the case where the globstar match is empty +// // by pruning it out, and testing the resulting pattern +// PROCESS(pattern[0..n] + pattern[n+1 .. $], false) +// // handle other cases. +// for ENTRY in ENTRIES (not dotfiles) +// // attach globstar + tail onto the entry +// // Mark that this entry is a globstar match +// PROCESS(pattern[0..n] + ENTRY + pattern[n .. $], true) +// +// else // not globstar +// for ENTRY in ENTRIES (not dotfiles, unless pattern[n] is dot) +// Test ENTRY against pattern[n] +// If fails, continue +// If passes, PROCESS(pattern[0..n] + item + pattern[n+1 .. $]) +// +// Caveat: +// Cache all stats and readdirs results to minimize syscall. Since all +// we ever care about is existence and directory-ness, we can just keep +// `true` for files, and [children,...] for directories, or `false` for +// things that don't exist. + +module.exports = glob + +var fs = require('fs') +var minimatch = require('minimatch') +var Minimatch = minimatch.Minimatch +var inherits = require('inherits') +var EE = require('events').EventEmitter +var path = require('path') +var assert = require('assert') +var isAbsolute = require('path-is-absolute') +var globSync = require('./sync.js') +var common = require('./common.js') +var alphasort = common.alphasort +var alphasorti = common.alphasorti +var setopts = common.setopts +var ownProp = common.ownProp +var inflight = require('inflight') +var util = require('util') +var childrenIgnored = common.childrenIgnored +var isIgnored = common.isIgnored + +var once = require('once') + +function glob (pattern, options, cb) { + if (typeof options === 'function') cb = options, options = {} + if (!options) options = {} + + if (options.sync) { + if (cb) + throw new TypeError('callback provided to sync glob') + return globSync(pattern, options) + } + + return new Glob(pattern, options, cb) +} + +glob.sync = globSync +var GlobSync = glob.GlobSync = globSync.GlobSync + +// old api surface +glob.glob = glob + +glob.hasMagic = function (pattern, options_) { + var options = util._extend({}, options_) + options.noprocess = true + + var g = new Glob(pattern, options) + var set = g.minimatch.set + if (set.length > 1) + return true + + for (var j = 0; j < set[0].length; j++) { + if (typeof set[0][j] !== 'string') + return true + } + + return false +} + +glob.Glob = Glob +inherits(Glob, EE) +function Glob (pattern, options, cb) { + if (typeof options === 'function') { + cb = options + options = null + } + + if (options && options.sync) { + if (cb) + throw new TypeError('callback provided to sync glob') + return new GlobSync(pattern, options) + } + + if (!(this instanceof Glob)) + return new Glob(pattern, options, cb) + + setopts(this, pattern, options) + this._didRealPath = false + + // process each pattern in the minimatch set + var n = this.minimatch.set.length + + // The matches are stored as {: true,...} so that + // duplicates are automagically pruned. + // Later, we do an Object.keys() on these. + // Keep them as a list so we can fill in when nonull is set. + this.matches = new Array(n) + + if (typeof cb === 'function') { + cb = once(cb) + this.on('error', cb) + this.on('end', function (matches) { + cb(null, matches) + }) + } + + var self = this + var n = this.minimatch.set.length + this._processing = 0 + this.matches = new Array(n) + + this._emitQueue = [] + this._processQueue = [] + this.paused = false + + if (this.noprocess) + return this + + if (n === 0) + return done() + + for (var i = 0; i < n; i ++) { + this._process(this.minimatch.set[i], i, false, done) + } + + function done () { + --self._processing + if (self._processing <= 0) + self._finish() + } +} + +Glob.prototype._finish = function () { + assert(this instanceof Glob) + if (this.aborted) + return + + if (this.realpath && !this._didRealpath) + return this._realpath() + + common.finish(this) + this.emit('end', this.found) +} + +Glob.prototype._realpath = function () { + if (this._didRealpath) + return + + this._didRealpath = true + + var n = this.matches.length + if (n === 0) + return this._finish() + + var self = this + for (var i = 0; i < this.matches.length; i++) + this._realpathSet(i, next) + + function next () { + if (--n === 0) + self._finish() + } +} + +Glob.prototype._realpathSet = function (index, cb) { + var matchset = this.matches[index] + if (!matchset) + return cb() + + var found = Object.keys(matchset) + var self = this + var n = found.length + + if (n === 0) + return cb() + + var set = this.matches[index] = Object.create(null) + found.forEach(function (p, i) { + // If there's a problem with the stat, then it means that + // one or more of the links in the realpath couldn't be + // resolved. just return the abs value in that case. + p = self._makeAbs(p) + fs.realpath(p, self.realpathCache, function (er, real) { + if (!er) + set[real] = true + else if (er.syscall === 'stat') + set[p] = true + else + self.emit('error', er) // srsly wtf right here + + if (--n === 0) { + self.matches[index] = set + cb() + } + }) + }) +} + +Glob.prototype._mark = function (p) { + return common.mark(this, p) +} + +Glob.prototype._makeAbs = function (f) { + return common.makeAbs(this, f) +} + +Glob.prototype.abort = function () { + this.aborted = true + this.emit('abort') +} + +Glob.prototype.pause = function () { + if (!this.paused) { + this.paused = true + this.emit('pause') + } +} + +Glob.prototype.resume = function () { + if (this.paused) { + this.emit('resume') + this.paused = false + if (this._emitQueue.length) { + var eq = this._emitQueue.slice(0) + this._emitQueue.length = 0 + for (var i = 0; i < eq.length; i ++) { + var e = eq[i] + this._emitMatch(e[0], e[1]) + } + } + if (this._processQueue.length) { + var pq = this._processQueue.slice(0) + this._processQueue.length = 0 + for (var i = 0; i < pq.length; i ++) { + var p = pq[i] + this._processing-- + this._process(p[0], p[1], p[2], p[3]) + } + } + } +} + +Glob.prototype._process = function (pattern, index, inGlobStar, cb) { + assert(this instanceof Glob) + assert(typeof cb === 'function') + + if (this.aborted) + return + + this._processing++ + if (this.paused) { + this._processQueue.push([pattern, index, inGlobStar, cb]) + return + } + + //console.error('PROCESS %d', this._processing, pattern) + + // Get the first [n] parts of pattern that are all strings. + var n = 0 + while (typeof pattern[n] === 'string') { + n ++ + } + // now n is the index of the first one that is *not* a string. + + // see if there's anything else + var prefix + switch (n) { + // if not, then this is rather simple + case pattern.length: + this._processSimple(pattern.join('/'), index, cb) + return + + case 0: + // pattern *starts* with some non-trivial item. + // going to readdir(cwd), but not include the prefix in matches. + prefix = null + break + + default: + // pattern has some string bits in the front. + // whatever it starts with, whether that's 'absolute' like /foo/bar, + // or 'relative' like '../baz' + prefix = pattern.slice(0, n).join('/') + break + } + + var remain = pattern.slice(n) + + // get the list of entries. + var read + if (prefix === null) + read = '.' + else if (isAbsolute(prefix) || isAbsolute(pattern.join('/'))) { + if (!prefix || !isAbsolute(prefix)) + prefix = '/' + prefix + read = prefix + } else + read = prefix + + var abs = this._makeAbs(read) + + //if ignored, skip _processing + if (childrenIgnored(this, read)) + return cb() + + var isGlobStar = remain[0] === minimatch.GLOBSTAR + if (isGlobStar) + this._processGlobStar(prefix, read, abs, remain, index, inGlobStar, cb) + else + this._processReaddir(prefix, read, abs, remain, index, inGlobStar, cb) +} + +Glob.prototype._processReaddir = function (prefix, read, abs, remain, index, inGlobStar, cb) { + var self = this + this._readdir(abs, inGlobStar, function (er, entries) { + return self._processReaddir2(prefix, read, abs, remain, index, inGlobStar, entries, cb) + }) +} + +Glob.prototype._processReaddir2 = function (prefix, read, abs, remain, index, inGlobStar, entries, cb) { + + // if the abs isn't a dir, then nothing can match! + if (!entries) + return cb() + + // It will only match dot entries if it starts with a dot, or if + // dot is set. Stuff like @(.foo|.bar) isn't allowed. + var pn = remain[0] + var negate = !!this.minimatch.negate + var rawGlob = pn._glob + var dotOk = this.dot || rawGlob.charAt(0) === '.' + + var matchedEntries = [] + for (var i = 0; i < entries.length; i++) { + var e = entries[i] + if (e.charAt(0) !== '.' || dotOk) { + var m + if (negate && !prefix) { + m = !e.match(pn) + } else { + m = e.match(pn) + } + if (m) + matchedEntries.push(e) + } + } + + //console.error('prd2', prefix, entries, remain[0]._glob, matchedEntries) + + var len = matchedEntries.length + // If there are no matched entries, then nothing matches. + if (len === 0) + return cb() + + // if this is the last remaining pattern bit, then no need for + // an additional stat *unless* the user has specified mark or + // stat explicitly. We know they exist, since readdir returned + // them. + + if (remain.length === 1 && !this.mark && !this.stat) { + if (!this.matches[index]) + this.matches[index] = Object.create(null) + + for (var i = 0; i < len; i ++) { + var e = matchedEntries[i] + if (prefix) { + if (prefix !== '/') + e = prefix + '/' + e + else + e = prefix + e + } + + if (e.charAt(0) === '/' && !this.nomount) { + e = path.join(this.root, e) + } + this._emitMatch(index, e) + } + // This was the last one, and no stats were needed + return cb() + } + + // now test all matched entries as stand-ins for that part + // of the pattern. + remain.shift() + for (var i = 0; i < len; i ++) { + var e = matchedEntries[i] + var newPattern + if (prefix) { + if (prefix !== '/') + e = prefix + '/' + e + else + e = prefix + e + } + this._process([e].concat(remain), index, inGlobStar, cb) + } + cb() +} + +Glob.prototype._emitMatch = function (index, e) { + if (this.aborted) + return + + if (this.matches[index][e]) + return + + if (isIgnored(this, e)) + return + + if (this.paused) { + this._emitQueue.push([index, e]) + return + } + + var abs = this._makeAbs(e) + + if (this.nodir) { + var c = this.cache[abs] + if (c === 'DIR' || Array.isArray(c)) + return + } + + if (this.mark) + e = this._mark(e) + + this.matches[index][e] = true + + var st = this.statCache[abs] + if (st) + this.emit('stat', e, st) + + this.emit('match', e) +} + +Glob.prototype._readdirInGlobStar = function (abs, cb) { + if (this.aborted) + return + + // follow all symlinked directories forever + // just proceed as if this is a non-globstar situation + if (this.follow) + return this._readdir(abs, false, cb) + + var lstatkey = 'lstat\0' + abs + var self = this + var lstatcb = inflight(lstatkey, lstatcb_) + + if (lstatcb) + fs.lstat(abs, lstatcb) + + function lstatcb_ (er, lstat) { + if (er) + return cb() + + var isSym = lstat.isSymbolicLink() + self.symlinks[abs] = isSym + + // If it's not a symlink or a dir, then it's definitely a regular file. + // don't bother doing a readdir in that case. + if (!isSym && !lstat.isDirectory()) { + self.cache[abs] = 'FILE' + cb() + } else + self._readdir(abs, false, cb) + } +} + +Glob.prototype._readdir = function (abs, inGlobStar, cb) { + if (this.aborted) + return + + cb = inflight('readdir\0'+abs+'\0'+inGlobStar, cb) + if (!cb) + return + + //console.error('RD %j %j', +inGlobStar, abs) + if (inGlobStar && !ownProp(this.symlinks, abs)) + return this._readdirInGlobStar(abs, cb) + + if (ownProp(this.cache, abs)) { + var c = this.cache[abs] + if (!c || c === 'FILE') + return cb() + + if (Array.isArray(c)) + return cb(null, c) + } + + var self = this + fs.readdir(abs, readdirCb(this, abs, cb)) +} + +function readdirCb (self, abs, cb) { + return function (er, entries) { + if (er) + self._readdirError(abs, er, cb) + else + self._readdirEntries(abs, entries, cb) + } +} + +Glob.prototype._readdirEntries = function (abs, entries, cb) { + if (this.aborted) + return + + // if we haven't asked to stat everything, then just + // assume that everything in there exists, so we can avoid + // having to stat it a second time. + if (!this.mark && !this.stat) { + for (var i = 0; i < entries.length; i ++) { + var e = entries[i] + if (abs === '/') + e = abs + e + else + e = abs + '/' + e + this.cache[e] = true + } + } + + this.cache[abs] = entries + return cb(null, entries) +} + +Glob.prototype._readdirError = function (f, er, cb) { + if (this.aborted) + return + + // handle errors, and cache the information + switch (er.code) { + case 'ENOTSUP': // https://github.com/isaacs/node-glob/issues/205 + case 'ENOTDIR': // totally normal. means it *does* exist. + this.cache[this._makeAbs(f)] = 'FILE' + break + + case 'ENOENT': // not terribly unusual + case 'ELOOP': + case 'ENAMETOOLONG': + case 'UNKNOWN': + this.cache[this._makeAbs(f)] = false + break + + default: // some unusual error. Treat as failure. + this.cache[this._makeAbs(f)] = false + if (this.strict) { + this.emit('error', er) + // If the error is handled, then we abort + // if not, we threw out of here + this.abort() + } + if (!this.silent) + console.error('glob error', er) + break + } + + return cb() +} + +Glob.prototype._processGlobStar = function (prefix, read, abs, remain, index, inGlobStar, cb) { + var self = this + this._readdir(abs, inGlobStar, function (er, entries) { + self._processGlobStar2(prefix, read, abs, remain, index, inGlobStar, entries, cb) + }) +} + + +Glob.prototype._processGlobStar2 = function (prefix, read, abs, remain, index, inGlobStar, entries, cb) { + //console.error('pgs2', prefix, remain[0], entries) + + // no entries means not a dir, so it can never have matches + // foo.txt/** doesn't match foo.txt + if (!entries) + return cb() + + // test without the globstar, and with every child both below + // and replacing the globstar. + var remainWithoutGlobStar = remain.slice(1) + var gspref = prefix ? [ prefix ] : [] + var noGlobStar = gspref.concat(remainWithoutGlobStar) + + // the noGlobStar pattern exits the inGlobStar state + this._process(noGlobStar, index, false, cb) + + var isSym = this.symlinks[abs] + var len = entries.length + + // If it's a symlink, and we're in a globstar, then stop + if (isSym && inGlobStar) + return cb() + + for (var i = 0; i < len; i++) { + var e = entries[i] + if (e.charAt(0) === '.' && !this.dot) + continue + + // these two cases enter the inGlobStar state + var instead = gspref.concat(entries[i], remainWithoutGlobStar) + this._process(instead, index, true, cb) + + var below = gspref.concat(entries[i], remain) + this._process(below, index, true, cb) + } + + cb() +} + +Glob.prototype._processSimple = function (prefix, index, cb) { + // XXX review this. Shouldn't it be doing the mounting etc + // before doing stat? kinda weird? + var self = this + this._stat(prefix, function (er, exists) { + self._processSimple2(prefix, index, er, exists, cb) + }) +} +Glob.prototype._processSimple2 = function (prefix, index, er, exists, cb) { + + //console.error('ps2', prefix, exists) + + if (!this.matches[index]) + this.matches[index] = Object.create(null) + + // If it doesn't exist, then just mark the lack of results + if (!exists) + return cb() + + if (prefix && isAbsolute(prefix) && !this.nomount) { + var trail = /[\/\\]$/.test(prefix) + if (prefix.charAt(0) === '/') { + prefix = path.join(this.root, prefix) + } else { + prefix = path.resolve(this.root, prefix) + if (trail) + prefix += '/' + } + } + + if (process.platform === 'win32') + prefix = prefix.replace(/\\/g, '/') + + // Mark this as a match + this._emitMatch(index, prefix) + cb() +} + +// Returns either 'DIR', 'FILE', or false +Glob.prototype._stat = function (f, cb) { + var abs = this._makeAbs(f) + var needDir = f.slice(-1) === '/' + + if (f.length > this.maxLength) + return cb() + + if (!this.stat && ownProp(this.cache, abs)) { + var c = this.cache[abs] + + if (Array.isArray(c)) + c = 'DIR' + + // It exists, but maybe not how we need it + if (!needDir || c === 'DIR') + return cb(null, c) + + if (needDir && c === 'FILE') + return cb() + + // otherwise we have to stat, because maybe c=true + // if we know it exists, but not what it is. + } + + var exists + var stat = this.statCache[abs] + if (stat !== undefined) { + if (stat === false) + return cb(null, stat) + else { + var type = stat.isDirectory() ? 'DIR' : 'FILE' + if (needDir && type === 'FILE') + return cb() + else + return cb(null, type, stat) + } + } + + var self = this + var statcb = inflight('stat\0' + abs, lstatcb_) + if (statcb) + fs.lstat(abs, statcb) + + function lstatcb_ (er, lstat) { + if (lstat && lstat.isSymbolicLink()) { + // If it's a symlink, then treat it as the target, unless + // the target does not exist, then treat it as a file. + return fs.stat(abs, function (er, stat) { + if (er) + self._stat2(f, abs, null, lstat, cb) + else + self._stat2(f, abs, er, stat, cb) + }) + } else { + self._stat2(f, abs, er, lstat, cb) + } + } +} + +Glob.prototype._stat2 = function (f, abs, er, stat, cb) { + if (er) { + this.statCache[abs] = false + return cb() + } + + var needDir = f.slice(-1) === '/' + this.statCache[abs] = stat + + if (abs.slice(-1) === '/' && !stat.isDirectory()) + return cb(null, false, stat) + + var c = stat.isDirectory() ? 'DIR' : 'FILE' + this.cache[abs] = this.cache[abs] || c + + if (needDir && c !== 'DIR') + return cb() + + return cb(null, c, stat) +} + +}).call(this,require('_process')) +},{"./common.js":15,"./sync.js":17,"_process":24,"assert":9,"events":14,"fs":12,"inflight":18,"inherits":19,"minimatch":20,"once":21,"path":22,"path-is-absolute":23,"util":28}],17:[function(require,module,exports){ +(function (process){ +module.exports = globSync +globSync.GlobSync = GlobSync + +var fs = require('fs') +var minimatch = require('minimatch') +var Minimatch = minimatch.Minimatch +var Glob = require('./glob.js').Glob +var util = require('util') +var path = require('path') +var assert = require('assert') +var isAbsolute = require('path-is-absolute') +var common = require('./common.js') +var alphasort = common.alphasort +var alphasorti = common.alphasorti +var setopts = common.setopts +var ownProp = common.ownProp +var childrenIgnored = common.childrenIgnored + +function globSync (pattern, options) { + if (typeof options === 'function' || arguments.length === 3) + throw new TypeError('callback provided to sync glob\n'+ + 'See: https://github.com/isaacs/node-glob/issues/167') + + return new GlobSync(pattern, options).found +} + +function GlobSync (pattern, options) { + if (!pattern) + throw new Error('must provide pattern') + + if (typeof options === 'function' || arguments.length === 3) + throw new TypeError('callback provided to sync glob\n'+ + 'See: https://github.com/isaacs/node-glob/issues/167') + + if (!(this instanceof GlobSync)) + return new GlobSync(pattern, options) + + setopts(this, pattern, options) + + if (this.noprocess) + return this + + var n = this.minimatch.set.length + this.matches = new Array(n) + for (var i = 0; i < n; i ++) { + this._process(this.minimatch.set[i], i, false) + } + this._finish() +} + +GlobSync.prototype._finish = function () { + assert(this instanceof GlobSync) + if (this.realpath) { + var self = this + this.matches.forEach(function (matchset, index) { + var set = self.matches[index] = Object.create(null) + for (var p in matchset) { + try { + p = self._makeAbs(p) + var real = fs.realpathSync(p, self.realpathCache) + set[real] = true + } catch (er) { + if (er.syscall === 'stat') + set[self._makeAbs(p)] = true + else + throw er + } + } + }) + } + common.finish(this) +} + + +GlobSync.prototype._process = function (pattern, index, inGlobStar) { + assert(this instanceof GlobSync) + + // Get the first [n] parts of pattern that are all strings. + var n = 0 + while (typeof pattern[n] === 'string') { + n ++ + } + // now n is the index of the first one that is *not* a string. + + // See if there's anything else + var prefix + switch (n) { + // if not, then this is rather simple + case pattern.length: + this._processSimple(pattern.join('/'), index) + return + + case 0: + // pattern *starts* with some non-trivial item. + // going to readdir(cwd), but not include the prefix in matches. + prefix = null + break + + default: + // pattern has some string bits in the front. + // whatever it starts with, whether that's 'absolute' like /foo/bar, + // or 'relative' like '../baz' + prefix = pattern.slice(0, n).join('/') + break + } + + var remain = pattern.slice(n) + + // get the list of entries. + var read + if (prefix === null) + read = '.' + else if (isAbsolute(prefix) || isAbsolute(pattern.join('/'))) { + if (!prefix || !isAbsolute(prefix)) + prefix = '/' + prefix + read = prefix + } else + read = prefix + + var abs = this._makeAbs(read) + + //if ignored, skip processing + if (childrenIgnored(this, read)) + return + + var isGlobStar = remain[0] === minimatch.GLOBSTAR + if (isGlobStar) + this._processGlobStar(prefix, read, abs, remain, index, inGlobStar) + else + this._processReaddir(prefix, read, abs, remain, index, inGlobStar) +} + + +GlobSync.prototype._processReaddir = function (prefix, read, abs, remain, index, inGlobStar) { + var entries = this._readdir(abs, inGlobStar) + + // if the abs isn't a dir, then nothing can match! + if (!entries) + return + + // It will only match dot entries if it starts with a dot, or if + // dot is set. Stuff like @(.foo|.bar) isn't allowed. + var pn = remain[0] + var negate = !!this.minimatch.negate + var rawGlob = pn._glob + var dotOk = this.dot || rawGlob.charAt(0) === '.' + + var matchedEntries = [] + for (var i = 0; i < entries.length; i++) { + var e = entries[i] + if (e.charAt(0) !== '.' || dotOk) { + var m + if (negate && !prefix) { + m = !e.match(pn) + } else { + m = e.match(pn) + } + if (m) + matchedEntries.push(e) + } + } + + var len = matchedEntries.length + // If there are no matched entries, then nothing matches. + if (len === 0) + return + + // if this is the last remaining pattern bit, then no need for + // an additional stat *unless* the user has specified mark or + // stat explicitly. We know they exist, since readdir returned + // them. + + if (remain.length === 1 && !this.mark && !this.stat) { + if (!this.matches[index]) + this.matches[index] = Object.create(null) + + for (var i = 0; i < len; i ++) { + var e = matchedEntries[i] + if (prefix) { + if (prefix.slice(-1) !== '/') + e = prefix + '/' + e + else + e = prefix + e + } + + if (e.charAt(0) === '/' && !this.nomount) { + e = path.join(this.root, e) + } + this.matches[index][e] = true + } + // This was the last one, and no stats were needed + return + } + + // now test all matched entries as stand-ins for that part + // of the pattern. + remain.shift() + for (var i = 0; i < len; i ++) { + var e = matchedEntries[i] + var newPattern + if (prefix) + newPattern = [prefix, e] + else + newPattern = [e] + this._process(newPattern.concat(remain), index, inGlobStar) + } +} + + +GlobSync.prototype._emitMatch = function (index, e) { + var abs = this._makeAbs(e) + if (this.mark) + e = this._mark(e) + + if (this.matches[index][e]) + return + + if (this.nodir) { + var c = this.cache[this._makeAbs(e)] + if (c === 'DIR' || Array.isArray(c)) + return + } + + this.matches[index][e] = true + if (this.stat) + this._stat(e) +} + + +GlobSync.prototype._readdirInGlobStar = function (abs) { + // follow all symlinked directories forever + // just proceed as if this is a non-globstar situation + if (this.follow) + return this._readdir(abs, false) + + var entries + var lstat + var stat + try { + lstat = fs.lstatSync(abs) + } catch (er) { + // lstat failed, doesn't exist + return null + } + + var isSym = lstat.isSymbolicLink() + this.symlinks[abs] = isSym + + // If it's not a symlink or a dir, then it's definitely a regular file. + // don't bother doing a readdir in that case. + if (!isSym && !lstat.isDirectory()) + this.cache[abs] = 'FILE' + else + entries = this._readdir(abs, false) + + return entries +} + +GlobSync.prototype._readdir = function (abs, inGlobStar) { + var entries + + if (inGlobStar && !ownProp(this.symlinks, abs)) + return this._readdirInGlobStar(abs) + + if (ownProp(this.cache, abs)) { + var c = this.cache[abs] + if (!c || c === 'FILE') + return null + + if (Array.isArray(c)) + return c + } + + try { + return this._readdirEntries(abs, fs.readdirSync(abs)) + } catch (er) { + this._readdirError(abs, er) + return null + } +} + +GlobSync.prototype._readdirEntries = function (abs, entries) { + // if we haven't asked to stat everything, then just + // assume that everything in there exists, so we can avoid + // having to stat it a second time. + if (!this.mark && !this.stat) { + for (var i = 0; i < entries.length; i ++) { + var e = entries[i] + if (abs === '/') + e = abs + e + else + e = abs + '/' + e + this.cache[e] = true + } + } + + this.cache[abs] = entries + + // mark and cache dir-ness + return entries +} + +GlobSync.prototype._readdirError = function (f, er) { + // handle errors, and cache the information + switch (er.code) { + case 'ENOTSUP': // https://github.com/isaacs/node-glob/issues/205 + case 'ENOTDIR': // totally normal. means it *does* exist. + this.cache[this._makeAbs(f)] = 'FILE' + break + + case 'ENOENT': // not terribly unusual + case 'ELOOP': + case 'ENAMETOOLONG': + case 'UNKNOWN': + this.cache[this._makeAbs(f)] = false + break + + default: // some unusual error. Treat as failure. + this.cache[this._makeAbs(f)] = false + if (this.strict) + throw er + if (!this.silent) + console.error('glob error', er) + break + } +} + +GlobSync.prototype._processGlobStar = function (prefix, read, abs, remain, index, inGlobStar) { + + var entries = this._readdir(abs, inGlobStar) + + // no entries means not a dir, so it can never have matches + // foo.txt/** doesn't match foo.txt + if (!entries) + return + + // test without the globstar, and with every child both below + // and replacing the globstar. + var remainWithoutGlobStar = remain.slice(1) + var gspref = prefix ? [ prefix ] : [] + var noGlobStar = gspref.concat(remainWithoutGlobStar) + + // the noGlobStar pattern exits the inGlobStar state + this._process(noGlobStar, index, false) + + var len = entries.length + var isSym = this.symlinks[abs] + + // If it's a symlink, and we're in a globstar, then stop + if (isSym && inGlobStar) + return + + for (var i = 0; i < len; i++) { + var e = entries[i] + if (e.charAt(0) === '.' && !this.dot) + continue + + // these two cases enter the inGlobStar state + var instead = gspref.concat(entries[i], remainWithoutGlobStar) + this._process(instead, index, true) + + var below = gspref.concat(entries[i], remain) + this._process(below, index, true) + } +} + +GlobSync.prototype._processSimple = function (prefix, index) { + // XXX review this. Shouldn't it be doing the mounting etc + // before doing stat? kinda weird? + var exists = this._stat(prefix) + + if (!this.matches[index]) + this.matches[index] = Object.create(null) + + // If it doesn't exist, then just mark the lack of results + if (!exists) + return + + if (prefix && isAbsolute(prefix) && !this.nomount) { + var trail = /[\/\\]$/.test(prefix) + if (prefix.charAt(0) === '/') { + prefix = path.join(this.root, prefix) + } else { + prefix = path.resolve(this.root, prefix) + if (trail) + prefix += '/' + } + } + + if (process.platform === 'win32') + prefix = prefix.replace(/\\/g, '/') + + // Mark this as a match + this.matches[index][prefix] = true +} + +// Returns either 'DIR', 'FILE', or false +GlobSync.prototype._stat = function (f) { + var abs = this._makeAbs(f) + var needDir = f.slice(-1) === '/' + + if (f.length > this.maxLength) + return false + + if (!this.stat && ownProp(this.cache, abs)) { + var c = this.cache[abs] + + if (Array.isArray(c)) + c = 'DIR' + + // It exists, but maybe not how we need it + if (!needDir || c === 'DIR') + return c + + if (needDir && c === 'FILE') + return false + + // otherwise we have to stat, because maybe c=true + // if we know it exists, but not what it is. + } + + var exists + var stat = this.statCache[abs] + if (!stat) { + var lstat + try { + lstat = fs.lstatSync(abs) + } catch (er) { + return false + } + + if (lstat.isSymbolicLink()) { + try { + stat = fs.statSync(abs) + } catch (er) { + stat = lstat + } + } else { + stat = lstat + } + } + + this.statCache[abs] = stat + + var c = stat.isDirectory() ? 'DIR' : 'FILE' + this.cache[abs] = this.cache[abs] || c + + if (needDir && c !== 'DIR') + return false + + return c +} + +GlobSync.prototype._mark = function (p) { + return common.mark(this, p) +} + +GlobSync.prototype._makeAbs = function (f) { + return common.makeAbs(this, f) +} + +}).call(this,require('_process')) +},{"./common.js":15,"./glob.js":16,"_process":24,"assert":9,"fs":12,"minimatch":20,"path":22,"path-is-absolute":23,"util":28}],18:[function(require,module,exports){ +(function (process){ +var wrappy = require('wrappy') +var reqs = Object.create(null) +var once = require('once') + +module.exports = wrappy(inflight) + +function inflight (key, cb) { + if (reqs[key]) { + reqs[key].push(cb) + return null + } else { + reqs[key] = [cb] + return makeres(key) + } +} + +function makeres (key) { + return once(function RES () { + var cbs = reqs[key] + var len = cbs.length + var args = slice(arguments) + + // XXX It's somewhat ambiguous whether a new callback added in this + // pass should be queued for later execution if something in the + // list of callbacks throws, or if it should just be discarded. + // However, it's such an edge case that it hardly matters, and either + // choice is likely as surprising as the other. + // As it happens, we do go ahead and schedule it for later execution. + try { + for (var i = 0; i < len; i++) { + cbs[i].apply(null, args) + } + } finally { + if (cbs.length > len) { + // added more in the interim. + // de-zalgo, just in case, but don't call again. + cbs.splice(0, len) + process.nextTick(function () { + RES.apply(null, args) + }) + } else { + delete reqs[key] + } + } + }) +} + +function slice (args) { + var length = args.length + var array = [] + + for (var i = 0; i < length; i++) array[i] = args[i] + return array +} + +}).call(this,require('_process')) +},{"_process":24,"once":21,"wrappy":29}],19:[function(require,module,exports){ +if (typeof Object.create === 'function') { + // implementation from standard node.js 'util' module + module.exports = function inherits(ctor, superCtor) { + ctor.super_ = superCtor + ctor.prototype = Object.create(superCtor.prototype, { + constructor: { + value: ctor, + enumerable: false, + writable: true, + configurable: true + } + }); + }; +} else { + // old school shim for old browsers + module.exports = function inherits(ctor, superCtor) { + ctor.super_ = superCtor + var TempCtor = function () {} + TempCtor.prototype = superCtor.prototype + ctor.prototype = new TempCtor() + ctor.prototype.constructor = ctor + } +} + +},{}],20:[function(require,module,exports){ +module.exports = minimatch +minimatch.Minimatch = Minimatch + +var path = { sep: '/' } +try { + path = require('path') +} catch (er) {} + +var GLOBSTAR = minimatch.GLOBSTAR = Minimatch.GLOBSTAR = {} +var expand = require('brace-expansion') + +var plTypes = { + '!': { open: '(?:(?!(?:', close: '))[^/]*?)'}, + '?': { open: '(?:', close: ')?' }, + '+': { open: '(?:', close: ')+' }, + '*': { open: '(?:', close: ')*' }, + '@': { open: '(?:', close: ')' } +} + +// any single thing other than / +// don't need to escape / when using new RegExp() +var qmark = '[^/]' + +// * => any number of characters +var star = qmark + '*?' + +// ** when dots are allowed. Anything goes, except .. and . +// not (^ or / followed by one or two dots followed by $ or /), +// followed by anything, any number of times. +var twoStarDot = '(?:(?!(?:\\\/|^)(?:\\.{1,2})($|\\\/)).)*?' + +// not a ^ or / followed by a dot, +// followed by anything, any number of times. +var twoStarNoDot = '(?:(?!(?:\\\/|^)\\.).)*?' + +// characters that need to be escaped in RegExp. +var reSpecials = charSet('().*{}+?[]^$\\!') + +// "abc" -> { a:true, b:true, c:true } +function charSet (s) { + return s.split('').reduce(function (set, c) { + set[c] = true + return set + }, {}) +} + +// normalizes slashes. +var slashSplit = /\/+/ + +minimatch.filter = filter +function filter (pattern, options) { + options = options || {} + return function (p, i, list) { + return minimatch(p, pattern, options) + } +} + +function ext (a, b) { + a = a || {} + b = b || {} + var t = {} + Object.keys(b).forEach(function (k) { + t[k] = b[k] + }) + Object.keys(a).forEach(function (k) { + t[k] = a[k] + }) + return t +} + +minimatch.defaults = function (def) { + if (!def || !Object.keys(def).length) return minimatch + + var orig = minimatch + + var m = function minimatch (p, pattern, options) { + return orig.minimatch(p, pattern, ext(def, options)) + } + + m.Minimatch = function Minimatch (pattern, options) { + return new orig.Minimatch(pattern, ext(def, options)) + } + + return m +} + +Minimatch.defaults = function (def) { + if (!def || !Object.keys(def).length) return Minimatch + return minimatch.defaults(def).Minimatch +} + +function minimatch (p, pattern, options) { + if (typeof pattern !== 'string') { + throw new TypeError('glob pattern string required') + } + + if (!options) options = {} + + // shortcut: comments match nothing. + if (!options.nocomment && pattern.charAt(0) === '#') { + return false + } + + // "" only matches "" + if (pattern.trim() === '') return p === '' + + return new Minimatch(pattern, options).match(p) +} + +function Minimatch (pattern, options) { + if (!(this instanceof Minimatch)) { + return new Minimatch(pattern, options) + } + + if (typeof pattern !== 'string') { + throw new TypeError('glob pattern string required') + } + + if (!options) options = {} + pattern = pattern.trim() + + // windows support: need to use /, not \ + if (path.sep !== '/') { + pattern = pattern.split(path.sep).join('/') + } + + this.options = options + this.set = [] + this.pattern = pattern + this.regexp = null + this.negate = false + this.comment = false + this.empty = false + + // make the set of regexps etc. + this.make() +} + +Minimatch.prototype.debug = function () {} + +Minimatch.prototype.make = make +function make () { + // don't do it more than once. + if (this._made) return + + var pattern = this.pattern + var options = this.options + + // empty patterns and comments match nothing. + if (!options.nocomment && pattern.charAt(0) === '#') { + this.comment = true + return + } + if (!pattern) { + this.empty = true + return + } + + // step 1: figure out negation, etc. + this.parseNegate() + + // step 2: expand braces + var set = this.globSet = this.braceExpand() + + if (options.debug) this.debug = console.error + + this.debug(this.pattern, set) + + // step 3: now we have a set, so turn each one into a series of path-portion + // matching patterns. + // These will be regexps, except in the case of "**", which is + // set to the GLOBSTAR object for globstar behavior, + // and will not contain any / characters + set = this.globParts = set.map(function (s) { + return s.split(slashSplit) + }) + + this.debug(this.pattern, set) + + // glob --> regexps + set = set.map(function (s, si, set) { + return s.map(this.parse, this) + }, this) + + this.debug(this.pattern, set) + + // filter out everything that didn't compile properly. + set = set.filter(function (s) { + return s.indexOf(false) === -1 + }) + + this.debug(this.pattern, set) + + this.set = set +} + +Minimatch.prototype.parseNegate = parseNegate +function parseNegate () { + var pattern = this.pattern + var negate = false + var options = this.options + var negateOffset = 0 + + if (options.nonegate) return + + for (var i = 0, l = pattern.length + ; i < l && pattern.charAt(i) === '!' + ; i++) { + negate = !negate + negateOffset++ + } + + if (negateOffset) this.pattern = pattern.substr(negateOffset) + this.negate = negate +} + +// Brace expansion: +// a{b,c}d -> abd acd +// a{b,}c -> abc ac +// a{0..3}d -> a0d a1d a2d a3d +// a{b,c{d,e}f}g -> abg acdfg acefg +// a{b,c}d{e,f}g -> abdeg acdeg abdeg abdfg +// +// Invalid sets are not expanded. +// a{2..}b -> a{2..}b +// a{b}c -> a{b}c +minimatch.braceExpand = function (pattern, options) { + return braceExpand(pattern, options) +} + +Minimatch.prototype.braceExpand = braceExpand + +function braceExpand (pattern, options) { + if (!options) { + if (this instanceof Minimatch) { + options = this.options + } else { + options = {} + } + } + + pattern = typeof pattern === 'undefined' + ? this.pattern : pattern + + if (typeof pattern === 'undefined') { + throw new TypeError('undefined pattern') + } + + if (options.nobrace || + !pattern.match(/\{.*\}/)) { + // shortcut. no need to expand. + return [pattern] + } + + return expand(pattern) +} + +// parse a component of the expanded set. +// At this point, no pattern may contain "/" in it +// so we're going to return a 2d array, where each entry is the full +// pattern, split on '/', and then turned into a regular expression. +// A regexp is made at the end which joins each array with an +// escaped /, and another full one which joins each regexp with |. +// +// Following the lead of Bash 4.1, note that "**" only has special meaning +// when it is the *only* thing in a path portion. Otherwise, any series +// of * is equivalent to a single *. Globstar behavior is enabled by +// default, and can be disabled by setting options.noglobstar. +Minimatch.prototype.parse = parse +var SUBPARSE = {} +function parse (pattern, isSub) { + if (pattern.length > 1024 * 64) { + throw new TypeError('pattern is too long') + } + + var options = this.options + + // shortcuts + if (!options.noglobstar && pattern === '**') return GLOBSTAR + if (pattern === '') return '' + + var re = '' + var hasMagic = !!options.nocase + var escaping = false + // ? => one single character + var patternListStack = [] + var negativeLists = [] + var stateChar + var inClass = false + var reClassStart = -1 + var classStart = -1 + // . and .. never match anything that doesn't start with ., + // even when options.dot is set. + var patternStart = pattern.charAt(0) === '.' ? '' // anything + // not (start or / followed by . or .. followed by / or end) + : options.dot ? '(?!(?:^|\\\/)\\.{1,2}(?:$|\\\/))' + : '(?!\\.)' + var self = this + + function clearStateChar () { + if (stateChar) { + // we had some state-tracking character + // that wasn't consumed by this pass. + switch (stateChar) { + case '*': + re += star + hasMagic = true + break + case '?': + re += qmark + hasMagic = true + break + default: + re += '\\' + stateChar + break + } + self.debug('clearStateChar %j %j', stateChar, re) + stateChar = false + } + } + + for (var i = 0, len = pattern.length, c + ; (i < len) && (c = pattern.charAt(i)) + ; i++) { + this.debug('%s\t%s %s %j', pattern, i, re, c) + + // skip over any that are escaped. + if (escaping && reSpecials[c]) { + re += '\\' + c + escaping = false + continue + } + + switch (c) { + case '/': + // completely not allowed, even escaped. + // Should already be path-split by now. + return false + + case '\\': + clearStateChar() + escaping = true + continue + + // the various stateChar values + // for the "extglob" stuff. + case '?': + case '*': + case '+': + case '@': + case '!': + this.debug('%s\t%s %s %j <-- stateChar', pattern, i, re, c) + + // all of those are literals inside a class, except that + // the glob [!a] means [^a] in regexp + if (inClass) { + this.debug(' in class') + if (c === '!' && i === classStart + 1) c = '^' + re += c + continue + } + + // if we already have a stateChar, then it means + // that there was something like ** or +? in there. + // Handle the stateChar, then proceed with this one. + self.debug('call clearStateChar %j', stateChar) + clearStateChar() + stateChar = c + // if extglob is disabled, then +(asdf|foo) isn't a thing. + // just clear the statechar *now*, rather than even diving into + // the patternList stuff. + if (options.noext) clearStateChar() + continue + + case '(': + if (inClass) { + re += '(' + continue + } + + if (!stateChar) { + re += '\\(' + continue + } + + patternListStack.push({ + type: stateChar, + start: i - 1, + reStart: re.length, + open: plTypes[stateChar].open, + close: plTypes[stateChar].close + }) + // negation is (?:(?!js)[^/]*) + re += stateChar === '!' ? '(?:(?!(?:' : '(?:' + this.debug('plType %j %j', stateChar, re) + stateChar = false + continue + + case ')': + if (inClass || !patternListStack.length) { + re += '\\)' + continue + } + + clearStateChar() + hasMagic = true + var pl = patternListStack.pop() + // negation is (?:(?!js)[^/]*) + // The others are (?:) + re += pl.close + if (pl.type === '!') { + negativeLists.push(pl) + } + pl.reEnd = re.length + continue + + case '|': + if (inClass || !patternListStack.length || escaping) { + re += '\\|' + escaping = false + continue + } + + clearStateChar() + re += '|' + continue + + // these are mostly the same in regexp and glob + case '[': + // swallow any state-tracking char before the [ + clearStateChar() + + if (inClass) { + re += '\\' + c + continue + } + + inClass = true + classStart = i + reClassStart = re.length + re += c + continue + + case ']': + // a right bracket shall lose its special + // meaning and represent itself in + // a bracket expression if it occurs + // first in the list. -- POSIX.2 2.8.3.2 + if (i === classStart + 1 || !inClass) { + re += '\\' + c + escaping = false + continue + } + + // handle the case where we left a class open. + // "[z-a]" is valid, equivalent to "\[z-a\]" + if (inClass) { + // split where the last [ was, make sure we don't have + // an invalid re. if so, re-walk the contents of the + // would-be class to re-translate any characters that + // were passed through as-is + // TODO: It would probably be faster to determine this + // without a try/catch and a new RegExp, but it's tricky + // to do safely. For now, this is safe and works. + var cs = pattern.substring(classStart + 1, i) + try { + RegExp('[' + cs + ']') + } catch (er) { + // not a valid class! + var sp = this.parse(cs, SUBPARSE) + re = re.substr(0, reClassStart) + '\\[' + sp[0] + '\\]' + hasMagic = hasMagic || sp[1] + inClass = false + continue + } + } + + // finish up the class. + hasMagic = true + inClass = false + re += c + continue + + default: + // swallow any state char that wasn't consumed + clearStateChar() + + if (escaping) { + // no need + escaping = false + } else if (reSpecials[c] + && !(c === '^' && inClass)) { + re += '\\' + } + + re += c + + } // switch + } // for + + // handle the case where we left a class open. + // "[abc" is valid, equivalent to "\[abc" + if (inClass) { + // split where the last [ was, and escape it + // this is a huge pita. We now have to re-walk + // the contents of the would-be class to re-translate + // any characters that were passed through as-is + cs = pattern.substr(classStart + 1) + sp = this.parse(cs, SUBPARSE) + re = re.substr(0, reClassStart) + '\\[' + sp[0] + hasMagic = hasMagic || sp[1] + } + + // handle the case where we had a +( thing at the *end* + // of the pattern. + // each pattern list stack adds 3 chars, and we need to go through + // and escape any | chars that were passed through as-is for the regexp. + // Go through and escape them, taking care not to double-escape any + // | chars that were already escaped. + for (pl = patternListStack.pop(); pl; pl = patternListStack.pop()) { + var tail = re.slice(pl.reStart + pl.open.length) + this.debug('setting tail', re, pl) + // maybe some even number of \, then maybe 1 \, followed by a | + tail = tail.replace(/((?:\\{2}){0,64})(\\?)\|/g, function (_, $1, $2) { + if (!$2) { + // the | isn't already escaped, so escape it. + $2 = '\\' + } + + // need to escape all those slashes *again*, without escaping the + // one that we need for escaping the | character. As it works out, + // escaping an even number of slashes can be done by simply repeating + // it exactly after itself. That's why this trick works. + // + // I am sorry that you have to see this. + return $1 + $1 + $2 + '|' + }) + + this.debug('tail=%j\n %s', tail, tail, pl, re) + var t = pl.type === '*' ? star + : pl.type === '?' ? qmark + : '\\' + pl.type + + hasMagic = true + re = re.slice(0, pl.reStart) + t + '\\(' + tail + } + + // handle trailing things that only matter at the very end. + clearStateChar() + if (escaping) { + // trailing \\ + re += '\\\\' + } + + // only need to apply the nodot start if the re starts with + // something that could conceivably capture a dot + var addPatternStart = false + switch (re.charAt(0)) { + case '.': + case '[': + case '(': addPatternStart = true + } + + // Hack to work around lack of negative lookbehind in JS + // A pattern like: *.!(x).!(y|z) needs to ensure that a name + // like 'a.xyz.yz' doesn't match. So, the first negative + // lookahead, has to look ALL the way ahead, to the end of + // the pattern. + for (var n = negativeLists.length - 1; n > -1; n--) { + var nl = negativeLists[n] + + var nlBefore = re.slice(0, nl.reStart) + var nlFirst = re.slice(nl.reStart, nl.reEnd - 8) + var nlLast = re.slice(nl.reEnd - 8, nl.reEnd) + var nlAfter = re.slice(nl.reEnd) + + nlLast += nlAfter + + // Handle nested stuff like *(*.js|!(*.json)), where open parens + // mean that we should *not* include the ) in the bit that is considered + // "after" the negated section. + var openParensBefore = nlBefore.split('(').length - 1 + var cleanAfter = nlAfter + for (i = 0; i < openParensBefore; i++) { + cleanAfter = cleanAfter.replace(/\)[+*?]?/, '') + } + nlAfter = cleanAfter + + var dollar = '' + if (nlAfter === '' && isSub !== SUBPARSE) { + dollar = '$' + } + var newRe = nlBefore + nlFirst + nlAfter + dollar + nlLast + re = newRe + } + + // if the re is not "" at this point, then we need to make sure + // it doesn't match against an empty path part. + // Otherwise a/* will match a/, which it should not. + if (re !== '' && hasMagic) { + re = '(?=.)' + re + } + + if (addPatternStart) { + re = patternStart + re + } + + // parsing just a piece of a larger pattern. + if (isSub === SUBPARSE) { + return [re, hasMagic] + } + + // skip the regexp for non-magical patterns + // unescape anything in it, though, so that it'll be + // an exact match against a file etc. + if (!hasMagic) { + return globUnescape(pattern) + } + + var flags = options.nocase ? 'i' : '' + try { + var regExp = new RegExp('^' + re + '$', flags) + } catch (er) { + // If it was an invalid regular expression, then it can't match + // anything. This trick looks for a character after the end of + // the string, which is of course impossible, except in multi-line + // mode, but it's not a /m regex. + return new RegExp('$.') + } + + regExp._glob = pattern + regExp._src = re + + return regExp +} + +minimatch.makeRe = function (pattern, options) { + return new Minimatch(pattern, options || {}).makeRe() +} + +Minimatch.prototype.makeRe = makeRe +function makeRe () { + if (this.regexp || this.regexp === false) return this.regexp + + // at this point, this.set is a 2d array of partial + // pattern strings, or "**". + // + // It's better to use .match(). This function shouldn't + // be used, really, but it's pretty convenient sometimes, + // when you just want to work with a regex. + var set = this.set + + if (!set.length) { + this.regexp = false + return this.regexp + } + var options = this.options + + var twoStar = options.noglobstar ? star + : options.dot ? twoStarDot + : twoStarNoDot + var flags = options.nocase ? 'i' : '' + + var re = set.map(function (pattern) { + return pattern.map(function (p) { + return (p === GLOBSTAR) ? twoStar + : (typeof p === 'string') ? regExpEscape(p) + : p._src + }).join('\\\/') + }).join('|') + + // must match entire pattern + // ending in a * or ** will make it less strict. + re = '^(?:' + re + ')$' + + // can match anything, as long as it's not this. + if (this.negate) re = '^(?!' + re + ').*$' + + try { + this.regexp = new RegExp(re, flags) + } catch (ex) { + this.regexp = false + } + return this.regexp +} + +minimatch.match = function (list, pattern, options) { + options = options || {} + var mm = new Minimatch(pattern, options) + list = list.filter(function (f) { + return mm.match(f) + }) + if (mm.options.nonull && !list.length) { + list.push(pattern) + } + return list +} + +Minimatch.prototype.match = match +function match (f, partial) { + this.debug('match', f, this.pattern) + // short-circuit in the case of busted things. + // comments, etc. + if (this.comment) return false + if (this.empty) return f === '' + + if (f === '/' && partial) return true + + var options = this.options + + // windows: need to use /, not \ + if (path.sep !== '/') { + f = f.split(path.sep).join('/') + } + + // treat the test path as a set of pathparts. + f = f.split(slashSplit) + this.debug(this.pattern, 'split', f) + + // just ONE of the pattern sets in this.set needs to match + // in order for it to be valid. If negating, then just one + // match means that we have failed. + // Either way, return on the first hit. + + var set = this.set + this.debug(this.pattern, 'set', set) + + // Find the basename of the path by looking for the last non-empty segment + var filename + var i + for (i = f.length - 1; i >= 0; i--) { + filename = f[i] + if (filename) break + } + + for (i = 0; i < set.length; i++) { + var pattern = set[i] + var file = f + if (options.matchBase && pattern.length === 1) { + file = [filename] + } + var hit = this.matchOne(file, pattern, partial) + if (hit) { + if (options.flipNegate) return true + return !this.negate + } + } + + // didn't get any hits. this is success if it's a negative + // pattern, failure otherwise. + if (options.flipNegate) return false + return this.negate +} + +// set partial to true to test if, for example, +// "/a/b" matches the start of "/*/b/*/d" +// Partial means, if you run out of file before you run +// out of pattern, then that's fine, as long as all +// the parts match. +Minimatch.prototype.matchOne = function (file, pattern, partial) { + var options = this.options + + this.debug('matchOne', + { 'this': this, file: file, pattern: pattern }) + + this.debug('matchOne', file.length, pattern.length) + + for (var fi = 0, + pi = 0, + fl = file.length, + pl = pattern.length + ; (fi < fl) && (pi < pl) + ; fi++, pi++) { + this.debug('matchOne loop') + var p = pattern[pi] + var f = file[fi] + + this.debug(pattern, p, f) + + // should be impossible. + // some invalid regexp stuff in the set. + if (p === false) return false + + if (p === GLOBSTAR) { + this.debug('GLOBSTAR', [pattern, p, f]) + + // "**" + // a/**/b/**/c would match the following: + // a/b/x/y/z/c + // a/x/y/z/b/c + // a/b/x/b/x/c + // a/b/c + // To do this, take the rest of the pattern after + // the **, and see if it would match the file remainder. + // If so, return success. + // If not, the ** "swallows" a segment, and try again. + // This is recursively awful. + // + // a/**/b/**/c matching a/b/x/y/z/c + // - a matches a + // - doublestar + // - matchOne(b/x/y/z/c, b/**/c) + // - b matches b + // - doublestar + // - matchOne(x/y/z/c, c) -> no + // - matchOne(y/z/c, c) -> no + // - matchOne(z/c, c) -> no + // - matchOne(c, c) yes, hit + var fr = fi + var pr = pi + 1 + if (pr === pl) { + this.debug('** at the end') + // a ** at the end will just swallow the rest. + // We have found a match. + // however, it will not swallow /.x, unless + // options.dot is set. + // . and .. are *never* matched by **, for explosively + // exponential reasons. + for (; fi < fl; fi++) { + if (file[fi] === '.' || file[fi] === '..' || + (!options.dot && file[fi].charAt(0) === '.')) return false + } + return true + } + + // ok, let's see if we can swallow whatever we can. + while (fr < fl) { + var swallowee = file[fr] + + this.debug('\nglobstar while', file, fr, pattern, pr, swallowee) + + // XXX remove this slice. Just pass the start index. + if (this.matchOne(file.slice(fr), pattern.slice(pr), partial)) { + this.debug('globstar found match!', fr, fl, swallowee) + // found a match. + return true + } else { + // can't swallow "." or ".." ever. + // can only swallow ".foo" when explicitly asked. + if (swallowee === '.' || swallowee === '..' || + (!options.dot && swallowee.charAt(0) === '.')) { + this.debug('dot detected!', file, fr, pattern, pr) + break + } + + // ** swallows a segment, and continue. + this.debug('globstar swallow a segment, and continue') + fr++ + } + } + + // no match was found. + // However, in partial mode, we can't say this is necessarily over. + // If there's more *pattern* left, then + if (partial) { + // ran out of file + this.debug('\n>>> no match, partial?', file, fr, pattern, pr) + if (fr === fl) return true + } + return false + } + + // something other than ** + // non-magic patterns just have to match exactly + // patterns with magic have been turned into regexps. + var hit + if (typeof p === 'string') { + if (options.nocase) { + hit = f.toLowerCase() === p.toLowerCase() + } else { + hit = f === p + } + this.debug('string match', p, f, hit) + } else { + hit = f.match(p) + this.debug('pattern match', p, f, hit) + } + + if (!hit) return false + } + + // Note: ending in / means that we'll get a final "" + // at the end of the pattern. This can only match a + // corresponding "" at the end of the file. + // If the file ends in /, then it can only match a + // a pattern that ends in /, unless the pattern just + // doesn't have any more for it. But, a/b/ should *not* + // match "a/b/*", even though "" matches against the + // [^/]*? pattern, except in partial mode, where it might + // simply not be reached yet. + // However, a/b/ should still satisfy a/* + + // now either we fell off the end of the pattern, or we're done. + if (fi === fl && pi === pl) { + // ran out of pattern and filename at the same time. + // an exact hit! + return true + } else if (fi === fl) { + // ran out of file, but still had pattern left. + // this is ok if we're doing the match as part of + // a glob fs traversal. + return partial + } else if (pi === pl) { + // ran out of pattern, still have file left. + // this is only acceptable if we're on the very last + // empty segment of a file with a trailing slash. + // a/* should match a/b/ + var emptyFileEnd = (fi === fl - 1) && (file[fi] === '') + return emptyFileEnd + } + + // should be unreachable. + throw new Error('wtf?') +} + +// replace stuff like \* with * +function globUnescape (s) { + return s.replace(/\\(.)/g, '$1') +} + +function regExpEscape (s) { + return s.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&') +} + +},{"brace-expansion":11,"path":22}],21:[function(require,module,exports){ +var wrappy = require('wrappy') +module.exports = wrappy(once) +module.exports.strict = wrappy(onceStrict) + +once.proto = once(function () { + Object.defineProperty(Function.prototype, 'once', { + value: function () { + return once(this) + }, + configurable: true + }) + + Object.defineProperty(Function.prototype, 'onceStrict', { + value: function () { + return onceStrict(this) + }, + configurable: true + }) +}) + +function once (fn) { + var f = function () { + if (f.called) return f.value + f.called = true + return f.value = fn.apply(this, arguments) + } + f.called = false + return f +} + +function onceStrict (fn) { + var f = function () { + if (f.called) + throw new Error(f.onceError) + f.called = true + return f.value = fn.apply(this, arguments) + } + var name = fn.name || 'Function wrapped with `once`' + f.onceError = name + " shouldn't be called more than once" + f.called = false + return f +} + +},{"wrappy":29}],22:[function(require,module,exports){ +(function (process){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +// resolves . and .. elements in a path array with directory names there +// must be no slashes, empty elements, or device names (c:\) in the array +// (so also no leading and trailing slashes - it does not distinguish +// relative and absolute paths) +function normalizeArray(parts, allowAboveRoot) { + // if the path tries to go above the root, `up` ends up > 0 + var up = 0; + for (var i = parts.length - 1; i >= 0; i--) { + var last = parts[i]; + if (last === '.') { + parts.splice(i, 1); + } else if (last === '..') { + parts.splice(i, 1); + up++; + } else if (up) { + parts.splice(i, 1); + up--; + } + } + + // if the path is allowed to go above the root, restore leading ..s + if (allowAboveRoot) { + for (; up--; up) { + parts.unshift('..'); + } + } + + return parts; +} + +// Split a filename into [root, dir, basename, ext], unix version +// 'root' is just a slash, or nothing. +var splitPathRe = + /^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/; +var splitPath = function(filename) { + return splitPathRe.exec(filename).slice(1); +}; + +// path.resolve([from ...], to) +// posix version +exports.resolve = function() { + var resolvedPath = '', + resolvedAbsolute = false; + + for (var i = arguments.length - 1; i >= -1 && !resolvedAbsolute; i--) { + var path = (i >= 0) ? arguments[i] : process.cwd(); + + // Skip empty and invalid entries + if (typeof path !== 'string') { + throw new TypeError('Arguments to path.resolve must be strings'); + } else if (!path) { + continue; + } + + resolvedPath = path + '/' + resolvedPath; + resolvedAbsolute = path.charAt(0) === '/'; + } + + // At this point the path should be resolved to a full absolute path, but + // handle relative paths to be safe (might happen when process.cwd() fails) + + // Normalize the path + resolvedPath = normalizeArray(filter(resolvedPath.split('/'), function(p) { + return !!p; + }), !resolvedAbsolute).join('/'); + + return ((resolvedAbsolute ? '/' : '') + resolvedPath) || '.'; +}; + +// path.normalize(path) +// posix version +exports.normalize = function(path) { + var isAbsolute = exports.isAbsolute(path), + trailingSlash = substr(path, -1) === '/'; + + // Normalize the path + path = normalizeArray(filter(path.split('/'), function(p) { + return !!p; + }), !isAbsolute).join('/'); + + if (!path && !isAbsolute) { + path = '.'; + } + if (path && trailingSlash) { + path += '/'; + } + + return (isAbsolute ? '/' : '') + path; +}; + +// posix version +exports.isAbsolute = function(path) { + return path.charAt(0) === '/'; +}; + +// posix version +exports.join = function() { + var paths = Array.prototype.slice.call(arguments, 0); + return exports.normalize(filter(paths, function(p, index) { + if (typeof p !== 'string') { + throw new TypeError('Arguments to path.join must be strings'); + } + return p; + }).join('/')); +}; + + +// path.relative(from, to) +// posix version +exports.relative = function(from, to) { + from = exports.resolve(from).substr(1); + to = exports.resolve(to).substr(1); + + function trim(arr) { + var start = 0; + for (; start < arr.length; start++) { + if (arr[start] !== '') break; + } + + var end = arr.length - 1; + for (; end >= 0; end--) { + if (arr[end] !== '') break; + } + + if (start > end) return []; + return arr.slice(start, end - start + 1); + } + + var fromParts = trim(from.split('/')); + var toParts = trim(to.split('/')); + + var length = Math.min(fromParts.length, toParts.length); + var samePartsLength = length; + for (var i = 0; i < length; i++) { + if (fromParts[i] !== toParts[i]) { + samePartsLength = i; + break; + } + } + + var outputParts = []; + for (var i = samePartsLength; i < fromParts.length; i++) { + outputParts.push('..'); + } + + outputParts = outputParts.concat(toParts.slice(samePartsLength)); + + return outputParts.join('/'); +}; + +exports.sep = '/'; +exports.delimiter = ':'; + +exports.dirname = function(path) { + var result = splitPath(path), + root = result[0], + dir = result[1]; + + if (!root && !dir) { + // No dirname whatsoever + return '.'; + } + + if (dir) { + // It has a dirname, strip trailing slash + dir = dir.substr(0, dir.length - 1); + } + + return root + dir; +}; + + +exports.basename = function(path, ext) { + var f = splitPath(path)[2]; + // TODO: make this comparison case-insensitive on windows? + if (ext && f.substr(-1 * ext.length) === ext) { + f = f.substr(0, f.length - ext.length); + } + return f; +}; + + +exports.extname = function(path) { + return splitPath(path)[3]; +}; + +function filter (xs, f) { + if (xs.filter) return xs.filter(f); + var res = []; + for (var i = 0; i < xs.length; i++) { + if (f(xs[i], i, xs)) res.push(xs[i]); + } + return res; +} + +// String.prototype.substr - negative index don't work in IE8 +var substr = 'ab'.substr(-1) === 'b' + ? function (str, start, len) { return str.substr(start, len) } + : function (str, start, len) { + if (start < 0) start = str.length + start; + return str.substr(start, len); + } +; + +}).call(this,require('_process')) +},{"_process":24}],23:[function(require,module,exports){ +(function (process){ +'use strict'; + +function posix(path) { + return path.charAt(0) === '/'; +} + +function win32(path) { + // https://github.com/nodejs/node/blob/b3fcc245fb25539909ef1d5eaa01dbf92e168633/lib/path.js#L56 + var splitDeviceRe = /^([a-zA-Z]:|[\\\/]{2}[^\\\/]+[\\\/]+[^\\\/]+)?([\\\/])?([\s\S]*?)$/; + var result = splitDeviceRe.exec(path); + var device = result[1] || ''; + var isUnc = Boolean(device && device.charAt(1) !== ':'); + + // UNC paths are always absolute + return Boolean(result[2] || isUnc); +} + +module.exports = process.platform === 'win32' ? win32 : posix; +module.exports.posix = posix; +module.exports.win32 = win32; + +}).call(this,require('_process')) +},{"_process":24}],24:[function(require,module,exports){ +// shim for using process in browser +var process = module.exports = {}; + +// cached from whatever global is present so that test runners that stub it +// don't break things. But we need to wrap it in a try catch in case it is +// wrapped in strict mode code which doesn't define any globals. It's inside a +// function because try/catches deoptimize in certain engines. + +var cachedSetTimeout; +var cachedClearTimeout; + +function defaultSetTimout() { + throw new Error('setTimeout has not been defined'); +} +function defaultClearTimeout () { + throw new Error('clearTimeout has not been defined'); +} +(function () { + try { + if (typeof setTimeout === 'function') { + cachedSetTimeout = setTimeout; + } else { + cachedSetTimeout = defaultSetTimout; + } + } catch (e) { + cachedSetTimeout = defaultSetTimout; + } + try { + if (typeof clearTimeout === 'function') { + cachedClearTimeout = clearTimeout; + } else { + cachedClearTimeout = defaultClearTimeout; + } + } catch (e) { + cachedClearTimeout = defaultClearTimeout; + } +} ()) +function runTimeout(fun) { + if (cachedSetTimeout === setTimeout) { + //normal enviroments in sane situations + return setTimeout(fun, 0); + } + // if setTimeout wasn't available but was latter defined + if ((cachedSetTimeout === defaultSetTimout || !cachedSetTimeout) && setTimeout) { + cachedSetTimeout = setTimeout; + return setTimeout(fun, 0); + } + try { + // when when somebody has screwed with setTimeout but no I.E. maddness + return cachedSetTimeout(fun, 0); + } catch(e){ + try { + // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally + return cachedSetTimeout.call(null, fun, 0); + } catch(e){ + // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error + return cachedSetTimeout.call(this, fun, 0); + } + } + + +} +function runClearTimeout(marker) { + if (cachedClearTimeout === clearTimeout) { + //normal enviroments in sane situations + return clearTimeout(marker); + } + // if clearTimeout wasn't available but was latter defined + if ((cachedClearTimeout === defaultClearTimeout || !cachedClearTimeout) && clearTimeout) { + cachedClearTimeout = clearTimeout; + return clearTimeout(marker); + } + try { + // when when somebody has screwed with setTimeout but no I.E. maddness + return cachedClearTimeout(marker); + } catch (e){ + try { + // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally + return cachedClearTimeout.call(null, marker); + } catch (e){ + // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error. + // Some versions of I.E. have different rules for clearTimeout vs setTimeout + return cachedClearTimeout.call(this, marker); + } + } + + + +} +var queue = []; +var draining = false; +var currentQueue; +var queueIndex = -1; + +function cleanUpNextTick() { + if (!draining || !currentQueue) { + return; + } + draining = false; + if (currentQueue.length) { + queue = currentQueue.concat(queue); + } else { + queueIndex = -1; + } + if (queue.length) { + drainQueue(); + } +} + +function drainQueue() { + if (draining) { + return; + } + var timeout = runTimeout(cleanUpNextTick); + draining = true; + + var len = queue.length; + while(len) { + currentQueue = queue; + queue = []; + while (++queueIndex < len) { + if (currentQueue) { + currentQueue[queueIndex].run(); + } + } + queueIndex = -1; + len = queue.length; + } + currentQueue = null; + draining = false; + runClearTimeout(timeout); +} + +process.nextTick = function (fun) { + var args = new Array(arguments.length - 1); + if (arguments.length > 1) { + for (var i = 1; i < arguments.length; i++) { + args[i - 1] = arguments[i]; + } + } + queue.push(new Item(fun, args)); + if (queue.length === 1 && !draining) { + runTimeout(drainQueue); + } +}; + +// v8 likes predictible objects +function Item(fun, array) { + this.fun = fun; + this.array = array; +} +Item.prototype.run = function () { + this.fun.apply(null, this.array); +}; +process.title = 'browser'; +process.browser = true; +process.env = {}; +process.argv = []; +process.version = ''; // empty string to avoid regexp issues +process.versions = {}; + +function noop() {} + +process.on = noop; +process.addListener = noop; +process.once = noop; +process.off = noop; +process.removeListener = noop; +process.removeAllListeners = noop; +process.emit = noop; +process.prependListener = noop; +process.prependOnceListener = noop; + +process.listeners = function (name) { return [] } + +process.binding = function (name) { + throw new Error('process.binding is not supported'); +}; + +process.cwd = function () { return '/' }; +process.chdir = function (dir) { + throw new Error('process.chdir is not supported'); +}; +process.umask = function() { return 0; }; + +},{}],25:[function(require,module,exports){ +// Underscore.js 1.8.3 +// http://underscorejs.org +// (c) 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors +// Underscore may be freely distributed under the MIT license. + +(function() { + + // Baseline setup + // -------------- + + // Establish the root object, `window` in the browser, or `exports` on the server. + var root = this; + + // Save the previous value of the `_` variable. + var previousUnderscore = root._; + + // Save bytes in the minified (but not gzipped) version: + var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype; + + // Create quick reference variables for speed access to core prototypes. + var + push = ArrayProto.push, + slice = ArrayProto.slice, + toString = ObjProto.toString, + hasOwnProperty = ObjProto.hasOwnProperty; + + // All **ECMAScript 5** native function implementations that we hope to use + // are declared here. + var + nativeIsArray = Array.isArray, + nativeKeys = Object.keys, + nativeBind = FuncProto.bind, + nativeCreate = Object.create; + + // Naked function reference for surrogate-prototype-swapping. + var Ctor = function(){}; + + // Create a safe reference to the Underscore object for use below. + var _ = function(obj) { + if (obj instanceof _) return obj; + if (!(this instanceof _)) return new _(obj); + this._wrapped = obj; + }; + + // Export the Underscore object for **Node.js**, with + // backwards-compatibility for the old `require()` API. If we're in + // the browser, add `_` as a global object. + if (typeof exports !== 'undefined') { + if (typeof module !== 'undefined' && module.exports) { + exports = module.exports = _; + } + exports._ = _; + } else { + root._ = _; + } + + // Current version. + _.VERSION = '1.8.3'; + + // Internal function that returns an efficient (for current engines) version + // of the passed-in callback, to be repeatedly applied in other Underscore + // functions. + var optimizeCb = function(func, context, argCount) { + if (context === void 0) return func; + switch (argCount == null ? 3 : argCount) { + case 1: return function(value) { + return func.call(context, value); + }; + case 2: return function(value, other) { + return func.call(context, value, other); + }; + case 3: return function(value, index, collection) { + return func.call(context, value, index, collection); + }; + case 4: return function(accumulator, value, index, collection) { + return func.call(context, accumulator, value, index, collection); + }; + } + return function() { + return func.apply(context, arguments); + }; + }; + + // A mostly-internal function to generate callbacks that can be applied + // to each element in a collection, returning the desired result — either + // identity, an arbitrary callback, a property matcher, or a property accessor. + var cb = function(value, context, argCount) { + if (value == null) return _.identity; + if (_.isFunction(value)) return optimizeCb(value, context, argCount); + if (_.isObject(value)) return _.matcher(value); + return _.property(value); + }; + _.iteratee = function(value, context) { + return cb(value, context, Infinity); + }; + + // An internal function for creating assigner functions. + var createAssigner = function(keysFunc, undefinedOnly) { + return function(obj) { + var length = arguments.length; + if (length < 2 || obj == null) return obj; + for (var index = 1; index < length; index++) { + var source = arguments[index], + keys = keysFunc(source), + l = keys.length; + for (var i = 0; i < l; i++) { + var key = keys[i]; + if (!undefinedOnly || obj[key] === void 0) obj[key] = source[key]; + } + } + return obj; + }; + }; + + // An internal function for creating a new object that inherits from another. + var baseCreate = function(prototype) { + if (!_.isObject(prototype)) return {}; + if (nativeCreate) return nativeCreate(prototype); + Ctor.prototype = prototype; + var result = new Ctor; + Ctor.prototype = null; + return result; + }; + + var property = function(key) { + return function(obj) { + return obj == null ? void 0 : obj[key]; + }; + }; + + // Helper for collection methods to determine whether a collection + // should be iterated as an array or as an object + // Related: http://people.mozilla.org/~jorendorff/es6-draft.html#sec-tolength + // Avoids a very nasty iOS 8 JIT bug on ARM-64. #2094 + var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1; + var getLength = property('length'); + var isArrayLike = function(collection) { + var length = getLength(collection); + return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX; + }; + + // Collection Functions + // -------------------- + + // The cornerstone, an `each` implementation, aka `forEach`. + // Handles raw objects in addition to array-likes. Treats all + // sparse array-likes as if they were dense. + _.each = _.forEach = function(obj, iteratee, context) { + iteratee = optimizeCb(iteratee, context); + var i, length; + if (isArrayLike(obj)) { + for (i = 0, length = obj.length; i < length; i++) { + iteratee(obj[i], i, obj); + } + } else { + var keys = _.keys(obj); + for (i = 0, length = keys.length; i < length; i++) { + iteratee(obj[keys[i]], keys[i], obj); + } + } + return obj; + }; + + // Return the results of applying the iteratee to each element. + _.map = _.collect = function(obj, iteratee, context) { + iteratee = cb(iteratee, context); + var keys = !isArrayLike(obj) && _.keys(obj), + length = (keys || obj).length, + results = Array(length); + for (var index = 0; index < length; index++) { + var currentKey = keys ? keys[index] : index; + results[index] = iteratee(obj[currentKey], currentKey, obj); + } + return results; + }; + + // Create a reducing function iterating left or right. + function createReduce(dir) { + // Optimized iterator function as using arguments.length + // in the main function will deoptimize the, see #1991. + function iterator(obj, iteratee, memo, keys, index, length) { + for (; index >= 0 && index < length; index += dir) { + var currentKey = keys ? keys[index] : index; + memo = iteratee(memo, obj[currentKey], currentKey, obj); + } + return memo; + } + + return function(obj, iteratee, memo, context) { + iteratee = optimizeCb(iteratee, context, 4); + var keys = !isArrayLike(obj) && _.keys(obj), + length = (keys || obj).length, + index = dir > 0 ? 0 : length - 1; + // Determine the initial value if none is provided. + if (arguments.length < 3) { + memo = obj[keys ? keys[index] : index]; + index += dir; + } + return iterator(obj, iteratee, memo, keys, index, length); + }; + } + + // **Reduce** builds up a single result from a list of values, aka `inject`, + // or `foldl`. + _.reduce = _.foldl = _.inject = createReduce(1); + + // The right-associative version of reduce, also known as `foldr`. + _.reduceRight = _.foldr = createReduce(-1); + + // Return the first value which passes a truth test. Aliased as `detect`. + _.find = _.detect = function(obj, predicate, context) { + var key; + if (isArrayLike(obj)) { + key = _.findIndex(obj, predicate, context); + } else { + key = _.findKey(obj, predicate, context); + } + if (key !== void 0 && key !== -1) return obj[key]; + }; + + // Return all the elements that pass a truth test. + // Aliased as `select`. + _.filter = _.select = function(obj, predicate, context) { + var results = []; + predicate = cb(predicate, context); + _.each(obj, function(value, index, list) { + if (predicate(value, index, list)) results.push(value); + }); + return results; + }; + + // Return all the elements for which a truth test fails. + _.reject = function(obj, predicate, context) { + return _.filter(obj, _.negate(cb(predicate)), context); + }; + + // Determine whether all of the elements match a truth test. + // Aliased as `all`. + _.every = _.all = function(obj, predicate, context) { + predicate = cb(predicate, context); + var keys = !isArrayLike(obj) && _.keys(obj), + length = (keys || obj).length; + for (var index = 0; index < length; index++) { + var currentKey = keys ? keys[index] : index; + if (!predicate(obj[currentKey], currentKey, obj)) return false; + } + return true; + }; + + // Determine if at least one element in the object matches a truth test. + // Aliased as `any`. + _.some = _.any = function(obj, predicate, context) { + predicate = cb(predicate, context); + var keys = !isArrayLike(obj) && _.keys(obj), + length = (keys || obj).length; + for (var index = 0; index < length; index++) { + var currentKey = keys ? keys[index] : index; + if (predicate(obj[currentKey], currentKey, obj)) return true; + } + return false; + }; + + // Determine if the array or object contains a given item (using `===`). + // Aliased as `includes` and `include`. + _.contains = _.includes = _.include = function(obj, item, fromIndex, guard) { + if (!isArrayLike(obj)) obj = _.values(obj); + if (typeof fromIndex != 'number' || guard) fromIndex = 0; + return _.indexOf(obj, item, fromIndex) >= 0; + }; + + // Invoke a method (with arguments) on every item in a collection. + _.invoke = function(obj, method) { + var args = slice.call(arguments, 2); + var isFunc = _.isFunction(method); + return _.map(obj, function(value) { + var func = isFunc ? method : value[method]; + return func == null ? func : func.apply(value, args); + }); + }; + + // Convenience version of a common use case of `map`: fetching a property. + _.pluck = function(obj, key) { + return _.map(obj, _.property(key)); + }; + + // Convenience version of a common use case of `filter`: selecting only objects + // containing specific `key:value` pairs. + _.where = function(obj, attrs) { + return _.filter(obj, _.matcher(attrs)); + }; + + // Convenience version of a common use case of `find`: getting the first object + // containing specific `key:value` pairs. + _.findWhere = function(obj, attrs) { + return _.find(obj, _.matcher(attrs)); + }; + + // Return the maximum element (or element-based computation). + _.max = function(obj, iteratee, context) { + var result = -Infinity, lastComputed = -Infinity, + value, computed; + if (iteratee == null && obj != null) { + obj = isArrayLike(obj) ? obj : _.values(obj); + for (var i = 0, length = obj.length; i < length; i++) { + value = obj[i]; + if (value > result) { + result = value; + } + } + } else { + iteratee = cb(iteratee, context); + _.each(obj, function(value, index, list) { + computed = iteratee(value, index, list); + if (computed > lastComputed || computed === -Infinity && result === -Infinity) { + result = value; + lastComputed = computed; + } + }); + } + return result; + }; + + // Return the minimum element (or element-based computation). + _.min = function(obj, iteratee, context) { + var result = Infinity, lastComputed = Infinity, + value, computed; + if (iteratee == null && obj != null) { + obj = isArrayLike(obj) ? obj : _.values(obj); + for (var i = 0, length = obj.length; i < length; i++) { + value = obj[i]; + if (value < result) { + result = value; + } + } + } else { + iteratee = cb(iteratee, context); + _.each(obj, function(value, index, list) { + computed = iteratee(value, index, list); + if (computed < lastComputed || computed === Infinity && result === Infinity) { + result = value; + lastComputed = computed; + } + }); + } + return result; + }; + + // Shuffle a collection, using the modern version of the + // [Fisher-Yates shuffle](http://en.wikipedia.org/wiki/Fisher–Yates_shuffle). + _.shuffle = function(obj) { + var set = isArrayLike(obj) ? obj : _.values(obj); + var length = set.length; + var shuffled = Array(length); + for (var index = 0, rand; index < length; index++) { + rand = _.random(0, index); + if (rand !== index) shuffled[index] = shuffled[rand]; + shuffled[rand] = set[index]; + } + return shuffled; + }; + + // Sample **n** random values from a collection. + // If **n** is not specified, returns a single random element. + // The internal `guard` argument allows it to work with `map`. + _.sample = function(obj, n, guard) { + if (n == null || guard) { + if (!isArrayLike(obj)) obj = _.values(obj); + return obj[_.random(obj.length - 1)]; + } + return _.shuffle(obj).slice(0, Math.max(0, n)); + }; + + // Sort the object's values by a criterion produced by an iteratee. + _.sortBy = function(obj, iteratee, context) { + iteratee = cb(iteratee, context); + return _.pluck(_.map(obj, function(value, index, list) { + return { + value: value, + index: index, + criteria: iteratee(value, index, list) + }; + }).sort(function(left, right) { + var a = left.criteria; + var b = right.criteria; + if (a !== b) { + if (a > b || a === void 0) return 1; + if (a < b || b === void 0) return -1; + } + return left.index - right.index; + }), 'value'); + }; + + // An internal function used for aggregate "group by" operations. + var group = function(behavior) { + return function(obj, iteratee, context) { + var result = {}; + iteratee = cb(iteratee, context); + _.each(obj, function(value, index) { + var key = iteratee(value, index, obj); + behavior(result, value, key); + }); + return result; + }; + }; + + // Groups the object's values by a criterion. Pass either a string attribute + // to group by, or a function that returns the criterion. + _.groupBy = group(function(result, value, key) { + if (_.has(result, key)) result[key].push(value); else result[key] = [value]; + }); + + // Indexes the object's values by a criterion, similar to `groupBy`, but for + // when you know that your index values will be unique. + _.indexBy = group(function(result, value, key) { + result[key] = value; + }); + + // Counts instances of an object that group by a certain criterion. Pass + // either a string attribute to count by, or a function that returns the + // criterion. + _.countBy = group(function(result, value, key) { + if (_.has(result, key)) result[key]++; else result[key] = 1; + }); + + // Safely create a real, live array from anything iterable. + _.toArray = function(obj) { + if (!obj) return []; + if (_.isArray(obj)) return slice.call(obj); + if (isArrayLike(obj)) return _.map(obj, _.identity); + return _.values(obj); + }; + + // Return the number of elements in an object. + _.size = function(obj) { + if (obj == null) return 0; + return isArrayLike(obj) ? obj.length : _.keys(obj).length; + }; + + // Split a collection into two arrays: one whose elements all satisfy the given + // predicate, and one whose elements all do not satisfy the predicate. + _.partition = function(obj, predicate, context) { + predicate = cb(predicate, context); + var pass = [], fail = []; + _.each(obj, function(value, key, obj) { + (predicate(value, key, obj) ? pass : fail).push(value); + }); + return [pass, fail]; + }; + + // Array Functions + // --------------- + + // Get the first element of an array. Passing **n** will return the first N + // values in the array. Aliased as `head` and `take`. The **guard** check + // allows it to work with `_.map`. + _.first = _.head = _.take = function(array, n, guard) { + if (array == null) return void 0; + if (n == null || guard) return array[0]; + return _.initial(array, array.length - n); + }; + + // Returns everything but the last entry of the array. Especially useful on + // the arguments object. Passing **n** will return all the values in + // the array, excluding the last N. + _.initial = function(array, n, guard) { + return slice.call(array, 0, Math.max(0, array.length - (n == null || guard ? 1 : n))); + }; + + // Get the last element of an array. Passing **n** will return the last N + // values in the array. + _.last = function(array, n, guard) { + if (array == null) return void 0; + if (n == null || guard) return array[array.length - 1]; + return _.rest(array, Math.max(0, array.length - n)); + }; + + // Returns everything but the first entry of the array. Aliased as `tail` and `drop`. + // Especially useful on the arguments object. Passing an **n** will return + // the rest N values in the array. + _.rest = _.tail = _.drop = function(array, n, guard) { + return slice.call(array, n == null || guard ? 1 : n); + }; + + // Trim out all falsy values from an array. + _.compact = function(array) { + return _.filter(array, _.identity); + }; + + // Internal implementation of a recursive `flatten` function. + var flatten = function(input, shallow, strict, startIndex) { + var output = [], idx = 0; + for (var i = startIndex || 0, length = getLength(input); i < length; i++) { + var value = input[i]; + if (isArrayLike(value) && (_.isArray(value) || _.isArguments(value))) { + //flatten current level of array or arguments object + if (!shallow) value = flatten(value, shallow, strict); + var j = 0, len = value.length; + output.length += len; + while (j < len) { + output[idx++] = value[j++]; + } + } else if (!strict) { + output[idx++] = value; + } + } + return output; + }; + + // Flatten out an array, either recursively (by default), or just one level. + _.flatten = function(array, shallow) { + return flatten(array, shallow, false); + }; + + // Return a version of the array that does not contain the specified value(s). + _.without = function(array) { + return _.difference(array, slice.call(arguments, 1)); + }; + + // Produce a duplicate-free version of the array. If the array has already + // been sorted, you have the option of using a faster algorithm. + // Aliased as `unique`. + _.uniq = _.unique = function(array, isSorted, iteratee, context) { + if (!_.isBoolean(isSorted)) { + context = iteratee; + iteratee = isSorted; + isSorted = false; + } + if (iteratee != null) iteratee = cb(iteratee, context); + var result = []; + var seen = []; + for (var i = 0, length = getLength(array); i < length; i++) { + var value = array[i], + computed = iteratee ? iteratee(value, i, array) : value; + if (isSorted) { + if (!i || seen !== computed) result.push(value); + seen = computed; + } else if (iteratee) { + if (!_.contains(seen, computed)) { + seen.push(computed); + result.push(value); + } + } else if (!_.contains(result, value)) { + result.push(value); + } + } + return result; + }; + + // Produce an array that contains the union: each distinct element from all of + // the passed-in arrays. + _.union = function() { + return _.uniq(flatten(arguments, true, true)); + }; + + // Produce an array that contains every item shared between all the + // passed-in arrays. + _.intersection = function(array) { + var result = []; + var argsLength = arguments.length; + for (var i = 0, length = getLength(array); i < length; i++) { + var item = array[i]; + if (_.contains(result, item)) continue; + for (var j = 1; j < argsLength; j++) { + if (!_.contains(arguments[j], item)) break; + } + if (j === argsLength) result.push(item); + } + return result; + }; + + // Take the difference between one array and a number of other arrays. + // Only the elements present in just the first array will remain. + _.difference = function(array) { + var rest = flatten(arguments, true, true, 1); + return _.filter(array, function(value){ + return !_.contains(rest, value); + }); + }; + + // Zip together multiple lists into a single array -- elements that share + // an index go together. + _.zip = function() { + return _.unzip(arguments); + }; + + // Complement of _.zip. Unzip accepts an array of arrays and groups + // each array's elements on shared indices + _.unzip = function(array) { + var length = array && _.max(array, getLength).length || 0; + var result = Array(length); + + for (var index = 0; index < length; index++) { + result[index] = _.pluck(array, index); + } + return result; + }; + + // Converts lists into objects. Pass either a single array of `[key, value]` + // pairs, or two parallel arrays of the same length -- one of keys, and one of + // the corresponding values. + _.object = function(list, values) { + var result = {}; + for (var i = 0, length = getLength(list); i < length; i++) { + if (values) { + result[list[i]] = values[i]; + } else { + result[list[i][0]] = list[i][1]; + } + } + return result; + }; + + // Generator function to create the findIndex and findLastIndex functions + function createPredicateIndexFinder(dir) { + return function(array, predicate, context) { + predicate = cb(predicate, context); + var length = getLength(array); + var index = dir > 0 ? 0 : length - 1; + for (; index >= 0 && index < length; index += dir) { + if (predicate(array[index], index, array)) return index; + } + return -1; + }; + } + + // Returns the first index on an array-like that passes a predicate test + _.findIndex = createPredicateIndexFinder(1); + _.findLastIndex = createPredicateIndexFinder(-1); + + // Use a comparator function to figure out the smallest index at which + // an object should be inserted so as to maintain order. Uses binary search. + _.sortedIndex = function(array, obj, iteratee, context) { + iteratee = cb(iteratee, context, 1); + var value = iteratee(obj); + var low = 0, high = getLength(array); + while (low < high) { + var mid = Math.floor((low + high) / 2); + if (iteratee(array[mid]) < value) low = mid + 1; else high = mid; + } + return low; + }; + + // Generator function to create the indexOf and lastIndexOf functions + function createIndexFinder(dir, predicateFind, sortedIndex) { + return function(array, item, idx) { + var i = 0, length = getLength(array); + if (typeof idx == 'number') { + if (dir > 0) { + i = idx >= 0 ? idx : Math.max(idx + length, i); + } else { + length = idx >= 0 ? Math.min(idx + 1, length) : idx + length + 1; + } + } else if (sortedIndex && idx && length) { + idx = sortedIndex(array, item); + return array[idx] === item ? idx : -1; + } + if (item !== item) { + idx = predicateFind(slice.call(array, i, length), _.isNaN); + return idx >= 0 ? idx + i : -1; + } + for (idx = dir > 0 ? i : length - 1; idx >= 0 && idx < length; idx += dir) { + if (array[idx] === item) return idx; + } + return -1; + }; + } + + // Return the position of the first occurrence of an item in an array, + // or -1 if the item is not included in the array. + // If the array is large and already in sort order, pass `true` + // for **isSorted** to use binary search. + _.indexOf = createIndexFinder(1, _.findIndex, _.sortedIndex); + _.lastIndexOf = createIndexFinder(-1, _.findLastIndex); + + // Generate an integer Array containing an arithmetic progression. A port of + // the native Python `range()` function. See + // [the Python documentation](http://docs.python.org/library/functions.html#range). + _.range = function(start, stop, step) { + if (stop == null) { + stop = start || 0; + start = 0; + } + step = step || 1; + + var length = Math.max(Math.ceil((stop - start) / step), 0); + var range = Array(length); + + for (var idx = 0; idx < length; idx++, start += step) { + range[idx] = start; + } + + return range; + }; + + // Function (ahem) Functions + // ------------------ + + // Determines whether to execute a function as a constructor + // or a normal function with the provided arguments + var executeBound = function(sourceFunc, boundFunc, context, callingContext, args) { + if (!(callingContext instanceof boundFunc)) return sourceFunc.apply(context, args); + var self = baseCreate(sourceFunc.prototype); + var result = sourceFunc.apply(self, args); + if (_.isObject(result)) return result; + return self; + }; + + // Create a function bound to a given object (assigning `this`, and arguments, + // optionally). Delegates to **ECMAScript 5**'s native `Function.bind` if + // available. + _.bind = function(func, context) { + if (nativeBind && func.bind === nativeBind) return nativeBind.apply(func, slice.call(arguments, 1)); + if (!_.isFunction(func)) throw new TypeError('Bind must be called on a function'); + var args = slice.call(arguments, 2); + var bound = function() { + return executeBound(func, bound, context, this, args.concat(slice.call(arguments))); + }; + return bound; + }; + + // Partially apply a function by creating a version that has had some of its + // arguments pre-filled, without changing its dynamic `this` context. _ acts + // as a placeholder, allowing any combination of arguments to be pre-filled. + _.partial = function(func) { + var boundArgs = slice.call(arguments, 1); + var bound = function() { + var position = 0, length = boundArgs.length; + var args = Array(length); + for (var i = 0; i < length; i++) { + args[i] = boundArgs[i] === _ ? arguments[position++] : boundArgs[i]; + } + while (position < arguments.length) args.push(arguments[position++]); + return executeBound(func, bound, this, this, args); + }; + return bound; + }; + + // Bind a number of an object's methods to that object. Remaining arguments + // are the method names to be bound. Useful for ensuring that all callbacks + // defined on an object belong to it. + _.bindAll = function(obj) { + var i, length = arguments.length, key; + if (length <= 1) throw new Error('bindAll must be passed function names'); + for (i = 1; i < length; i++) { + key = arguments[i]; + obj[key] = _.bind(obj[key], obj); + } + return obj; + }; + + // Memoize an expensive function by storing its results. + _.memoize = function(func, hasher) { + var memoize = function(key) { + var cache = memoize.cache; + var address = '' + (hasher ? hasher.apply(this, arguments) : key); + if (!_.has(cache, address)) cache[address] = func.apply(this, arguments); + return cache[address]; + }; + memoize.cache = {}; + return memoize; + }; + + // Delays a function for the given number of milliseconds, and then calls + // it with the arguments supplied. + _.delay = function(func, wait) { + var args = slice.call(arguments, 2); + return setTimeout(function(){ + return func.apply(null, args); + }, wait); + }; + + // Defers a function, scheduling it to run after the current call stack has + // cleared. + _.defer = _.partial(_.delay, _, 1); + + // Returns a function, that, when invoked, will only be triggered at most once + // during a given window of time. Normally, the throttled function will run + // as much as it can, without ever going more than once per `wait` duration; + // but if you'd like to disable the execution on the leading edge, pass + // `{leading: false}`. To disable execution on the trailing edge, ditto. + _.throttle = function(func, wait, options) { + var context, args, result; + var timeout = null; + var previous = 0; + if (!options) options = {}; + var later = function() { + previous = options.leading === false ? 0 : _.now(); + timeout = null; + result = func.apply(context, args); + if (!timeout) context = args = null; + }; + return function() { + var now = _.now(); + if (!previous && options.leading === false) previous = now; + var remaining = wait - (now - previous); + context = this; + args = arguments; + if (remaining <= 0 || remaining > wait) { + if (timeout) { + clearTimeout(timeout); + timeout = null; + } + previous = now; + result = func.apply(context, args); + if (!timeout) context = args = null; + } else if (!timeout && options.trailing !== false) { + timeout = setTimeout(later, remaining); + } + return result; + }; + }; + + // Returns a function, that, as long as it continues to be invoked, will not + // be triggered. The function will be called after it stops being called for + // N milliseconds. If `immediate` is passed, trigger the function on the + // leading edge, instead of the trailing. + _.debounce = function(func, wait, immediate) { + var timeout, args, context, timestamp, result; + + var later = function() { + var last = _.now() - timestamp; + + if (last < wait && last >= 0) { + timeout = setTimeout(later, wait - last); + } else { + timeout = null; + if (!immediate) { + result = func.apply(context, args); + if (!timeout) context = args = null; + } + } + }; + + return function() { + context = this; + args = arguments; + timestamp = _.now(); + var callNow = immediate && !timeout; + if (!timeout) timeout = setTimeout(later, wait); + if (callNow) { + result = func.apply(context, args); + context = args = null; + } + + return result; + }; + }; + + // Returns the first function passed as an argument to the second, + // allowing you to adjust arguments, run code before and after, and + // conditionally execute the original function. + _.wrap = function(func, wrapper) { + return _.partial(wrapper, func); + }; + + // Returns a negated version of the passed-in predicate. + _.negate = function(predicate) { + return function() { + return !predicate.apply(this, arguments); + }; + }; + + // Returns a function that is the composition of a list of functions, each + // consuming the return value of the function that follows. + _.compose = function() { + var args = arguments; + var start = args.length - 1; + return function() { + var i = start; + var result = args[start].apply(this, arguments); + while (i--) result = args[i].call(this, result); + return result; + }; + }; + + // Returns a function that will only be executed on and after the Nth call. + _.after = function(times, func) { + return function() { + if (--times < 1) { + return func.apply(this, arguments); + } + }; + }; + + // Returns a function that will only be executed up to (but not including) the Nth call. + _.before = function(times, func) { + var memo; + return function() { + if (--times > 0) { + memo = func.apply(this, arguments); + } + if (times <= 1) func = null; + return memo; + }; + }; + + // Returns a function that will be executed at most one time, no matter how + // often you call it. Useful for lazy initialization. + _.once = _.partial(_.before, 2); + + // Object Functions + // ---------------- + + // Keys in IE < 9 that won't be iterated by `for key in ...` and thus missed. + var hasEnumBug = !{toString: null}.propertyIsEnumerable('toString'); + var nonEnumerableProps = ['valueOf', 'isPrototypeOf', 'toString', + 'propertyIsEnumerable', 'hasOwnProperty', 'toLocaleString']; + + function collectNonEnumProps(obj, keys) { + var nonEnumIdx = nonEnumerableProps.length; + var constructor = obj.constructor; + var proto = (_.isFunction(constructor) && constructor.prototype) || ObjProto; + + // Constructor is a special case. + var prop = 'constructor'; + if (_.has(obj, prop) && !_.contains(keys, prop)) keys.push(prop); + + while (nonEnumIdx--) { + prop = nonEnumerableProps[nonEnumIdx]; + if (prop in obj && obj[prop] !== proto[prop] && !_.contains(keys, prop)) { + keys.push(prop); + } + } + } + + // Retrieve the names of an object's own properties. + // Delegates to **ECMAScript 5**'s native `Object.keys` + _.keys = function(obj) { + if (!_.isObject(obj)) return []; + if (nativeKeys) return nativeKeys(obj); + var keys = []; + for (var key in obj) if (_.has(obj, key)) keys.push(key); + // Ahem, IE < 9. + if (hasEnumBug) collectNonEnumProps(obj, keys); + return keys; + }; + + // Retrieve all the property names of an object. + _.allKeys = function(obj) { + if (!_.isObject(obj)) return []; + var keys = []; + for (var key in obj) keys.push(key); + // Ahem, IE < 9. + if (hasEnumBug) collectNonEnumProps(obj, keys); + return keys; + }; + + // Retrieve the values of an object's properties. + _.values = function(obj) { + var keys = _.keys(obj); + var length = keys.length; + var values = Array(length); + for (var i = 0; i < length; i++) { + values[i] = obj[keys[i]]; + } + return values; + }; + + // Returns the results of applying the iteratee to each element of the object + // In contrast to _.map it returns an object + _.mapObject = function(obj, iteratee, context) { + iteratee = cb(iteratee, context); + var keys = _.keys(obj), + length = keys.length, + results = {}, + currentKey; + for (var index = 0; index < length; index++) { + currentKey = keys[index]; + results[currentKey] = iteratee(obj[currentKey], currentKey, obj); + } + return results; + }; + + // Convert an object into a list of `[key, value]` pairs. + _.pairs = function(obj) { + var keys = _.keys(obj); + var length = keys.length; + var pairs = Array(length); + for (var i = 0; i < length; i++) { + pairs[i] = [keys[i], obj[keys[i]]]; + } + return pairs; + }; + + // Invert the keys and values of an object. The values must be serializable. + _.invert = function(obj) { + var result = {}; + var keys = _.keys(obj); + for (var i = 0, length = keys.length; i < length; i++) { + result[obj[keys[i]]] = keys[i]; + } + return result; + }; + + // Return a sorted list of the function names available on the object. + // Aliased as `methods` + _.functions = _.methods = function(obj) { + var names = []; + for (var key in obj) { + if (_.isFunction(obj[key])) names.push(key); + } + return names.sort(); + }; + + // Extend a given object with all the properties in passed-in object(s). + _.extend = createAssigner(_.allKeys); + + // Assigns a given object with all the own properties in the passed-in object(s) + // (https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/assign) + _.extendOwn = _.assign = createAssigner(_.keys); + + // Returns the first key on an object that passes a predicate test + _.findKey = function(obj, predicate, context) { + predicate = cb(predicate, context); + var keys = _.keys(obj), key; + for (var i = 0, length = keys.length; i < length; i++) { + key = keys[i]; + if (predicate(obj[key], key, obj)) return key; + } + }; + + // Return a copy of the object only containing the whitelisted properties. + _.pick = function(object, oiteratee, context) { + var result = {}, obj = object, iteratee, keys; + if (obj == null) return result; + if (_.isFunction(oiteratee)) { + keys = _.allKeys(obj); + iteratee = optimizeCb(oiteratee, context); + } else { + keys = flatten(arguments, false, false, 1); + iteratee = function(value, key, obj) { return key in obj; }; + obj = Object(obj); + } + for (var i = 0, length = keys.length; i < length; i++) { + var key = keys[i]; + var value = obj[key]; + if (iteratee(value, key, obj)) result[key] = value; + } + return result; + }; + + // Return a copy of the object without the blacklisted properties. + _.omit = function(obj, iteratee, context) { + if (_.isFunction(iteratee)) { + iteratee = _.negate(iteratee); + } else { + var keys = _.map(flatten(arguments, false, false, 1), String); + iteratee = function(value, key) { + return !_.contains(keys, key); + }; + } + return _.pick(obj, iteratee, context); + }; + + // Fill in a given object with default properties. + _.defaults = createAssigner(_.allKeys, true); + + // Creates an object that inherits from the given prototype object. + // If additional properties are provided then they will be added to the + // created object. + _.create = function(prototype, props) { + var result = baseCreate(prototype); + if (props) _.extendOwn(result, props); + return result; + }; + + // Create a (shallow-cloned) duplicate of an object. + _.clone = function(obj) { + if (!_.isObject(obj)) return obj; + return _.isArray(obj) ? obj.slice() : _.extend({}, obj); + }; + + // Invokes interceptor with the obj, and then returns obj. + // The primary purpose of this method is to "tap into" a method chain, in + // order to perform operations on intermediate results within the chain. + _.tap = function(obj, interceptor) { + interceptor(obj); + return obj; + }; + + // Returns whether an object has a given set of `key:value` pairs. + _.isMatch = function(object, attrs) { + var keys = _.keys(attrs), length = keys.length; + if (object == null) return !length; + var obj = Object(object); + for (var i = 0; i < length; i++) { + var key = keys[i]; + if (attrs[key] !== obj[key] || !(key in obj)) return false; + } + return true; + }; + + + // Internal recursive comparison function for `isEqual`. + var eq = function(a, b, aStack, bStack) { + // Identical objects are equal. `0 === -0`, but they aren't identical. + // See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal). + if (a === b) return a !== 0 || 1 / a === 1 / b; + // A strict comparison is necessary because `null == undefined`. + if (a == null || b == null) return a === b; + // Unwrap any wrapped objects. + if (a instanceof _) a = a._wrapped; + if (b instanceof _) b = b._wrapped; + // Compare `[[Class]]` names. + var className = toString.call(a); + if (className !== toString.call(b)) return false; + switch (className) { + // Strings, numbers, regular expressions, dates, and booleans are compared by value. + case '[object RegExp]': + // RegExps are coerced to strings for comparison (Note: '' + /a/i === '/a/i') + case '[object String]': + // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is + // equivalent to `new String("5")`. + return '' + a === '' + b; + case '[object Number]': + // `NaN`s are equivalent, but non-reflexive. + // Object(NaN) is equivalent to NaN + if (+a !== +a) return +b !== +b; + // An `egal` comparison is performed for other numeric values. + return +a === 0 ? 1 / +a === 1 / b : +a === +b; + case '[object Date]': + case '[object Boolean]': + // Coerce dates and booleans to numeric primitive values. Dates are compared by their + // millisecond representations. Note that invalid dates with millisecond representations + // of `NaN` are not equivalent. + return +a === +b; + } + + var areArrays = className === '[object Array]'; + if (!areArrays) { + if (typeof a != 'object' || typeof b != 'object') return false; + + // Objects with different constructors are not equivalent, but `Object`s or `Array`s + // from different frames are. + var aCtor = a.constructor, bCtor = b.constructor; + if (aCtor !== bCtor && !(_.isFunction(aCtor) && aCtor instanceof aCtor && + _.isFunction(bCtor) && bCtor instanceof bCtor) + && ('constructor' in a && 'constructor' in b)) { + return false; + } + } + // Assume equality for cyclic structures. The algorithm for detecting cyclic + // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`. + + // Initializing stack of traversed objects. + // It's done here since we only need them for objects and arrays comparison. + aStack = aStack || []; + bStack = bStack || []; + var length = aStack.length; + while (length--) { + // Linear search. Performance is inversely proportional to the number of + // unique nested structures. + if (aStack[length] === a) return bStack[length] === b; + } + + // Add the first object to the stack of traversed objects. + aStack.push(a); + bStack.push(b); + + // Recursively compare objects and arrays. + if (areArrays) { + // Compare array lengths to determine if a deep comparison is necessary. + length = a.length; + if (length !== b.length) return false; + // Deep compare the contents, ignoring non-numeric properties. + while (length--) { + if (!eq(a[length], b[length], aStack, bStack)) return false; + } + } else { + // Deep compare objects. + var keys = _.keys(a), key; + length = keys.length; + // Ensure that both objects contain the same number of properties before comparing deep equality. + if (_.keys(b).length !== length) return false; + while (length--) { + // Deep compare each member + key = keys[length]; + if (!(_.has(b, key) && eq(a[key], b[key], aStack, bStack))) return false; + } + } + // Remove the first object from the stack of traversed objects. + aStack.pop(); + bStack.pop(); + return true; + }; + + // Perform a deep comparison to check if two objects are equal. + _.isEqual = function(a, b) { + return eq(a, b); + }; + + // Is a given array, string, or object empty? + // An "empty" object has no enumerable own-properties. + _.isEmpty = function(obj) { + if (obj == null) return true; + if (isArrayLike(obj) && (_.isArray(obj) || _.isString(obj) || _.isArguments(obj))) return obj.length === 0; + return _.keys(obj).length === 0; + }; + + // Is a given value a DOM element? + _.isElement = function(obj) { + return !!(obj && obj.nodeType === 1); + }; + + // Is a given value an array? + // Delegates to ECMA5's native Array.isArray + _.isArray = nativeIsArray || function(obj) { + return toString.call(obj) === '[object Array]'; + }; + + // Is a given variable an object? + _.isObject = function(obj) { + var type = typeof obj; + return type === 'function' || type === 'object' && !!obj; + }; + + // Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp, isError. + _.each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp', 'Error'], function(name) { + _['is' + name] = function(obj) { + return toString.call(obj) === '[object ' + name + ']'; + }; + }); + + // Define a fallback version of the method in browsers (ahem, IE < 9), where + // there isn't any inspectable "Arguments" type. + if (!_.isArguments(arguments)) { + _.isArguments = function(obj) { + return _.has(obj, 'callee'); + }; + } + + // Optimize `isFunction` if appropriate. Work around some typeof bugs in old v8, + // IE 11 (#1621), and in Safari 8 (#1929). + if (typeof /./ != 'function' && typeof Int8Array != 'object') { + _.isFunction = function(obj) { + return typeof obj == 'function' || false; + }; + } + + // Is a given object a finite number? + _.isFinite = function(obj) { + return isFinite(obj) && !isNaN(parseFloat(obj)); + }; + + // Is the given value `NaN`? (NaN is the only number which does not equal itself). + _.isNaN = function(obj) { + return _.isNumber(obj) && obj !== +obj; + }; + + // Is a given value a boolean? + _.isBoolean = function(obj) { + return obj === true || obj === false || toString.call(obj) === '[object Boolean]'; + }; + + // Is a given value equal to null? + _.isNull = function(obj) { + return obj === null; + }; + + // Is a given variable undefined? + _.isUndefined = function(obj) { + return obj === void 0; + }; + + // Shortcut function for checking if an object has a given property directly + // on itself (in other words, not on a prototype). + _.has = function(obj, key) { + return obj != null && hasOwnProperty.call(obj, key); + }; + + // Utility Functions + // ----------------- + + // Run Underscore.js in *noConflict* mode, returning the `_` variable to its + // previous owner. Returns a reference to the Underscore object. + _.noConflict = function() { + root._ = previousUnderscore; + return this; + }; + + // Keep the identity function around for default iteratees. + _.identity = function(value) { + return value; + }; + + // Predicate-generating functions. Often useful outside of Underscore. + _.constant = function(value) { + return function() { + return value; + }; + }; + + _.noop = function(){}; + + _.property = property; + + // Generates a function for a given object that returns a given property. + _.propertyOf = function(obj) { + return obj == null ? function(){} : function(key) { + return obj[key]; + }; + }; + + // Returns a predicate for checking whether an object has a given set of + // `key:value` pairs. + _.matcher = _.matches = function(attrs) { + attrs = _.extendOwn({}, attrs); + return function(obj) { + return _.isMatch(obj, attrs); + }; + }; + + // Run a function **n** times. + _.times = function(n, iteratee, context) { + var accum = Array(Math.max(0, n)); + iteratee = optimizeCb(iteratee, context, 1); + for (var i = 0; i < n; i++) accum[i] = iteratee(i); + return accum; + }; + + // Return a random integer between min and max (inclusive). + _.random = function(min, max) { + if (max == null) { + max = min; + min = 0; + } + return min + Math.floor(Math.random() * (max - min + 1)); + }; + + // A (possibly faster) way to get the current timestamp as an integer. + _.now = Date.now || function() { + return new Date().getTime(); + }; + + // List of HTML entities for escaping. + var escapeMap = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''', + '`': '`' + }; + var unescapeMap = _.invert(escapeMap); + + // Functions for escaping and unescaping strings to/from HTML interpolation. + var createEscaper = function(map) { + var escaper = function(match) { + return map[match]; + }; + // Regexes for identifying a key that needs to be escaped + var source = '(?:' + _.keys(map).join('|') + ')'; + var testRegexp = RegExp(source); + var replaceRegexp = RegExp(source, 'g'); + return function(string) { + string = string == null ? '' : '' + string; + return testRegexp.test(string) ? string.replace(replaceRegexp, escaper) : string; + }; + }; + _.escape = createEscaper(escapeMap); + _.unescape = createEscaper(unescapeMap); + + // If the value of the named `property` is a function then invoke it with the + // `object` as context; otherwise, return it. + _.result = function(object, property, fallback) { + var value = object == null ? void 0 : object[property]; + if (value === void 0) { + value = fallback; + } + return _.isFunction(value) ? value.call(object) : value; + }; + + // Generate a unique integer id (unique within the entire client session). + // Useful for temporary DOM ids. + var idCounter = 0; + _.uniqueId = function(prefix) { + var id = ++idCounter + ''; + return prefix ? prefix + id : id; + }; + + // By default, Underscore uses ERB-style template delimiters, change the + // following template settings to use alternative delimiters. + _.templateSettings = { + evaluate : /<%([\s\S]+?)%>/g, + interpolate : /<%=([\s\S]+?)%>/g, + escape : /<%-([\s\S]+?)%>/g + }; + + // When customizing `templateSettings`, if you don't want to define an + // interpolation, evaluation or escaping regex, we need one that is + // guaranteed not to match. + var noMatch = /(.)^/; + + // Certain characters need to be escaped so that they can be put into a + // string literal. + var escapes = { + "'": "'", + '\\': '\\', + '\r': 'r', + '\n': 'n', + '\u2028': 'u2028', + '\u2029': 'u2029' + }; + + var escaper = /\\|'|\r|\n|\u2028|\u2029/g; + + var escapeChar = function(match) { + return '\\' + escapes[match]; + }; + + // JavaScript micro-templating, similar to John Resig's implementation. + // Underscore templating handles arbitrary delimiters, preserves whitespace, + // and correctly escapes quotes within interpolated code. + // NB: `oldSettings` only exists for backwards compatibility. + _.template = function(text, settings, oldSettings) { + if (!settings && oldSettings) settings = oldSettings; + settings = _.defaults({}, settings, _.templateSettings); + + // Combine delimiters into one regular expression via alternation. + var matcher = RegExp([ + (settings.escape || noMatch).source, + (settings.interpolate || noMatch).source, + (settings.evaluate || noMatch).source + ].join('|') + '|$', 'g'); + + // Compile the template source, escaping string literals appropriately. + var index = 0; + var source = "__p+='"; + text.replace(matcher, function(match, escape, interpolate, evaluate, offset) { + source += text.slice(index, offset).replace(escaper, escapeChar); + index = offset + match.length; + + if (escape) { + source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'"; + } else if (interpolate) { + source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'"; + } else if (evaluate) { + source += "';\n" + evaluate + "\n__p+='"; + } + + // Adobe VMs need the match returned to produce the correct offest. + return match; + }); + source += "';\n"; + + // If a variable is not specified, place data values in local scope. + if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n'; + + source = "var __t,__p='',__j=Array.prototype.join," + + "print=function(){__p+=__j.call(arguments,'');};\n" + + source + 'return __p;\n'; + + try { + var render = new Function(settings.variable || 'obj', '_', source); + } catch (e) { + e.source = source; + throw e; + } + + var template = function(data) { + return render.call(this, data, _); + }; + + // Provide the compiled source as a convenience for precompilation. + var argument = settings.variable || 'obj'; + template.source = 'function(' + argument + '){\n' + source + '}'; + + return template; + }; + + // Add a "chain" function. Start chaining a wrapped Underscore object. + _.chain = function(obj) { + var instance = _(obj); + instance._chain = true; + return instance; + }; + + // OOP + // --------------- + // If Underscore is called as a function, it returns a wrapped object that + // can be used OO-style. This wrapper holds altered versions of all the + // underscore functions. Wrapped objects may be chained. + + // Helper function to continue chaining intermediate results. + var result = function(instance, obj) { + return instance._chain ? _(obj).chain() : obj; + }; + + // Add your own custom functions to the Underscore object. + _.mixin = function(obj) { + _.each(_.functions(obj), function(name) { + var func = _[name] = obj[name]; + _.prototype[name] = function() { + var args = [this._wrapped]; + push.apply(args, arguments); + return result(this, func.apply(_, args)); + }; + }); + }; + + // Add all of the Underscore functions to the wrapper object. + _.mixin(_); + + // Add all mutator Array functions to the wrapper. + _.each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) { + var method = ArrayProto[name]; + _.prototype[name] = function() { + var obj = this._wrapped; + method.apply(obj, arguments); + if ((name === 'shift' || name === 'splice') && obj.length === 0) delete obj[0]; + return result(this, obj); + }; + }); + + // Add all accessor Array functions to the wrapper. + _.each(['concat', 'join', 'slice'], function(name) { + var method = ArrayProto[name]; + _.prototype[name] = function() { + return result(this, method.apply(this._wrapped, arguments)); + }; + }); + + // Extracts the result from a wrapped and chained object. + _.prototype.value = function() { + return this._wrapped; + }; + + // Provide unwrapping proxy for some methods used in engine operations + // such as arithmetic and JSON stringification. + _.prototype.valueOf = _.prototype.toJSON = _.prototype.value; + + _.prototype.toString = function() { + return '' + this._wrapped; + }; + + // AMD registration happens at the end for compatibility with AMD loaders + // that may not enforce next-turn semantics on modules. Even though general + // practice for AMD registration is to be anonymous, underscore registers + // as a named module because, like jQuery, it is a base library that is + // popular enough to be bundled in a third party lib, but not be part of + // an AMD load request. Those cases could generate an error when an + // anonymous define() is called outside of a loader request. + if (typeof define === 'function' && define.amd) { + define('underscore', [], function() { + return _; + }); + } +}.call(this)); + +},{}],26:[function(require,module,exports){ +arguments[4][19][0].apply(exports,arguments) +},{"dup":19}],27:[function(require,module,exports){ +module.exports = function isBuffer(arg) { + return arg && typeof arg === 'object' + && typeof arg.copy === 'function' + && typeof arg.fill === 'function' + && typeof arg.readUInt8 === 'function'; +} +},{}],28:[function(require,module,exports){ +(function (process,global){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +var formatRegExp = /%[sdj%]/g; +exports.format = function(f) { + if (!isString(f)) { + var objects = []; + for (var i = 0; i < arguments.length; i++) { + objects.push(inspect(arguments[i])); + } + return objects.join(' '); + } + + var i = 1; + var args = arguments; + var len = args.length; + var str = String(f).replace(formatRegExp, function(x) { + if (x === '%%') return '%'; + if (i >= len) return x; + switch (x) { + case '%s': return String(args[i++]); + case '%d': return Number(args[i++]); + case '%j': + try { + return JSON.stringify(args[i++]); + } catch (_) { + return '[Circular]'; + } + default: + return x; + } + }); + for (var x = args[i]; i < len; x = args[++i]) { + if (isNull(x) || !isObject(x)) { + str += ' ' + x; + } else { + str += ' ' + inspect(x); + } + } + return str; +}; + + +// Mark that a method should not be used. +// Returns a modified function which warns once by default. +// If --no-deprecation is set, then it is a no-op. +exports.deprecate = function(fn, msg) { + // Allow for deprecating things in the process of starting up. + if (isUndefined(global.process)) { + return function() { + return exports.deprecate(fn, msg).apply(this, arguments); + }; + } + + if (process.noDeprecation === true) { + return fn; + } + + var warned = false; + function deprecated() { + if (!warned) { + if (process.throwDeprecation) { + throw new Error(msg); + } else if (process.traceDeprecation) { + console.trace(msg); + } else { + console.error(msg); + } + warned = true; + } + return fn.apply(this, arguments); + } + + return deprecated; +}; + + +var debugs = {}; +var debugEnviron; +exports.debuglog = function(set) { + if (isUndefined(debugEnviron)) + debugEnviron = process.env.NODE_DEBUG || ''; + set = set.toUpperCase(); + if (!debugs[set]) { + if (new RegExp('\\b' + set + '\\b', 'i').test(debugEnviron)) { + var pid = process.pid; + debugs[set] = function() { + var msg = exports.format.apply(exports, arguments); + console.error('%s %d: %s', set, pid, msg); + }; + } else { + debugs[set] = function() {}; + } + } + return debugs[set]; +}; + + +/** + * Echos the value of a value. Trys to print the value out + * in the best way possible given the different types. + * + * @param {Object} obj The object to print out. + * @param {Object} opts Optional options object that alters the output. + */ +/* legacy: obj, showHidden, depth, colors*/ +function inspect(obj, opts) { + // default options + var ctx = { + seen: [], + stylize: stylizeNoColor + }; + // legacy... + if (arguments.length >= 3) ctx.depth = arguments[2]; + if (arguments.length >= 4) ctx.colors = arguments[3]; + if (isBoolean(opts)) { + // legacy... + ctx.showHidden = opts; + } else if (opts) { + // got an "options" object + exports._extend(ctx, opts); + } + // set default options + if (isUndefined(ctx.showHidden)) ctx.showHidden = false; + if (isUndefined(ctx.depth)) ctx.depth = 2; + if (isUndefined(ctx.colors)) ctx.colors = false; + if (isUndefined(ctx.customInspect)) ctx.customInspect = true; + if (ctx.colors) ctx.stylize = stylizeWithColor; + return formatValue(ctx, obj, ctx.depth); +} +exports.inspect = inspect; + + +// http://en.wikipedia.org/wiki/ANSI_escape_code#graphics +inspect.colors = { + 'bold' : [1, 22], + 'italic' : [3, 23], + 'underline' : [4, 24], + 'inverse' : [7, 27], + 'white' : [37, 39], + 'grey' : [90, 39], + 'black' : [30, 39], + 'blue' : [34, 39], + 'cyan' : [36, 39], + 'green' : [32, 39], + 'magenta' : [35, 39], + 'red' : [31, 39], + 'yellow' : [33, 39] +}; + +// Don't use 'blue' not visible on cmd.exe +inspect.styles = { + 'special': 'cyan', + 'number': 'yellow', + 'boolean': 'yellow', + 'undefined': 'grey', + 'null': 'bold', + 'string': 'green', + 'date': 'magenta', + // "name": intentionally not styling + 'regexp': 'red' +}; + + +function stylizeWithColor(str, styleType) { + var style = inspect.styles[styleType]; + + if (style) { + return '\u001b[' + inspect.colors[style][0] + 'm' + str + + '\u001b[' + inspect.colors[style][1] + 'm'; + } else { + return str; + } +} + + +function stylizeNoColor(str, styleType) { + return str; +} + + +function arrayToHash(array) { + var hash = {}; + + array.forEach(function(val, idx) { + hash[val] = true; + }); + + return hash; +} + + +function formatValue(ctx, value, recurseTimes) { + // Provide a hook for user-specified inspect functions. + // Check that value is an object with an inspect function on it + if (ctx.customInspect && + value && + isFunction(value.inspect) && + // Filter out the util module, it's inspect function is special + value.inspect !== exports.inspect && + // Also filter out any prototype objects using the circular check. + !(value.constructor && value.constructor.prototype === value)) { + var ret = value.inspect(recurseTimes, ctx); + if (!isString(ret)) { + ret = formatValue(ctx, ret, recurseTimes); + } + return ret; + } + + // Primitive types cannot have properties + var primitive = formatPrimitive(ctx, value); + if (primitive) { + return primitive; + } + + // Look up the keys of the object. + var keys = Object.keys(value); + var visibleKeys = arrayToHash(keys); + + if (ctx.showHidden) { + keys = Object.getOwnPropertyNames(value); + } + + // IE doesn't make error fields non-enumerable + // http://msdn.microsoft.com/en-us/library/ie/dww52sbt(v=vs.94).aspx + if (isError(value) + && (keys.indexOf('message') >= 0 || keys.indexOf('description') >= 0)) { + return formatError(value); + } + + // Some type of object without properties can be shortcutted. + if (keys.length === 0) { + if (isFunction(value)) { + var name = value.name ? ': ' + value.name : ''; + return ctx.stylize('[Function' + name + ']', 'special'); + } + if (isRegExp(value)) { + return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp'); + } + if (isDate(value)) { + return ctx.stylize(Date.prototype.toString.call(value), 'date'); + } + if (isError(value)) { + return formatError(value); + } + } + + var base = '', array = false, braces = ['{', '}']; + + // Make Array say that they are Array + if (isArray(value)) { + array = true; + braces = ['[', ']']; + } + + // Make functions say that they are functions + if (isFunction(value)) { + var n = value.name ? ': ' + value.name : ''; + base = ' [Function' + n + ']'; + } + + // Make RegExps say that they are RegExps + if (isRegExp(value)) { + base = ' ' + RegExp.prototype.toString.call(value); + } + + // Make dates with properties first say the date + if (isDate(value)) { + base = ' ' + Date.prototype.toUTCString.call(value); + } + + // Make error with message first say the error + if (isError(value)) { + base = ' ' + formatError(value); + } + + if (keys.length === 0 && (!array || value.length == 0)) { + return braces[0] + base + braces[1]; + } + + if (recurseTimes < 0) { + if (isRegExp(value)) { + return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp'); + } else { + return ctx.stylize('[Object]', 'special'); + } + } + + ctx.seen.push(value); + + var output; + if (array) { + output = formatArray(ctx, value, recurseTimes, visibleKeys, keys); + } else { + output = keys.map(function(key) { + return formatProperty(ctx, value, recurseTimes, visibleKeys, key, array); + }); + } + + ctx.seen.pop(); + + return reduceToSingleString(output, base, braces); +} + + +function formatPrimitive(ctx, value) { + if (isUndefined(value)) + return ctx.stylize('undefined', 'undefined'); + if (isString(value)) { + var simple = '\'' + JSON.stringify(value).replace(/^"|"$/g, '') + .replace(/'/g, "\\'") + .replace(/\\"/g, '"') + '\''; + return ctx.stylize(simple, 'string'); + } + if (isNumber(value)) + return ctx.stylize('' + value, 'number'); + if (isBoolean(value)) + return ctx.stylize('' + value, 'boolean'); + // For some reason typeof null is "object", so special case here. + if (isNull(value)) + return ctx.stylize('null', 'null'); +} + + +function formatError(value) { + return '[' + Error.prototype.toString.call(value) + ']'; +} + + +function formatArray(ctx, value, recurseTimes, visibleKeys, keys) { + var output = []; + for (var i = 0, l = value.length; i < l; ++i) { + if (hasOwnProperty(value, String(i))) { + output.push(formatProperty(ctx, value, recurseTimes, visibleKeys, + String(i), true)); + } else { + output.push(''); + } + } + keys.forEach(function(key) { + if (!key.match(/^\d+$/)) { + output.push(formatProperty(ctx, value, recurseTimes, visibleKeys, + key, true)); + } + }); + return output; +} + + +function formatProperty(ctx, value, recurseTimes, visibleKeys, key, array) { + var name, str, desc; + desc = Object.getOwnPropertyDescriptor(value, key) || { value: value[key] }; + if (desc.get) { + if (desc.set) { + str = ctx.stylize('[Getter/Setter]', 'special'); + } else { + str = ctx.stylize('[Getter]', 'special'); + } + } else { + if (desc.set) { + str = ctx.stylize('[Setter]', 'special'); + } + } + if (!hasOwnProperty(visibleKeys, key)) { + name = '[' + key + ']'; + } + if (!str) { + if (ctx.seen.indexOf(desc.value) < 0) { + if (isNull(recurseTimes)) { + str = formatValue(ctx, desc.value, null); + } else { + str = formatValue(ctx, desc.value, recurseTimes - 1); + } + if (str.indexOf('\n') > -1) { + if (array) { + str = str.split('\n').map(function(line) { + return ' ' + line; + }).join('\n').substr(2); + } else { + str = '\n' + str.split('\n').map(function(line) { + return ' ' + line; + }).join('\n'); + } + } + } else { + str = ctx.stylize('[Circular]', 'special'); + } + } + if (isUndefined(name)) { + if (array && key.match(/^\d+$/)) { + return str; + } + name = JSON.stringify('' + key); + if (name.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)) { + name = name.substr(1, name.length - 2); + name = ctx.stylize(name, 'name'); + } else { + name = name.replace(/'/g, "\\'") + .replace(/\\"/g, '"') + .replace(/(^"|"$)/g, "'"); + name = ctx.stylize(name, 'string'); + } + } + + return name + ': ' + str; +} + + +function reduceToSingleString(output, base, braces) { + var numLinesEst = 0; + var length = output.reduce(function(prev, cur) { + numLinesEst++; + if (cur.indexOf('\n') >= 0) numLinesEst++; + return prev + cur.replace(/\u001b\[\d\d?m/g, '').length + 1; + }, 0); + + if (length > 60) { + return braces[0] + + (base === '' ? '' : base + '\n ') + + ' ' + + output.join(',\n ') + + ' ' + + braces[1]; + } + + return braces[0] + base + ' ' + output.join(', ') + ' ' + braces[1]; +} + + +// NOTE: These type checking functions intentionally don't use `instanceof` +// because it is fragile and can be easily faked with `Object.create()`. +function isArray(ar) { + return Array.isArray(ar); +} +exports.isArray = isArray; + +function isBoolean(arg) { + return typeof arg === 'boolean'; +} +exports.isBoolean = isBoolean; + +function isNull(arg) { + return arg === null; +} +exports.isNull = isNull; + +function isNullOrUndefined(arg) { + return arg == null; +} +exports.isNullOrUndefined = isNullOrUndefined; + +function isNumber(arg) { + return typeof arg === 'number'; +} +exports.isNumber = isNumber; + +function isString(arg) { + return typeof arg === 'string'; +} +exports.isString = isString; + +function isSymbol(arg) { + return typeof arg === 'symbol'; +} +exports.isSymbol = isSymbol; + +function isUndefined(arg) { + return arg === void 0; +} +exports.isUndefined = isUndefined; + +function isRegExp(re) { + return isObject(re) && objectToString(re) === '[object RegExp]'; +} +exports.isRegExp = isRegExp; + +function isObject(arg) { + return typeof arg === 'object' && arg !== null; +} +exports.isObject = isObject; + +function isDate(d) { + return isObject(d) && objectToString(d) === '[object Date]'; +} +exports.isDate = isDate; + +function isError(e) { + return isObject(e) && + (objectToString(e) === '[object Error]' || e instanceof Error); +} +exports.isError = isError; + +function isFunction(arg) { + return typeof arg === 'function'; +} +exports.isFunction = isFunction; + +function isPrimitive(arg) { + return arg === null || + typeof arg === 'boolean' || + typeof arg === 'number' || + typeof arg === 'string' || + typeof arg === 'symbol' || // ES6 symbol + typeof arg === 'undefined'; +} +exports.isPrimitive = isPrimitive; + +exports.isBuffer = require('./support/isBuffer'); + +function objectToString(o) { + return Object.prototype.toString.call(o); +} + + +function pad(n) { + return n < 10 ? '0' + n.toString(10) : n.toString(10); +} + + +var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', + 'Oct', 'Nov', 'Dec']; + +// 26 Feb 16:19:34 +function timestamp() { + var d = new Date(); + var time = [pad(d.getHours()), + pad(d.getMinutes()), + pad(d.getSeconds())].join(':'); + return [d.getDate(), months[d.getMonth()], time].join(' '); +} + + +// log is just a thin wrapper to console.log that prepends a timestamp +exports.log = function() { + console.log('%s - %s', timestamp(), exports.format.apply(exports, arguments)); +}; + + +/** + * Inherit the prototype methods from one constructor into another. + * + * The Function.prototype.inherits from lang.js rewritten as a standalone + * function (not on Function.prototype). NOTE: If this file is to be loaded + * during bootstrapping this function needs to be rewritten using some native + * functions as prototype setup using normal JavaScript does not work as + * expected during bootstrapping (see mirror.js in r114903). + * + * @param {function} ctor Constructor function which needs to inherit the + * prototype. + * @param {function} superCtor Constructor function to inherit prototype from. + */ +exports.inherits = require('inherits'); + +exports._extend = function(origin, add) { + // Don't do anything if add isn't an object + if (!add || !isObject(add)) return origin; + + var keys = Object.keys(add); + var i = keys.length; + while (i--) { + origin[keys[i]] = add[keys[i]]; + } + return origin; +}; + +function hasOwnProperty(obj, prop) { + return Object.prototype.hasOwnProperty.call(obj, prop); +} + +}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"./support/isBuffer":27,"_process":24,"inherits":26}],29:[function(require,module,exports){ +// Returns a wrapper function that returns a wrapped callback +// The wrapper function should do some stuff, and return a +// presumably different callback function. +// This makes sure that own properties are retained, so that +// decorations and such are not lost along the way. +module.exports = wrappy +function wrappy (fn, cb) { + if (fn && cb) return wrappy(fn)(cb) + + if (typeof fn !== 'function') + throw new TypeError('need wrapper function') + + Object.keys(fn).forEach(function (k) { + wrapper[k] = fn[k] + }) + + return wrapper + + function wrapper() { + var args = new Array(arguments.length) + for (var i = 0; i < args.length; i++) { + args[i] = arguments[i] + } + var ret = fn.apply(this, args) + var cb = args[args.length-1] + if (typeof ret === 'function' && ret !== cb) { + Object.keys(cb).forEach(function (k) { + ret[k] = cb[k] + }) + } + return ret + } +} + +},{}]},{},[7])(7) +}); \ No newline at end of file diff --git a/assets/javascripts/workers/search.c7c1ca2c.min.js b/assets/javascripts/workers/search.c7c1ca2c.min.js new file mode 100644 index 0000000..2d6f767 --- /dev/null +++ b/assets/javascripts/workers/search.c7c1ca2c.min.js @@ -0,0 +1,2 @@ +"use strict";(()=>{var xe=Object.create;var G=Object.defineProperty,ve=Object.defineProperties,Se=Object.getOwnPropertyDescriptor,Te=Object.getOwnPropertyDescriptors,Qe=Object.getOwnPropertyNames,J=Object.getOwnPropertySymbols,Ee=Object.getPrototypeOf,Z=Object.prototype.hasOwnProperty,be=Object.prototype.propertyIsEnumerable;var K=Math.pow,X=(t,e,r)=>e in t?G(t,e,{enumerable:!0,configurable:!0,writable:!0,value:r}):t[e]=r,_=(t,e)=>{for(var r in e||(e={}))Z.call(e,r)&&X(t,r,e[r]);if(J)for(var r of J(e))be.call(e,r)&&X(t,r,e[r]);return t},B=(t,e)=>ve(t,Te(e));var Le=(t,e)=>()=>(e||t((e={exports:{}}).exports,e),e.exports);var we=(t,e,r,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of Qe(e))!Z.call(t,i)&&i!==r&&G(t,i,{get:()=>e[i],enumerable:!(n=Se(e,i))||n.enumerable});return t};var Pe=(t,e,r)=>(r=t!=null?xe(Ee(t)):{},we(e||!t||!t.__esModule?G(r,"default",{value:t,enumerable:!0}):r,t));var W=(t,e,r)=>new Promise((n,i)=>{var s=u=>{try{a(r.next(u))}catch(c){i(c)}},o=u=>{try{a(r.throw(u))}catch(c){i(c)}},a=u=>u.done?n(u.value):Promise.resolve(u.value).then(s,o);a((r=r.apply(t,e)).next())});var re=Le((ee,te)=>{(function(){var t=function(e){var r=new t.Builder;return r.pipeline.add(t.trimmer,t.stopWordFilter,t.stemmer),r.searchPipeline.add(t.stemmer),e.call(r,r),r.build()};t.version="2.3.9";t.utils={},t.utils.warn=function(e){return function(r){e.console&&console.warn&&console.warn(r)}}(this),t.utils.asString=function(e){return e==null?"":e.toString()},t.utils.clone=function(e){if(e==null)return e;for(var r=Object.create(null),n=Object.keys(e),i=0;i0){var f=t.utils.clone(r)||{};f.position=[a,c],f.index=s.length,s.push(new t.Token(n.slice(a,o),f))}a=o+1}}return s},t.tokenizer.separator=/[\s\-]+/;t.Pipeline=function(){this._stack=[]},t.Pipeline.registeredFunctions=Object.create(null),t.Pipeline.registerFunction=function(e,r){r in this.registeredFunctions&&t.utils.warn("Overwriting existing registered function: "+r),e.label=r,t.Pipeline.registeredFunctions[e.label]=e},t.Pipeline.warnIfFunctionNotRegistered=function(e){var r=e.label&&e.label in this.registeredFunctions;r||t.utils.warn(`Function is not registered with pipeline. This may cause problems when serialising the index. +`,e)},t.Pipeline.load=function(e){var r=new t.Pipeline;return e.forEach(function(n){var i=t.Pipeline.registeredFunctions[n];if(i)r.add(i);else throw new Error("Cannot load unregistered function: "+n)}),r},t.Pipeline.prototype.add=function(){var e=Array.prototype.slice.call(arguments);e.forEach(function(r){t.Pipeline.warnIfFunctionNotRegistered(r),this._stack.push(r)},this)},t.Pipeline.prototype.after=function(e,r){t.Pipeline.warnIfFunctionNotRegistered(r);var n=this._stack.indexOf(e);if(n==-1)throw new Error("Cannot find existingFn");n=n+1,this._stack.splice(n,0,r)},t.Pipeline.prototype.before=function(e,r){t.Pipeline.warnIfFunctionNotRegistered(r);var n=this._stack.indexOf(e);if(n==-1)throw new Error("Cannot find existingFn");this._stack.splice(n,0,r)},t.Pipeline.prototype.remove=function(e){var r=this._stack.indexOf(e);r!=-1&&this._stack.splice(r,1)},t.Pipeline.prototype.run=function(e){for(var r=this._stack.length,n=0;n1&&(oe&&(n=s),o!=e);)i=n-r,s=r+Math.floor(i/2),o=this.elements[s*2];if(o==e||o>e)return s*2;if(ou?f+=2:a==u&&(r+=n[c+1]*i[f+1],c+=2,f+=2);return r},t.Vector.prototype.similarity=function(e){return this.dot(e)/this.magnitude()||0},t.Vector.prototype.toArray=function(){for(var e=new Array(this.elements.length/2),r=1,n=0;r0){var o=s.str.charAt(0),a;o in s.node.edges?a=s.node.edges[o]:(a=new t.TokenSet,s.node.edges[o]=a),s.str.length==1&&(a.final=!0),i.push({node:a,editsRemaining:s.editsRemaining,str:s.str.slice(1)})}if(s.editsRemaining!=0){if("*"in s.node.edges)var u=s.node.edges["*"];else{var u=new t.TokenSet;s.node.edges["*"]=u}if(s.str.length==0&&(u.final=!0),i.push({node:u,editsRemaining:s.editsRemaining-1,str:s.str}),s.str.length>1&&i.push({node:s.node,editsRemaining:s.editsRemaining-1,str:s.str.slice(1)}),s.str.length==1&&(s.node.final=!0),s.str.length>=1){if("*"in s.node.edges)var c=s.node.edges["*"];else{var c=new t.TokenSet;s.node.edges["*"]=c}s.str.length==1&&(c.final=!0),i.push({node:c,editsRemaining:s.editsRemaining-1,str:s.str.slice(1)})}if(s.str.length>1){var f=s.str.charAt(0),g=s.str.charAt(1),l;g in s.node.edges?l=s.node.edges[g]:(l=new t.TokenSet,s.node.edges[g]=l),s.str.length==1&&(l.final=!0),i.push({node:l,editsRemaining:s.editsRemaining-1,str:f+s.str.slice(2)})}}}return n},t.TokenSet.fromString=function(e){for(var r=new t.TokenSet,n=r,i=0,s=e.length;i=e;r--){var n=this.uncheckedNodes[r],i=n.child.toString();i in this.minimizedNodes?n.parent.edges[n.char]=this.minimizedNodes[i]:(n.child._str=i,this.minimizedNodes[i]=n.child),this.uncheckedNodes.pop()}};t.Index=function(e){this.invertedIndex=e.invertedIndex,this.fieldVectors=e.fieldVectors,this.tokenSet=e.tokenSet,this.fields=e.fields,this.pipeline=e.pipeline},t.Index.prototype.search=function(e){return this.query(function(r){var n=new t.QueryParser(e,r);n.parse()})},t.Index.prototype.query=function(e){for(var r=new t.Query(this.fields),n=Object.create(null),i=Object.create(null),s=Object.create(null),o=Object.create(null),a=Object.create(null),u=0;u1?this._b=1:this._b=e},t.Builder.prototype.k1=function(e){this._k1=e},t.Builder.prototype.add=function(e,r){var n=e[this._ref],i=Object.keys(this._fields);this._documents[n]=r||{},this.documentCount+=1;for(var s=0;s=this.length)return t.QueryLexer.EOS;var e=this.str.charAt(this.pos);return this.pos+=1,e},t.QueryLexer.prototype.width=function(){return this.pos-this.start},t.QueryLexer.prototype.ignore=function(){this.start==this.pos&&(this.pos+=1),this.start=this.pos},t.QueryLexer.prototype.backup=function(){this.pos-=1},t.QueryLexer.prototype.acceptDigitRun=function(){var e,r;do e=this.next(),r=e.charCodeAt(0);while(r>47&&r<58);e!=t.QueryLexer.EOS&&this.backup()},t.QueryLexer.prototype.more=function(){return this.pos1&&(e.backup(),e.emit(t.QueryLexer.TERM)),e.ignore(),e.more())return t.QueryLexer.lexText},t.QueryLexer.lexEditDistance=function(e){return e.ignore(),e.acceptDigitRun(),e.emit(t.QueryLexer.EDIT_DISTANCE),t.QueryLexer.lexText},t.QueryLexer.lexBoost=function(e){return e.ignore(),e.acceptDigitRun(),e.emit(t.QueryLexer.BOOST),t.QueryLexer.lexText},t.QueryLexer.lexEOS=function(e){e.width()>0&&e.emit(t.QueryLexer.TERM)},t.QueryLexer.termSeparator=t.tokenizer.separator,t.QueryLexer.lexText=function(e){for(;;){var r=e.next();if(r==t.QueryLexer.EOS)return t.QueryLexer.lexEOS;if(r.charCodeAt(0)==92){e.escapeCharacter();continue}if(r==":")return t.QueryLexer.lexField;if(r=="~")return e.backup(),e.width()>0&&e.emit(t.QueryLexer.TERM),t.QueryLexer.lexEditDistance;if(r=="^")return e.backup(),e.width()>0&&e.emit(t.QueryLexer.TERM),t.QueryLexer.lexBoost;if(r=="+"&&e.width()===1||r=="-"&&e.width()===1)return e.emit(t.QueryLexer.PRESENCE),t.QueryLexer.lexText;if(r.match(t.QueryLexer.termSeparator))return t.QueryLexer.lexTerm}},t.QueryParser=function(e,r){this.lexer=new t.QueryLexer(e),this.query=r,this.currentClause={},this.lexemeIdx=0},t.QueryParser.prototype.parse=function(){this.lexer.run(),this.lexemes=this.lexer.lexemes;for(var e=t.QueryParser.parseClause;e;)e=e(this);return this.query},t.QueryParser.prototype.peekLexeme=function(){return this.lexemes[this.lexemeIdx]},t.QueryParser.prototype.consumeLexeme=function(){var e=this.peekLexeme();return this.lexemeIdx+=1,e},t.QueryParser.prototype.nextClause=function(){var e=this.currentClause;this.query.clause(e),this.currentClause={}},t.QueryParser.parseClause=function(e){var r=e.peekLexeme();if(r!=null)switch(r.type){case t.QueryLexer.PRESENCE:return t.QueryParser.parsePresence;case t.QueryLexer.FIELD:return t.QueryParser.parseField;case t.QueryLexer.TERM:return t.QueryParser.parseTerm;default:var n="expected either a field or a term, found "+r.type;throw r.str.length>=1&&(n+=" with value '"+r.str+"'"),new t.QueryParseError(n,r.start,r.end)}},t.QueryParser.parsePresence=function(e){var r=e.consumeLexeme();if(r!=null){switch(r.str){case"-":e.currentClause.presence=t.Query.presence.PROHIBITED;break;case"+":e.currentClause.presence=t.Query.presence.REQUIRED;break;default:var n="unrecognised presence operator'"+r.str+"'";throw new t.QueryParseError(n,r.start,r.end)}var i=e.peekLexeme();if(i==null){var n="expecting term or field, found nothing";throw new t.QueryParseError(n,r.start,r.end)}switch(i.type){case t.QueryLexer.FIELD:return t.QueryParser.parseField;case t.QueryLexer.TERM:return t.QueryParser.parseTerm;default:var n="expecting term or field, found '"+i.type+"'";throw new t.QueryParseError(n,i.start,i.end)}}},t.QueryParser.parseField=function(e){var r=e.consumeLexeme();if(r!=null){if(e.query.allFields.indexOf(r.str)==-1){var n=e.query.allFields.map(function(o){return"'"+o+"'"}).join(", "),i="unrecognised field '"+r.str+"', possible fields: "+n;throw new t.QueryParseError(i,r.start,r.end)}e.currentClause.fields=[r.str];var s=e.peekLexeme();if(s==null){var i="expecting term, found nothing";throw new t.QueryParseError(i,r.start,r.end)}switch(s.type){case t.QueryLexer.TERM:return t.QueryParser.parseTerm;default:var i="expecting term, found '"+s.type+"'";throw new t.QueryParseError(i,s.start,s.end)}}},t.QueryParser.parseTerm=function(e){var r=e.consumeLexeme();if(r!=null){e.currentClause.term=r.str.toLowerCase(),r.str.indexOf("*")!=-1&&(e.currentClause.usePipeline=!1);var n=e.peekLexeme();if(n==null){e.nextClause();return}switch(n.type){case t.QueryLexer.TERM:return e.nextClause(),t.QueryParser.parseTerm;case t.QueryLexer.FIELD:return e.nextClause(),t.QueryParser.parseField;case t.QueryLexer.EDIT_DISTANCE:return t.QueryParser.parseEditDistance;case t.QueryLexer.BOOST:return t.QueryParser.parseBoost;case t.QueryLexer.PRESENCE:return e.nextClause(),t.QueryParser.parsePresence;default:var i="Unexpected lexeme type '"+n.type+"'";throw new t.QueryParseError(i,n.start,n.end)}}},t.QueryParser.parseEditDistance=function(e){var r=e.consumeLexeme();if(r!=null){var n=parseInt(r.str,10);if(isNaN(n)){var i="edit distance must be numeric";throw new t.QueryParseError(i,r.start,r.end)}e.currentClause.editDistance=n;var s=e.peekLexeme();if(s==null){e.nextClause();return}switch(s.type){case t.QueryLexer.TERM:return e.nextClause(),t.QueryParser.parseTerm;case t.QueryLexer.FIELD:return e.nextClause(),t.QueryParser.parseField;case t.QueryLexer.EDIT_DISTANCE:return t.QueryParser.parseEditDistance;case t.QueryLexer.BOOST:return t.QueryParser.parseBoost;case t.QueryLexer.PRESENCE:return e.nextClause(),t.QueryParser.parsePresence;default:var i="Unexpected lexeme type '"+s.type+"'";throw new t.QueryParseError(i,s.start,s.end)}}},t.QueryParser.parseBoost=function(e){var r=e.consumeLexeme();if(r!=null){var n=parseInt(r.str,10);if(isNaN(n)){var i="boost must be numeric";throw new t.QueryParseError(i,r.start,r.end)}e.currentClause.boost=n;var s=e.peekLexeme();if(s==null){e.nextClause();return}switch(s.type){case t.QueryLexer.TERM:return e.nextClause(),t.QueryParser.parseTerm;case t.QueryLexer.FIELD:return e.nextClause(),t.QueryParser.parseField;case t.QueryLexer.EDIT_DISTANCE:return t.QueryParser.parseEditDistance;case t.QueryLexer.BOOST:return t.QueryParser.parseBoost;case t.QueryLexer.PRESENCE:return e.nextClause(),t.QueryParser.parsePresence;default:var i="Unexpected lexeme type '"+s.type+"'";throw new t.QueryParseError(i,s.start,s.end)}}},function(e,r){typeof define=="function"&&define.amd?define(r):typeof ee=="object"?te.exports=r():e.lunr=r()}(this,function(){return t})})()});var Y=Pe(re());function ne(t,e=document){let r=ke(t,e);if(typeof r=="undefined")throw new ReferenceError(`Missing element: expected "${t}" to be present`);return r}function ke(t,e=document){return e.querySelector(t)||void 0}Object.entries||(Object.entries=function(t){let e=[];for(let r of Object.keys(t))e.push([r,t[r]]);return e});Object.values||(Object.values=function(t){let e=[];for(let r of Object.keys(t))e.push(t[r]);return e});typeof Element!="undefined"&&(Element.prototype.scrollTo||(Element.prototype.scrollTo=function(t,e){typeof t=="object"?(this.scrollLeft=t.left,this.scrollTop=t.top):(this.scrollLeft=t,this.scrollTop=e)}),Element.prototype.replaceWith||(Element.prototype.replaceWith=function(...t){let e=this.parentNode;if(e){t.length===0&&e.removeChild(this);for(let r=t.length-1;r>=0;r--){let n=t[r];typeof n=="string"?n=document.createTextNode(n):n.parentNode&&n.parentNode.removeChild(n),r?e.insertBefore(this.previousSibling,n):e.replaceChild(n,this)}}}));function ie(t){let e=new Map;for(let r of t){let[n]=r.location.split("#"),i=e.get(n);typeof i=="undefined"?e.set(n,r):(e.set(r.location,r),r.parent=i)}return e}function H(t,e,r){var s;e=new RegExp(e,"g");let n,i=0;do{n=e.exec(t);let o=(s=n==null?void 0:n.index)!=null?s:t.length;if(in?e(r,1,n,n=i):t.charAt(i)===">"&&(t.charAt(n+1)==="/"?--s===0&&e(r++,2,n,i+1):t.charAt(i-1)!=="/"&&s++===0&&e(r,0,n,i+1),n=i+1);i>n&&e(r,1,n,i)}function oe(t,e,r,n=!1){return q([t],e,r,n).pop()}function q(t,e,r,n=!1){let i=[0];for(let s=1;s>>2&1023,c=a[0]>>>12;i.push(+(u>c)+i[i.length-1])}return t.map((s,o)=>{let a=0,u=new Map;for(let f of r.sort((g,l)=>g-l)){let g=f&1048575,l=f>>>20;if(i[l]!==o)continue;let m=u.get(l);typeof m=="undefined"&&u.set(l,m=[]),m.push(g)}if(u.size===0)return s;let c=[];for(let[f,g]of u){let l=e[f],m=l[0]>>>12,x=l[l.length-1]>>>12,v=l[l.length-1]>>>2&1023;n&&m>a&&c.push(s.slice(a,m));let d=s.slice(m,x+v);for(let y of g.sort((b,E)=>E-b)){let b=(l[y]>>>12)-m,E=(l[y]>>>2&1023)+b;d=[d.slice(0,b),"",d.slice(b,E),"",d.slice(E)].join("")}if(a=x+v,c.push(d)===2)break}return n&&a{var f;switch(i[f=o+=s]||(i[f]=[]),a){case 0:case 2:i[o].push(u<<12|c-u<<2|a);break;case 1:let g=r[n].slice(u,c);H(g,lunr.tokenizer.separator,(l,m)=>{if(typeof lunr.segmenter!="undefined"){let x=g.slice(l,m);if(/^[MHIK]$/.test(lunr.segmenter.ctype_(x))){let v=lunr.segmenter.segment(x);for(let d=0,y=0;dr){return t.trim().split(/"([^"]+)"/g).map((r,n)=>n&1?r.replace(/^\b|^(?![^\x00-\x7F]|$)|\s+/g," +"):r).join("").replace(/"|(?:^|\s+)[*+\-:^~]+(?=\s+|$)/g,"").split(/\s+/g).reduce((r,n)=>{let i=e(n);return[...r,...Array.isArray(i)?i:[i]]},[]).map(r=>/([~^]$)/.test(r)?`${r}1`:r).map(r=>/(^[+-]|[~^]\d+$)/.test(r)?r:`${r}*`).join(" ")}function ce(t){return ue(t,e=>{let r=[],n=new lunr.QueryLexer(e);n.run();for(let{type:i,str:s,start:o,end:a}of n.lexemes)switch(i){case"FIELD":["title","text","tags"].includes(s)||(e=[e.slice(0,a)," ",e.slice(a+1)].join(""));break;case"TERM":H(s,lunr.tokenizer.separator,(...u)=>{r.push([e.slice(0,o),s.slice(...u),e.slice(a)].join(""))})}return r})}function le(t){let e=new lunr.Query(["title","text","tags"]);new lunr.QueryParser(t,e).parse();for(let n of e.clauses)n.usePipeline=!0,n.term.startsWith("*")&&(n.wildcard=lunr.Query.wildcard.LEADING,n.term=n.term.slice(1)),n.term.endsWith("*")&&(n.wildcard=lunr.Query.wildcard.TRAILING,n.term=n.term.slice(0,-1));return e.clauses}function he(t,e){var i;let r=new Set(t),n={};for(let s=0;s0;){let o=i[--s];for(let u=1;un[o]-u&&(r.add(t.slice(o,o+u)),i[s++]=o+u);let a=o+n[o];n[a]&&ar=>{if(typeof r[e]=="undefined")return;let n=[r.location,e].join(":");return t.set(n,lunr.tokenizer.table=[]),r[e]}}function Re(t,e){let[r,n]=[new Set(t),new Set(e)];return[...new Set([...r].filter(i=>!n.has(i)))]}var U=class{constructor({config:e,docs:r,options:n}){let i=Oe(this.table=new Map);this.map=ie(r),this.options=n,this.index=lunr(function(){this.metadataWhitelist=["position"],this.b(0),e.lang.length===1&&e.lang[0]!=="en"?this.use(lunr[e.lang[0]]):e.lang.length>1&&this.use(lunr.multiLanguage(...e.lang)),this.tokenizer=ae,lunr.tokenizer.separator=new RegExp(e.separator),lunr.segmenter="TinySegmenter"in lunr?new lunr.TinySegmenter:void 0;let s=Re(["trimmer","stopWordFilter","stemmer"],e.pipeline);for(let o of e.lang.map(a=>a==="en"?lunr:lunr[a]))for(let a of s)this.pipeline.remove(o[a]),this.searchPipeline.remove(o[a]);this.ref("location");for(let[o,a]of Object.entries(e.fields))this.field(o,B(_({},a),{extractor:i(o)}));for(let o of r)this.add(o,{boost:o.boost})})}search(e){if(e=e.replace(new RegExp("\\p{sc=Han}+","gu"),s=>[...fe(s,this.index.invertedIndex)].join("* ")),e=ce(e),!e)return{items:[]};let r=le(e).filter(s=>s.presence!==lunr.Query.presence.PROHIBITED),n=this.index.search(e).reduce((s,{ref:o,score:a,matchData:u})=>{let c=this.map.get(o);if(typeof c!="undefined"){c=_({},c),c.tags&&(c.tags=[...c.tags]);let f=he(r,Object.keys(u.metadata));for(let l of this.index.fields){if(typeof c[l]=="undefined")continue;let m=[];for(let d of Object.values(u.metadata))typeof d[l]!="undefined"&&m.push(...d[l].position);if(!m.length)continue;let x=this.table.get([c.location,l].join(":")),v=Array.isArray(c[l])?q:oe;c[l]=v(c[l],x,m,l!=="text")}let g=+!c.parent+Object.values(f).filter(l=>l).length/Object.keys(f).length;s.push(B(_({},c),{score:a*(1+K(g,2)),terms:f}))}return s},[]).sort((s,o)=>o.score-s.score).reduce((s,o)=>{let a=this.map.get(o.location);if(typeof a!="undefined"){let u=a.parent?a.parent.location:a.location;s.set(u,[...s.get(u)||[],o])}return s},new Map);for(let[s,o]of n)if(!o.find(a=>a.location===s)){let a=this.map.get(s);o.push(B(_({},a),{score:0,terms:{}}))}let i;if(this.options.suggest){let s=this.index.query(o=>{for(let a of r)o.term(a.term,{fields:["title"],presence:lunr.Query.presence.REQUIRED,wildcard:lunr.Query.wildcard.TRAILING})});i=s.length?Object.keys(s[0].matchData.metadata):[]}return _({items:[...n.values()]},typeof i!="undefined"&&{suggest:i})}};var de;function Ie(t){return W(this,null,function*(){let e="../lunr";if(typeof parent!="undefined"&&"IFrameWorker"in parent){let n=ne("script[src]"),[i]=n.src.split("/worker");e=e.replace("..",i)}let r=[];for(let n of t.lang){switch(n){case"ja":r.push(`${e}/tinyseg.js`);break;case"hi":case"th":r.push(`${e}/wordcut.js`);break}n!=="en"&&r.push(`${e}/min/lunr.${n}.min.js`)}t.lang.length>1&&r.push(`${e}/min/lunr.multi.min.js`),r.length&&(yield importScripts(`${e}/min/lunr.stemmer.support.min.js`,...r))})}function Fe(t){return W(this,null,function*(){switch(t.type){case 0:return yield Ie(t.data.config),de=new U(t.data),{type:1};case 2:let e=t.data;try{return{type:3,data:de.search(e)}}catch(r){return console.warn(`Invalid query: ${e} \u2013 see https://bit.ly/2s3ChXG`),console.warn(r),{type:3,data:{items:[]}}}default:throw new TypeError("Invalid message type")}})}self.lunr=Y.default;Y.default.utils.warn=console.warn;addEventListener("message",t=>W(void 0,null,function*(){postMessage(yield Fe(t.data))}));})(); diff --git a/assets/mac_get_info.png b/assets/mac_get_info.png new file mode 100644 index 0000000..cc97ec8 Binary files /dev/null and b/assets/mac_get_info.png differ diff --git a/assets/mac_microSD_FAT32.png b/assets/mac_microSD_FAT32.png new file mode 100644 index 0000000..2d92974 Binary files /dev/null and b/assets/mac_microSD_FAT32.png differ diff --git a/assets/mac_microSD_exFat.png b/assets/mac_microSD_exFat.png new file mode 100644 index 0000000..eec0d42 Binary files /dev/null and b/assets/mac_microSD_exFat.png differ diff --git a/assets/microSD_Card_Check_File_System_FAT32.JPG b/assets/microSD_Card_Check_File_System_FAT32.JPG new file mode 100644 index 0000000..b8cc221 Binary files /dev/null and b/assets/microSD_Card_Check_File_System_FAT32.JPG differ diff --git a/assets/microSD_Card_Check_File_System_exFAT.JPG b/assets/microSD_Card_Check_File_System_exFAT.JPG new file mode 100644 index 0000000..34c9abb Binary files /dev/null and b/assets/microSD_Card_Check_File_System_exFAT.JPG differ diff --git a/assets/microSD_Card_Properties.JPG b/assets/microSD_Card_Properties.JPG new file mode 100644 index 0000000..52afe76 Binary files /dev/null and b/assets/microSD_Card_Properties.JPG differ diff --git a/assets/powered-by-aws.png b/assets/powered-by-aws.png new file mode 100644 index 0000000..0e299a8 Binary files /dev/null and b/assets/powered-by-aws.png differ diff --git a/assets/sfe_favicon.png b/assets/sfe_favicon.png new file mode 100644 index 0000000..3003abf Binary files /dev/null and b/assets/sfe_favicon.png differ diff --git a/assets/sfe_logo_sm.png b/assets/sfe_logo_sm.png new file mode 100644 index 0000000..cca6dcb Binary files /dev/null and b/assets/sfe_logo_sm.png differ diff --git a/assets/stylesheets/main.12320a83.min.css b/assets/stylesheets/main.12320a83.min.css new file mode 100644 index 0000000..b33c690 --- /dev/null +++ b/assets/stylesheets/main.12320a83.min.css @@ -0,0 +1 @@ +@charset "UTF-8";html{-webkit-text-size-adjust:none;-moz-text-size-adjust:none;text-size-adjust:none;box-sizing:border-box}*,:after,:before{box-sizing:inherit}@media (prefers-reduced-motion){*,:after,:before{transition:none!important}}body{margin:0}a,button,input,label{-webkit-tap-highlight-color:transparent}a{color:inherit;text-decoration:none}hr{border:0;box-sizing:initial;display:block;height:.05rem;overflow:visible;padding:0}small{font-size:80%}sub,sup{line-height:1em}img{border-style:none}table{border-collapse:initial;border-spacing:0}td,th{font-weight:400;vertical-align:top}button{background:#0000;border:0;font-family:inherit;font-size:inherit;margin:0;padding:0}input{border:0;outline:none}:root{--md-primary-fg-color:#4051b5;--md-primary-fg-color--light:#5d6cc0;--md-primary-fg-color--dark:#303fa1;--md-primary-bg-color:#fff;--md-primary-bg-color--light:#ffffffb3;--md-accent-fg-color:#526cfe;--md-accent-fg-color--transparent:#526cfe1a;--md-accent-bg-color:#fff;--md-accent-bg-color--light:#ffffffb3}[data-md-color-scheme=default]{color-scheme:light}[data-md-color-scheme=default] img[src$="#gh-dark-mode-only"],[data-md-color-scheme=default] img[src$="#only-dark"]{display:none}:root,[data-md-color-scheme=default]{--md-hue:225deg;--md-default-fg-color:#000000de;--md-default-fg-color--light:#0000008a;--md-default-fg-color--lighter:#00000052;--md-default-fg-color--lightest:#00000012;--md-default-bg-color:#fff;--md-default-bg-color--light:#ffffffb3;--md-default-bg-color--lighter:#ffffff4d;--md-default-bg-color--lightest:#ffffff1f;--md-code-fg-color:#36464e;--md-code-bg-color:#f5f5f5;--md-code-bg-color--light:#f5f5f5b3;--md-code-bg-color--lighter:#f5f5f54d;--md-code-hl-color:#4287ff;--md-code-hl-color--light:#4287ff1a;--md-code-hl-number-color:#d52a2a;--md-code-hl-special-color:#db1457;--md-code-hl-function-color:#a846b9;--md-code-hl-constant-color:#6e59d9;--md-code-hl-keyword-color:#3f6ec6;--md-code-hl-string-color:#1c7d4d;--md-code-hl-name-color:var(--md-code-fg-color);--md-code-hl-operator-color:var(--md-default-fg-color--light);--md-code-hl-punctuation-color:var(--md-default-fg-color--light);--md-code-hl-comment-color:var(--md-default-fg-color--light);--md-code-hl-generic-color:var(--md-default-fg-color--light);--md-code-hl-variable-color:var(--md-default-fg-color--light);--md-typeset-color:var(--md-default-fg-color);--md-typeset-a-color:var(--md-primary-fg-color);--md-typeset-del-color:#f5503d26;--md-typeset-ins-color:#0bd57026;--md-typeset-kbd-color:#fafafa;--md-typeset-kbd-accent-color:#fff;--md-typeset-kbd-border-color:#b8b8b8;--md-typeset-mark-color:#ffff0080;--md-typeset-table-color:#0000001f;--md-typeset-table-color--light:rgba(0,0,0,.035);--md-admonition-fg-color:var(--md-default-fg-color);--md-admonition-bg-color:var(--md-default-bg-color);--md-warning-fg-color:#000000de;--md-warning-bg-color:#ff9;--md-footer-fg-color:#fff;--md-footer-fg-color--light:#ffffffb3;--md-footer-fg-color--lighter:#ffffff73;--md-footer-bg-color:#000000de;--md-footer-bg-color--dark:#00000052;--md-shadow-z1:0 0.2rem 0.5rem #0000000d,0 0 0.05rem #0000001a;--md-shadow-z2:0 0.2rem 0.5rem #0000001a,0 0 0.05rem #00000040;--md-shadow-z3:0 0.2rem 0.5rem #0003,0 0 0.05rem #00000059}.md-icon svg{fill:currentcolor;display:block;height:1.2rem;width:1.2rem}body{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;--md-text-font-family:var(--md-text-font,_),-apple-system,BlinkMacSystemFont,Helvetica,Arial,sans-serif;--md-code-font-family:var(--md-code-font,_),SFMono-Regular,Consolas,Menlo,monospace}aside,body,input{font-feature-settings:"kern","liga";color:var(--md-typeset-color);font-family:var(--md-text-font-family)}code,kbd,pre{font-feature-settings:"kern";font-family:var(--md-code-font-family)}:root{--md-typeset-table-sort-icon:url('data:image/svg+xml;charset=utf-8,');--md-typeset-table-sort-icon--asc:url('data:image/svg+xml;charset=utf-8,');--md-typeset-table-sort-icon--desc:url('data:image/svg+xml;charset=utf-8,')}.md-typeset{-webkit-print-color-adjust:exact;color-adjust:exact;font-size:.8rem;line-height:1.6}@media print{.md-typeset{font-size:.68rem}}.md-typeset blockquote,.md-typeset dl,.md-typeset figure,.md-typeset ol,.md-typeset pre,.md-typeset ul{margin-bottom:1em;margin-top:1em}.md-typeset h1{color:var(--md-default-fg-color--light);font-size:2em;line-height:1.3;margin:0 0 1.25em}.md-typeset h1,.md-typeset h2{font-weight:300;letter-spacing:-.01em}.md-typeset h2{font-size:1.5625em;line-height:1.4;margin:1.6em 0 .64em}.md-typeset h3{font-size:1.25em;font-weight:400;letter-spacing:-.01em;line-height:1.5;margin:1.6em 0 .8em}.md-typeset h2+h3{margin-top:.8em}.md-typeset h4{font-weight:700;letter-spacing:-.01em;margin:1em 0}.md-typeset h5,.md-typeset h6{color:var(--md-default-fg-color--light);font-size:.8em;font-weight:700;letter-spacing:-.01em;margin:1.25em 0}.md-typeset h5{text-transform:uppercase}.md-typeset hr{border-bottom:.05rem solid var(--md-default-fg-color--lightest);display:flow-root;margin:1.5em 0}.md-typeset a{color:var(--md-typeset-a-color);word-break:break-word}.md-typeset a,.md-typeset a:before{transition:color 125ms}.md-typeset a:focus,.md-typeset a:hover{color:var(--md-accent-fg-color)}.md-typeset a:focus code,.md-typeset a:hover code{background-color:var(--md-accent-fg-color--transparent);color:var(--md-accent-fg-color)}.md-typeset a code{color:var(--md-typeset-a-color)}.md-typeset a.focus-visible{outline-color:var(--md-accent-fg-color);outline-offset:.2rem}.md-typeset code,.md-typeset kbd,.md-typeset pre{color:var(--md-code-fg-color);direction:ltr;font-variant-ligatures:none;transition:background-color 125ms}@media print{.md-typeset code,.md-typeset kbd,.md-typeset pre{white-space:pre-wrap}}.md-typeset code{background-color:var(--md-code-bg-color);border-radius:.1rem;-webkit-box-decoration-break:clone;box-decoration-break:clone;font-size:.85em;padding:0 .2941176471em;transition:color 125ms,background-color 125ms;word-break:break-word}.md-typeset code:not(.focus-visible){-webkit-tap-highlight-color:transparent;outline:none}.md-typeset pre{display:flow-root;line-height:1.4;position:relative}.md-typeset pre>code{-webkit-box-decoration-break:slice;box-decoration-break:slice;box-shadow:none;display:block;margin:0;outline-color:var(--md-accent-fg-color);overflow:auto;padding:.7720588235em 1.1764705882em;scrollbar-color:var(--md-default-fg-color--lighter) #0000;scrollbar-width:thin;touch-action:auto;word-break:normal}.md-typeset pre>code:hover{scrollbar-color:var(--md-accent-fg-color) #0000}.md-typeset pre>code::-webkit-scrollbar{height:.2rem;width:.2rem}.md-typeset pre>code::-webkit-scrollbar-thumb{background-color:var(--md-default-fg-color--lighter)}.md-typeset pre>code::-webkit-scrollbar-thumb:hover{background-color:var(--md-accent-fg-color)}.md-typeset kbd{background-color:var(--md-typeset-kbd-color);border-radius:.1rem;box-shadow:0 .1rem 0 .05rem var(--md-typeset-kbd-border-color),0 .1rem 0 var(--md-typeset-kbd-border-color),0 -.1rem .2rem var(--md-typeset-kbd-accent-color) inset;color:var(--md-default-fg-color);display:inline-block;font-size:.75em;padding:0 .6666666667em;vertical-align:text-top;word-break:break-word}.md-typeset mark{background-color:var(--md-typeset-mark-color);-webkit-box-decoration-break:clone;box-decoration-break:clone;color:inherit;word-break:break-word}.md-typeset abbr{cursor:help;text-decoration:none}.md-typeset [data-preview],.md-typeset abbr{border-bottom:.05rem dotted var(--md-default-fg-color--light)}.md-typeset small{opacity:.75}[dir=ltr] .md-typeset sub,[dir=ltr] .md-typeset sup{margin-left:.078125em}[dir=rtl] .md-typeset sub,[dir=rtl] .md-typeset sup{margin-right:.078125em}[dir=ltr] .md-typeset blockquote{padding-left:.6rem}[dir=rtl] .md-typeset blockquote{padding-right:.6rem}[dir=ltr] .md-typeset blockquote{border-left:.2rem solid var(--md-default-fg-color--lighter)}[dir=rtl] .md-typeset blockquote{border-right:.2rem solid var(--md-default-fg-color--lighter)}.md-typeset blockquote{color:var(--md-default-fg-color--light);margin-left:0;margin-right:0}.md-typeset ul{list-style-type:disc}.md-typeset ul[type]{list-style-type:revert-layer}[dir=ltr] .md-typeset ol,[dir=ltr] .md-typeset ul{margin-left:.625em}[dir=rtl] .md-typeset ol,[dir=rtl] .md-typeset ul{margin-right:.625em}.md-typeset ol,.md-typeset ul{padding:0}.md-typeset ol:not([hidden]),.md-typeset ul:not([hidden]){display:flow-root}.md-typeset ol ol,.md-typeset ul ol{list-style-type:lower-alpha}.md-typeset ol ol ol,.md-typeset ul ol ol{list-style-type:lower-roman}.md-typeset ol ol ol ol,.md-typeset ul ol ol ol{list-style-type:upper-alpha}.md-typeset ol ol ol ol ol,.md-typeset ul ol ol ol ol{list-style-type:upper-roman}.md-typeset ol[type],.md-typeset ul[type]{list-style-type:revert-layer}[dir=ltr] .md-typeset ol li,[dir=ltr] .md-typeset ul li{margin-left:1.25em}[dir=rtl] .md-typeset ol li,[dir=rtl] .md-typeset ul li{margin-right:1.25em}.md-typeset ol li,.md-typeset ul li{margin-bottom:.5em}.md-typeset ol li blockquote,.md-typeset ol li p,.md-typeset ul li blockquote,.md-typeset ul li p{margin:.5em 0}.md-typeset ol li:last-child,.md-typeset ul li:last-child{margin-bottom:0}[dir=ltr] .md-typeset ol li ol,[dir=ltr] .md-typeset ol li ul,[dir=ltr] .md-typeset ul li ol,[dir=ltr] .md-typeset ul li ul{margin-left:.625em}[dir=rtl] .md-typeset ol li ol,[dir=rtl] .md-typeset ol li ul,[dir=rtl] .md-typeset ul li ol,[dir=rtl] .md-typeset ul li ul{margin-right:.625em}.md-typeset ol li ol,.md-typeset ol li ul,.md-typeset ul li ol,.md-typeset ul li ul{margin-bottom:.5em;margin-top:.5em}[dir=ltr] .md-typeset dd{margin-left:1.875em}[dir=rtl] .md-typeset dd{margin-right:1.875em}.md-typeset dd{margin-bottom:1.5em;margin-top:1em}.md-typeset img,.md-typeset svg,.md-typeset video{height:auto;max-width:100%}.md-typeset img[align=left]{margin:1em 1em 1em 0}.md-typeset img[align=right]{margin:1em 0 1em 1em}.md-typeset img[align]:only-child{margin-top:0}.md-typeset figure{display:flow-root;margin:1em auto;max-width:100%;text-align:center;width:-moz-fit-content;width:fit-content}.md-typeset figure img{display:block;margin:0 auto}.md-typeset figcaption{font-style:italic;margin:1em auto;max-width:24rem}.md-typeset iframe{max-width:100%}.md-typeset table:not([class]){background-color:var(--md-default-bg-color);border:.05rem solid var(--md-typeset-table-color);border-radius:.1rem;display:inline-block;font-size:.64rem;max-width:100%;overflow:auto;touch-action:auto}@media print{.md-typeset table:not([class]){display:table}}.md-typeset table:not([class])+*{margin-top:1.5em}.md-typeset table:not([class]) td>:first-child,.md-typeset table:not([class]) th>:first-child{margin-top:0}.md-typeset table:not([class]) td>:last-child,.md-typeset table:not([class]) th>:last-child{margin-bottom:0}.md-typeset table:not([class]) td:not([align]),.md-typeset table:not([class]) th:not([align]){text-align:left}[dir=rtl] .md-typeset table:not([class]) td:not([align]),[dir=rtl] .md-typeset table:not([class]) th:not([align]){text-align:right}.md-typeset table:not([class]) th{font-weight:700;min-width:5rem;padding:.9375em 1.25em;vertical-align:top}.md-typeset table:not([class]) td{border-top:.05rem solid var(--md-typeset-table-color);padding:.9375em 1.25em;vertical-align:top}.md-typeset table:not([class]) tbody tr{transition:background-color 125ms}.md-typeset table:not([class]) tbody tr:hover{background-color:var(--md-typeset-table-color--light);box-shadow:0 .05rem 0 var(--md-default-bg-color) inset}.md-typeset table:not([class]) a{word-break:normal}.md-typeset table th[role=columnheader]{cursor:pointer}[dir=ltr] .md-typeset table th[role=columnheader]:after{margin-left:.5em}[dir=rtl] .md-typeset table th[role=columnheader]:after{margin-right:.5em}.md-typeset table th[role=columnheader]:after{content:"";display:inline-block;height:1.2em;-webkit-mask-image:var(--md-typeset-table-sort-icon);mask-image:var(--md-typeset-table-sort-icon);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;transition:background-color 125ms;vertical-align:text-bottom;width:1.2em}.md-typeset table th[role=columnheader]:hover:after{background-color:var(--md-default-fg-color--lighter)}.md-typeset table th[role=columnheader][aria-sort=ascending]:after{background-color:var(--md-default-fg-color--light);-webkit-mask-image:var(--md-typeset-table-sort-icon--asc);mask-image:var(--md-typeset-table-sort-icon--asc)}.md-typeset table th[role=columnheader][aria-sort=descending]:after{background-color:var(--md-default-fg-color--light);-webkit-mask-image:var(--md-typeset-table-sort-icon--desc);mask-image:var(--md-typeset-table-sort-icon--desc)}.md-typeset__scrollwrap{margin:1em -.8rem;overflow-x:auto;touch-action:auto}.md-typeset__table{display:inline-block;margin-bottom:.5em;padding:0 .8rem}@media print{.md-typeset__table{display:block}}html .md-typeset__table table{display:table;margin:0;overflow:hidden;width:100%}@media screen and (max-width:44.984375em){.md-content__inner>pre{margin:1em -.8rem}.md-content__inner>pre code{border-radius:0}}.md-typeset .md-author{border-radius:100%;display:block;flex-shrink:0;height:1.6rem;overflow:hidden;position:relative;transition:color 125ms,transform 125ms;width:1.6rem}.md-typeset .md-author img{display:block}.md-typeset .md-author--more{background:var(--md-default-fg-color--lightest);color:var(--md-default-fg-color--lighter);font-size:.6rem;font-weight:700;line-height:1.6rem;text-align:center}.md-typeset .md-author--long{height:2.4rem;width:2.4rem}.md-typeset a.md-author{transform:scale(1)}.md-typeset a.md-author img{border-radius:100%;filter:grayscale(100%) opacity(75%);transition:filter 125ms}.md-typeset a.md-author:focus,.md-typeset a.md-author:hover{transform:scale(1.1);z-index:1}.md-typeset a.md-author:focus img,.md-typeset a.md-author:hover img{filter:grayscale(0)}.md-banner{background-color:var(--md-footer-bg-color);color:var(--md-footer-fg-color);overflow:auto}@media print{.md-banner{display:none}}.md-banner--warning{background-color:var(--md-warning-bg-color);color:var(--md-warning-fg-color)}.md-banner__inner{font-size:.7rem;margin:.6rem auto;padding:0 .8rem}[dir=ltr] .md-banner__button{float:right}[dir=rtl] .md-banner__button{float:left}.md-banner__button{color:inherit;cursor:pointer;transition:opacity .25s}.no-js .md-banner__button{display:none}.md-banner__button:hover{opacity:.7}html{font-size:125%;height:100%;overflow-x:hidden}@media screen and (min-width:100em){html{font-size:137.5%}}@media screen and (min-width:125em){html{font-size:150%}}body{background-color:var(--md-default-bg-color);display:flex;flex-direction:column;font-size:.5rem;min-height:100%;position:relative;width:100%}@media print{body{display:block}}@media screen and (max-width:59.984375em){body[data-md-scrolllock]{position:fixed}}.md-grid{margin-left:auto;margin-right:auto;max-width:61rem}.md-container{display:flex;flex-direction:column;flex-grow:1}@media print{.md-container{display:block}}.md-main{flex-grow:1}.md-main__inner{display:flex;height:100%;margin-top:1.5rem}.md-ellipsis{overflow:hidden;text-overflow:ellipsis}.md-toggle{display:none}.md-option{height:0;opacity:0;position:absolute;width:0}.md-option:checked+label:not([hidden]){display:block}.md-option.focus-visible+label{outline-color:var(--md-accent-fg-color);outline-style:auto}.md-skip{background-color:var(--md-default-fg-color);border-radius:.1rem;color:var(--md-default-bg-color);font-size:.64rem;margin:.5rem;opacity:0;outline-color:var(--md-accent-fg-color);padding:.3rem .5rem;position:fixed;transform:translateY(.4rem);z-index:-1}.md-skip:focus{opacity:1;transform:translateY(0);transition:transform .25s cubic-bezier(.4,0,.2,1),opacity 175ms 75ms;z-index:10}@page{margin:25mm}:root{--md-clipboard-icon:url('data:image/svg+xml;charset=utf-8,')}.md-clipboard{border-radius:.1rem;color:var(--md-default-fg-color--lightest);cursor:pointer;height:1.5em;outline-color:var(--md-accent-fg-color);outline-offset:.1rem;transition:color .25s;width:1.5em;z-index:1}@media print{.md-clipboard{display:none}}.md-clipboard:not(.focus-visible){-webkit-tap-highlight-color:transparent;outline:none}:hover>.md-clipboard{color:var(--md-default-fg-color--light)}.md-clipboard:focus,.md-clipboard:hover{color:var(--md-accent-fg-color)}.md-clipboard:after{background-color:currentcolor;content:"";display:block;height:1.125em;margin:0 auto;-webkit-mask-image:var(--md-clipboard-icon);mask-image:var(--md-clipboard-icon);-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;width:1.125em}.md-clipboard--inline{cursor:pointer}.md-clipboard--inline code{transition:color .25s,background-color .25s}.md-clipboard--inline:focus code,.md-clipboard--inline:hover code{background-color:var(--md-accent-fg-color--transparent);color:var(--md-accent-fg-color)}:root{--md-code-select-icon:url('data:image/svg+xml;charset=utf-8,');--md-code-copy-icon:url('data:image/svg+xml;charset=utf-8,')}.md-typeset .md-code__content{display:grid}.md-code__nav{background-color:var(--md-code-bg-color--lighter);border-radius:.1rem;display:flex;gap:.2rem;padding:.2rem;position:absolute;right:.25em;top:.25em;transition:background-color .25s;z-index:1}:hover>.md-code__nav{background-color:var(--md-code-bg-color--light)}.md-code__button{color:var(--md-default-fg-color--lightest);cursor:pointer;display:block;height:1.5em;outline-color:var(--md-accent-fg-color);outline-offset:.1rem;transition:color .25s;width:1.5em}:hover>*>.md-code__button{color:var(--md-default-fg-color--light)}.md-code__button.focus-visible,.md-code__button:hover{color:var(--md-accent-fg-color)}.md-code__button--active{color:var(--md-default-fg-color)!important}.md-code__button:after{background-color:currentcolor;content:"";display:block;height:1.125em;margin:0 auto;-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;width:1.125em}.md-code__button[data-md-type=select]:after{-webkit-mask-image:var(--md-code-select-icon);mask-image:var(--md-code-select-icon)}.md-code__button[data-md-type=copy]:after{-webkit-mask-image:var(--md-code-copy-icon);mask-image:var(--md-code-copy-icon)}@keyframes consent{0%{opacity:0;transform:translateY(100%)}to{opacity:1;transform:translateY(0)}}@keyframes overlay{0%{opacity:0}to{opacity:1}}.md-consent__overlay{animation:overlay .25s both;-webkit-backdrop-filter:blur(.1rem);backdrop-filter:blur(.1rem);background-color:#0000008a;height:100%;opacity:1;position:fixed;top:0;width:100%;z-index:5}.md-consent__inner{animation:consent .5s cubic-bezier(.1,.7,.1,1) both;background-color:var(--md-default-bg-color);border:0;border-radius:.1rem;bottom:0;box-shadow:0 0 .2rem #0000001a,0 .2rem .4rem #0003;max-height:100%;overflow:auto;padding:0;position:fixed;width:100%;z-index:5}.md-consent__form{padding:.8rem}.md-consent__settings{display:none;margin:1em 0}input:checked+.md-consent__settings{display:block}.md-consent__controls{margin-bottom:.8rem}.md-typeset .md-consent__controls .md-button{display:inline}@media screen and (max-width:44.984375em){.md-typeset .md-consent__controls .md-button{display:block;margin-top:.4rem;text-align:center;width:100%}}.md-consent label{cursor:pointer}.md-content{flex-grow:1;min-width:0}.md-content__inner{margin:0 .8rem 1.2rem;padding-top:.6rem}@media screen and (min-width:76.25em){[dir=ltr] .md-sidebar--primary:not([hidden])~.md-content>.md-content__inner{margin-left:1.2rem}[dir=ltr] .md-sidebar--secondary:not([hidden])~.md-content>.md-content__inner,[dir=rtl] .md-sidebar--primary:not([hidden])~.md-content>.md-content__inner{margin-right:1.2rem}[dir=rtl] .md-sidebar--secondary:not([hidden])~.md-content>.md-content__inner{margin-left:1.2rem}}.md-content__inner:before{content:"";display:block;height:.4rem}.md-content__inner>:last-child{margin-bottom:0}[dir=ltr] .md-content__button{float:right}[dir=rtl] .md-content__button{float:left}[dir=ltr] .md-content__button{margin-left:.4rem}[dir=rtl] .md-content__button{margin-right:.4rem}.md-content__button{margin:.4rem 0;padding:0}@media print{.md-content__button{display:none}}.md-typeset .md-content__button{color:var(--md-default-fg-color--lighter)}.md-content__button svg{display:inline;vertical-align:top}[dir=rtl] .md-content__button svg{transform:scaleX(-1)}[dir=ltr] .md-dialog{right:.8rem}[dir=rtl] .md-dialog{left:.8rem}.md-dialog{background-color:var(--md-default-fg-color);border-radius:.1rem;bottom:.8rem;box-shadow:var(--md-shadow-z3);min-width:11.1rem;opacity:0;padding:.4rem .6rem;pointer-events:none;position:fixed;transform:translateY(100%);transition:transform 0ms .4s,opacity .4s;z-index:4}@media print{.md-dialog{display:none}}.md-dialog--active{opacity:1;pointer-events:auto;transform:translateY(0);transition:transform .4s cubic-bezier(.075,.85,.175,1),opacity .4s}.md-dialog__inner{color:var(--md-default-bg-color);font-size:.7rem}.md-feedback{margin:2em 0 1em;text-align:center}.md-feedback fieldset{border:none;margin:0;padding:0}.md-feedback__title{font-weight:700;margin:1em auto}.md-feedback__inner{position:relative}.md-feedback__list{display:flex;flex-wrap:wrap;place-content:baseline center;position:relative}.md-feedback__list:hover .md-icon:not(:disabled){color:var(--md-default-fg-color--lighter)}:disabled .md-feedback__list{min-height:1.8rem}.md-feedback__icon{color:var(--md-default-fg-color--light);cursor:pointer;flex-shrink:0;margin:0 .1rem;transition:color 125ms}.md-feedback__icon:not(:disabled).md-icon:hover{color:var(--md-accent-fg-color)}.md-feedback__icon:disabled{color:var(--md-default-fg-color--lightest);pointer-events:none}.md-feedback__note{opacity:0;position:relative;transform:translateY(.4rem);transition:transform .4s cubic-bezier(.1,.7,.1,1),opacity .15s}.md-feedback__note>*{margin:0 auto;max-width:16rem}:disabled .md-feedback__note{opacity:1;transform:translateY(0)}@media print{.md-feedback{display:none}}.md-footer{background-color:var(--md-footer-bg-color);color:var(--md-footer-fg-color)}@media print{.md-footer{display:none}}.md-footer__inner{justify-content:space-between;overflow:auto;padding:.2rem}.md-footer__inner:not([hidden]){display:flex}.md-footer__link{align-items:end;display:flex;flex-grow:0.01;margin-bottom:.4rem;margin-top:1rem;max-width:100%;outline-color:var(--md-accent-fg-color);overflow:hidden;transition:opacity .25s}.md-footer__link:focus,.md-footer__link:hover{opacity:.7}[dir=rtl] .md-footer__link svg{transform:scaleX(-1)}@media screen and (max-width:44.984375em){.md-footer__link--prev{flex-shrink:0}.md-footer__link--prev .md-footer__title{display:none}}[dir=ltr] .md-footer__link--next{margin-left:auto}[dir=rtl] .md-footer__link--next{margin-right:auto}.md-footer__link--next{text-align:right}[dir=rtl] .md-footer__link--next{text-align:left}.md-footer__title{flex-grow:1;font-size:.9rem;margin-bottom:.7rem;max-width:calc(100% - 2.4rem);padding:0 1rem;white-space:nowrap}.md-footer__button{margin:.2rem;padding:.4rem}.md-footer__direction{font-size:.64rem;opacity:.7}.md-footer-meta{background-color:var(--md-footer-bg-color--dark)}.md-footer-meta__inner{display:flex;flex-wrap:wrap;justify-content:space-between;padding:.2rem}html .md-footer-meta.md-typeset a{color:var(--md-footer-fg-color--light)}html .md-footer-meta.md-typeset a:focus,html .md-footer-meta.md-typeset a:hover{color:var(--md-footer-fg-color)}.md-copyright{color:var(--md-footer-fg-color--lighter);font-size:.64rem;margin:auto .6rem;padding:.4rem 0;width:100%}@media screen and (min-width:45em){.md-copyright{width:auto}}.md-copyright__highlight{color:var(--md-footer-fg-color--light)}.md-social{display:inline-flex;gap:.2rem;margin:0 .4rem;padding:.2rem 0 .6rem}@media screen and (min-width:45em){.md-social{padding:.6rem 0}}.md-social__link{display:inline-block;height:1.6rem;text-align:center;width:1.6rem}.md-social__link:before{line-height:1.9}.md-social__link svg{fill:currentcolor;max-height:.8rem;vertical-align:-25%}.md-typeset .md-button{border:.1rem solid;border-radius:.1rem;color:var(--md-primary-fg-color);cursor:pointer;display:inline-block;font-weight:700;padding:.625em 2em;transition:color 125ms,background-color 125ms,border-color 125ms}.md-typeset .md-button--primary{background-color:var(--md-primary-fg-color);border-color:var(--md-primary-fg-color);color:var(--md-primary-bg-color)}.md-typeset .md-button:focus,.md-typeset .md-button:hover{background-color:var(--md-accent-fg-color);border-color:var(--md-accent-fg-color);color:var(--md-accent-bg-color)}[dir=ltr] .md-typeset .md-input{border-top-left-radius:.1rem}[dir=ltr] .md-typeset .md-input,[dir=rtl] .md-typeset .md-input{border-top-right-radius:.1rem}[dir=rtl] .md-typeset .md-input{border-top-left-radius:.1rem}.md-typeset .md-input{border-bottom:.1rem solid var(--md-default-fg-color--lighter);box-shadow:var(--md-shadow-z1);font-size:.8rem;height:1.8rem;padding:0 .6rem;transition:border .25s,box-shadow .25s}.md-typeset .md-input:focus,.md-typeset .md-input:hover{border-bottom-color:var(--md-accent-fg-color);box-shadow:var(--md-shadow-z2)}.md-typeset .md-input--stretch{width:100%}.md-header{background-color:var(--md-primary-fg-color);box-shadow:0 0 .2rem #0000,0 .2rem .4rem #0000;color:var(--md-primary-bg-color);display:block;left:0;position:sticky;right:0;top:0;z-index:4}@media print{.md-header{display:none}}.md-header[hidden]{transform:translateY(-100%);transition:transform .25s cubic-bezier(.8,0,.6,1),box-shadow .25s}.md-header--shadow{box-shadow:0 0 .2rem #0000001a,0 .2rem .4rem #0003;transition:transform .25s cubic-bezier(.1,.7,.1,1),box-shadow .25s}.md-header__inner{align-items:center;display:flex;padding:0 .2rem}.md-header__button{color:currentcolor;cursor:pointer;margin:.2rem;outline-color:var(--md-accent-fg-color);padding:.4rem;position:relative;transition:opacity .25s;vertical-align:middle;z-index:1}.md-header__button:hover{opacity:.7}.md-header__button:not([hidden]){display:inline-block}.md-header__button:not(.focus-visible){-webkit-tap-highlight-color:transparent;outline:none}.md-header__button.md-logo{margin:.2rem;padding:.4rem}@media screen and (max-width:76.234375em){.md-header__button.md-logo{display:none}}.md-header__button.md-logo img,.md-header__button.md-logo svg{fill:currentcolor;display:block;height:1.2rem;width:auto}@media screen and (min-width:60em){.md-header__button[for=__search]{display:none}}.no-js .md-header__button[for=__search]{display:none}[dir=rtl] .md-header__button[for=__search] svg{transform:scaleX(-1)}@media screen and (min-width:76.25em){.md-header__button[for=__drawer]{display:none}}.md-header__topic{display:flex;max-width:100%;position:absolute;transition:transform .4s cubic-bezier(.1,.7,.1,1),opacity .15s;white-space:nowrap}.md-header__topic+.md-header__topic{opacity:0;pointer-events:none;transform:translateX(1.25rem);transition:transform .4s cubic-bezier(1,.7,.1,.1),opacity .15s;z-index:-1}[dir=rtl] .md-header__topic+.md-header__topic{transform:translateX(-1.25rem)}.md-header__topic:first-child{font-weight:700}[dir=ltr] .md-header__title{margin-left:1rem;margin-right:.4rem}[dir=rtl] .md-header__title{margin-left:.4rem;margin-right:1rem}.md-header__title{flex-grow:1;font-size:.9rem;height:2.4rem;line-height:2.4rem}.md-header__title--active .md-header__topic{opacity:0;pointer-events:none;transform:translateX(-1.25rem);transition:transform .4s cubic-bezier(1,.7,.1,.1),opacity .15s;z-index:-1}[dir=rtl] .md-header__title--active .md-header__topic{transform:translateX(1.25rem)}.md-header__title--active .md-header__topic+.md-header__topic{opacity:1;pointer-events:auto;transform:translateX(0);transition:transform .4s cubic-bezier(.1,.7,.1,1),opacity .15s;z-index:0}.md-header__title>.md-header__ellipsis{height:100%;position:relative;width:100%}.md-header__option{display:flex;flex-shrink:0;max-width:100%;transition:max-width 0ms .25s,opacity .25s .25s;white-space:nowrap}[data-md-toggle=search]:checked~.md-header .md-header__option{max-width:0;opacity:0;transition:max-width 0ms,opacity 0ms}.md-header__option>input{bottom:0}.md-header__source{display:none}@media screen and (min-width:60em){[dir=ltr] .md-header__source{margin-left:1rem}[dir=rtl] .md-header__source{margin-right:1rem}.md-header__source{display:block;max-width:11.7rem;width:11.7rem}}@media screen and (min-width:76.25em){[dir=ltr] .md-header__source{margin-left:1.4rem}[dir=rtl] .md-header__source{margin-right:1.4rem}}.md-meta{color:var(--md-default-fg-color--light);font-size:.7rem;line-height:1.3}.md-meta__list{display:inline-flex;flex-wrap:wrap;list-style:none;margin:0;padding:0}.md-meta__item:not(:last-child):after{content:"·";margin-left:.2rem;margin-right:.2rem}.md-meta__link{color:var(--md-typeset-a-color)}.md-meta__link:focus,.md-meta__link:hover{color:var(--md-accent-fg-color)}.md-draft{background-color:#ff1744;border-radius:.125em;color:#fff;display:inline-block;font-weight:700;padding-left:.5714285714em;padding-right:.5714285714em}:root{--md-nav-icon--prev:url('data:image/svg+xml;charset=utf-8,');--md-nav-icon--next:url('data:image/svg+xml;charset=utf-8,');--md-toc-icon:url('data:image/svg+xml;charset=utf-8,')}.md-nav{font-size:.7rem;line-height:1.3}.md-nav__title{color:var(--md-default-fg-color--light);display:block;font-weight:700;overflow:hidden;padding:0 .6rem;text-overflow:ellipsis}.md-nav__title .md-nav__button{display:none}.md-nav__title .md-nav__button img{height:100%;width:auto}.md-nav__title .md-nav__button.md-logo img,.md-nav__title .md-nav__button.md-logo svg{fill:currentcolor;display:block;height:2.4rem;max-width:100%;object-fit:contain;width:auto}.md-nav__list{list-style:none;margin:0;padding:0}.md-nav__link{align-items:flex-start;display:flex;gap:.4rem;margin-top:.625em;scroll-snap-align:start;transition:color 125ms}.md-nav__link--passed,.md-nav__link--passed code{color:var(--md-default-fg-color--light)}.md-nav__item .md-nav__link--active,.md-nav__item .md-nav__link--active code{color:var(--md-typeset-a-color)}.md-nav__link .md-ellipsis{position:relative}.md-nav__link .md-ellipsis code{word-break:normal}[dir=ltr] .md-nav__link .md-icon:last-child{margin-left:auto}[dir=rtl] .md-nav__link .md-icon:last-child{margin-right:auto}.md-nav__link .md-typeset{font-size:.7rem;line-height:1.3}.md-nav__link svg{fill:currentcolor;flex-shrink:0;height:1.3em;position:relative}.md-nav__link[for]:focus,.md-nav__link[for]:hover,.md-nav__link[href]:focus,.md-nav__link[href]:hover{color:var(--md-accent-fg-color);cursor:pointer}.md-nav__link[for]:focus code,.md-nav__link[for]:hover code,.md-nav__link[href]:focus code,.md-nav__link[href]:hover code{background-color:var(--md-accent-fg-color--transparent);color:var(--md-accent-fg-color)}.md-nav__link.focus-visible{outline-color:var(--md-accent-fg-color);outline-offset:.2rem}.md-nav--primary .md-nav__link[for=__toc]{display:none}.md-nav--primary .md-nav__link[for=__toc] .md-icon:after{background-color:currentcolor;display:block;height:100%;-webkit-mask-image:var(--md-toc-icon);mask-image:var(--md-toc-icon);width:100%}.md-nav--primary .md-nav__link[for=__toc]~.md-nav{display:none}.md-nav__container>.md-nav__link{margin-top:0}.md-nav__container>.md-nav__link:first-child{flex-grow:1;min-width:0}.md-nav__icon{flex-shrink:0}.md-nav__source{display:none}@media screen and (max-width:76.234375em){.md-nav--primary,.md-nav--primary .md-nav{background-color:var(--md-default-bg-color);display:flex;flex-direction:column;height:100%;left:0;position:absolute;right:0;top:0;z-index:1}.md-nav--primary .md-nav__item,.md-nav--primary .md-nav__title{font-size:.8rem;line-height:1.5}.md-nav--primary .md-nav__title{background-color:var(--md-default-fg-color--lightest);color:var(--md-default-fg-color--light);cursor:pointer;height:5.6rem;line-height:2.4rem;padding:3rem .8rem .2rem;position:relative;white-space:nowrap}[dir=ltr] .md-nav--primary .md-nav__title .md-nav__icon{left:.4rem}[dir=rtl] .md-nav--primary .md-nav__title .md-nav__icon{right:.4rem}.md-nav--primary .md-nav__title .md-nav__icon{display:block;height:1.2rem;margin:.2rem;position:absolute;top:.4rem;width:1.2rem}.md-nav--primary .md-nav__title .md-nav__icon:after{background-color:currentcolor;content:"";display:block;height:100%;-webkit-mask-image:var(--md-nav-icon--prev);mask-image:var(--md-nav-icon--prev);-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;width:100%}.md-nav--primary .md-nav__title~.md-nav__list{background-color:var(--md-default-bg-color);box-shadow:0 .05rem 0 var(--md-default-fg-color--lightest) inset;overflow-y:auto;scroll-snap-type:y mandatory;touch-action:pan-y}.md-nav--primary .md-nav__title~.md-nav__list>:first-child{border-top:0}.md-nav--primary .md-nav__title[for=__drawer]{background-color:var(--md-primary-fg-color);color:var(--md-primary-bg-color);font-weight:700}.md-nav--primary .md-nav__title .md-logo{display:block;left:.2rem;margin:.2rem;padding:.4rem;position:absolute;right:.2rem;top:.2rem}.md-nav--primary .md-nav__list{flex:1}.md-nav--primary .md-nav__item{border-top:.05rem solid var(--md-default-fg-color--lightest)}.md-nav--primary .md-nav__item--active>.md-nav__link{color:var(--md-typeset-a-color)}.md-nav--primary .md-nav__item--active>.md-nav__link:focus,.md-nav--primary .md-nav__item--active>.md-nav__link:hover{color:var(--md-accent-fg-color)}.md-nav--primary .md-nav__link{margin-top:0;padding:.6rem .8rem}.md-nav--primary .md-nav__link svg{margin-top:.1em}.md-nav--primary .md-nav__link>.md-nav__link{padding:0}[dir=ltr] .md-nav--primary .md-nav__link .md-nav__icon{margin-right:-.2rem}[dir=rtl] .md-nav--primary .md-nav__link .md-nav__icon{margin-left:-.2rem}.md-nav--primary .md-nav__link .md-nav__icon{font-size:1.2rem;height:1.2rem;width:1.2rem}.md-nav--primary .md-nav__link .md-nav__icon:after{background-color:currentcolor;content:"";display:block;height:100%;-webkit-mask-image:var(--md-nav-icon--next);mask-image:var(--md-nav-icon--next);-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;width:100%}[dir=rtl] .md-nav--primary .md-nav__icon:after{transform:scale(-1)}.md-nav--primary .md-nav--secondary .md-nav{background-color:initial;position:static}[dir=ltr] .md-nav--primary .md-nav--secondary .md-nav .md-nav__link{padding-left:1.4rem}[dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav__link{padding-right:1.4rem}[dir=ltr] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav__link{padding-left:2rem}[dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav__link{padding-right:2rem}[dir=ltr] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav__link{padding-left:2.6rem}[dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav__link{padding-right:2.6rem}[dir=ltr] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav .md-nav__link{padding-left:3.2rem}[dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav .md-nav__link{padding-right:3.2rem}.md-nav--secondary{background-color:initial}.md-nav__toggle~.md-nav{display:flex;opacity:0;transform:translateX(100%);transition:transform .25s cubic-bezier(.8,0,.6,1),opacity 125ms 50ms}[dir=rtl] .md-nav__toggle~.md-nav{transform:translateX(-100%)}.md-nav__toggle:checked~.md-nav{opacity:1;transform:translateX(0);transition:transform .25s cubic-bezier(.4,0,.2,1),opacity 125ms 125ms}.md-nav__toggle:checked~.md-nav>.md-nav__list{-webkit-backface-visibility:hidden;backface-visibility:hidden}}@media screen and (max-width:59.984375em){.md-nav--primary .md-nav__link[for=__toc]{display:flex}.md-nav--primary .md-nav__link[for=__toc] .md-icon:after{content:""}.md-nav--primary .md-nav__link[for=__toc]+.md-nav__link{display:none}.md-nav--primary .md-nav__link[for=__toc]~.md-nav{display:flex}.md-nav__source{background-color:var(--md-primary-fg-color--dark);color:var(--md-primary-bg-color);display:block;padding:0 .2rem}}@media screen and (min-width:60em) and (max-width:76.234375em){.md-nav--integrated .md-nav__link[for=__toc]{display:flex}.md-nav--integrated .md-nav__link[for=__toc] .md-icon:after{content:""}.md-nav--integrated .md-nav__link[for=__toc]+.md-nav__link{display:none}.md-nav--integrated .md-nav__link[for=__toc]~.md-nav{display:flex}}@media screen and (min-width:60em){.md-nav{margin-bottom:-.4rem}.md-nav--secondary .md-nav__title{background:var(--md-default-bg-color);box-shadow:0 0 .4rem .4rem var(--md-default-bg-color);position:sticky;top:0;z-index:1}.md-nav--secondary .md-nav__title[for=__toc]{scroll-snap-align:start}.md-nav--secondary .md-nav__title .md-nav__icon{display:none}[dir=ltr] .md-nav--secondary .md-nav__list{padding-left:.6rem}[dir=rtl] .md-nav--secondary .md-nav__list{padding-right:.6rem}.md-nav--secondary .md-nav__list{padding-bottom:.4rem}[dir=ltr] .md-nav--secondary .md-nav__item>.md-nav__link{margin-right:.4rem}[dir=rtl] .md-nav--secondary .md-nav__item>.md-nav__link{margin-left:.4rem}}@media screen and (min-width:76.25em){.md-nav{margin-bottom:-.4rem;transition:max-height .25s cubic-bezier(.86,0,.07,1)}.md-nav--primary .md-nav__title{background:var(--md-default-bg-color);box-shadow:0 0 .4rem .4rem var(--md-default-bg-color);position:sticky;top:0;z-index:1}.md-nav--primary .md-nav__title[for=__drawer]{scroll-snap-align:start}.md-nav--primary .md-nav__title .md-nav__icon{display:none}[dir=ltr] .md-nav--primary .md-nav__list{padding-left:.6rem}[dir=rtl] .md-nav--primary .md-nav__list{padding-right:.6rem}.md-nav--primary .md-nav__list{padding-bottom:.4rem}[dir=ltr] .md-nav--primary .md-nav__item>.md-nav__link{margin-right:.4rem}[dir=rtl] .md-nav--primary .md-nav__item>.md-nav__link{margin-left:.4rem}.md-nav__toggle~.md-nav{display:grid;grid-template-rows:0fr;opacity:0;transition:grid-template-rows .25s cubic-bezier(.86,0,.07,1),opacity .25s,visibility 0ms .25s;visibility:collapse}.md-nav__toggle~.md-nav>.md-nav__list{overflow:hidden}.md-nav__toggle.md-toggle--indeterminate~.md-nav,.md-nav__toggle:checked~.md-nav{grid-template-rows:1fr;opacity:1;transition:grid-template-rows .25s cubic-bezier(.86,0,.07,1),opacity .15s .1s,visibility 0ms;visibility:visible}.md-nav__toggle.md-toggle--indeterminate~.md-nav{transition:none}.md-nav__item--nested>.md-nav>.md-nav__title{display:none}.md-nav__item--section{display:block;margin:1.25em 0}.md-nav__item--section:last-child{margin-bottom:0}.md-nav__item--section>.md-nav__link{font-weight:700}.md-nav__item--section>.md-nav__link[for]{color:var(--md-default-fg-color--light)}.md-nav__item--section>.md-nav__link:not(.md-nav__container){pointer-events:none}.md-nav__item--section>.md-nav__link .md-icon,.md-nav__item--section>.md-nav__link>[for]{display:none}[dir=ltr] .md-nav__item--section>.md-nav{margin-left:-.6rem}[dir=rtl] .md-nav__item--section>.md-nav{margin-right:-.6rem}.md-nav__item--section>.md-nav{display:block;opacity:1;visibility:visible}.md-nav__item--section>.md-nav>.md-nav__list>.md-nav__item{padding:0}.md-nav__icon{border-radius:100%;height:.9rem;transition:background-color .25s;width:.9rem}.md-nav__icon:hover{background-color:var(--md-accent-fg-color--transparent)}.md-nav__icon:after{background-color:currentcolor;border-radius:100%;content:"";display:inline-block;height:100%;-webkit-mask-image:var(--md-nav-icon--next);mask-image:var(--md-nav-icon--next);-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;transition:transform .25s;vertical-align:-.1rem;width:100%}[dir=rtl] .md-nav__icon:after{transform:rotate(180deg)}.md-nav__item--nested .md-nav__toggle:checked~.md-nav__link .md-nav__icon:after,.md-nav__item--nested .md-toggle--indeterminate~.md-nav__link .md-nav__icon:after{transform:rotate(90deg)}.md-nav--lifted>.md-nav__list>.md-nav__item,.md-nav--lifted>.md-nav__title{display:none}.md-nav--lifted>.md-nav__list>.md-nav__item--active{display:block}.md-nav--lifted>.md-nav__list>.md-nav__item--active>.md-nav__link{background:var(--md-default-bg-color);box-shadow:0 0 .4rem .4rem var(--md-default-bg-color);margin-top:0;position:sticky;top:0;z-index:1}.md-nav--lifted>.md-nav__list>.md-nav__item--active>.md-nav__link:not(.md-nav__container){pointer-events:none}.md-nav--lifted>.md-nav__list>.md-nav__item--active.md-nav__item--section{margin:0}[dir=ltr] .md-nav--lifted>.md-nav__list>.md-nav__item>.md-nav:not(.md-nav--secondary){margin-left:-.6rem}[dir=rtl] .md-nav--lifted>.md-nav__list>.md-nav__item>.md-nav:not(.md-nav--secondary){margin-right:-.6rem}.md-nav--lifted>.md-nav__list>.md-nav__item>[for]{color:var(--md-default-fg-color--light)}.md-nav--lifted .md-nav[data-md-level="1"]{grid-template-rows:1fr;opacity:1;visibility:visible}[dir=ltr] .md-nav--integrated>.md-nav__list>.md-nav__item--active .md-nav--secondary{border-left:.05rem solid var(--md-primary-fg-color)}[dir=rtl] .md-nav--integrated>.md-nav__list>.md-nav__item--active .md-nav--secondary{border-right:.05rem solid var(--md-primary-fg-color)}.md-nav--integrated>.md-nav__list>.md-nav__item--active .md-nav--secondary{display:block;margin-bottom:1.25em;opacity:1;visibility:visible}.md-nav--integrated>.md-nav__list>.md-nav__item--active .md-nav--secondary>.md-nav__list{overflow:visible;padding-bottom:0}.md-nav--integrated>.md-nav__list>.md-nav__item--active .md-nav--secondary>.md-nav__title{display:none}}.md-pagination{font-size:.8rem;font-weight:700;gap:.4rem}.md-pagination,.md-pagination>*{align-items:center;display:flex;justify-content:center}.md-pagination>*{border-radius:.2rem;height:1.8rem;min-width:1.8rem;text-align:center}.md-pagination__current{background-color:var(--md-default-fg-color--lightest);color:var(--md-default-fg-color--light)}.md-pagination__link{transition:color 125ms,background-color 125ms}.md-pagination__link:focus,.md-pagination__link:hover{background-color:var(--md-accent-fg-color--transparent);color:var(--md-accent-fg-color)}.md-pagination__link:focus svg,.md-pagination__link:hover svg{color:var(--md-accent-fg-color)}.md-pagination__link.focus-visible{outline-color:var(--md-accent-fg-color);outline-offset:.2rem}.md-pagination__link svg{fill:currentcolor;color:var(--md-default-fg-color--lighter);display:block;max-height:100%;width:1.2rem}:root{--md-path-icon:url('data:image/svg+xml;charset=utf-8,')}.md-path{font-size:.7rem;margin:0 .8rem;overflow:auto;padding-top:1.2rem}.md-path:not([hidden]){display:block}@media screen and (min-width:76.25em){.md-path{margin:0 1.2rem}}.md-path__list{align-items:center;display:flex;gap:.2rem;list-style:none;margin:0;padding:0}.md-path__item:not(:first-child){display:inline-flex;gap:.2rem;white-space:nowrap}.md-path__item:not(:first-child):before{background-color:var(--md-default-fg-color--lighter);content:"";display:inline;height:.8rem;-webkit-mask-image:var(--md-path-icon);mask-image:var(--md-path-icon);width:.8rem}.md-path__link{align-items:center;color:var(--md-default-fg-color--light);display:flex}.md-path__link:focus,.md-path__link:hover{color:var(--md-accent-fg-color)}:root{--md-post-pin-icon:url('data:image/svg+xml;charset=utf-8,')}.md-post__back{border-bottom:.05rem solid var(--md-default-fg-color--lightest);margin-bottom:1.2rem;padding-bottom:1.2rem}@media screen and (max-width:76.234375em){.md-post__back{display:none}}[dir=rtl] .md-post__back svg{transform:scaleX(-1)}.md-post__authors{display:flex;flex-direction:column;gap:.6rem;margin:0 .6rem 1.2rem}.md-post .md-post__meta a{transition:color 125ms}.md-post .md-post__meta a:focus,.md-post .md-post__meta a:hover{color:var(--md-accent-fg-color)}.md-post__title{color:var(--md-default-fg-color--light);font-weight:700}.md-post--excerpt{margin-bottom:3.2rem}.md-post--excerpt .md-post__header{align-items:center;display:flex;gap:.6rem;min-height:1.6rem}.md-post--excerpt .md-post__authors{align-items:center;display:inline-flex;flex-direction:row;gap:.2rem;margin:0;min-height:2.4rem}[dir=ltr] .md-post--excerpt .md-post__meta .md-meta__list{margin-right:.4rem}[dir=rtl] .md-post--excerpt .md-post__meta .md-meta__list{margin-left:.4rem}.md-post--excerpt .md-post__content>:first-child{--md-scroll-margin:6rem;margin-top:0}.md-post>.md-nav--secondary{margin:1em 0}.md-pin{background:var(--md-default-fg-color--lightest);border-radius:1rem;margin-top:-.05rem;padding:.2rem}.md-pin:after{background-color:currentcolor;content:"";display:block;height:.6rem;margin:0 auto;-webkit-mask-image:var(--md-post-pin-icon);mask-image:var(--md-post-pin-icon);-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;width:.6rem}.md-profile{align-items:center;display:flex;font-size:.7rem;gap:.6rem;line-height:1.4;width:100%}.md-profile__description{flex-grow:1}.md-content--post{display:flex}@media screen and (max-width:76.234375em){.md-content--post{flex-flow:column-reverse}}.md-content--post>.md-content__inner{min-width:0}@media screen and (min-width:76.25em){[dir=ltr] .md-content--post>.md-content__inner{margin-left:1.2rem}[dir=rtl] .md-content--post>.md-content__inner{margin-right:1.2rem}}@media screen and (max-width:76.234375em){.md-sidebar.md-sidebar--post{padding:0;position:static;width:100%}.md-sidebar.md-sidebar--post .md-sidebar__scrollwrap{overflow:visible}.md-sidebar.md-sidebar--post .md-sidebar__inner{padding:0}.md-sidebar.md-sidebar--post .md-post__meta{margin-left:.6rem;margin-right:.6rem}.md-sidebar.md-sidebar--post .md-nav__item{border:none;display:inline}.md-sidebar.md-sidebar--post .md-nav__list{display:inline-flex;flex-wrap:wrap;gap:.6rem;padding-bottom:.6rem;padding-top:.6rem}.md-sidebar.md-sidebar--post .md-nav__link{padding:0}.md-sidebar.md-sidebar--post .md-nav{height:auto;margin-bottom:0;position:static}}:root{--md-progress-value:0;--md-progress-delay:400ms}.md-progress{background:var(--md-primary-bg-color);height:.075rem;opacity:min(clamp(0,var(--md-progress-value),1),clamp(0,100 - var(--md-progress-value),1));position:fixed;top:0;transform:scaleX(calc(var(--md-progress-value)*1%));transform-origin:left;transition:transform .5s cubic-bezier(.19,1,.22,1),opacity .25s var(--md-progress-delay);width:100%;z-index:4}:root{--md-search-result-icon:url('data:image/svg+xml;charset=utf-8,')}.md-search{position:relative}@media screen and (min-width:60em){.md-search{padding:.2rem 0}}.no-js .md-search{display:none}.md-search__overlay{opacity:0;z-index:1}@media screen and (max-width:59.984375em){[dir=ltr] .md-search__overlay{left:-2.2rem}[dir=rtl] .md-search__overlay{right:-2.2rem}.md-search__overlay{background-color:var(--md-default-bg-color);border-radius:1rem;height:2rem;overflow:hidden;pointer-events:none;position:absolute;top:-1rem;transform-origin:center;transition:transform .3s .1s,opacity .2s .2s;width:2rem}[data-md-toggle=search]:checked~.md-header .md-search__overlay{opacity:1;transition:transform .4s,opacity .1s}}@media screen and (min-width:60em){[dir=ltr] .md-search__overlay{left:0}[dir=rtl] .md-search__overlay{right:0}.md-search__overlay{background-color:#0000008a;cursor:pointer;height:0;position:fixed;top:0;transition:width 0ms .25s,height 0ms .25s,opacity .25s;width:0}[data-md-toggle=search]:checked~.md-header .md-search__overlay{height:200vh;opacity:1;transition:width 0ms,height 0ms,opacity .25s;width:100%}}@media screen and (max-width:29.984375em){[data-md-toggle=search]:checked~.md-header .md-search__overlay{transform:scale(45)}}@media screen and (min-width:30em) and (max-width:44.984375em){[data-md-toggle=search]:checked~.md-header .md-search__overlay{transform:scale(60)}}@media screen and (min-width:45em) and (max-width:59.984375em){[data-md-toggle=search]:checked~.md-header .md-search__overlay{transform:scale(75)}}.md-search__inner{-webkit-backface-visibility:hidden;backface-visibility:hidden}@media screen and (max-width:59.984375em){[dir=ltr] .md-search__inner{left:0}[dir=rtl] .md-search__inner{right:0}.md-search__inner{height:0;opacity:0;overflow:hidden;position:fixed;top:0;transform:translateX(5%);transition:width 0ms .3s,height 0ms .3s,transform .15s cubic-bezier(.4,0,.2,1) .15s,opacity .15s .15s;width:0;z-index:2}[dir=rtl] .md-search__inner{transform:translateX(-5%)}[data-md-toggle=search]:checked~.md-header .md-search__inner{height:100%;opacity:1;transform:translateX(0);transition:width 0ms 0ms,height 0ms 0ms,transform .15s cubic-bezier(.1,.7,.1,1) .15s,opacity .15s .15s;width:100%}}@media screen and (min-width:60em){[dir=ltr] .md-search__inner{float:right}[dir=rtl] .md-search__inner{float:left}.md-search__inner{padding:.1rem 0;position:relative;transition:width .25s cubic-bezier(.1,.7,.1,1);width:11.7rem}}@media screen and (min-width:60em) and (max-width:76.234375em){[data-md-toggle=search]:checked~.md-header .md-search__inner{width:23.4rem}}@media screen and (min-width:76.25em){[data-md-toggle=search]:checked~.md-header .md-search__inner{width:34.4rem}}.md-search__form{background-color:var(--md-default-bg-color);box-shadow:0 0 .6rem #0000;height:2.4rem;position:relative;transition:color .25s,background-color .25s;z-index:2}@media screen and (min-width:60em){.md-search__form{background-color:#00000042;border-radius:.1rem;height:1.8rem}.md-search__form:hover{background-color:#ffffff1f}}[data-md-toggle=search]:checked~.md-header .md-search__form{background-color:var(--md-default-bg-color);border-radius:.1rem .1rem 0 0;box-shadow:0 0 .6rem #00000012;color:var(--md-default-fg-color)}[dir=ltr] .md-search__input{padding-left:3.6rem;padding-right:2.2rem}[dir=rtl] .md-search__input{padding-left:2.2rem;padding-right:3.6rem}.md-search__input{background:#0000;font-size:.9rem;height:100%;position:relative;text-overflow:ellipsis;width:100%;z-index:2}.md-search__input::placeholder{transition:color .25s}.md-search__input::placeholder,.md-search__input~.md-search__icon{color:var(--md-default-fg-color--light)}.md-search__input::-ms-clear{display:none}@media screen and (max-width:59.984375em){.md-search__input{font-size:.9rem;height:2.4rem;width:100%}}@media screen and (min-width:60em){[dir=ltr] .md-search__input{padding-left:2.2rem}[dir=rtl] .md-search__input{padding-right:2.2rem}.md-search__input{color:inherit;font-size:.8rem}.md-search__input::placeholder{color:var(--md-primary-bg-color--light)}.md-search__input+.md-search__icon{color:var(--md-primary-bg-color)}[data-md-toggle=search]:checked~.md-header .md-search__input{text-overflow:clip}[data-md-toggle=search]:checked~.md-header .md-search__input+.md-search__icon{color:var(--md-default-fg-color--light)}[data-md-toggle=search]:checked~.md-header .md-search__input::placeholder{color:#0000}}.md-search__icon{cursor:pointer;display:inline-block;height:1.2rem;transition:color .25s,opacity .25s;width:1.2rem}.md-search__icon:hover{opacity:.7}[dir=ltr] .md-search__icon[for=__search]{left:.5rem}[dir=rtl] .md-search__icon[for=__search]{right:.5rem}.md-search__icon[for=__search]{position:absolute;top:.3rem;z-index:2}[dir=rtl] .md-search__icon[for=__search] svg{transform:scaleX(-1)}@media screen and (max-width:59.984375em){[dir=ltr] .md-search__icon[for=__search]{left:.8rem}[dir=rtl] .md-search__icon[for=__search]{right:.8rem}.md-search__icon[for=__search]{top:.6rem}.md-search__icon[for=__search] svg:first-child{display:none}}@media screen and (min-width:60em){.md-search__icon[for=__search]{pointer-events:none}.md-search__icon[for=__search] svg:last-child{display:none}}[dir=ltr] .md-search__options{right:.5rem}[dir=rtl] .md-search__options{left:.5rem}.md-search__options{pointer-events:none;position:absolute;top:.3rem;z-index:2}@media screen and (max-width:59.984375em){[dir=ltr] .md-search__options{right:.8rem}[dir=rtl] .md-search__options{left:.8rem}.md-search__options{top:.6rem}}[dir=ltr] .md-search__options>.md-icon{margin-left:.2rem}[dir=rtl] .md-search__options>.md-icon{margin-right:.2rem}.md-search__options>.md-icon{color:var(--md-default-fg-color--light);opacity:0;transform:scale(.75);transition:transform .15s cubic-bezier(.1,.7,.1,1),opacity .15s}.md-search__options>.md-icon:not(.focus-visible){-webkit-tap-highlight-color:transparent;outline:none}[data-md-toggle=search]:checked~.md-header .md-search__input:valid~.md-search__options>.md-icon{opacity:1;pointer-events:auto;transform:scale(1)}[data-md-toggle=search]:checked~.md-header .md-search__input:valid~.md-search__options>.md-icon:hover{opacity:.7}[dir=ltr] .md-search__suggest{padding-left:3.6rem;padding-right:2.2rem}[dir=rtl] .md-search__suggest{padding-left:2.2rem;padding-right:3.6rem}.md-search__suggest{align-items:center;color:var(--md-default-fg-color--lighter);display:flex;font-size:.9rem;height:100%;opacity:0;position:absolute;top:0;transition:opacity 50ms;white-space:nowrap;width:100%}@media screen and (min-width:60em){[dir=ltr] .md-search__suggest{padding-left:2.2rem}[dir=rtl] .md-search__suggest{padding-right:2.2rem}.md-search__suggest{font-size:.8rem}}[data-md-toggle=search]:checked~.md-header .md-search__suggest{opacity:1;transition:opacity .3s .1s}[dir=ltr] .md-search__output{border-bottom-left-radius:.1rem}[dir=ltr] .md-search__output,[dir=rtl] .md-search__output{border-bottom-right-radius:.1rem}[dir=rtl] .md-search__output{border-bottom-left-radius:.1rem}.md-search__output{overflow:hidden;position:absolute;width:100%;z-index:1}@media screen and (max-width:59.984375em){.md-search__output{bottom:0;top:2.4rem}}@media screen and (min-width:60em){.md-search__output{opacity:0;top:1.9rem;transition:opacity .4s}[data-md-toggle=search]:checked~.md-header .md-search__output{box-shadow:var(--md-shadow-z3);opacity:1}}.md-search__scrollwrap{-webkit-backface-visibility:hidden;backface-visibility:hidden;background-color:var(--md-default-bg-color);height:100%;overflow-y:auto;touch-action:pan-y}@media (-webkit-max-device-pixel-ratio:1),(max-resolution:1dppx){.md-search__scrollwrap{transform:translateZ(0)}}@media screen and (min-width:60em) and (max-width:76.234375em){.md-search__scrollwrap{width:23.4rem}}@media screen and (min-width:76.25em){.md-search__scrollwrap{width:34.4rem}}@media screen and (min-width:60em){.md-search__scrollwrap{max-height:0;scrollbar-color:var(--md-default-fg-color--lighter) #0000;scrollbar-width:thin}[data-md-toggle=search]:checked~.md-header .md-search__scrollwrap{max-height:75vh}.md-search__scrollwrap:hover{scrollbar-color:var(--md-accent-fg-color) #0000}.md-search__scrollwrap::-webkit-scrollbar{height:.2rem;width:.2rem}.md-search__scrollwrap::-webkit-scrollbar-thumb{background-color:var(--md-default-fg-color--lighter)}.md-search__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:var(--md-accent-fg-color)}}.md-search-result{color:var(--md-default-fg-color);word-break:break-word}.md-search-result__meta{background-color:var(--md-default-fg-color--lightest);color:var(--md-default-fg-color--light);font-size:.64rem;line-height:1.8rem;padding:0 .8rem;scroll-snap-align:start}@media screen and (min-width:60em){[dir=ltr] .md-search-result__meta{padding-left:2.2rem}[dir=rtl] .md-search-result__meta{padding-right:2.2rem}}.md-search-result__list{list-style:none;margin:0;padding:0;-webkit-user-select:none;user-select:none}.md-search-result__item{box-shadow:0 -.05rem var(--md-default-fg-color--lightest)}.md-search-result__item:first-child{box-shadow:none}.md-search-result__link{display:block;outline:none;scroll-snap-align:start;transition:background-color .25s}.md-search-result__link:focus,.md-search-result__link:hover{background-color:var(--md-accent-fg-color--transparent)}.md-search-result__link:last-child p:last-child{margin-bottom:.6rem}.md-search-result__more>summary{cursor:pointer;display:block;outline:none;position:sticky;scroll-snap-align:start;top:0;z-index:1}.md-search-result__more>summary::marker{display:none}.md-search-result__more>summary::-webkit-details-marker{display:none}.md-search-result__more>summary>div{color:var(--md-typeset-a-color);font-size:.64rem;padding:.75em .8rem;transition:color .25s,background-color .25s}@media screen and (min-width:60em){[dir=ltr] .md-search-result__more>summary>div{padding-left:2.2rem}[dir=rtl] .md-search-result__more>summary>div{padding-right:2.2rem}}.md-search-result__more>summary:focus>div,.md-search-result__more>summary:hover>div{background-color:var(--md-accent-fg-color--transparent);color:var(--md-accent-fg-color)}.md-search-result__more[open]>summary{background-color:var(--md-default-bg-color)}.md-search-result__article{overflow:hidden;padding:0 .8rem;position:relative}@media screen and (min-width:60em){[dir=ltr] .md-search-result__article{padding-left:2.2rem}[dir=rtl] .md-search-result__article{padding-right:2.2rem}}[dir=ltr] .md-search-result__icon{left:0}[dir=rtl] .md-search-result__icon{right:0}.md-search-result__icon{color:var(--md-default-fg-color--light);height:1.2rem;margin:.5rem;position:absolute;width:1.2rem}@media screen and (max-width:59.984375em){.md-search-result__icon{display:none}}.md-search-result__icon:after{background-color:currentcolor;content:"";display:inline-block;height:100%;-webkit-mask-image:var(--md-search-result-icon);mask-image:var(--md-search-result-icon);-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;width:100%}[dir=rtl] .md-search-result__icon:after{transform:scaleX(-1)}.md-search-result .md-typeset{color:var(--md-default-fg-color--light);font-size:.64rem;line-height:1.6}.md-search-result .md-typeset h1{color:var(--md-default-fg-color);font-size:.8rem;font-weight:400;line-height:1.4;margin:.55rem 0}.md-search-result .md-typeset h1 mark{text-decoration:none}.md-search-result .md-typeset h2{color:var(--md-default-fg-color);font-size:.64rem;font-weight:700;line-height:1.6;margin:.5em 0}.md-search-result .md-typeset h2 mark{text-decoration:none}.md-search-result__terms{color:var(--md-default-fg-color);display:block;font-size:.64rem;font-style:italic;margin:.5em 0}.md-search-result mark{background-color:initial;color:var(--md-accent-fg-color);text-decoration:underline}.md-select{position:relative;z-index:1}.md-select__inner{background-color:var(--md-default-bg-color);border-radius:.1rem;box-shadow:var(--md-shadow-z2);color:var(--md-default-fg-color);left:50%;margin-top:.2rem;max-height:0;opacity:0;position:absolute;top:calc(100% - .2rem);transform:translate3d(-50%,.3rem,0);transition:transform .25s 375ms,opacity .25s .25s,max-height 0ms .5s}.md-select:focus-within .md-select__inner,.md-select:hover .md-select__inner{max-height:10rem;opacity:1;transform:translate3d(-50%,0,0);transition:transform .25s cubic-bezier(.1,.7,.1,1),opacity .25s,max-height 0ms}.md-select__inner:after{border-bottom:.2rem solid #0000;border-bottom-color:var(--md-default-bg-color);border-left:.2rem solid #0000;border-right:.2rem solid #0000;border-top:0;content:"";height:0;left:50%;margin-left:-.2rem;margin-top:-.2rem;position:absolute;top:0;width:0}.md-select__list{border-radius:.1rem;font-size:.8rem;list-style-type:none;margin:0;max-height:inherit;overflow:auto;padding:0}.md-select__item{line-height:1.8rem}[dir=ltr] .md-select__link{padding-left:.6rem;padding-right:1.2rem}[dir=rtl] .md-select__link{padding-left:1.2rem;padding-right:.6rem}.md-select__link{cursor:pointer;display:block;outline:none;scroll-snap-align:start;transition:background-color .25s,color .25s;width:100%}.md-select__link:focus,.md-select__link:hover{color:var(--md-accent-fg-color)}.md-select__link:focus{background-color:var(--md-default-fg-color--lightest)}.md-sidebar{align-self:flex-start;flex-shrink:0;padding:1.2rem 0;position:sticky;top:2.4rem;width:12.1rem}@media print{.md-sidebar{display:none}}@media screen and (max-width:76.234375em){[dir=ltr] .md-sidebar--primary{left:-12.1rem}[dir=rtl] .md-sidebar--primary{right:-12.1rem}.md-sidebar--primary{background-color:var(--md-default-bg-color);display:block;height:100%;position:fixed;top:0;transform:translateX(0);transition:transform .25s cubic-bezier(.4,0,.2,1),box-shadow .25s;width:12.1rem;z-index:5}[data-md-toggle=drawer]:checked~.md-container .md-sidebar--primary{box-shadow:var(--md-shadow-z3);transform:translateX(12.1rem)}[dir=rtl] [data-md-toggle=drawer]:checked~.md-container .md-sidebar--primary{transform:translateX(-12.1rem)}.md-sidebar--primary .md-sidebar__scrollwrap{bottom:0;left:0;margin:0;overflow:hidden;position:absolute;right:0;scroll-snap-type:none;top:0}}@media screen and (min-width:76.25em){.md-sidebar{height:0}.no-js .md-sidebar{height:auto}.md-header--lifted~.md-container .md-sidebar{top:4.8rem}}.md-sidebar--secondary{display:none;order:2}@media screen and (min-width:60em){.md-sidebar--secondary{height:0}.no-js .md-sidebar--secondary{height:auto}.md-sidebar--secondary:not([hidden]){display:block}.md-sidebar--secondary .md-sidebar__scrollwrap{touch-action:pan-y}}.md-sidebar__scrollwrap{scrollbar-gutter:stable;-webkit-backface-visibility:hidden;backface-visibility:hidden;margin:0 .2rem;overflow-y:auto;scrollbar-color:var(--md-default-fg-color--lighter) #0000;scrollbar-width:thin}.md-sidebar__scrollwrap::-webkit-scrollbar{height:.2rem;width:.2rem}.md-sidebar__scrollwrap:focus-within,.md-sidebar__scrollwrap:hover{scrollbar-color:var(--md-accent-fg-color) #0000}.md-sidebar__scrollwrap:focus-within::-webkit-scrollbar-thumb,.md-sidebar__scrollwrap:hover::-webkit-scrollbar-thumb{background-color:var(--md-default-fg-color--lighter)}.md-sidebar__scrollwrap:focus-within::-webkit-scrollbar-thumb:hover,.md-sidebar__scrollwrap:hover::-webkit-scrollbar-thumb:hover{background-color:var(--md-accent-fg-color)}@supports selector(::-webkit-scrollbar){.md-sidebar__scrollwrap{scrollbar-gutter:auto}[dir=ltr] .md-sidebar__inner{padding-right:calc(100% - 11.5rem)}[dir=rtl] .md-sidebar__inner{padding-left:calc(100% - 11.5rem)}}@media screen and (max-width:76.234375em){.md-overlay{background-color:#0000008a;height:0;opacity:0;position:fixed;top:0;transition:width 0ms .25s,height 0ms .25s,opacity .25s;width:0;z-index:5}[data-md-toggle=drawer]:checked~.md-overlay{height:100%;opacity:1;transition:width 0ms,height 0ms,opacity .25s;width:100%}}@keyframes facts{0%{height:0}to{height:.65rem}}@keyframes fact{0%{opacity:0;transform:translateY(100%)}50%{opacity:0}to{opacity:1;transform:translateY(0)}}:root{--md-source-forks-icon:url('data:image/svg+xml;charset=utf-8,');--md-source-repositories-icon:url('data:image/svg+xml;charset=utf-8,');--md-source-stars-icon:url('data:image/svg+xml;charset=utf-8,');--md-source-version-icon:url('data:image/svg+xml;charset=utf-8,')}.md-source{-webkit-backface-visibility:hidden;backface-visibility:hidden;display:block;font-size:.65rem;line-height:1.2;outline-color:var(--md-accent-fg-color);transition:opacity .25s;white-space:nowrap}.md-source:hover{opacity:.7}.md-source__icon{display:inline-block;height:2.4rem;vertical-align:middle;width:2rem}[dir=ltr] .md-source__icon svg{margin-left:.6rem}[dir=rtl] .md-source__icon svg{margin-right:.6rem}.md-source__icon svg{margin-top:.6rem}[dir=ltr] .md-source__icon+.md-source__repository{padding-left:2rem}[dir=rtl] .md-source__icon+.md-source__repository{padding-right:2rem}[dir=ltr] .md-source__icon+.md-source__repository{margin-left:-2rem}[dir=rtl] .md-source__icon+.md-source__repository{margin-right:-2rem}[dir=ltr] .md-source__repository{margin-left:.6rem}[dir=rtl] .md-source__repository{margin-right:.6rem}.md-source__repository{display:inline-block;max-width:calc(100% - 1.2rem);overflow:hidden;text-overflow:ellipsis;vertical-align:middle}.md-source__facts{display:flex;font-size:.55rem;gap:.4rem;list-style-type:none;margin:.1rem 0 0;opacity:.75;overflow:hidden;padding:0;width:100%}.md-source__repository--active .md-source__facts{animation:facts .25s ease-in}.md-source__fact{overflow:hidden;text-overflow:ellipsis}.md-source__repository--active .md-source__fact{animation:fact .4s ease-out}[dir=ltr] .md-source__fact:before{margin-right:.1rem}[dir=rtl] .md-source__fact:before{margin-left:.1rem}.md-source__fact:before{background-color:currentcolor;content:"";display:inline-block;height:.6rem;-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;vertical-align:text-top;width:.6rem}.md-source__fact:nth-child(1n+2){flex-shrink:0}.md-source__fact--version:before{-webkit-mask-image:var(--md-source-version-icon);mask-image:var(--md-source-version-icon)}.md-source__fact--stars:before{-webkit-mask-image:var(--md-source-stars-icon);mask-image:var(--md-source-stars-icon)}.md-source__fact--forks:before{-webkit-mask-image:var(--md-source-forks-icon);mask-image:var(--md-source-forks-icon)}.md-source__fact--repositories:before{-webkit-mask-image:var(--md-source-repositories-icon);mask-image:var(--md-source-repositories-icon)}.md-source-file{margin:1em 0}[dir=ltr] .md-source-file__fact{margin-right:.6rem}[dir=rtl] .md-source-file__fact{margin-left:.6rem}.md-source-file__fact{align-items:center;color:var(--md-default-fg-color--light);display:inline-flex;font-size:.68rem;gap:.3rem}.md-source-file__fact .md-icon{flex-shrink:0;margin-bottom:.05rem}[dir=ltr] .md-source-file__fact .md-author{float:left}[dir=rtl] .md-source-file__fact .md-author{float:right}.md-source-file__fact .md-author{margin-right:.2rem}.md-source-file__fact svg{width:.9rem}:root{--md-status:url('data:image/svg+xml;charset=utf-8,');--md-status--new:url('data:image/svg+xml;charset=utf-8,');--md-status--deprecated:url('data:image/svg+xml;charset=utf-8,');--md-status--encrypted:url('data:image/svg+xml;charset=utf-8,')}.md-status:after{background-color:var(--md-default-fg-color--light);content:"";display:inline-block;height:1.125em;-webkit-mask-image:var(--md-status);mask-image:var(--md-status);-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;vertical-align:text-bottom;width:1.125em}.md-status:hover:after{background-color:currentcolor}.md-status--new:after{-webkit-mask-image:var(--md-status--new);mask-image:var(--md-status--new)}.md-status--deprecated:after{-webkit-mask-image:var(--md-status--deprecated);mask-image:var(--md-status--deprecated)}.md-status--encrypted:after{-webkit-mask-image:var(--md-status--encrypted);mask-image:var(--md-status--encrypted)}.md-tabs{background-color:var(--md-primary-fg-color);color:var(--md-primary-bg-color);display:block;line-height:1.3;overflow:auto;width:100%;z-index:3}@media print{.md-tabs{display:none}}@media screen and (max-width:76.234375em){.md-tabs{display:none}}.md-tabs[hidden]{pointer-events:none}[dir=ltr] .md-tabs__list{margin-left:.2rem}[dir=rtl] .md-tabs__list{margin-right:.2rem}.md-tabs__list{contain:content;display:flex;list-style:none;margin:0;overflow:auto;padding:0;scrollbar-width:none;white-space:nowrap}.md-tabs__list::-webkit-scrollbar{display:none}.md-tabs__item{height:2.4rem;padding-left:.6rem;padding-right:.6rem}.md-tabs__item--active .md-tabs__link{color:inherit;opacity:1}.md-tabs__link{-webkit-backface-visibility:hidden;backface-visibility:hidden;display:flex;font-size:.7rem;margin-top:.8rem;opacity:.7;outline-color:var(--md-accent-fg-color);outline-offset:.2rem;transition:transform .4s cubic-bezier(.1,.7,.1,1),opacity .25s}.md-tabs__link:focus,.md-tabs__link:hover{color:inherit;opacity:1}[dir=ltr] .md-tabs__link svg{margin-right:.4rem}[dir=rtl] .md-tabs__link svg{margin-left:.4rem}.md-tabs__link svg{fill:currentcolor;height:1.3em}.md-tabs__item:nth-child(2) .md-tabs__link{transition-delay:20ms}.md-tabs__item:nth-child(3) .md-tabs__link{transition-delay:40ms}.md-tabs__item:nth-child(4) .md-tabs__link{transition-delay:60ms}.md-tabs__item:nth-child(5) .md-tabs__link{transition-delay:80ms}.md-tabs__item:nth-child(6) .md-tabs__link{transition-delay:.1s}.md-tabs__item:nth-child(7) .md-tabs__link{transition-delay:.12s}.md-tabs__item:nth-child(8) .md-tabs__link{transition-delay:.14s}.md-tabs__item:nth-child(9) .md-tabs__link{transition-delay:.16s}.md-tabs__item:nth-child(10) .md-tabs__link{transition-delay:.18s}.md-tabs__item:nth-child(11) .md-tabs__link{transition-delay:.2s}.md-tabs__item:nth-child(12) .md-tabs__link{transition-delay:.22s}.md-tabs__item:nth-child(13) .md-tabs__link{transition-delay:.24s}.md-tabs__item:nth-child(14) .md-tabs__link{transition-delay:.26s}.md-tabs__item:nth-child(15) .md-tabs__link{transition-delay:.28s}.md-tabs__item:nth-child(16) .md-tabs__link{transition-delay:.3s}.md-tabs[hidden] .md-tabs__link{opacity:0;transform:translateY(50%);transition:transform 0ms .1s,opacity .1s}:root{--md-tag-icon:url('data:image/svg+xml;charset=utf-8,')}.md-typeset .md-tags:not([hidden]){display:inline-flex;flex-wrap:wrap;gap:.5em;margin-bottom:.75em;margin-top:-.125em}.md-typeset .md-tag{align-items:center;background:var(--md-default-fg-color--lightest);border-radius:2.4rem;display:inline-flex;font-size:.64rem;font-size:min(.8em,.64rem);font-weight:700;gap:.5em;letter-spacing:normal;line-height:1.6;padding:.3125em .78125em}.md-typeset .md-tag[href]{-webkit-tap-highlight-color:transparent;color:inherit;outline:none;transition:color 125ms,background-color 125ms}.md-typeset .md-tag[href]:focus,.md-typeset .md-tag[href]:hover{background-color:var(--md-accent-fg-color);color:var(--md-accent-bg-color)}[id]>.md-typeset .md-tag{vertical-align:text-top}.md-typeset .md-tag-shadow{opacity:.5}.md-typeset .md-tag-icon:before{background-color:var(--md-default-fg-color--lighter);content:"";display:inline-block;height:1.2em;-webkit-mask-image:var(--md-tag-icon);mask-image:var(--md-tag-icon);-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;transition:background-color 125ms;vertical-align:text-bottom;width:1.2em}.md-typeset .md-tag-icon[href]:focus:before,.md-typeset .md-tag-icon[href]:hover:before{background-color:var(--md-accent-bg-color)}@keyframes pulse{0%{transform:scale(.95)}75%{transform:scale(1)}to{transform:scale(.95)}}:root{--md-annotation-bg-icon:url('data:image/svg+xml;charset=utf-8,');--md-annotation-icon:url('data:image/svg+xml;charset=utf-8,')}.md-tooltip{-webkit-backface-visibility:hidden;backface-visibility:hidden;background-color:var(--md-default-bg-color);border-radius:.1rem;box-shadow:var(--md-shadow-z2);color:var(--md-default-fg-color);font-family:var(--md-text-font-family);left:clamp(var(--md-tooltip-0,0rem) + .8rem,var(--md-tooltip-x),100vw + var(--md-tooltip-0,0rem) + .8rem - var(--md-tooltip-width) - 2 * .8rem);max-width:calc(100vw - 1.6rem);opacity:0;position:absolute;top:var(--md-tooltip-y);transform:translateY(-.4rem);transition:transform 0ms .25s,opacity .25s,z-index .25s;width:var(--md-tooltip-width);z-index:0}.md-tooltip--active{opacity:1;transform:translateY(0);transition:transform .25s cubic-bezier(.1,.7,.1,1),opacity .25s,z-index 0ms;z-index:2}.md-tooltip--inline{font-weight:700;-webkit-user-select:none;user-select:none;width:auto}.md-tooltip--inline:not(.md-tooltip--active){transform:translateY(.2rem) scale(.9)}.md-tooltip--inline .md-tooltip__inner{font-size:.5rem;padding:.2rem .4rem}[hidden]+.md-tooltip--inline{display:none}.focus-visible>.md-tooltip,.md-tooltip:target{outline:var(--md-accent-fg-color) auto}.md-tooltip__inner{font-size:.64rem;padding:.8rem}.md-tooltip__inner.md-typeset>:first-child{margin-top:0}.md-tooltip__inner.md-typeset>:last-child{margin-bottom:0}.md-annotation{font-style:normal;font-weight:400;outline:none;text-align:initial;vertical-align:text-bottom;white-space:normal}[dir=rtl] .md-annotation{direction:rtl}code .md-annotation{font-family:var(--md-code-font-family);font-size:inherit}.md-annotation:not([hidden]){display:inline-block;line-height:1.25}.md-annotation__index{border-radius:.01px;cursor:pointer;display:inline-block;margin-left:.4ch;margin-right:.4ch;outline:none;overflow:hidden;position:relative;-webkit-user-select:none;user-select:none;vertical-align:text-top;z-index:0}.md-annotation .md-annotation__index{transition:z-index .25s}@media screen{.md-annotation__index{width:2.2ch}[data-md-visible]>.md-annotation__index{animation:pulse 2s infinite}.md-annotation__index:before{background:var(--md-default-bg-color);-webkit-mask-image:var(--md-annotation-bg-icon);mask-image:var(--md-annotation-bg-icon)}.md-annotation__index:after,.md-annotation__index:before{content:"";height:2.2ch;-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;position:absolute;top:-.1ch;width:2.2ch;z-index:-1}.md-annotation__index:after{background-color:var(--md-default-fg-color--lighter);-webkit-mask-image:var(--md-annotation-icon);mask-image:var(--md-annotation-icon);transform:scale(1.0001);transition:background-color .25s,transform .25s}.md-tooltip--active+.md-annotation__index:after{transform:rotate(45deg)}.md-tooltip--active+.md-annotation__index:after,:hover>.md-annotation__index:after{background-color:var(--md-accent-fg-color)}}.md-tooltip--active+.md-annotation__index{animation-play-state:paused;transition-duration:0ms;z-index:2}.md-annotation__index [data-md-annotation-id]{display:inline-block}@media print{.md-annotation__index [data-md-annotation-id]{background:var(--md-default-fg-color--lighter);border-radius:2ch;color:var(--md-default-bg-color);font-weight:700;padding:0 .6ch;white-space:nowrap}.md-annotation__index [data-md-annotation-id]:after{content:attr(data-md-annotation-id)}}.md-typeset .md-annotation-list{counter-reset:xxx;list-style:none}.md-typeset .md-annotation-list li{position:relative}[dir=ltr] .md-typeset .md-annotation-list li:before{left:-2.125em}[dir=rtl] .md-typeset .md-annotation-list li:before{right:-2.125em}.md-typeset .md-annotation-list li:before{background:var(--md-default-fg-color--lighter);border-radius:2ch;color:var(--md-default-bg-color);content:counter(xxx);counter-increment:xxx;font-size:.8875em;font-weight:700;height:2ch;line-height:1.25;min-width:2ch;padding:0 .6ch;position:absolute;text-align:center;top:.25em}:root{--md-tooltip-width:20rem;--md-tooltip-tail:0.3rem}.md-tooltip2{-webkit-backface-visibility:hidden;backface-visibility:hidden;color:var(--md-default-fg-color);font-family:var(--md-text-font-family);opacity:0;pointer-events:none;position:absolute;top:calc(var(--md-tooltip-host-y) + var(--md-tooltip-y));transform:translateY(-.4rem);transform-origin:calc(var(--md-tooltip-host-x) + var(--md-tooltip-x)) 0;transition:transform 0ms .25s,opacity .25s,z-index .25s;width:100%;z-index:0}.md-tooltip2:before{border-left:var(--md-tooltip-tail) solid #0000;border-right:var(--md-tooltip-tail) solid #0000;content:"";display:block;left:clamp(1.5 * .8rem,var(--md-tooltip-host-x) + var(--md-tooltip-x) - var(--md-tooltip-tail),100vw - 2 * var(--md-tooltip-tail) - 1.5 * .8rem);position:absolute;z-index:1}.md-tooltip2--top:before{border-top:var(--md-tooltip-tail) solid var(--md-default-bg-color);bottom:calc(var(--md-tooltip-tail)*-1 + .025rem);filter:drop-shadow(0 1px 0 hsla(0,0%,0%,.05))}.md-tooltip2--bottom:before{border-bottom:var(--md-tooltip-tail) solid var(--md-default-bg-color);filter:drop-shadow(0 -1px 0 hsla(0,0%,0%,.05));top:calc(var(--md-tooltip-tail)*-1 + .025rem)}.md-tooltip2--active{opacity:1;transform:translateY(0);transition:transform .4s cubic-bezier(0,1,.5,1),opacity .25s,z-index 0ms;z-index:2}.md-tooltip2__inner{scrollbar-gutter:stable;background-color:var(--md-default-bg-color);border-radius:.1rem;box-shadow:var(--md-shadow-z2);left:clamp(.8rem,var(--md-tooltip-host-x) - .8rem,100vw - var(--md-tooltip-width) - .8rem);max-height:40vh;max-width:calc(100vw - 1.6rem);position:relative;scrollbar-width:thin}.md-tooltip2__inner::-webkit-scrollbar{height:.2rem;width:.2rem}.md-tooltip2__inner::-webkit-scrollbar-thumb{background-color:var(--md-default-fg-color--lighter)}.md-tooltip2__inner::-webkit-scrollbar-thumb:hover{background-color:var(--md-accent-fg-color)}[role=dialog]>.md-tooltip2__inner{font-size:.64rem;overflow:auto;padding:0 .8rem;pointer-events:auto;width:var(--md-tooltip-width)}[role=dialog]>.md-tooltip2__inner:after,[role=dialog]>.md-tooltip2__inner:before{content:"";display:block;height:.8rem;position:sticky;width:100%;z-index:10}[role=dialog]>.md-tooltip2__inner:before{background:linear-gradient(var(--md-default-bg-color),#0000 75%);top:0}[role=dialog]>.md-tooltip2__inner:after{background:linear-gradient(#0000,var(--md-default-bg-color) 75%);bottom:0}[role=tooltip]>.md-tooltip2__inner{font-size:.5rem;font-weight:700;left:clamp(.8rem,var(--md-tooltip-host-x) + var(--md-tooltip-x) - var(--md-tooltip-width)/2,100vw - var(--md-tooltip-width) - .8rem);max-width:min(100vw - 2 * .8rem,400px);padding:.2rem .4rem;-webkit-user-select:none;user-select:none;width:-moz-fit-content;width:fit-content}.md-tooltip2__inner.md-typeset>:first-child{margin-top:0}.md-tooltip2__inner.md-typeset>:last-child{margin-bottom:0}[dir=ltr] .md-top{margin-left:50%}[dir=rtl] .md-top{margin-right:50%}.md-top{background-color:var(--md-default-bg-color);border-radius:1.6rem;box-shadow:var(--md-shadow-z2);color:var(--md-default-fg-color--light);cursor:pointer;display:block;font-size:.7rem;outline:none;padding:.4rem .8rem;position:fixed;top:3.2rem;transform:translate(-50%);transition:color 125ms,background-color 125ms,transform 125ms cubic-bezier(.4,0,.2,1),opacity 125ms;z-index:2}@media print{.md-top{display:none}}[dir=rtl] .md-top{transform:translate(50%)}.md-top[hidden]{opacity:0;pointer-events:none;transform:translate(-50%,.2rem);transition-duration:0ms}[dir=rtl] .md-top[hidden]{transform:translate(50%,.2rem)}.md-top:focus,.md-top:hover{background-color:var(--md-accent-fg-color);color:var(--md-accent-bg-color)}.md-top svg{display:inline-block;vertical-align:-.5em}@keyframes hoverfix{0%{pointer-events:none}}:root{--md-version-icon:url('data:image/svg+xml;charset=utf-8,')}.md-version{flex-shrink:0;font-size:.8rem;height:2.4rem}[dir=ltr] .md-version__current{margin-left:1.4rem;margin-right:.4rem}[dir=rtl] .md-version__current{margin-left:.4rem;margin-right:1.4rem}.md-version__current{color:inherit;cursor:pointer;outline:none;position:relative;top:.05rem}[dir=ltr] .md-version__current:after{margin-left:.4rem}[dir=rtl] .md-version__current:after{margin-right:.4rem}.md-version__current:after{background-color:currentcolor;content:"";display:inline-block;height:.6rem;-webkit-mask-image:var(--md-version-icon);mask-image:var(--md-version-icon);-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;width:.4rem}.md-version__alias{margin-left:.3rem;opacity:.7}.md-version__list{background-color:var(--md-default-bg-color);border-radius:.1rem;box-shadow:var(--md-shadow-z2);color:var(--md-default-fg-color);list-style-type:none;margin:.2rem .8rem;max-height:0;opacity:0;overflow:auto;padding:0;position:absolute;scroll-snap-type:y mandatory;top:.15rem;transition:max-height 0ms .5s,opacity .25s .25s;z-index:3}.md-version:focus-within .md-version__list,.md-version:hover .md-version__list{max-height:10rem;opacity:1;transition:max-height 0ms,opacity .25s}@media (hover:none),(pointer:coarse){.md-version:hover .md-version__list{animation:hoverfix .25s forwards}.md-version:focus-within .md-version__list{animation:none}}.md-version__item{line-height:1.8rem}[dir=ltr] .md-version__link{padding-left:.6rem;padding-right:1.2rem}[dir=rtl] .md-version__link{padding-left:1.2rem;padding-right:.6rem}.md-version__link{cursor:pointer;display:block;outline:none;scroll-snap-align:start;transition:color .25s,background-color .25s;white-space:nowrap;width:100%}.md-version__link:focus,.md-version__link:hover{color:var(--md-accent-fg-color)}.md-version__link:focus{background-color:var(--md-default-fg-color--lightest)}:root{--md-admonition-icon--note:url('data:image/svg+xml;charset=utf-8,');--md-admonition-icon--abstract:url('data:image/svg+xml;charset=utf-8,');--md-admonition-icon--info:url('data:image/svg+xml;charset=utf-8,');--md-admonition-icon--tip:url('data:image/svg+xml;charset=utf-8,');--md-admonition-icon--success:url('data:image/svg+xml;charset=utf-8,');--md-admonition-icon--question:url('data:image/svg+xml;charset=utf-8,');--md-admonition-icon--warning:url('data:image/svg+xml;charset=utf-8,');--md-admonition-icon--failure:url('data:image/svg+xml;charset=utf-8,');--md-admonition-icon--danger:url('data:image/svg+xml;charset=utf-8,');--md-admonition-icon--bug:url('data:image/svg+xml;charset=utf-8,');--md-admonition-icon--example:url('data:image/svg+xml;charset=utf-8,');--md-admonition-icon--quote:url('data:image/svg+xml;charset=utf-8,')}.md-typeset .admonition,.md-typeset details{background-color:var(--md-admonition-bg-color);border:.075rem solid #448aff;border-radius:.2rem;box-shadow:var(--md-shadow-z1);color:var(--md-admonition-fg-color);display:flow-root;font-size:.64rem;margin:1.5625em 0;padding:0 .6rem;page-break-inside:avoid;transition:box-shadow 125ms}@media print{.md-typeset .admonition,.md-typeset details{box-shadow:none}}.md-typeset .admonition:focus-within,.md-typeset details:focus-within{box-shadow:0 0 0 .2rem #448aff1a}.md-typeset .admonition>*,.md-typeset details>*{box-sizing:border-box}.md-typeset .admonition .admonition,.md-typeset .admonition details,.md-typeset details .admonition,.md-typeset details details{margin-bottom:1em;margin-top:1em}.md-typeset .admonition .md-typeset__scrollwrap,.md-typeset details .md-typeset__scrollwrap{margin:1em -.6rem}.md-typeset .admonition .md-typeset__table,.md-typeset details .md-typeset__table{padding:0 .6rem}.md-typeset .admonition>.tabbed-set:only-child,.md-typeset details>.tabbed-set:only-child{margin-top:0}html .md-typeset .admonition>:last-child,html .md-typeset details>:last-child{margin-bottom:.6rem}[dir=ltr] .md-typeset .admonition-title,[dir=ltr] .md-typeset summary{padding-left:2rem;padding-right:.6rem}[dir=rtl] .md-typeset .admonition-title,[dir=rtl] .md-typeset summary{padding-left:.6rem;padding-right:2rem}[dir=ltr] .md-typeset .admonition-title,[dir=ltr] .md-typeset summary{border-left-width:.2rem}[dir=rtl] .md-typeset .admonition-title,[dir=rtl] .md-typeset summary{border-right-width:.2rem}[dir=ltr] .md-typeset .admonition-title,[dir=ltr] .md-typeset summary{border-top-left-radius:.1rem}[dir=ltr] .md-typeset .admonition-title,[dir=ltr] .md-typeset summary,[dir=rtl] .md-typeset .admonition-title,[dir=rtl] .md-typeset summary{border-top-right-radius:.1rem}[dir=rtl] .md-typeset .admonition-title,[dir=rtl] .md-typeset summary{border-top-left-radius:.1rem}.md-typeset .admonition-title,.md-typeset summary{background-color:#448aff1a;border:none;font-weight:700;margin:0 -.6rem;padding-bottom:.4rem;padding-top:.4rem;position:relative}html .md-typeset .admonition-title:last-child,html .md-typeset summary:last-child{margin-bottom:0}[dir=ltr] .md-typeset .admonition-title:before,[dir=ltr] .md-typeset summary:before{left:.6rem}[dir=rtl] .md-typeset .admonition-title:before,[dir=rtl] .md-typeset summary:before{right:.6rem}.md-typeset .admonition-title:before,.md-typeset summary:before{background-color:#448aff;content:"";height:1rem;-webkit-mask-image:var(--md-admonition-icon--note);mask-image:var(--md-admonition-icon--note);-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;position:absolute;top:.625em;width:1rem}.md-typeset .admonition-title code,.md-typeset summary code{box-shadow:0 0 0 .05rem var(--md-default-fg-color--lightest)}.md-typeset .admonition.note,.md-typeset details.note{border-color:#448aff}.md-typeset .admonition.note:focus-within,.md-typeset details.note:focus-within{box-shadow:0 0 0 .2rem #448aff1a}.md-typeset .note>.admonition-title,.md-typeset .note>summary{background-color:#448aff1a}.md-typeset .note>.admonition-title:before,.md-typeset .note>summary:before{background-color:#448aff;-webkit-mask-image:var(--md-admonition-icon--note);mask-image:var(--md-admonition-icon--note)}.md-typeset .note>.admonition-title:after,.md-typeset .note>summary:after{color:#448aff}.md-typeset .admonition.abstract,.md-typeset details.abstract{border-color:#00b0ff}.md-typeset .admonition.abstract:focus-within,.md-typeset details.abstract:focus-within{box-shadow:0 0 0 .2rem #00b0ff1a}.md-typeset .abstract>.admonition-title,.md-typeset .abstract>summary{background-color:#00b0ff1a}.md-typeset .abstract>.admonition-title:before,.md-typeset .abstract>summary:before{background-color:#00b0ff;-webkit-mask-image:var(--md-admonition-icon--abstract);mask-image:var(--md-admonition-icon--abstract)}.md-typeset .abstract>.admonition-title:after,.md-typeset .abstract>summary:after{color:#00b0ff}.md-typeset .admonition.info,.md-typeset details.info{border-color:#00b8d4}.md-typeset .admonition.info:focus-within,.md-typeset details.info:focus-within{box-shadow:0 0 0 .2rem #00b8d41a}.md-typeset .info>.admonition-title,.md-typeset .info>summary{background-color:#00b8d41a}.md-typeset .info>.admonition-title:before,.md-typeset .info>summary:before{background-color:#00b8d4;-webkit-mask-image:var(--md-admonition-icon--info);mask-image:var(--md-admonition-icon--info)}.md-typeset .info>.admonition-title:after,.md-typeset .info>summary:after{color:#00b8d4}.md-typeset .admonition.tip,.md-typeset details.tip{border-color:#00bfa5}.md-typeset .admonition.tip:focus-within,.md-typeset details.tip:focus-within{box-shadow:0 0 0 .2rem #00bfa51a}.md-typeset .tip>.admonition-title,.md-typeset .tip>summary{background-color:#00bfa51a}.md-typeset .tip>.admonition-title:before,.md-typeset .tip>summary:before{background-color:#00bfa5;-webkit-mask-image:var(--md-admonition-icon--tip);mask-image:var(--md-admonition-icon--tip)}.md-typeset .tip>.admonition-title:after,.md-typeset .tip>summary:after{color:#00bfa5}.md-typeset .admonition.success,.md-typeset details.success{border-color:#00c853}.md-typeset .admonition.success:focus-within,.md-typeset details.success:focus-within{box-shadow:0 0 0 .2rem #00c8531a}.md-typeset .success>.admonition-title,.md-typeset .success>summary{background-color:#00c8531a}.md-typeset .success>.admonition-title:before,.md-typeset .success>summary:before{background-color:#00c853;-webkit-mask-image:var(--md-admonition-icon--success);mask-image:var(--md-admonition-icon--success)}.md-typeset .success>.admonition-title:after,.md-typeset .success>summary:after{color:#00c853}.md-typeset .admonition.question,.md-typeset details.question{border-color:#64dd17}.md-typeset .admonition.question:focus-within,.md-typeset details.question:focus-within{box-shadow:0 0 0 .2rem #64dd171a}.md-typeset .question>.admonition-title,.md-typeset .question>summary{background-color:#64dd171a}.md-typeset .question>.admonition-title:before,.md-typeset .question>summary:before{background-color:#64dd17;-webkit-mask-image:var(--md-admonition-icon--question);mask-image:var(--md-admonition-icon--question)}.md-typeset .question>.admonition-title:after,.md-typeset .question>summary:after{color:#64dd17}.md-typeset .admonition.warning,.md-typeset details.warning{border-color:#ff9100}.md-typeset .admonition.warning:focus-within,.md-typeset details.warning:focus-within{box-shadow:0 0 0 .2rem #ff91001a}.md-typeset .warning>.admonition-title,.md-typeset .warning>summary{background-color:#ff91001a}.md-typeset .warning>.admonition-title:before,.md-typeset .warning>summary:before{background-color:#ff9100;-webkit-mask-image:var(--md-admonition-icon--warning);mask-image:var(--md-admonition-icon--warning)}.md-typeset .warning>.admonition-title:after,.md-typeset .warning>summary:after{color:#ff9100}.md-typeset .admonition.failure,.md-typeset details.failure{border-color:#ff5252}.md-typeset .admonition.failure:focus-within,.md-typeset details.failure:focus-within{box-shadow:0 0 0 .2rem #ff52521a}.md-typeset .failure>.admonition-title,.md-typeset .failure>summary{background-color:#ff52521a}.md-typeset .failure>.admonition-title:before,.md-typeset .failure>summary:before{background-color:#ff5252;-webkit-mask-image:var(--md-admonition-icon--failure);mask-image:var(--md-admonition-icon--failure)}.md-typeset .failure>.admonition-title:after,.md-typeset .failure>summary:after{color:#ff5252}.md-typeset .admonition.danger,.md-typeset details.danger{border-color:#ff1744}.md-typeset .admonition.danger:focus-within,.md-typeset details.danger:focus-within{box-shadow:0 0 0 .2rem #ff17441a}.md-typeset .danger>.admonition-title,.md-typeset .danger>summary{background-color:#ff17441a}.md-typeset .danger>.admonition-title:before,.md-typeset .danger>summary:before{background-color:#ff1744;-webkit-mask-image:var(--md-admonition-icon--danger);mask-image:var(--md-admonition-icon--danger)}.md-typeset .danger>.admonition-title:after,.md-typeset .danger>summary:after{color:#ff1744}.md-typeset .admonition.bug,.md-typeset details.bug{border-color:#f50057}.md-typeset .admonition.bug:focus-within,.md-typeset details.bug:focus-within{box-shadow:0 0 0 .2rem #f500571a}.md-typeset .bug>.admonition-title,.md-typeset .bug>summary{background-color:#f500571a}.md-typeset .bug>.admonition-title:before,.md-typeset .bug>summary:before{background-color:#f50057;-webkit-mask-image:var(--md-admonition-icon--bug);mask-image:var(--md-admonition-icon--bug)}.md-typeset .bug>.admonition-title:after,.md-typeset .bug>summary:after{color:#f50057}.md-typeset .admonition.example,.md-typeset details.example{border-color:#7c4dff}.md-typeset .admonition.example:focus-within,.md-typeset details.example:focus-within{box-shadow:0 0 0 .2rem #7c4dff1a}.md-typeset .example>.admonition-title,.md-typeset .example>summary{background-color:#7c4dff1a}.md-typeset .example>.admonition-title:before,.md-typeset .example>summary:before{background-color:#7c4dff;-webkit-mask-image:var(--md-admonition-icon--example);mask-image:var(--md-admonition-icon--example)}.md-typeset .example>.admonition-title:after,.md-typeset .example>summary:after{color:#7c4dff}.md-typeset .admonition.quote,.md-typeset details.quote{border-color:#9e9e9e}.md-typeset .admonition.quote:focus-within,.md-typeset details.quote:focus-within{box-shadow:0 0 0 .2rem #9e9e9e1a}.md-typeset .quote>.admonition-title,.md-typeset .quote>summary{background-color:#9e9e9e1a}.md-typeset .quote>.admonition-title:before,.md-typeset .quote>summary:before{background-color:#9e9e9e;-webkit-mask-image:var(--md-admonition-icon--quote);mask-image:var(--md-admonition-icon--quote)}.md-typeset .quote>.admonition-title:after,.md-typeset .quote>summary:after{color:#9e9e9e}:root{--md-footnotes-icon:url('data:image/svg+xml;charset=utf-8,')}.md-typeset .footnote{color:var(--md-default-fg-color--light);font-size:.64rem}[dir=ltr] .md-typeset .footnote>ol{margin-left:0}[dir=rtl] .md-typeset .footnote>ol{margin-right:0}.md-typeset .footnote>ol>li{transition:color 125ms}.md-typeset .footnote>ol>li:target{color:var(--md-default-fg-color)}.md-typeset .footnote>ol>li:focus-within .footnote-backref{opacity:1;transform:translateX(0);transition:none}.md-typeset .footnote>ol>li:hover .footnote-backref,.md-typeset .footnote>ol>li:target .footnote-backref{opacity:1;transform:translateX(0)}.md-typeset .footnote>ol>li>:first-child{margin-top:0}.md-typeset .footnote-ref{font-size:.75em;font-weight:700}html .md-typeset .footnote-ref{outline-offset:.1rem}.md-typeset [id^="fnref:"]:target>.footnote-ref{outline:auto}.md-typeset .footnote-backref{color:var(--md-typeset-a-color);display:inline-block;font-size:0;opacity:0;transform:translateX(.25rem);transition:color .25s,transform .25s .25s,opacity 125ms .25s;vertical-align:text-bottom}@media print{.md-typeset .footnote-backref{color:var(--md-typeset-a-color);opacity:1;transform:translateX(0)}}[dir=rtl] .md-typeset .footnote-backref{transform:translateX(-.25rem)}.md-typeset .footnote-backref:hover{color:var(--md-accent-fg-color)}.md-typeset .footnote-backref:before{background-color:currentcolor;content:"";display:inline-block;height:.8rem;-webkit-mask-image:var(--md-footnotes-icon);mask-image:var(--md-footnotes-icon);-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;width:.8rem}[dir=rtl] .md-typeset .footnote-backref:before svg{transform:scaleX(-1)}[dir=ltr] .md-typeset .headerlink{margin-left:.5rem}[dir=rtl] .md-typeset .headerlink{margin-right:.5rem}.md-typeset .headerlink{color:var(--md-default-fg-color--lighter);display:inline-block;opacity:0;transition:color .25s,opacity 125ms}@media print{.md-typeset .headerlink{display:none}}.md-typeset .headerlink:focus,.md-typeset :hover>.headerlink,.md-typeset :target>.headerlink{opacity:1;transition:color .25s,opacity 125ms}.md-typeset .headerlink:focus,.md-typeset .headerlink:hover,.md-typeset :target>.headerlink{color:var(--md-accent-fg-color)}.md-typeset :target{--md-scroll-margin:3.6rem;--md-scroll-offset:0rem;scroll-margin-top:calc(var(--md-scroll-margin) - var(--md-scroll-offset))}@media screen and (min-width:76.25em){.md-header--lifted~.md-container .md-typeset :target{--md-scroll-margin:6rem}}.md-typeset h1:target,.md-typeset h2:target,.md-typeset h3:target{--md-scroll-offset:0.2rem}.md-typeset h4:target{--md-scroll-offset:0.15rem}.md-typeset div.arithmatex{overflow:auto}@media screen and (max-width:44.984375em){.md-typeset div.arithmatex{margin:0 -.8rem}.md-typeset div.arithmatex>*{width:min-content}}.md-typeset div.arithmatex>*{margin-left:auto!important;margin-right:auto!important;padding:0 .8rem;touch-action:auto}.md-typeset div.arithmatex>* mjx-container{margin:0!important}.md-typeset div.arithmatex mjx-assistive-mml{height:0}.md-typeset del.critic{background-color:var(--md-typeset-del-color)}.md-typeset del.critic,.md-typeset ins.critic{-webkit-box-decoration-break:clone;box-decoration-break:clone}.md-typeset ins.critic{background-color:var(--md-typeset-ins-color)}.md-typeset .critic.comment{-webkit-box-decoration-break:clone;box-decoration-break:clone;color:var(--md-code-hl-comment-color)}.md-typeset .critic.comment:before{content:"/* "}.md-typeset .critic.comment:after{content:" */"}.md-typeset .critic.block{box-shadow:none;display:block;margin:1em 0;overflow:auto;padding-left:.8rem;padding-right:.8rem}.md-typeset .critic.block>:first-child{margin-top:.5em}.md-typeset .critic.block>:last-child{margin-bottom:.5em}:root{--md-details-icon:url('data:image/svg+xml;charset=utf-8,')}.md-typeset details{display:flow-root;overflow:visible;padding-top:0}.md-typeset details[open]>summary:after{transform:rotate(90deg)}.md-typeset details:not([open]){box-shadow:none;padding-bottom:0}.md-typeset details:not([open])>summary{border-radius:.1rem}[dir=ltr] .md-typeset summary{padding-right:1.8rem}[dir=rtl] .md-typeset summary{padding-left:1.8rem}[dir=ltr] .md-typeset summary{border-top-left-radius:.1rem}[dir=ltr] .md-typeset summary,[dir=rtl] .md-typeset summary{border-top-right-radius:.1rem}[dir=rtl] .md-typeset summary{border-top-left-radius:.1rem}.md-typeset summary{cursor:pointer;display:block;min-height:1rem;overflow:hidden}.md-typeset summary.focus-visible{outline-color:var(--md-accent-fg-color);outline-offset:.2rem}.md-typeset summary:not(.focus-visible){-webkit-tap-highlight-color:transparent;outline:none}[dir=ltr] .md-typeset summary:after{right:.4rem}[dir=rtl] .md-typeset summary:after{left:.4rem}.md-typeset summary:after{background-color:currentcolor;content:"";height:1rem;-webkit-mask-image:var(--md-details-icon);mask-image:var(--md-details-icon);-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;position:absolute;top:.625em;transform:rotate(0deg);transition:transform .25s;width:1rem}[dir=rtl] .md-typeset summary:after{transform:rotate(180deg)}.md-typeset summary::marker{display:none}.md-typeset summary::-webkit-details-marker{display:none}.md-typeset .emojione,.md-typeset .gemoji,.md-typeset .twemoji{--md-icon-size:1.125em;display:inline-flex;height:var(--md-icon-size);vertical-align:text-top}.md-typeset .emojione svg,.md-typeset .gemoji svg,.md-typeset .twemoji svg{fill:currentcolor;max-height:100%;width:var(--md-icon-size)}.md-typeset .lg,.md-typeset .xl,.md-typeset .xxl,.md-typeset .xxxl{vertical-align:text-bottom}.md-typeset .middle{vertical-align:middle}.md-typeset .lg{--md-icon-size:1.5em}.md-typeset .xl{--md-icon-size:2.25em}.md-typeset .xxl{--md-icon-size:3em}.md-typeset .xxxl{--md-icon-size:4em}.highlight .o,.highlight .ow{color:var(--md-code-hl-operator-color)}.highlight .p{color:var(--md-code-hl-punctuation-color)}.highlight .cpf,.highlight .l,.highlight .s,.highlight .s1,.highlight .s2,.highlight .sb,.highlight .sc,.highlight .si,.highlight .ss{color:var(--md-code-hl-string-color)}.highlight .cp,.highlight .se,.highlight .sh,.highlight .sr,.highlight .sx{color:var(--md-code-hl-special-color)}.highlight .il,.highlight .m,.highlight .mb,.highlight .mf,.highlight .mh,.highlight .mi,.highlight .mo{color:var(--md-code-hl-number-color)}.highlight .k,.highlight .kd,.highlight .kn,.highlight .kp,.highlight .kr,.highlight .kt{color:var(--md-code-hl-keyword-color)}.highlight .kc,.highlight .n{color:var(--md-code-hl-name-color)}.highlight .bp,.highlight .nb,.highlight .no{color:var(--md-code-hl-constant-color)}.highlight .nc,.highlight .ne,.highlight .nf,.highlight .nn{color:var(--md-code-hl-function-color)}.highlight .nd,.highlight .ni,.highlight .nl,.highlight .nt{color:var(--md-code-hl-keyword-color)}.highlight .c,.highlight .c1,.highlight .ch,.highlight .cm,.highlight .cs,.highlight .sd{color:var(--md-code-hl-comment-color)}.highlight .na,.highlight .nv,.highlight .vc,.highlight .vg,.highlight .vi{color:var(--md-code-hl-variable-color)}.highlight .ge,.highlight .gh,.highlight .go,.highlight .gp,.highlight .gr,.highlight .gs,.highlight .gt,.highlight .gu{color:var(--md-code-hl-generic-color)}.highlight .gd,.highlight .gi{border-radius:.1rem;margin:0 -.125em;padding:0 .125em}.highlight .gd{background-color:var(--md-typeset-del-color)}.highlight .gi{background-color:var(--md-typeset-ins-color)}.highlight .hll{background-color:var(--md-code-hl-color--light);box-shadow:2px 0 0 0 var(--md-code-hl-color) inset;display:block;margin:0 -1.1764705882em;padding:0 1.1764705882em}.highlight span.filename{background-color:var(--md-code-bg-color);border-bottom:.05rem solid var(--md-default-fg-color--lightest);border-top-left-radius:.1rem;border-top-right-radius:.1rem;display:flow-root;font-size:.85em;font-weight:700;margin-top:1em;padding:.6617647059em 1.1764705882em;position:relative}.highlight span.filename+pre{margin-top:0}.highlight span.filename+pre>code{border-top-left-radius:0;border-top-right-radius:0}.highlight [data-linenos]:before{background-color:var(--md-code-bg-color);box-shadow:-.05rem 0 var(--md-default-fg-color--lightest) inset;color:var(--md-default-fg-color--light);content:attr(data-linenos);float:left;left:-1.1764705882em;margin-left:-1.1764705882em;margin-right:1.1764705882em;padding-left:1.1764705882em;position:sticky;-webkit-user-select:none;user-select:none;z-index:3}.highlight code a[id]{position:absolute;visibility:hidden}.highlight code[data-md-copying]{display:initial}.highlight code[data-md-copying] .hll{display:contents}.highlight code[data-md-copying] .md-annotation{display:none}.highlighttable{display:flow-root}.highlighttable tbody,.highlighttable td{display:block;padding:0}.highlighttable tr{display:flex}.highlighttable pre{margin:0}.highlighttable th.filename{flex-grow:1;padding:0;text-align:left}.highlighttable th.filename span.filename{margin-top:0}.highlighttable .linenos{background-color:var(--md-code-bg-color);border-bottom-left-radius:.1rem;border-top-left-radius:.1rem;font-size:.85em;padding:.7720588235em 0 .7720588235em 1.1764705882em;-webkit-user-select:none;user-select:none}.highlighttable .linenodiv{box-shadow:-.05rem 0 var(--md-default-fg-color--lightest) inset}.highlighttable .linenodiv pre{color:var(--md-default-fg-color--light);text-align:right}.highlighttable .linenodiv span[class]{padding-right:.5882352941em}.highlighttable .code{flex:1;min-width:0}.linenodiv a{color:inherit}.md-typeset .highlighttable{direction:ltr;margin:1em 0}.md-typeset .highlighttable>tbody>tr>.code>div>pre>code{border-bottom-left-radius:0;border-top-left-radius:0}.md-typeset .highlight+.result{border:.05rem solid var(--md-code-bg-color);border-bottom-left-radius:.1rem;border-bottom-right-radius:.1rem;border-top-width:.1rem;margin-top:-1.125em;overflow:visible;padding:0 1em}.md-typeset .highlight+.result:after{clear:both;content:"";display:block}@media screen and (max-width:44.984375em){.md-content__inner>.highlight{margin:1em -.8rem}.md-content__inner>.highlight>.filename,.md-content__inner>.highlight>.highlighttable>tbody>tr>.code>div>pre>code,.md-content__inner>.highlight>.highlighttable>tbody>tr>.filename span.filename,.md-content__inner>.highlight>.highlighttable>tbody>tr>.linenos,.md-content__inner>.highlight>pre>code{border-radius:0}.md-content__inner>.highlight+.result{border-left-width:0;border-radius:0;border-right-width:0;margin-left:-.8rem;margin-right:-.8rem}}.md-typeset .keys kbd:after,.md-typeset .keys kbd:before{-moz-osx-font-smoothing:initial;-webkit-font-smoothing:initial;color:inherit;margin:0;position:relative}.md-typeset .keys span{color:var(--md-default-fg-color--light);padding:0 .2em}.md-typeset .keys .key-alt:before,.md-typeset .keys .key-left-alt:before,.md-typeset .keys .key-right-alt:before{content:"⎇";padding-right:.4em}.md-typeset .keys .key-command:before,.md-typeset .keys .key-left-command:before,.md-typeset .keys .key-right-command:before{content:"⌘";padding-right:.4em}.md-typeset .keys .key-control:before,.md-typeset .keys .key-left-control:before,.md-typeset .keys .key-right-control:before{content:"⌃";padding-right:.4em}.md-typeset .keys .key-left-meta:before,.md-typeset .keys .key-meta:before,.md-typeset .keys .key-right-meta:before{content:"◆";padding-right:.4em}.md-typeset .keys .key-left-option:before,.md-typeset .keys .key-option:before,.md-typeset .keys .key-right-option:before{content:"⌥";padding-right:.4em}.md-typeset .keys .key-left-shift:before,.md-typeset .keys .key-right-shift:before,.md-typeset .keys .key-shift:before{content:"⇧";padding-right:.4em}.md-typeset .keys .key-left-super:before,.md-typeset .keys .key-right-super:before,.md-typeset .keys .key-super:before{content:"❖";padding-right:.4em}.md-typeset .keys .key-left-windows:before,.md-typeset .keys .key-right-windows:before,.md-typeset .keys .key-windows:before{content:"⊞";padding-right:.4em}.md-typeset .keys .key-arrow-down:before{content:"↓";padding-right:.4em}.md-typeset .keys .key-arrow-left:before{content:"←";padding-right:.4em}.md-typeset .keys .key-arrow-right:before{content:"→";padding-right:.4em}.md-typeset .keys .key-arrow-up:before{content:"↑";padding-right:.4em}.md-typeset .keys .key-backspace:before{content:"⌫";padding-right:.4em}.md-typeset .keys .key-backtab:before{content:"⇤";padding-right:.4em}.md-typeset .keys .key-caps-lock:before{content:"⇪";padding-right:.4em}.md-typeset .keys .key-clear:before{content:"⌧";padding-right:.4em}.md-typeset .keys .key-context-menu:before{content:"☰";padding-right:.4em}.md-typeset .keys .key-delete:before{content:"⌦";padding-right:.4em}.md-typeset .keys .key-eject:before{content:"⏏";padding-right:.4em}.md-typeset .keys .key-end:before{content:"⤓";padding-right:.4em}.md-typeset .keys .key-escape:before{content:"⎋";padding-right:.4em}.md-typeset .keys .key-home:before{content:"⤒";padding-right:.4em}.md-typeset .keys .key-insert:before{content:"⎀";padding-right:.4em}.md-typeset .keys .key-page-down:before{content:"⇟";padding-right:.4em}.md-typeset .keys .key-page-up:before{content:"⇞";padding-right:.4em}.md-typeset .keys .key-print-screen:before{content:"⎙";padding-right:.4em}.md-typeset .keys .key-tab:after{content:"⇥";padding-left:.4em}.md-typeset .keys .key-num-enter:after{content:"⌤";padding-left:.4em}.md-typeset .keys .key-enter:after{content:"⏎";padding-left:.4em}:root{--md-tabbed-icon--prev:url('data:image/svg+xml;charset=utf-8,');--md-tabbed-icon--next:url('data:image/svg+xml;charset=utf-8,')}.md-typeset .tabbed-set{border-radius:.1rem;display:flex;flex-flow:column wrap;margin:1em 0;position:relative}.md-typeset .tabbed-set>input{height:0;opacity:0;position:absolute;width:0}.md-typeset .tabbed-set>input:target{--md-scroll-offset:0.625em}.md-typeset .tabbed-set>input.focus-visible~.tabbed-labels:before{background-color:var(--md-accent-fg-color)}.md-typeset .tabbed-labels{-ms-overflow-style:none;box-shadow:0 -.05rem var(--md-default-fg-color--lightest) inset;display:flex;max-width:100%;overflow:auto;scrollbar-width:none}@media print{.md-typeset .tabbed-labels{display:contents}}@media screen{.js .md-typeset .tabbed-labels{position:relative}.js .md-typeset .tabbed-labels:before{background:var(--md-default-fg-color);bottom:0;content:"";display:block;height:2px;left:0;position:absolute;transform:translateX(var(--md-indicator-x));transition:width 225ms,background-color .25s,transform .25s;transition-timing-function:cubic-bezier(.4,0,.2,1);width:var(--md-indicator-width)}}.md-typeset .tabbed-labels::-webkit-scrollbar{display:none}.md-typeset .tabbed-labels>label{border-bottom:.1rem solid #0000;border-radius:.1rem .1rem 0 0;color:var(--md-default-fg-color--light);cursor:pointer;flex-shrink:0;font-size:.64rem;font-weight:700;padding:.78125em 1.25em .625em;scroll-margin-inline-start:1rem;transition:background-color .25s,color .25s;white-space:nowrap;width:auto}@media print{.md-typeset .tabbed-labels>label:first-child{order:1}.md-typeset .tabbed-labels>label:nth-child(2){order:2}.md-typeset .tabbed-labels>label:nth-child(3){order:3}.md-typeset .tabbed-labels>label:nth-child(4){order:4}.md-typeset .tabbed-labels>label:nth-child(5){order:5}.md-typeset .tabbed-labels>label:nth-child(6){order:6}.md-typeset .tabbed-labels>label:nth-child(7){order:7}.md-typeset .tabbed-labels>label:nth-child(8){order:8}.md-typeset .tabbed-labels>label:nth-child(9){order:9}.md-typeset .tabbed-labels>label:nth-child(10){order:10}.md-typeset .tabbed-labels>label:nth-child(11){order:11}.md-typeset .tabbed-labels>label:nth-child(12){order:12}.md-typeset .tabbed-labels>label:nth-child(13){order:13}.md-typeset .tabbed-labels>label:nth-child(14){order:14}.md-typeset .tabbed-labels>label:nth-child(15){order:15}.md-typeset .tabbed-labels>label:nth-child(16){order:16}.md-typeset .tabbed-labels>label:nth-child(17){order:17}.md-typeset .tabbed-labels>label:nth-child(18){order:18}.md-typeset .tabbed-labels>label:nth-child(19){order:19}.md-typeset .tabbed-labels>label:nth-child(20){order:20}}.md-typeset .tabbed-labels>label:hover{color:var(--md-default-fg-color)}.md-typeset .tabbed-labels>label>[href]:first-child{color:inherit}.md-typeset .tabbed-labels--linked>label{padding:0}.md-typeset .tabbed-labels--linked>label>a{display:block;padding:.78125em 1.25em .625em}.md-typeset .tabbed-content{width:100%}@media print{.md-typeset .tabbed-content{display:contents}}.md-typeset .tabbed-block{display:none}@media print{.md-typeset .tabbed-block{display:block}.md-typeset .tabbed-block:first-child{order:1}.md-typeset .tabbed-block:nth-child(2){order:2}.md-typeset .tabbed-block:nth-child(3){order:3}.md-typeset .tabbed-block:nth-child(4){order:4}.md-typeset .tabbed-block:nth-child(5){order:5}.md-typeset .tabbed-block:nth-child(6){order:6}.md-typeset .tabbed-block:nth-child(7){order:7}.md-typeset .tabbed-block:nth-child(8){order:8}.md-typeset .tabbed-block:nth-child(9){order:9}.md-typeset .tabbed-block:nth-child(10){order:10}.md-typeset .tabbed-block:nth-child(11){order:11}.md-typeset .tabbed-block:nth-child(12){order:12}.md-typeset .tabbed-block:nth-child(13){order:13}.md-typeset .tabbed-block:nth-child(14){order:14}.md-typeset .tabbed-block:nth-child(15){order:15}.md-typeset .tabbed-block:nth-child(16){order:16}.md-typeset .tabbed-block:nth-child(17){order:17}.md-typeset .tabbed-block:nth-child(18){order:18}.md-typeset .tabbed-block:nth-child(19){order:19}.md-typeset .tabbed-block:nth-child(20){order:20}}.md-typeset .tabbed-block>.highlight:first-child>pre,.md-typeset .tabbed-block>pre:first-child{margin:0}.md-typeset .tabbed-block>.highlight:first-child>pre>code,.md-typeset .tabbed-block>pre:first-child>code{border-top-left-radius:0;border-top-right-radius:0}.md-typeset .tabbed-block>.highlight:first-child>.filename{border-top-left-radius:0;border-top-right-radius:0;margin:0}.md-typeset .tabbed-block>.highlight:first-child>.highlighttable{margin:0}.md-typeset .tabbed-block>.highlight:first-child>.highlighttable>tbody>tr>.filename span.filename,.md-typeset .tabbed-block>.highlight:first-child>.highlighttable>tbody>tr>.linenos{border-top-left-radius:0;border-top-right-radius:0;margin:0}.md-typeset .tabbed-block>.highlight:first-child>.highlighttable>tbody>tr>.code>div>pre>code{border-top-left-radius:0;border-top-right-radius:0}.md-typeset .tabbed-block>.highlight:first-child+.result{margin-top:-.125em}.md-typeset .tabbed-block>.tabbed-set{margin:0}.md-typeset .tabbed-button{align-self:center;border-radius:100%;color:var(--md-default-fg-color--light);cursor:pointer;display:block;height:.9rem;margin-top:.1rem;pointer-events:auto;transition:background-color .25s;width:.9rem}.md-typeset .tabbed-button:hover{background-color:var(--md-accent-fg-color--transparent);color:var(--md-accent-fg-color)}.md-typeset .tabbed-button:after{background-color:currentcolor;content:"";display:block;height:100%;-webkit-mask-image:var(--md-tabbed-icon--prev);mask-image:var(--md-tabbed-icon--prev);-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;transition:background-color .25s,transform .25s;width:100%}.md-typeset .tabbed-control{background:linear-gradient(to right,var(--md-default-bg-color) 60%,#0000);display:flex;height:1.9rem;justify-content:start;pointer-events:none;position:absolute;transition:opacity 125ms;width:1.2rem}[dir=rtl] .md-typeset .tabbed-control{transform:rotate(180deg)}.md-typeset .tabbed-control[hidden]{opacity:0}.md-typeset .tabbed-control--next{background:linear-gradient(to left,var(--md-default-bg-color) 60%,#0000);justify-content:end;right:0}.md-typeset .tabbed-control--next .tabbed-button:after{-webkit-mask-image:var(--md-tabbed-icon--next);mask-image:var(--md-tabbed-icon--next)}@media screen and (max-width:44.984375em){[dir=ltr] .md-content__inner>.tabbed-set .tabbed-labels{padding-left:.8rem}[dir=rtl] .md-content__inner>.tabbed-set .tabbed-labels{padding-right:.8rem}.md-content__inner>.tabbed-set .tabbed-labels{margin:0 -.8rem;max-width:100vw;scroll-padding-inline-start:.8rem}[dir=ltr] .md-content__inner>.tabbed-set .tabbed-labels:after{padding-right:.8rem}[dir=rtl] .md-content__inner>.tabbed-set .tabbed-labels:after{padding-left:.8rem}.md-content__inner>.tabbed-set .tabbed-labels:after{content:""}[dir=ltr] .md-content__inner>.tabbed-set .tabbed-labels~.tabbed-control--prev{padding-left:.8rem}[dir=rtl] .md-content__inner>.tabbed-set .tabbed-labels~.tabbed-control--prev{padding-right:.8rem}[dir=ltr] .md-content__inner>.tabbed-set .tabbed-labels~.tabbed-control--prev{margin-left:-.8rem}[dir=rtl] .md-content__inner>.tabbed-set .tabbed-labels~.tabbed-control--prev{margin-right:-.8rem}.md-content__inner>.tabbed-set .tabbed-labels~.tabbed-control--prev{width:2rem}[dir=ltr] .md-content__inner>.tabbed-set .tabbed-labels~.tabbed-control--next{padding-right:.8rem}[dir=rtl] .md-content__inner>.tabbed-set .tabbed-labels~.tabbed-control--next{padding-left:.8rem}[dir=ltr] .md-content__inner>.tabbed-set .tabbed-labels~.tabbed-control--next{margin-right:-.8rem}[dir=rtl] .md-content__inner>.tabbed-set .tabbed-labels~.tabbed-control--next{margin-left:-.8rem}.md-content__inner>.tabbed-set .tabbed-labels~.tabbed-control--next{width:2rem}}@media screen{.md-typeset .tabbed-set>input:first-child:checked~.tabbed-labels>:first-child,.md-typeset .tabbed-set>input:nth-child(10):checked~.tabbed-labels>:nth-child(10),.md-typeset .tabbed-set>input:nth-child(11):checked~.tabbed-labels>:nth-child(11),.md-typeset .tabbed-set>input:nth-child(12):checked~.tabbed-labels>:nth-child(12),.md-typeset .tabbed-set>input:nth-child(13):checked~.tabbed-labels>:nth-child(13),.md-typeset .tabbed-set>input:nth-child(14):checked~.tabbed-labels>:nth-child(14),.md-typeset .tabbed-set>input:nth-child(15):checked~.tabbed-labels>:nth-child(15),.md-typeset .tabbed-set>input:nth-child(16):checked~.tabbed-labels>:nth-child(16),.md-typeset .tabbed-set>input:nth-child(17):checked~.tabbed-labels>:nth-child(17),.md-typeset .tabbed-set>input:nth-child(18):checked~.tabbed-labels>:nth-child(18),.md-typeset .tabbed-set>input:nth-child(19):checked~.tabbed-labels>:nth-child(19),.md-typeset .tabbed-set>input:nth-child(2):checked~.tabbed-labels>:nth-child(2),.md-typeset .tabbed-set>input:nth-child(20):checked~.tabbed-labels>:nth-child(20),.md-typeset .tabbed-set>input:nth-child(3):checked~.tabbed-labels>:nth-child(3),.md-typeset .tabbed-set>input:nth-child(4):checked~.tabbed-labels>:nth-child(4),.md-typeset .tabbed-set>input:nth-child(5):checked~.tabbed-labels>:nth-child(5),.md-typeset .tabbed-set>input:nth-child(6):checked~.tabbed-labels>:nth-child(6),.md-typeset .tabbed-set>input:nth-child(7):checked~.tabbed-labels>:nth-child(7),.md-typeset .tabbed-set>input:nth-child(8):checked~.tabbed-labels>:nth-child(8),.md-typeset .tabbed-set>input:nth-child(9):checked~.tabbed-labels>:nth-child(9){color:var(--md-default-fg-color)}.md-typeset .no-js .tabbed-set>input:first-child:checked~.tabbed-labels>:first-child,.md-typeset .no-js .tabbed-set>input:nth-child(10):checked~.tabbed-labels>:nth-child(10),.md-typeset .no-js .tabbed-set>input:nth-child(11):checked~.tabbed-labels>:nth-child(11),.md-typeset .no-js .tabbed-set>input:nth-child(12):checked~.tabbed-labels>:nth-child(12),.md-typeset .no-js .tabbed-set>input:nth-child(13):checked~.tabbed-labels>:nth-child(13),.md-typeset .no-js .tabbed-set>input:nth-child(14):checked~.tabbed-labels>:nth-child(14),.md-typeset .no-js .tabbed-set>input:nth-child(15):checked~.tabbed-labels>:nth-child(15),.md-typeset .no-js .tabbed-set>input:nth-child(16):checked~.tabbed-labels>:nth-child(16),.md-typeset .no-js .tabbed-set>input:nth-child(17):checked~.tabbed-labels>:nth-child(17),.md-typeset .no-js .tabbed-set>input:nth-child(18):checked~.tabbed-labels>:nth-child(18),.md-typeset .no-js .tabbed-set>input:nth-child(19):checked~.tabbed-labels>:nth-child(19),.md-typeset .no-js .tabbed-set>input:nth-child(2):checked~.tabbed-labels>:nth-child(2),.md-typeset .no-js .tabbed-set>input:nth-child(20):checked~.tabbed-labels>:nth-child(20),.md-typeset .no-js .tabbed-set>input:nth-child(3):checked~.tabbed-labels>:nth-child(3),.md-typeset .no-js .tabbed-set>input:nth-child(4):checked~.tabbed-labels>:nth-child(4),.md-typeset .no-js .tabbed-set>input:nth-child(5):checked~.tabbed-labels>:nth-child(5),.md-typeset .no-js .tabbed-set>input:nth-child(6):checked~.tabbed-labels>:nth-child(6),.md-typeset .no-js .tabbed-set>input:nth-child(7):checked~.tabbed-labels>:nth-child(7),.md-typeset .no-js .tabbed-set>input:nth-child(8):checked~.tabbed-labels>:nth-child(8),.md-typeset .no-js .tabbed-set>input:nth-child(9):checked~.tabbed-labels>:nth-child(9),.md-typeset [role=dialog] .tabbed-set>input:first-child:checked~.tabbed-labels>:first-child,.md-typeset [role=dialog] .tabbed-set>input:nth-child(10):checked~.tabbed-labels>:nth-child(10),.md-typeset [role=dialog] .tabbed-set>input:nth-child(11):checked~.tabbed-labels>:nth-child(11),.md-typeset [role=dialog] .tabbed-set>input:nth-child(12):checked~.tabbed-labels>:nth-child(12),.md-typeset [role=dialog] .tabbed-set>input:nth-child(13):checked~.tabbed-labels>:nth-child(13),.md-typeset [role=dialog] .tabbed-set>input:nth-child(14):checked~.tabbed-labels>:nth-child(14),.md-typeset [role=dialog] .tabbed-set>input:nth-child(15):checked~.tabbed-labels>:nth-child(15),.md-typeset [role=dialog] .tabbed-set>input:nth-child(16):checked~.tabbed-labels>:nth-child(16),.md-typeset [role=dialog] .tabbed-set>input:nth-child(17):checked~.tabbed-labels>:nth-child(17),.md-typeset [role=dialog] .tabbed-set>input:nth-child(18):checked~.tabbed-labels>:nth-child(18),.md-typeset [role=dialog] .tabbed-set>input:nth-child(19):checked~.tabbed-labels>:nth-child(19),.md-typeset [role=dialog] .tabbed-set>input:nth-child(2):checked~.tabbed-labels>:nth-child(2),.md-typeset [role=dialog] .tabbed-set>input:nth-child(20):checked~.tabbed-labels>:nth-child(20),.md-typeset [role=dialog] .tabbed-set>input:nth-child(3):checked~.tabbed-labels>:nth-child(3),.md-typeset [role=dialog] .tabbed-set>input:nth-child(4):checked~.tabbed-labels>:nth-child(4),.md-typeset [role=dialog] .tabbed-set>input:nth-child(5):checked~.tabbed-labels>:nth-child(5),.md-typeset [role=dialog] .tabbed-set>input:nth-child(6):checked~.tabbed-labels>:nth-child(6),.md-typeset [role=dialog] .tabbed-set>input:nth-child(7):checked~.tabbed-labels>:nth-child(7),.md-typeset [role=dialog] .tabbed-set>input:nth-child(8):checked~.tabbed-labels>:nth-child(8),.md-typeset [role=dialog] .tabbed-set>input:nth-child(9):checked~.tabbed-labels>:nth-child(9),.no-js .md-typeset .tabbed-set>input:first-child:checked~.tabbed-labels>:first-child,.no-js .md-typeset .tabbed-set>input:nth-child(10):checked~.tabbed-labels>:nth-child(10),.no-js .md-typeset .tabbed-set>input:nth-child(11):checked~.tabbed-labels>:nth-child(11),.no-js .md-typeset .tabbed-set>input:nth-child(12):checked~.tabbed-labels>:nth-child(12),.no-js .md-typeset .tabbed-set>input:nth-child(13):checked~.tabbed-labels>:nth-child(13),.no-js .md-typeset .tabbed-set>input:nth-child(14):checked~.tabbed-labels>:nth-child(14),.no-js .md-typeset .tabbed-set>input:nth-child(15):checked~.tabbed-labels>:nth-child(15),.no-js .md-typeset .tabbed-set>input:nth-child(16):checked~.tabbed-labels>:nth-child(16),.no-js .md-typeset .tabbed-set>input:nth-child(17):checked~.tabbed-labels>:nth-child(17),.no-js .md-typeset .tabbed-set>input:nth-child(18):checked~.tabbed-labels>:nth-child(18),.no-js .md-typeset .tabbed-set>input:nth-child(19):checked~.tabbed-labels>:nth-child(19),.no-js .md-typeset .tabbed-set>input:nth-child(2):checked~.tabbed-labels>:nth-child(2),.no-js .md-typeset .tabbed-set>input:nth-child(20):checked~.tabbed-labels>:nth-child(20),.no-js .md-typeset .tabbed-set>input:nth-child(3):checked~.tabbed-labels>:nth-child(3),.no-js .md-typeset .tabbed-set>input:nth-child(4):checked~.tabbed-labels>:nth-child(4),.no-js .md-typeset .tabbed-set>input:nth-child(5):checked~.tabbed-labels>:nth-child(5),.no-js .md-typeset .tabbed-set>input:nth-child(6):checked~.tabbed-labels>:nth-child(6),.no-js .md-typeset .tabbed-set>input:nth-child(7):checked~.tabbed-labels>:nth-child(7),.no-js .md-typeset .tabbed-set>input:nth-child(8):checked~.tabbed-labels>:nth-child(8),.no-js .md-typeset .tabbed-set>input:nth-child(9):checked~.tabbed-labels>:nth-child(9),[role=dialog] .md-typeset .tabbed-set>input:first-child:checked~.tabbed-labels>:first-child,[role=dialog] .md-typeset .tabbed-set>input:nth-child(10):checked~.tabbed-labels>:nth-child(10),[role=dialog] .md-typeset .tabbed-set>input:nth-child(11):checked~.tabbed-labels>:nth-child(11),[role=dialog] .md-typeset .tabbed-set>input:nth-child(12):checked~.tabbed-labels>:nth-child(12),[role=dialog] .md-typeset .tabbed-set>input:nth-child(13):checked~.tabbed-labels>:nth-child(13),[role=dialog] .md-typeset .tabbed-set>input:nth-child(14):checked~.tabbed-labels>:nth-child(14),[role=dialog] .md-typeset .tabbed-set>input:nth-child(15):checked~.tabbed-labels>:nth-child(15),[role=dialog] .md-typeset .tabbed-set>input:nth-child(16):checked~.tabbed-labels>:nth-child(16),[role=dialog] .md-typeset .tabbed-set>input:nth-child(17):checked~.tabbed-labels>:nth-child(17),[role=dialog] .md-typeset .tabbed-set>input:nth-child(18):checked~.tabbed-labels>:nth-child(18),[role=dialog] .md-typeset .tabbed-set>input:nth-child(19):checked~.tabbed-labels>:nth-child(19),[role=dialog] .md-typeset .tabbed-set>input:nth-child(2):checked~.tabbed-labels>:nth-child(2),[role=dialog] .md-typeset .tabbed-set>input:nth-child(20):checked~.tabbed-labels>:nth-child(20),[role=dialog] .md-typeset .tabbed-set>input:nth-child(3):checked~.tabbed-labels>:nth-child(3),[role=dialog] .md-typeset .tabbed-set>input:nth-child(4):checked~.tabbed-labels>:nth-child(4),[role=dialog] .md-typeset .tabbed-set>input:nth-child(5):checked~.tabbed-labels>:nth-child(5),[role=dialog] .md-typeset .tabbed-set>input:nth-child(6):checked~.tabbed-labels>:nth-child(6),[role=dialog] .md-typeset .tabbed-set>input:nth-child(7):checked~.tabbed-labels>:nth-child(7),[role=dialog] .md-typeset .tabbed-set>input:nth-child(8):checked~.tabbed-labels>:nth-child(8),[role=dialog] .md-typeset .tabbed-set>input:nth-child(9):checked~.tabbed-labels>:nth-child(9){border-color:var(--md-default-fg-color)}}.md-typeset .tabbed-set>input:first-child.focus-visible~.tabbed-labels>:first-child,.md-typeset .tabbed-set>input:nth-child(10).focus-visible~.tabbed-labels>:nth-child(10),.md-typeset .tabbed-set>input:nth-child(11).focus-visible~.tabbed-labels>:nth-child(11),.md-typeset .tabbed-set>input:nth-child(12).focus-visible~.tabbed-labels>:nth-child(12),.md-typeset .tabbed-set>input:nth-child(13).focus-visible~.tabbed-labels>:nth-child(13),.md-typeset .tabbed-set>input:nth-child(14).focus-visible~.tabbed-labels>:nth-child(14),.md-typeset .tabbed-set>input:nth-child(15).focus-visible~.tabbed-labels>:nth-child(15),.md-typeset .tabbed-set>input:nth-child(16).focus-visible~.tabbed-labels>:nth-child(16),.md-typeset .tabbed-set>input:nth-child(17).focus-visible~.tabbed-labels>:nth-child(17),.md-typeset .tabbed-set>input:nth-child(18).focus-visible~.tabbed-labels>:nth-child(18),.md-typeset .tabbed-set>input:nth-child(19).focus-visible~.tabbed-labels>:nth-child(19),.md-typeset .tabbed-set>input:nth-child(2).focus-visible~.tabbed-labels>:nth-child(2),.md-typeset .tabbed-set>input:nth-child(20).focus-visible~.tabbed-labels>:nth-child(20),.md-typeset .tabbed-set>input:nth-child(3).focus-visible~.tabbed-labels>:nth-child(3),.md-typeset .tabbed-set>input:nth-child(4).focus-visible~.tabbed-labels>:nth-child(4),.md-typeset .tabbed-set>input:nth-child(5).focus-visible~.tabbed-labels>:nth-child(5),.md-typeset .tabbed-set>input:nth-child(6).focus-visible~.tabbed-labels>:nth-child(6),.md-typeset .tabbed-set>input:nth-child(7).focus-visible~.tabbed-labels>:nth-child(7),.md-typeset .tabbed-set>input:nth-child(8).focus-visible~.tabbed-labels>:nth-child(8),.md-typeset .tabbed-set>input:nth-child(9).focus-visible~.tabbed-labels>:nth-child(9){color:var(--md-accent-fg-color)}.md-typeset .tabbed-set>input:first-child:checked~.tabbed-content>:first-child,.md-typeset .tabbed-set>input:nth-child(10):checked~.tabbed-content>:nth-child(10),.md-typeset .tabbed-set>input:nth-child(11):checked~.tabbed-content>:nth-child(11),.md-typeset .tabbed-set>input:nth-child(12):checked~.tabbed-content>:nth-child(12),.md-typeset .tabbed-set>input:nth-child(13):checked~.tabbed-content>:nth-child(13),.md-typeset .tabbed-set>input:nth-child(14):checked~.tabbed-content>:nth-child(14),.md-typeset .tabbed-set>input:nth-child(15):checked~.tabbed-content>:nth-child(15),.md-typeset .tabbed-set>input:nth-child(16):checked~.tabbed-content>:nth-child(16),.md-typeset .tabbed-set>input:nth-child(17):checked~.tabbed-content>:nth-child(17),.md-typeset .tabbed-set>input:nth-child(18):checked~.tabbed-content>:nth-child(18),.md-typeset .tabbed-set>input:nth-child(19):checked~.tabbed-content>:nth-child(19),.md-typeset .tabbed-set>input:nth-child(2):checked~.tabbed-content>:nth-child(2),.md-typeset .tabbed-set>input:nth-child(20):checked~.tabbed-content>:nth-child(20),.md-typeset .tabbed-set>input:nth-child(3):checked~.tabbed-content>:nth-child(3),.md-typeset .tabbed-set>input:nth-child(4):checked~.tabbed-content>:nth-child(4),.md-typeset .tabbed-set>input:nth-child(5):checked~.tabbed-content>:nth-child(5),.md-typeset .tabbed-set>input:nth-child(6):checked~.tabbed-content>:nth-child(6),.md-typeset .tabbed-set>input:nth-child(7):checked~.tabbed-content>:nth-child(7),.md-typeset .tabbed-set>input:nth-child(8):checked~.tabbed-content>:nth-child(8),.md-typeset .tabbed-set>input:nth-child(9):checked~.tabbed-content>:nth-child(9){display:block}:root{--md-tasklist-icon:url('data:image/svg+xml;charset=utf-8,');--md-tasklist-icon--checked:url('data:image/svg+xml;charset=utf-8,')}.md-typeset .task-list-item{list-style-type:none;position:relative}[dir=ltr] .md-typeset .task-list-item [type=checkbox]{left:-2em}[dir=rtl] .md-typeset .task-list-item [type=checkbox]{right:-2em}.md-typeset .task-list-item [type=checkbox]{position:absolute;top:.45em}.md-typeset .task-list-control [type=checkbox]{opacity:0;z-index:-1}[dir=ltr] .md-typeset .task-list-indicator:before{left:-1.5em}[dir=rtl] .md-typeset .task-list-indicator:before{right:-1.5em}.md-typeset .task-list-indicator:before{background-color:var(--md-default-fg-color--lightest);content:"";height:1.25em;-webkit-mask-image:var(--md-tasklist-icon);mask-image:var(--md-tasklist-icon);-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;position:absolute;top:.15em;width:1.25em}.md-typeset [type=checkbox]:checked+.task-list-indicator:before{background-color:#00e676;-webkit-mask-image:var(--md-tasklist-icon--checked);mask-image:var(--md-tasklist-icon--checked)}@media print{.giscus,[id=__comments]{display:none}}:root>*{--md-mermaid-font-family:var(--md-text-font-family),sans-serif;--md-mermaid-edge-color:var(--md-code-fg-color);--md-mermaid-node-bg-color:var(--md-accent-fg-color--transparent);--md-mermaid-node-fg-color:var(--md-accent-fg-color);--md-mermaid-label-bg-color:var(--md-default-bg-color);--md-mermaid-label-fg-color:var(--md-code-fg-color);--md-mermaid-sequence-actor-bg-color:var(--md-mermaid-label-bg-color);--md-mermaid-sequence-actor-fg-color:var(--md-mermaid-label-fg-color);--md-mermaid-sequence-actor-border-color:var(--md-mermaid-node-fg-color);--md-mermaid-sequence-actor-line-color:var(--md-default-fg-color--lighter);--md-mermaid-sequence-actorman-bg-color:var(--md-mermaid-label-bg-color);--md-mermaid-sequence-actorman-line-color:var(--md-mermaid-node-fg-color);--md-mermaid-sequence-box-bg-color:var(--md-mermaid-node-bg-color);--md-mermaid-sequence-box-fg-color:var(--md-mermaid-edge-color);--md-mermaid-sequence-label-bg-color:var(--md-mermaid-node-bg-color);--md-mermaid-sequence-label-fg-color:var(--md-mermaid-node-fg-color);--md-mermaid-sequence-loop-bg-color:var(--md-mermaid-node-bg-color);--md-mermaid-sequence-loop-fg-color:var(--md-mermaid-edge-color);--md-mermaid-sequence-loop-border-color:var(--md-mermaid-node-fg-color);--md-mermaid-sequence-message-fg-color:var(--md-mermaid-edge-color);--md-mermaid-sequence-message-line-color:var(--md-mermaid-edge-color);--md-mermaid-sequence-note-bg-color:var(--md-mermaid-label-bg-color);--md-mermaid-sequence-note-fg-color:var(--md-mermaid-edge-color);--md-mermaid-sequence-note-border-color:var(--md-mermaid-label-fg-color);--md-mermaid-sequence-number-bg-color:var(--md-mermaid-node-fg-color);--md-mermaid-sequence-number-fg-color:var(--md-accent-bg-color)}.mermaid{line-height:normal;margin:1em 0}.md-typeset .grid{grid-gap:.4rem;display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,16rem),1fr));margin:1em 0}.md-typeset .grid.cards>ol,.md-typeset .grid.cards>ul{display:contents}.md-typeset .grid.cards>ol>li,.md-typeset .grid.cards>ul>li,.md-typeset .grid>.card{border:.05rem solid var(--md-default-fg-color--lightest);border-radius:.1rem;display:block;margin:0;padding:.8rem;transition:border .25s,box-shadow .25s}.md-typeset .grid.cards>ol>li:focus-within,.md-typeset .grid.cards>ol>li:hover,.md-typeset .grid.cards>ul>li:focus-within,.md-typeset .grid.cards>ul>li:hover,.md-typeset .grid>.card:focus-within,.md-typeset .grid>.card:hover{border-color:#0000;box-shadow:var(--md-shadow-z2)}.md-typeset .grid.cards>ol>li>hr,.md-typeset .grid.cards>ul>li>hr,.md-typeset .grid>.card>hr{margin-bottom:1em;margin-top:1em}.md-typeset .grid.cards>ol>li>:first-child,.md-typeset .grid.cards>ul>li>:first-child,.md-typeset .grid>.card>:first-child{margin-top:0}.md-typeset .grid.cards>ol>li>:last-child,.md-typeset .grid.cards>ul>li>:last-child,.md-typeset .grid>.card>:last-child{margin-bottom:0}.md-typeset .grid>*,.md-typeset .grid>.admonition,.md-typeset .grid>.highlight>*,.md-typeset .grid>.highlighttable,.md-typeset .grid>.md-typeset details,.md-typeset .grid>details,.md-typeset .grid>pre{margin-bottom:0;margin-top:0}.md-typeset .grid>.highlight>pre:only-child,.md-typeset .grid>.highlight>pre>code,.md-typeset .grid>.highlighttable,.md-typeset .grid>.highlighttable>tbody,.md-typeset .grid>.highlighttable>tbody>tr,.md-typeset .grid>.highlighttable>tbody>tr>.code,.md-typeset .grid>.highlighttable>tbody>tr>.code>.highlight,.md-typeset .grid>.highlighttable>tbody>tr>.code>.highlight>pre,.md-typeset .grid>.highlighttable>tbody>tr>.code>.highlight>pre>code{height:100%}.md-typeset .grid>.tabbed-set{margin-bottom:0;margin-top:0}@media screen and (min-width:45em){[dir=ltr] .md-typeset .inline{float:left}[dir=rtl] .md-typeset .inline{float:right}[dir=ltr] .md-typeset .inline{margin-right:.8rem}[dir=rtl] .md-typeset .inline{margin-left:.8rem}.md-typeset .inline{margin-bottom:.8rem;margin-top:0;width:11.7rem}[dir=ltr] .md-typeset .inline.end{float:right}[dir=rtl] .md-typeset .inline.end{float:left}[dir=ltr] .md-typeset .inline.end{margin-left:.8rem;margin-right:0}[dir=rtl] .md-typeset .inline.end{margin-left:0;margin-right:.8rem}} \ No newline at end of file diff --git a/assets/stylesheets/palette.ab4e12ef.min.css b/assets/stylesheets/palette.ab4e12ef.min.css new file mode 100644 index 0000000..75aaf84 --- /dev/null +++ b/assets/stylesheets/palette.ab4e12ef.min.css @@ -0,0 +1 @@ +@media screen{[data-md-color-scheme=slate]{--md-default-fg-color:hsla(var(--md-hue),15%,90%,0.82);--md-default-fg-color--light:hsla(var(--md-hue),15%,90%,0.56);--md-default-fg-color--lighter:hsla(var(--md-hue),15%,90%,0.32);--md-default-fg-color--lightest:hsla(var(--md-hue),15%,90%,0.12);--md-default-bg-color:hsla(var(--md-hue),15%,14%,1);--md-default-bg-color--light:hsla(var(--md-hue),15%,14%,0.54);--md-default-bg-color--lighter:hsla(var(--md-hue),15%,14%,0.26);--md-default-bg-color--lightest:hsla(var(--md-hue),15%,14%,0.07);--md-code-fg-color:hsla(var(--md-hue),18%,86%,0.82);--md-code-bg-color:hsla(var(--md-hue),15%,18%,1);--md-code-bg-color--light:hsla(var(--md-hue),15%,18%,0.9);--md-code-bg-color--lighter:hsla(var(--md-hue),15%,18%,0.54);--md-code-hl-color:#2977ff;--md-code-hl-color--light:#2977ff1a;--md-code-hl-number-color:#e6695b;--md-code-hl-special-color:#f06090;--md-code-hl-function-color:#c973d9;--md-code-hl-constant-color:#9383e2;--md-code-hl-keyword-color:#6791e0;--md-code-hl-string-color:#2fb170;--md-code-hl-name-color:var(--md-code-fg-color);--md-code-hl-operator-color:var(--md-default-fg-color--light);--md-code-hl-punctuation-color:var(--md-default-fg-color--light);--md-code-hl-comment-color:var(--md-default-fg-color--light);--md-code-hl-generic-color:var(--md-default-fg-color--light);--md-code-hl-variable-color:var(--md-default-fg-color--light);--md-typeset-color:var(--md-default-fg-color);--md-typeset-a-color:var(--md-primary-fg-color);--md-typeset-kbd-color:hsla(var(--md-hue),15%,90%,0.12);--md-typeset-kbd-accent-color:hsla(var(--md-hue),15%,90%,0.2);--md-typeset-kbd-border-color:hsla(var(--md-hue),15%,14%,1);--md-typeset-mark-color:#4287ff4d;--md-typeset-table-color:hsla(var(--md-hue),15%,95%,0.12);--md-typeset-table-color--light:hsla(var(--md-hue),15%,95%,0.035);--md-admonition-fg-color:var(--md-default-fg-color);--md-admonition-bg-color:var(--md-default-bg-color);--md-footer-bg-color:hsla(var(--md-hue),15%,10%,0.87);--md-footer-bg-color--dark:hsla(var(--md-hue),15%,8%,1);--md-shadow-z1:0 0.2rem 0.5rem #0000000d,0 0 0.05rem #0000001a;--md-shadow-z2:0 0.2rem 0.5rem #00000040,0 0 0.05rem #00000040;--md-shadow-z3:0 0.2rem 0.5rem #0006,0 0 0.05rem #00000059;color-scheme:dark}[data-md-color-scheme=slate] img[src$="#gh-light-mode-only"],[data-md-color-scheme=slate] img[src$="#only-light"]{display:none}[data-md-color-scheme=slate][data-md-color-primary=pink]{--md-typeset-a-color:#ed5487}[data-md-color-scheme=slate][data-md-color-primary=purple]{--md-typeset-a-color:#c46fd3}[data-md-color-scheme=slate][data-md-color-primary=deep-purple]{--md-typeset-a-color:#a47bea}[data-md-color-scheme=slate][data-md-color-primary=indigo]{--md-typeset-a-color:#5488e8}[data-md-color-scheme=slate][data-md-color-primary=teal]{--md-typeset-a-color:#00ccb8}[data-md-color-scheme=slate][data-md-color-primary=green]{--md-typeset-a-color:#71c174}[data-md-color-scheme=slate][data-md-color-primary=deep-orange]{--md-typeset-a-color:#ff764d}[data-md-color-scheme=slate][data-md-color-primary=brown]{--md-typeset-a-color:#c1775c}[data-md-color-scheme=slate][data-md-color-primary=black],[data-md-color-scheme=slate][data-md-color-primary=blue-grey],[data-md-color-scheme=slate][data-md-color-primary=grey],[data-md-color-scheme=slate][data-md-color-primary=white]{--md-typeset-a-color:#5e8bde}[data-md-color-switching] *,[data-md-color-switching] :after,[data-md-color-switching] :before{transition-duration:0ms!important}}[data-md-color-accent=red]{--md-accent-fg-color:#ff1947;--md-accent-fg-color--transparent:#ff19471a;--md-accent-bg-color:#fff;--md-accent-bg-color--light:#ffffffb3}[data-md-color-accent=pink]{--md-accent-fg-color:#f50056;--md-accent-fg-color--transparent:#f500561a;--md-accent-bg-color:#fff;--md-accent-bg-color--light:#ffffffb3}[data-md-color-accent=purple]{--md-accent-fg-color:#df41fb;--md-accent-fg-color--transparent:#df41fb1a;--md-accent-bg-color:#fff;--md-accent-bg-color--light:#ffffffb3}[data-md-color-accent=deep-purple]{--md-accent-fg-color:#7c4dff;--md-accent-fg-color--transparent:#7c4dff1a;--md-accent-bg-color:#fff;--md-accent-bg-color--light:#ffffffb3}[data-md-color-accent=indigo]{--md-accent-fg-color:#526cfe;--md-accent-fg-color--transparent:#526cfe1a;--md-accent-bg-color:#fff;--md-accent-bg-color--light:#ffffffb3}[data-md-color-accent=blue]{--md-accent-fg-color:#4287ff;--md-accent-fg-color--transparent:#4287ff1a;--md-accent-bg-color:#fff;--md-accent-bg-color--light:#ffffffb3}[data-md-color-accent=light-blue]{--md-accent-fg-color:#0091eb;--md-accent-fg-color--transparent:#0091eb1a;--md-accent-bg-color:#fff;--md-accent-bg-color--light:#ffffffb3}[data-md-color-accent=cyan]{--md-accent-fg-color:#00bad6;--md-accent-fg-color--transparent:#00bad61a;--md-accent-bg-color:#fff;--md-accent-bg-color--light:#ffffffb3}[data-md-color-accent=teal]{--md-accent-fg-color:#00bda4;--md-accent-fg-color--transparent:#00bda41a;--md-accent-bg-color:#fff;--md-accent-bg-color--light:#ffffffb3}[data-md-color-accent=green]{--md-accent-fg-color:#00c753;--md-accent-fg-color--transparent:#00c7531a;--md-accent-bg-color:#fff;--md-accent-bg-color--light:#ffffffb3}[data-md-color-accent=light-green]{--md-accent-fg-color:#63de17;--md-accent-fg-color--transparent:#63de171a;--md-accent-bg-color:#fff;--md-accent-bg-color--light:#ffffffb3}[data-md-color-accent=lime]{--md-accent-fg-color:#b0eb00;--md-accent-fg-color--transparent:#b0eb001a;--md-accent-bg-color:#000000de;--md-accent-bg-color--light:#0000008a}[data-md-color-accent=yellow]{--md-accent-fg-color:#ffd500;--md-accent-fg-color--transparent:#ffd5001a;--md-accent-bg-color:#000000de;--md-accent-bg-color--light:#0000008a}[data-md-color-accent=amber]{--md-accent-fg-color:#fa0;--md-accent-fg-color--transparent:#ffaa001a;--md-accent-bg-color:#000000de;--md-accent-bg-color--light:#0000008a}[data-md-color-accent=orange]{--md-accent-fg-color:#ff9100;--md-accent-fg-color--transparent:#ff91001a;--md-accent-bg-color:#000000de;--md-accent-bg-color--light:#0000008a}[data-md-color-accent=deep-orange]{--md-accent-fg-color:#ff6e42;--md-accent-fg-color--transparent:#ff6e421a;--md-accent-bg-color:#fff;--md-accent-bg-color--light:#ffffffb3}[data-md-color-primary=red]{--md-primary-fg-color:#ef5552;--md-primary-fg-color--light:#e57171;--md-primary-fg-color--dark:#e53734;--md-primary-bg-color:#fff;--md-primary-bg-color--light:#ffffffb3}[data-md-color-primary=pink]{--md-primary-fg-color:#e92063;--md-primary-fg-color--light:#ec417a;--md-primary-fg-color--dark:#c3185d;--md-primary-bg-color:#fff;--md-primary-bg-color--light:#ffffffb3}[data-md-color-primary=purple]{--md-primary-fg-color:#ab47bd;--md-primary-fg-color--light:#bb69c9;--md-primary-fg-color--dark:#8c24a8;--md-primary-bg-color:#fff;--md-primary-bg-color--light:#ffffffb3}[data-md-color-primary=deep-purple]{--md-primary-fg-color:#7e56c2;--md-primary-fg-color--light:#9574cd;--md-primary-fg-color--dark:#673ab6;--md-primary-bg-color:#fff;--md-primary-bg-color--light:#ffffffb3}[data-md-color-primary=indigo]{--md-primary-fg-color:#4051b5;--md-primary-fg-color--light:#5d6cc0;--md-primary-fg-color--dark:#303fa1;--md-primary-bg-color:#fff;--md-primary-bg-color--light:#ffffffb3}[data-md-color-primary=blue]{--md-primary-fg-color:#2094f3;--md-primary-fg-color--light:#42a5f5;--md-primary-fg-color--dark:#1975d2;--md-primary-bg-color:#fff;--md-primary-bg-color--light:#ffffffb3}[data-md-color-primary=light-blue]{--md-primary-fg-color:#02a6f2;--md-primary-fg-color--light:#28b5f6;--md-primary-fg-color--dark:#0287cf;--md-primary-bg-color:#fff;--md-primary-bg-color--light:#ffffffb3}[data-md-color-primary=cyan]{--md-primary-fg-color:#00bdd6;--md-primary-fg-color--light:#25c5da;--md-primary-fg-color--dark:#0097a8;--md-primary-bg-color:#fff;--md-primary-bg-color--light:#ffffffb3}[data-md-color-primary=teal]{--md-primary-fg-color:#009485;--md-primary-fg-color--light:#26a699;--md-primary-fg-color--dark:#007a6c;--md-primary-bg-color:#fff;--md-primary-bg-color--light:#ffffffb3}[data-md-color-primary=green]{--md-primary-fg-color:#4cae4f;--md-primary-fg-color--light:#68bb6c;--md-primary-fg-color--dark:#398e3d;--md-primary-bg-color:#fff;--md-primary-bg-color--light:#ffffffb3}[data-md-color-primary=light-green]{--md-primary-fg-color:#8bc34b;--md-primary-fg-color--light:#9ccc66;--md-primary-fg-color--dark:#689f38;--md-primary-bg-color:#fff;--md-primary-bg-color--light:#ffffffb3}[data-md-color-primary=lime]{--md-primary-fg-color:#cbdc38;--md-primary-fg-color--light:#d3e156;--md-primary-fg-color--dark:#b0b52c;--md-primary-bg-color:#000000de;--md-primary-bg-color--light:#0000008a}[data-md-color-primary=yellow]{--md-primary-fg-color:#ffec3d;--md-primary-fg-color--light:#ffee57;--md-primary-fg-color--dark:#fbc02d;--md-primary-bg-color:#000000de;--md-primary-bg-color--light:#0000008a}[data-md-color-primary=amber]{--md-primary-fg-color:#ffc105;--md-primary-fg-color--light:#ffc929;--md-primary-fg-color--dark:#ffa200;--md-primary-bg-color:#000000de;--md-primary-bg-color--light:#0000008a}[data-md-color-primary=orange]{--md-primary-fg-color:#ffa724;--md-primary-fg-color--light:#ffa724;--md-primary-fg-color--dark:#fa8900;--md-primary-bg-color:#000000de;--md-primary-bg-color--light:#0000008a}[data-md-color-primary=deep-orange]{--md-primary-fg-color:#ff6e42;--md-primary-fg-color--light:#ff8a66;--md-primary-fg-color--dark:#f4511f;--md-primary-bg-color:#fff;--md-primary-bg-color--light:#ffffffb3}[data-md-color-primary=brown]{--md-primary-fg-color:#795649;--md-primary-fg-color--light:#8d6e62;--md-primary-fg-color--dark:#5d4037;--md-primary-bg-color:#fff;--md-primary-bg-color--light:#ffffffb3}[data-md-color-primary=grey]{--md-primary-fg-color:#757575;--md-primary-fg-color--light:#9e9e9e;--md-primary-fg-color--dark:#616161;--md-primary-bg-color:#fff;--md-primary-bg-color--light:#ffffffb3;--md-typeset-a-color:#4051b5}[data-md-color-primary=blue-grey]{--md-primary-fg-color:#546d78;--md-primary-fg-color--light:#607c8a;--md-primary-fg-color--dark:#455a63;--md-primary-bg-color:#fff;--md-primary-bg-color--light:#ffffffb3;--md-typeset-a-color:#4051b5}[data-md-color-primary=light-green]:not([data-md-color-scheme=slate]){--md-typeset-a-color:#72ad2e}[data-md-color-primary=lime]:not([data-md-color-scheme=slate]){--md-typeset-a-color:#8b990a}[data-md-color-primary=yellow]:not([data-md-color-scheme=slate]){--md-typeset-a-color:#b8a500}[data-md-color-primary=amber]:not([data-md-color-scheme=slate]){--md-typeset-a-color:#d19d00}[data-md-color-primary=orange]:not([data-md-color-scheme=slate]){--md-typeset-a-color:#e68a00}[data-md-color-primary=white]{--md-primary-fg-color:hsla(var(--md-hue),0%,100%,1);--md-primary-fg-color--light:hsla(var(--md-hue),0%,100%,0.7);--md-primary-fg-color--dark:hsla(var(--md-hue),0%,0%,0.07);--md-primary-bg-color:hsla(var(--md-hue),0%,0%,0.87);--md-primary-bg-color--light:hsla(var(--md-hue),0%,0%,0.54);--md-typeset-a-color:#4051b5}[data-md-color-primary=white] .md-button{color:var(--md-typeset-a-color)}[data-md-color-primary=white] .md-button--primary{background-color:var(--md-typeset-a-color);border-color:var(--md-typeset-a-color);color:hsla(var(--md-hue),0%,100%,1)}@media screen and (min-width:60em){[data-md-color-primary=white] .md-search__form{background-color:hsla(var(--md-hue),0%,0%,.07)}[data-md-color-primary=white] .md-search__form:hover{background-color:hsla(var(--md-hue),0%,0%,.32)}[data-md-color-primary=white] .md-search__input+.md-search__icon{color:hsla(var(--md-hue),0%,0%,.87)}}@media screen and (min-width:76.25em){[data-md-color-primary=white] .md-tabs{border-bottom:.05rem solid #00000012}}[data-md-color-primary=black]{--md-primary-fg-color:hsla(var(--md-hue),15%,9%,1);--md-primary-fg-color--light:hsla(var(--md-hue),15%,9%,0.54);--md-primary-fg-color--dark:hsla(var(--md-hue),15%,9%,1);--md-primary-bg-color:hsla(var(--md-hue),15%,100%,1);--md-primary-bg-color--light:hsla(var(--md-hue),15%,100%,0.7);--md-typeset-a-color:#4051b5}[data-md-color-primary=black] .md-button{color:var(--md-typeset-a-color)}[data-md-color-primary=black] .md-button--primary{background-color:var(--md-typeset-a-color);border-color:var(--md-typeset-a-color);color:hsla(var(--md-hue),0%,100%,1)}[data-md-color-primary=black] .md-header{background-color:hsla(var(--md-hue),15%,9%,1)}@media screen and (max-width:59.984375em){[data-md-color-primary=black] .md-nav__source{background-color:hsla(var(--md-hue),15%,11%,.87)}}@media screen and (max-width:76.234375em){html [data-md-color-primary=black] .md-nav--primary .md-nav__title[for=__drawer]{background-color:hsla(var(--md-hue),15%,9%,1)}}@media screen and (min-width:76.25em){[data-md-color-primary=black] .md-tabs{background-color:hsla(var(--md-hue),15%,9%,1)}} \ No newline at end of file diff --git a/assets/thingspeak_logo.png b/assets/thingspeak_logo.png new file mode 100644 index 0000000..8515b79 Binary files /dev/null and b/assets/thingspeak_logo.png differ diff --git a/configuration/index.html b/configuration/index.html new file mode 100644 index 0000000..0c23fc8 --- /dev/null +++ b/configuration/index.html @@ -0,0 +1,4107 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Configuration - SparkFun DataLogger IoT Hookup Guide + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + + + + + + + + + + +
+ +
+ + + + +
+
+ + + +
+
+
+ + + + + + + + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +

Configuration

+ + + +

Configuring the settings is as easy as opening a serial menu. You can use any serial monitor or terminal emulator to quickly and easily change and store the DataLogger IoT settings via its USB-C interface.

+

There are plenty of free alternatives out there to configure the DataLogger IoT. For the scope of this tutorial we will be using Tera Term.

+ +
+

Note

+

You will need a serial terminal client that supports edit characters. Most if not all modern serial terminal programs will have the ability to support interactive edits. Unfortunately, we have not had any success with CoolTerm. We have tested the DataLogger IoT with Tera Term, Minicom, and Screen.

+
+

If this is the your first time using a terminal window, We recommend checking out the tutorial below for more information on serial terminal basics.

+ + + + +

The above guides will show you how to open the correct port for the DataLogger IoT and how to set the baud rate to 115200 baud. You can change the DataLogger IoT's baud rate through the configuration menus too should you need to.

+
+

Note

+

For users with an Arduino IDE, you could also use the Arduino Serial Monitor by setting the line ending to Newline. Users will also need to CTRL + Enter when sending any character to the DataLogger IoT. However, we recommend using one of the terminals mentioned earlier.

+
+

Initialization and Serial Output

+

Connect the DataLogger IoT to a USB cable and connect to your computer. The addressable RGB LED will light up green as it initializes. As of firmware v1.0.2.00 - build 00013e, a Startup Menu was added to the system. This allows you to change the behavior of the DataLogger at start-up. This change only affects the current system session.

+
+ Output when DataLogger IoT - 9DoF Start-up menu +
+ +
    +
  • 'n' — Normal startup
  • +
  • 'a' — Disable I2C device auto load on startup
  • +
  • 'l' — List the I2C devices supported. This device table is discarded after auto-load
  • +
  • 'w' — Disable WiFi
  • +
  • 's' — Disable preference restore during startup
  • +
+
+

Note

+

The amount of time the start-up menu is displayed is adjustable. This settings can be configured in the Settings/Application Settings page, under the Advanced section.

+
+

You should see the following output when the board initializes:

+
+ Output when DataLogger IoT - 9DoF is initializing +
+ +

The messages in the serial terminal provide us with the DataLogger's configuration and will vary depending on the firmware version that is loaded on the board.

+
    +
  • The DataLogger IoT software version (in this case is v01.02.00 - build 00013e).
  • +
  • As the DataLogger IoT is initializing, the system settings are being restored from the last saved preference.
  • +
  • There no WiFi credentials and the board has failed to connect. This output will change once you provide the WiFi credentials and are able to connect to the network.
  • +
  • There are 3x devices currently detected and they are connected through I2C through the Qwiic port and SPI. These are the on-board sensors for the DataLogger IoT. There may be more devices that are detected depending on the firmware and what is connected to the ports. Since these were recognized, they were loaded onto the DataLogger IoT.
  • +
  • The current date and time is shown (by default), the date and time is set to 1-1-1970 and 00:00:00). This value will change depending on the clock source through NTP, RTC, or a u-blox GNSS module.
  • +
  • The time the board has been running will be shown in the uptime.
  • +
  • The primary external time source that the board syncs is currently through the NTP client. This can be configured depending on your clock source.
  • +
  • The board name (in this case, it was SparkFun DataLogger IoT - 9DoF)
  • +
  • The board ID (in this case, it was SFD16C8F0D1AD6B8)
  • +
  • The microSD card has been found, the type of memory card it is, the size of the memory card, how much memory is used, and how much is available.
  • +
  • If there is a WiFi network name saved, the SSID will be shown along with information indicating whether the board was able to connect to the WiFi network. By default there is no SSID saved in memory.
  • +
  • If there is a battery connected, the LiPo Battery Fuel Guage will indicate if there is one attached to the board.
  • +
  • Parameters for low power mode will be provided indicating if deep sleep is enabled, sleep interval, and wake interval.
  • +
  • Parameters for logging are also provided for the logging interval, the format for the serial output, format for the microSD card, current saved filename, and file rotation period.
  • +
  • The board will also show the available IoT services that are enabled for the DataLogger IoT.
  • +
  • Current settings to download log files via a web interface (included in firmware v01.02.00)
  • +
  • Supported devices through Qwiic or SPI will be listed if they are connected.
  • +
  • The output will finish by telling you what devices are connected to the DataLogger IoT again.
  • +
+
+

Note

+

As of firmware v01.02.00, there is also a compact mode! By adjusting the setting, the ESP32 will output less at startup. This settings can be configured in the Settings/Application Settings page, under the Advanced section.

+

+ Output when DataLogger IoT - 9DoF is initializing, compact +

+
+

Once the DataLogger IoT has initialized, the DataLogger IoT will begin outputting comma separated values (CSV). This is the default output that is set for the DataLogger IoT - 9DoF. Of course, you will not have as many readings on the DataLogger IoT since the 6DoF IMU and magnetometer are not populated on that version of the board.

+
+ CSV Output on the DataLogger IoT - 9DoF v01.02.00 +
+ +
+

Note

+

Depending on your DataLogger IoT preferences, your device may output as a JSON format like the image shown below. +

+ JSON Output on the DataLogger -IoT - 9DoF +

+
+

The data scrolling up the screen show what each device's output is along with their associated unit if it is available. Your mileage will vary depending on the board version that you have and what device is connected:

+
    +
  • MAX17048.Voltage (V)
  • +
  • MAX17048.State of Charge (%)
  • +
  • MAX17048.Charge Rate (%/hr)
  • +
  • ISM330.Accel X (milli-g)
  • +
  • ISM330.Accel Y (milli-g)
  • +
  • ISM330.Accel Z (milli-g)
  • +
  • ISM330.Gyro X (milli-dps)
  • +
  • ISM330.Gyro Y (milli-dps)
  • +
  • ISM330.Gyro Z (milli-dps)
  • +
  • ISM330.Temperature (C)
  • +
  • MMC5983.X Field (Gauss)
  • +
  • MMC5983.Y Field (Gauss)
  • +
  • MMC5983.Z Field (Gauss)
  • +
  • MMC5983.Temperature (C)
  • +
+

The output will vary depending on what is connected so you may get additional readings in the output and it may not be in the following order listed above. The logging rate defaults to about 0.067Hz (or 15000ms), so as the data scrolls past, you will see the last value settle at about 0.067Hz.

+ +

Right! Let's open the main menu by pressing on any key in the serial terminal program.

+
+ DataLogger IoT Main Menu +
+ +

You will be prompted with a few options. Once in the configuration menu, all three colors of the addressable RGB LED will turn on to produce the color white indicating that you are navigating through the menu. Before we dive into the settings, lets check out a few commands and saving settings.

+

Quick (!) Command Reference

+

As of firmware v01.02.00, commands can be executed directly from the serial console thus bypassing the serial menu system! The following commands are supported.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Quick Command + Command Description +
+ !about + + Display the system about page +
+ !clear-settings + + Clear the on board system preferences with a yes/no prompt +
+ !clear-settings-forced + + Clear the on board system preferences with no prompt +
+ !devices + + List the currently connected devices +
+ !factory-reset + + Perform a factory reset - presents a Y/N prompt +
+ !heap + + Display the current system heap memory usage +
+ !help + + List the available quick commands +
+ !json-settings + + For setting the device settings via a serial connection. When this command is sent, the system expects to receive a JSON settings file +
+ !log-now + + Perform a log observation event +
+ !log-rate + + If log rate measurement is enabled, the current log rate is printed +
+ !reset-device + + Reset the device - erasing any saved settings and restarting the device +
+ !reset-device-forced + + Reset the device, but without a Y/N prompt +
+ !restart + + Restart the device +
+ !restart-forced + + Restart the device without a Y/N prompt +
+ !save-settings + + Save the current settings to on-board flash +
+ !sdcard + + Output the current SD card usage statistics +
+ !systime + + Output current system time +
+ !uptime + + The uptime of the device +
+ !device-id + + The ID for the device +
+ !version + + The version of the firmware +
+ !wifi + + Output current system WiFi state +
+
+ +

Typing a quick command and hitting the Enter button will result in the DataLogger IoT executing the command without the need to go through the menu system. Below is an example showing the !about quick command being sent and then executing the command as the DataLogger IoT is outputting CSV values to the serial terminal.

+
+ Quick Command Entered +
+ +

Exiting and Saving

+

When exiting the menus, you will be prompted with either an x or b. You can use either character when exiting the menus as well as X or B. Note that you will need to use either of these keys when making a change in order for the DataLogger IoT to save any changes in memory. Make sure that you receive the following message indicating that the settings were saved: [I] Saving System Settings. The DataLogger IoT will the continue reading the devices and outputting the readings through the serial terminal.

+
+ Output Save Settings +
+ +

Cancelling Changes

+

You can also use any of your Esc or arrow keys (i.e. , , , ) to exit. However, using the escape or arrow keys will not save any changes in memory once the reset button is hit or whenever power is cycled.

+
+ Output when Cancelling Changes +
+ +

Timeout from Inactivity

+

The menus will slowly exit out after 2 minutes of inactivity, so if you do not press a key the DataLogger IoT will return to its previous menu. It will continue to move back until it reaches the main menu. After another additional 2 minutes of inactivity, the board will exit begin logging data again. When the menu exits from inactivity, any changes will not be saved in memory as well.

+
+ Output when Timing Out +
+ +

Settings

+

Let's start by configuring the DataLogger's system settings. Send a 1 through the serial terminal. You will have the option to adjust various settings ranging from the your preferences, time source to synchronize the date and time, WiFi network, how the device logs data, which IoT service to use, and firmware updates.

+
+ Settings Menu Options +
+ +
+

Note

+

You may notice after entering a 1 that there is a slight delay before the DataLogger IoT responds. The delay was added to allow some time for the DataLogger IoT to receive an additional digit for any option greater than 9. If you want to head to option 1 immediately without the slight delay, you can hit the Enter key to enter the Application Settings.

+
+

We'll go over each of these options below.

+

General: Application Settings

+

In the Settings Menu, send a 1 to adjust the Application Settings. As of firmware v01.00.02, users can now adjust the baud rate of the serial console output and the menu system's timeout value.

+
+ Application Settings Options +
+ +

In the Application Settings Menu, users will be able to configure the addressable RGB's LED through software, menu timeout, microSD card's output format, serial console's output format, terminal's baud rate, deep sleep parameters, and view the current settings of the DataLogger IoT similar to when the board was initialized. Depending on your preference and how you are logging data, you can adjust the data as CSV or JSON.

+
    +
  • 1 LED Enabled — Enable/Disable the on-board RGB LED activity
      +
    • Accepts a boolean value:
        +
      • 1 to enable (default)
      • +
      • 0 to disable
      • +
      +
    • +
    +
  • +
  • 2 Menu Timeout — Inactivity timeout period for the menu system
      +
    • Accepts the following values:
        +
      • 1 30 Seconds = 30
      • +
      • 2 60 Seconds = 60 (default)
      • +
      • 3 2 Minutes = 120
      • +
      • 4 5 Minutes = 300
      • +
      • 5 10 Minutes = 600
      • +
      • b Back
      • +
      +
    • +
    +
  • +
  • 3 Color Output — Use color output with the Serial console. (added as of firmware v01.02.00)
      +
    • Accepts a boolean value:
        +
      • 1 to enable (default)
      • +
      • 0 to disable
      • +
      +
    • +
    +
  • +
  • 4 Board Name — A specific name for this DataLogger
      +
    • Accepts a string
    • +
    +
  • +
  • 5 SD Card Format — Enable and set the output format
      +
    • Accepts the following values:
        +
      • 1 to disable = 0
      • +
      • 2 CSV format (default) = 1
      • +
      • 3 JSON format = 2
      • +
      +
    • +
    +
  • +
  • 6 Serial Console Format — Enable and set the output format
      +
    • Accepts the following values:
        +
      • 1 to disable = 0
      • +
      • 2 CSV format (default) = 1
      • +
      • 3 JSON format = 2
      • +
      +
    • +
    +
  • +
  • 7 JSON Buffer Size — Output buffer size in bytes
      +
    • Accepts an integer between 100 to 5000 :
        +
      • 1600 bytes (default)
      • +
      +
    • +
    +
  • +
  • 8 Terminal Baud Rate — Update terminal baud rate. Changes take effect on restart.
      +
    • Accepts an unsigned integer between 1200 to 50000:
        +
      • 115200 (default)
      • +
      +
    • +
    +
  • +
  • 9 Enable System Sleep — If enabled, sleep the system
      +
    • Accepts a boolean value:
        +
      • 1 to enable
      • +
      • 0 to disable (default)
      • +
      +
    • +
    +
  • +
  • 10 Sleep Interval (sec) — The interval the system will sleep for
      +
    • Accepts an integer between 5 to 86400 :
        +
      • 30 seconds (default)
      • +
      +
    • +
    +
  • +
  • 11 Wake Interval (sec) — The interval the system will operate between sleep period
      +
    • Accepts an unsigned integer between 60 to 86400 :
        +
      • 120 seconds (default)
      • +
      +
    • +
    +
  • +
  • 12 Startup Messages Level of message output at startup
      +
    • Accepts a value between 1 to 3 :
    • +
    • 1 Normal = 0 (default)
    • +
    • 2 Compact = 1
    • +
    • 3 Disabled = 2
    • +
    +
  • +
  • 13 Startup Delay Startup Menu Delay in Seconds
      +
    • Accepts a value between 0 to 60 :
        +
      • 2 seconds (default)
      • +
      +
    • +
    +
  • +
  • 14 Device Names Name always includes the device address
      +
    • Accepts a boolean value:
        +
      • 1 to enable (default)
      • +
      • 0 to disable
      • +
      +
    • +
    +
  • +
  • 15 About... — Details about the system
  • +
  • b Back
  • +
+
+

Note

+

Once the baud rate is changed and saved, make sure to adjust the baud rate of your serial terminal when the board is reset. If you forgot the baud rate, you can hold the BOOT button down for 20 seconds to erase the on-board preferences (besides the baud rate, this also includes any other settings that were saved) and restart the board.

+
+

When finished, you will need to exit the menus so that the DataLogger IoT saves the changes. Send a b to exit out this menu, b to exit out of the DataLogger IoT settings, and x to exit out of the main menu.

+
+ Save Settings Menu +
+ +

General: Save Settings

+

In the Settings menu, send a 2 to adjust the Save Settings. As of firmware v01.01.01, the JSON output buffer size is now user configurable. This will be under option "JSON File Buffer Size" when in the Save Settings Menu.

+
+ +
+ +

In the Save Settings Menu, users will be able to save, restore, or clear any preferences in memory (i.e. persistent storage) or a saved file to a fallback device (i.e. microSD card). Note that any passwords and secret keys are not saved in the save settings file. You will need to manually enter those values in the file saved on the microSD card.

+
    +
  • 1 Fallback Restore — If unable to restore settings, use the fallback source (JSON File)
      +
    • Accepts a boolean value:
        +
      • 1 to enable (default)
      • +
      • 0 to disable
      • +
      +
    • +
    +
  • +
  • 2 Fallback Save — Save settings also saves on the fallback storage (JSON File)
      +
    • Accepts a boolean value:
        +
      • 1 to enable
      • +
      • 0 to disable (default)
      • +
      +
    • +
    +
  • +
  • 3 JSON File Buffer Size — The size in bytes used for the internal I/O buffer
      +
    • Accepts an unsigned integer:
    • +
    • 6400 (default, as of firmware v01.01.01)
    • +
    +
  • +
  • 4 Save Settings — Save current settings to persistent storage
      +
    • Accepts a yes/no:
        +
      • Y or y for yes
      • +
      • N or n for no
      • +
      +
    • +
    +
  • +
  • 5 Restore Settings — Restore saved settings
      +
    • Accepts a yes/no:
        +
      • Y or y for yes
      • +
      • N or n for no
      • +
      +
    • +
    +
  • +
  • 6 Clear Settings — Erase the saved settings on the device
      +
    • Accepts a yes/no:
        +
      • Y or y for yes
      • +
      • N or n for no
      • +
      +
    • +
    +
  • +
  • 7 Save to Fallback — Save System Settings to the fallback storage (JSON File)
      +
    • Accepts a yes/no:
        +
      • Y or y for yes
      • +
      • N or n for no
      • +
      +
    • +
    +
  • +
  • 8 Restore from Fallback — Restore system settings from the fallback storage (JSON File)
      +
    • Accepts a yes/no:
        +
      • Y or y for yes
      • +
      • N or n for no
      • +
      +
    • +
    +
  • +
  • b Back
  • +
+

If you have the Fallback Save enabled or selected the option Save to Fallback, you will notice an additional file called datalogger.json saved in the microSD card. This is the fallback file that is saved. Using a text editor, you can edit this file to adjust the settings or provide WiFi credentials, certificates, and keys. You can use option 7 to restore the settings on your DataLogger IoT.

+
+ Fall Back Saved Settings Saved in the MicroSD Card as a JSON File +
+ +

When finished, you will need to exit the menus so that the DataLogger IoT saves the changes. Send a b to exit out this menu, b to exit out of the DataLogger IoT settings, and x to exit out of the main menu.

+
+ Output Save Settings Confirmation +
+ +

General: Time Sources

+
+

Note

+

Make sure to connect the ESP32-WROOM to a 2.4GHz WiFi network and ensure that is not a guest network that requires you to sign in. Unfortunately, 5GHz WiFi is not supported on the ESP32-WROOM module.

+
+

In the Settings Menu, send 3 to manage the time reference sources. As of firmware v01.01.01, time zone support is at the clock level, not tied to NTP. The option to adjust the Time Zone is moved to the Time Sources menu.

+
+ Time Source Menu Options +
+ +

In this menu, you will have options to update the primary reference clock, update interval, add a secondary reference clock, and update it's interval. By default, the primary reference clock is set to use the Network Time Protocol (NTP). To synchronization the time, you will need to connect to a 2.4GHz WiFi network in order to update the time. To add a secondary clock, make sure to connect a compatible Qwiic-enabled devices that can keep track of time (i.e. Qwiic Real Time Clock Module - RV-8803 or a Qwiic-enabled u-blox GNSS module).

+
+

Note

+

To adjust the time zone, you will need to enter a POSIX timezone string variable. Try checking out this CSV in this GitHub repo and searching for the timezone string variable in your area. For more information about POSIX format specification check out this article from IBM.

+
+
    +
  • 1 The Time Zone — Time zone setting string for the device
      +
    • Accepts a string:
        +
      • MST7MDT,M3.2.0,M11.1.0 (default, as of firmware v01.01.01)
      • +
      +
    • +
    +
  • +
  • 2 Reference Clock — The current reference clock source
      +
    • Accepts the following values:
        +
      • 1 for no clock
      • +
      • 2 for NTP Client (default)
      • +
      +
    • +
    +
  • +
  • 3 Update Interval — Main clock update interval in minutes. 0 = No update
      +
    • Accepts an unsigned integer:
        +
      • 0 = No update
      • +
      • 60 seconds (default)
      • +
      +
    • +
    +
  • +
  • 4 Enable Clock Fallback — Use a valid reference clock if the primary is not available
      +
    • Accepts a boolean value:
        +
      • 1 to enable (default)
      • +
      • 0 to disable
      • +
      +
    • +
    +
  • +
  • 5 Dependant Interval — Connected depedant clock update interval in minutes. 0 = No update
      +
    • Accepts an unsigned integer:
        +
      • 0 = No update
      • +
      • 60 seconds (default)
      • +
      +
    • +
    +
  • +
  • 6 Update Connected — Update connected clocks on main clock update
      +
    • Accepts a boolean value:
        +
      • 1 to enable (default)
      • +
      • 0 to disable
      • +
      +
    • +
    +
  • +
  • b Back
  • +
+
+

Note

+

As an alternative to using the NTP, users can also add a compatible Qwiic-enabled device that can keep track of time (i.e. Qwiic Real Time Clock Module - RV-8803 or a Qwiic-enabled u-blox GNSS module). These can be set as the primary or secondary clock.

+

+ + + + + +
u-blox GNSS Module Attached via QwiicQwiic Real Time Clock Module - RV-8803 Attached via Qwiic
+

+

Once attached, you will be prompted with additional options to select a primary reference clock.

+

+ Time Source Reference Clock Options +

+

If you are using a u-blox GNSS module, make sure that you have enough satellites in view. The option to add or configure the GNSS will not be available if there are not enough satellites in view. If you are using the Qwiic Real Time Clock Module - RV-8803, you may need to go into the device settings to manually adjust the date and time.

+
+

When finished, you will need to exit the menus so that the DataLogger IoT saves the changes. Send a b to exit out this menu, b to exit out of the DataLogger IoT settings, and x to exit out of the main menu.

+
+ Output Save Settings Confirmation +
+ +

Network: WiFi Network

+
+

Note

+

The ESP32-WROOM can only connect to a 2.4GHz WiFi network. Unfortunately, 5GHz is not supported on the ESP32-WROOM module.

+
+

In the Settings Menu, send a 4 to configure the WiFi settings. As of firmware v01.00.02, up to 4 sets of WiFi credentials can be saved.

+
+ WiFi Network Menu Options +
+ +

Once you are in the WiFi Network menu, you can enable/disable WiFi and save the WiFi network credentials. Once connected to a 2.4GHz WiFi network, you can synchronize the date and time, connect to an IoT service to log data, and update the latest firmware over-the-air. Since the WiFi is turned on by default, you will simply need to save the WiFi network's name and password.

+
    +
  • 1 Enabled — Enable or Disable the WiFi Network connection
      +
    • Accepts a boolean value:
        +
      • 1 to enable (default)
      • +
      • 0 to disable
      • +
      +
    • +
    +
  • +
  • 2 Network Name — The SSID of the WiFi network
      +
    • Accepts a string:
        +
      • For example, if my network name is "MY_NETWORK_NAME", you would manually type MY_NETWORK_NAME. When finished hit the ENTER key
      • +
      +
    • +
    +
  • +
  • 3 Password — The Password to connect to the WiFi network
      +
    • Accepts a string:
        +
      • For example, if my network name is "MY_SUPER_SECRET_PASSWORD", you would manually type MY_SUPER_SECRET_PASSWORD. Note that as you type the password, each character will be replaced with an asterisk (*). When finished hit the ENTER key.
      • +
      +
    • +
    +
  • +
  • 4 Network 2 Name — Alternative network 2 SSID
      +
    • Accepts a string:
        +
      • For example, if my network name is "MY_NETWORK_NAME_2", you would manually type MY_NETWORK_NAME_2. When finished hit the ENTER key
      • +
      +
    • +
    +
  • +
  • 5 Network 2 Password — Alternative network 2 Password
      +
    • Accepts a string:
        +
      • For example, if my network name is "MY_SUPER_SECRET_PASSWORD_2", you would manually type MY_SUPER_SECRET_PASSWORD_2. Note that as you type the password, each character will be replaced with an asterisk (*). When finished hit the ENTER key.
      • +
      +
    • +
    +
  • +
  • 6 Network 3 Name — Alternative network 2 SSID
      +
    • Accepts a string:
        +
      • For example, if my network name is "MY_NETWORK_NAME_3", you would manually type MY_NETWORK_NAME_3. When finished hit the ENTER key
      • +
      +
    • +
    +
  • +
  • 7 Network 3 Password — Alternative network 3 Password
      +
    • Accepts a string:
        +
      • For example, if my network name is "MY_SUPER_SECRET_PASSWORD_3", you would manually type MY_SUPER_SECRET_PASSWORD_3. Note that as you type the password, each character will be replaced with an asterisk (*). When finished hit the ENTER key.
      • +
      +
    • +
    +
  • +
  • 8 Network 4 Name — Alternative network 2 SSID
      +
    • Accepts a string:
        +
      • For example, if my network name is "MY_NETWORK_NAME_4", you would manually type MY_NETWORK_NAME_4. When finished hit the ENTER key
      • +
      +
    • +
    +
  • +
  • 9 Network 4 Password — Alternative network 4 Password
      +
    • Accepts a string:
        +
      • For example, if my network name is "MY_SUPER_SECRET_PASSWORD_4", you would manually type MY_SUPER_SECRET_PASSWORD_4. Note that as you type the password, each character will be replaced with an asterisk (*). When finished hit the ENTER key.
      • +
      +
    • +
    +
  • +
  • b Back
  • +
+

When finished, you will need to exit the menus so that the DataLogger IoT saves the changes. Send a b to exit out this menu, b to exit out of the DataLogger IoT settings, and x to exit out of the main menu.

+
+ Output Save Settings Confirmation +
+ +

Press the reset button or cycle power to restart the DataLogger IoT. You can also go through the menu and reset the device through software as well. Once the board is reset, the DataLogger will attempt to connect to a WiFi network. If you are successful, the output will indicate that the board connected to a WiFi network and will update the current time through a NTP Client.

+
+ DataLogger IoT Re-initializing and Outputting WiFi Connected Message +
+ +
+

Note

+

If you have a Qwiic Dynamic NFC/RFID Tag connected to the board's Qwiic connector, you can easily update your WiFi credentials! Just make sure to save the WiFi credentials to the tag.

+
+
+

Note

+

If you saved your preferences to a JSON file on your microSD card's root directory, you can also save your WiFi credentials and load the system settings from the menu as well!

+
+

Network: NTP Client

+

In the Settings menu, send a 5 to adjust the NTP Client settings. As of firmware v01.01.01, time zone support is at the clock level, not tied to the NTP. The option to adjust the Time Zone is moved to the Time Sources menu.

+
+ NTP Client Menu Options +
+ +

In this menu, users will have the option to enable/disable the NTP client, select the primary/secondary server, or adjust the time zone for your area.

+
    +
  • 1 Enabled — Enable or Disable the NTP Client
      +
    • Accepts a boolean value:
        +
      • 1 to enable (default)
      • +
      • 0 to disable
      • +
      +
    • +
    +
  • +
  • 2 NTP Server One — The primary NTP Server to use
      +
    • Accepts a string:
        +
      • time.nist.gov (default)
      • +
      +
    • +
    +
  • +
  • 3 NTP Server Two — The secondary NTP Server to use
      +
    • Accepts a string:
        +
      • pool.ntp.org (default)
      • +
      +
    • +
    +
  • +
  • b Back
  • +
+

When finished, you will need to exit the menus so that the DataLogger IoT saves the changes. Send a b to exit out this menu, b to exit out of the DataLogger IoT settings, and x to exit out of the main menu.

+
+ Output Save Settings Confirmation +
+ +

Logging: Logger

+

In the Settings menu, send a 6 to adjust how data is logged.

+
+ Logger Menu Options +
+ +

In the Logger menu, users will have the option to add a timestamp, increment sample numbering, data format, or reset the sample counter. Note that the timestamp is the system clock and syncs with the reference clock that was chosen. Data from the Qwiic-enabled devices that keep track of time can also be included for each data entry by default.

+
    +
  • 1 Timestamp Mode — Enable timestamp output and set the format of a log entry timestamp
      +
    • 1 for no timestamp (default) = 0
    • +
    • 2 for milliseconds since program start = 1
    • +
    • 3 for seconds since Epoch = 2
    • +
    • 4 for Date Time - USA Date format = 3
    • +
    • 5 for Date Time = 4
    • +
    • 6 for ISO08601 Timestamp = 5
    • +
    • 7 for ISO08601 Timestamp with Time Zone = 6
    • +
    +
  • +
  • 2 Sample Numbering — An incremental count of the current log entry
      +
    • Accepts a boolean value:
        +
      • 1 to enable
      • +
      • 0 to disable (default)
      • +
      +
    • +
    +
  • +
  • 3 Numbering Increment — Increment amount for Sample Numbering
      +
    • Accepts an unsigned integer between 1 to 10000:
        +
      • 1 (default)
      • +
      +
    • +
    +
  • +
  • 4 Output ID — Include the Board ID in the log output (added as of firmware v01.02.00)
      +
    • Accepts a boolean value:
        +
      • 1 to enable
      • +
      • 0 to disable (default)
      • +
      +
    • +
    +
  • +
  • 5 Output Name — Include the Board Name in the log output (added as of firmware v01.02.00)
      +
    • Accepts a boolean value:
        +
      • 1 to enable
      • +
      • 0 to disable (default)
      • +
      +
    • +
    +
  • +
  • 6 Rate Metric — Enable to record the logging rate data (added as of firmware v01.02.00)
      +
    • Accepts a boolean value:
        +
      • 1 to enable
      • +
      • 0 to disable (default)
      • +
      +
    • +
    +
  • +
  • 7 SD Card Format — Enable and set the output format
      +
    • Accepts an integer:
        +
      • 1 to disable = 0
      • +
      • 2 CSV format = 1 (default)
      • +
      • 3 JSON format = 2
      • +
      +
    • +
    +
  • +
  • 8 Serial Console Format — Enable and set the output format
      +
    • Accepts an integer:
        +
      • 1 to disable = 0
      • +
      • 2 CSV format = 1 (default)
      • +
      • 3 JSON format = 2
      • +
      +
    • +
    +
  • +
  • 9 System Info — Log system information (added as of firmware v01.02.00)
      +
    • Accepts a boolean value:
        +
      • 1 to enable
      • +
      • 0 to disable (default)
      • +
      +
    • +
    +
  • +
  • 10 Reset Sample Counter — Reset the sample number counter to the provided value
      +
    • Accepts an unsigned integer between 0 to 10000:
        +
      • 0 (default)
      • +
      +
    • +
    +
  • +
  • b Back
  • +
+

When finished, you will need to exit the menus so that the DataLogger IoT saves the changes. Send a b to exit out this menu, b to exit out of the DataLogger IoT settings, and x to exit out of the main menu.

+
+ Output Save Settings Confirmation +
+ +

Press the reset button or cycle power to restart the DataLogger IoT. You can also go through the menu and reset the device through software as well. Below is an example with the ISO08601 time that was added to the output.

+
+ DataLogger IoT Re-initializing and Outputting Time in ISO08601 Time Format +
+ +

Logging: Logging Timer

+

In the Settings menu, send an 7 to adjust the Logging Timer.

+
+ Logging Timer Menu Options +
+ +

Adjusting the interval for the Logging Timer will change the amount of time between log entries.

+
    +
  • 1 Interval — The timer interval in milliseconds
      +
    • Accepts an integer:
        +
      • 15000 milliseconds (default)
      • +
      +
    • +
    +
  • +
  • b Back
  • +
+

When finished, you will need to exit the menus so that the DataLogger IoT saves the changes. Send a b to exit out this menu, b to exit out of the DataLogger IoT settings, and x to exit out of the main menu.

+
+ Output Save Settings Confirmation +
+ +

Logging: Data File

+

In the Settings menu, send an 8 to adjust the Logging Data File.

+
+ Data File Menu Options +
+ +

Adjusting these parameters allows you to change the filename prefix, the number the files starts at, and how often the DataLogger will create a new file on the microSD card. For example, the default file will be saved as sfe0001.txt. After 1 day, the DataLogger will rotate files by creating a new file named sfe0002.txt. The DataLogger will begin logging data in this new file. The purpose of this log rotation is to limit the size of each file prevent issues when opening large files.

+
    +
  • 1 Rotate Period — Time between file rotation
      +
    • Accepts the following values:
        +
      • 1 for 6 hours = 6
      • +
      • 2 for 12 hours = 12
      • +
      • 3 for 1 day (24 hours) = 24 (default)
      • +
      • 4 for 2 days (48 hours) = 48
      • +
      • 5 for 1 week (168 hours) = 168
      • +
      +
    • +
    +
  • +
  • 2 File Start Number — The number the filename rotation starts with
      +
    • Accepts an unsigned integer:
        +
      • 1 (default)
      • +
      +
    • +
    +
  • +
  • 3 Filename Prefix — The prefix string for the generated filenames
      +
    • Accepts a string:
        +
      • sfe (default)
      • +
      +
    • +
    +
  • +
  • b Back
  • +
+

When finished, you will need to exit the menus so that the DataLogger IoT saves the changes. Send a b to exit out this menu, b to exit out of the DataLogger IoT settings, and x to exit out of the main menu.

+
+ Output Save Settings Confirmation +
+ +

The contents of the file will depend on how the data was saved (either CSV or JSON). Make sure that the SD Card format is enabled to either CSV or JSON with your desired device outputs turned on so that the DataLogger can save the readings.

+

When removing the microSD card, make sure to remove your power source. Then insert into it into microSD card adapter or USB reader. When connecting the memory card to your computer, you can use a text editor to view the saved readings. In this case, a Windows operating system was viewing the file sfe0000.txt and it was only file available in the microSD card.

+
+ Readings Saved in Text File Shown in a Windows File Explorer +
+ +

IoT Services: MQTT Client

+

In the Settings menu, send an 9 to adjust settings for the MQTT Client.

+
+ MQTT Client Menu Options +
+ +
    +
  • 1 Enabled — Enable or Disable MQTT Client
      +
    • Accepts a boolean value:
        +
      • 1 to enable
      • +
      • 0 to disable (default)
      • +
      +
    • +
    +
  • +
  • 2 Port — The MQTT broker port to connect to
      +
    • Accepts an unsigned integer:
        +
      • 1883 (default)
      • +
      +
    • +
    +
  • +
  • 3 Server — The MQTT server to connect to
      +
    • Accepts a string
    • +
    +
  • +
  • 4 MQTT Topic — The MQTT topic to publish to
      +
    • Accepts a string
    • +
    +
  • +
  • 5 Client Name — Name of this device used for MQTT Communications
      +
    • Accepts a string
    • +
    +
  • +
  • 6 Username — Username to connect to an MQTT broker, if required.
      +
    • Accepts a string
    • +
    +
  • +
  • 7 Password — Password to connect to an MQTT broker, if required.
      +
    • Accepts a string
    • +
    +
  • +
  • 8 Buffer Size — MQTT payload buffer size. If 0, the buffer size is dynamic
      +
    • Accepts an unsigned int16:
        +
      • 0 for dynamic buffer size (default)
      • +
      +
    • +
    +
  • +
  • b Back
  • +
+

IoT Services: MQTT Secure Client

+

In the Settings menu, send an 10 to adjust settings for the MQTT Secure Client.

+
+ MQTT Secure Client Menu Options +
+ +
    +
  • 1 Enabled — Enable or Disable MQTT Secure Client
      +
    • Accepts a boolean value:
        +
      • 1 to enable
      • +
      • 0 to disable (default)
      • +
      +
    • +
    +
  • +
  • 2 Port — The MQTT broker port to connect to
      +
    • Accepts an unsigned integer:
        +
      • 8883 (default, as of firmware v01.00.04)
      • +
      +
    • +
    +
  • +
  • 3 Server — The MQTT server to connect to
      +
    • Accepts a string
    • +
    +
  • +
  • 4 MQTT Topic — The MQTT topic to publish to
      +
    • Accepts a string
    • +
    +
  • +
  • 5 Client Name — Name of this device used for MQTT Communications
      +
    • Accepts a string
    • +
    +
  • +
  • 6 Username — Username to connect to an MQTT broker, if required.
      +
    • Accepts a string
    • +
    +
  • +
  • 7 Password — Password to connect to an MQTT broker, if required.
      +
    • Accepts a string
    • +
    +
  • +
  • 8 Buffer Size — MQTT payload buffer size. If 0, the buffer size is dynamic
      +
    • Accepts an unsigned int16:
        +
      • 0 for dynamic buffer size (default)
      • +
      +
    • +
    +
  • +
  • 9 CA Cert Filename — The File to load the certificate from
      +
    • Accepts a string
    • +
    +
  • +
  • 10 Client Cert Filename — The File to load the client certificate from
      +
    • Accepts a string
    • +
    +
  • +
  • 11 Client Key Filename — The File to load the client key from
      +
    • Accepts a string
    • +
    +
  • +
  • b Back
  • +
+

IoT Services: AWS IoT

+

In the Settings menu, send an 11 to adjust settings for the AWS IoT.

+
+ AWS IoT Menu Options +
+ +
    +
  • 1 Enabled — Enable or Disable AWS IoT
      +
    • Accepts a boolean value:
        +
      • 1 to enable
      • +
      • 0 to disable (default)
      • +
      +
    • +
    +
  • +
  • 2 Port — The MQTT broker port to connect to
      +
    • Accepts an unsigned integer:
        +
      • 8883 (default, as of firmware v01.00.04)
      • +
      +
    • +
    +
  • +
  • 3 Server — The MQTT server to connect to
      +
    • Accepts a string
    • +
    +
  • +
  • 4 MQTT Topic — The MQTT topic to publish to
      +
    • Accepts a string
        +
      • $aws/things//shadow/update (default)
      • +
      +
    • +
    +
  • +
  • 5 Client Name — Name of this device used for MQTT Communications
      +
    • Accepts a string
    • +
    +
  • +
  • 6 Username — Username to connect to an MQTT broker, if required.
      +
    • Accepts a string
    • +
    +
  • +
  • 7 Password — Password to connect to an MQTT broker, if required.
      +
    • Accepts a string
    • +
    +
  • +
  • 8 Buffer Size — MQTT payload buffer size. If 0, the buffer size is dynamic
      +
    • Accepts an unsigned int16:
        +
      • 0 for dynamic buffer size (default)
      • +
      +
    • +
    +
  • +
  • 9 CA Cert Filename — The File to load the certificate from
      +
    • Accepts a string
    • +
    +
  • +
  • 10 Client Cert Filename — The File to load the client certificate from
      +
    • Accepts a string
    • +
    +
  • +
  • 11 Client Key Filename — The File to load the client key from
      +
    • Accepts a string
    • +
    +
  • +
  • b Back
  • +
+

IoT Services: ThingSpeak MQTT

+

In the Settings menu, send an 12 to adjust settings for ThingSpeak MQTT

+
+ ThingSpeak MQTT Menu Options +
+ +
    +
  • 1 Enabled — Enable or Disable ThingSpeak MQTT
      +
    • Accepts a boolean value:
        +
      • 1 to enable
      • +
      • 0 to disable (default)
      • +
      +
    • +
    +
  • +
  • 2 Port — The MQTT broker port to connect to
      +
    • Accepts an unsigned integer:
        +
      • 8883 (default, as of firmware v01.00.04)
      • +
      +
    • +
    +
  • +
  • 3 Server — The MQTT server to connect to
      +
    • Accepts a string
    • +
    +
  • +
  • 4 MQTT Topic — The MQTT topic to publish to
      +
    • Accepts a string
    • +
    +
  • +
  • 5 Client Name — Name of this device used for MQTT Communications
      +
    • Accepts a string
    • +
    +
  • +
  • 6 Username — Username to connect to an MQTT broker, if required.
      +
    • Accepts a string
    • +
    +
  • +
  • 7 Password — Password to connect to an MQTT broker, if required.
      +
    • Accepts a string
    • +
    +
  • +
  • 8 Buffer Size — MQTT payload buffer size. If 0, the buffer size is dynamic
      +
    • Accepts an unsigned int16:
        +
      • 0 for dynamic buffer size (default)
      • +
      +
    • +
    +
  • +
  • 9 CA Cert Filename — The File to load the certificate from
      +
    • Accepts a string
    • +
    +
  • +
  • 10 Client Cert Filename — The File to load the client certificate from
      +
    • Accepts a string
    • +
    +
  • +
  • 11 Client Key Filename — The File to load the client key from
      +
    • Accepts a string
    • +
    +
  • +
  • 12 Channels — Comma separated list of =
      +
    • Accepts a string
    • +
    +
  • +
  • b Back
  • +
+

IoT Services: Azure IoT

+

In the Settings menu, send an 13 to adjust settings for the Azure IoT.

+
+ Azure IoT Menu Options +
+ +
    +
  • 1 Enabled — Enable or Disable Azure IoT
      +
    • Accepts a boolean value:
        +
      • 1 to enable
      • +
      • 0 to disable (default)
      • +
      +
    • +
    +
  • +
  • 2 Port — The MQTT broker port to connect to
      +
    • Accepts an unsigned integer:
        +
      • 8883 (default, as of firmware v01.00.04)
      • +
      +
    • +
    +
  • +
  • 3 Server — The MQTT server to connect to
      +
    • Accepts a string
    • +
    +
  • +
  • 4 MQTT Topic — The MQTT topic to publish to
      +
    • Accepts a string
    • +
    +
  • +
  • 5 Client Name — Name of this device used for MQTT Communications
      +
    • Accepts a string
    • +
    +
  • +
  • 6 Username — Username to connect to an MQTT broker, if required.
      +
    • Accepts a string
    • +
    +
  • +
  • 7 Password — Password to connect to an MQTT broker, if required.
      +
    • Accepts a string
    • +
    +
  • +
  • 8 Buffer Size — MQTT payload buffer size. If 0, the buffer size is dynamic
      +
    • Accepts an unsigned int16:
        +
      • 0 for dynamic buffer size (default)
      • +
      +
    • +
    +
  • +
  • 9 CA Cert Filename — The File to load the certificate from
      +
    • Accepts a string
    • +
    +
  • +
  • 10 Client Cert Filename — The File to load the client certificate from
      +
    • Accepts a string
    • +
    +
  • +
  • 11 Client Key Filename — The File to load the client key from
      +
    • Accepts a string
    • +
    +
  • +
  • 11 Device ID — The device id for the Azure IoT device
      +
    • Accepts a string
    • +
    +
  • +
  • 12 Device Key — The device key for the Azure IoT device
      +
    • Accepts a string
    • +
    +
  • +
  • b Back
  • +
+

IoT Services: HTTP IoT

+

In the Settings menu, send an 14 to adjust settings for the Azure IoT.

+
+ HTTP IoT Menu Options +
+ +
    +
  • 1 Enabled — Enable or Disable the HTTP Client
      +
    • Accepts a boolean value:
        +
      • 1 to enable
      • +
      • 0 to disable (default)
      • +
      +
    • +
    +
  • +
  • 2 URL — The URL to call with log information
      +
    • Accepts a string
    • +
    +
  • +
  • 3 CA Cert Filename — The File to load the certificate from
      +
    • Accepts a string
    • +
    +
  • +
  • b Back
  • +
+

IoT Services: MachineChat

+

In the Settings menu, send an 15 to adjust settings for MachineChat.

+
+ Machine Chat Menu Options +
+ +
    +
  • 1 Enabled — Enable or Disable the HTTP Client
      +
    • Accepts a boolean value:
        +
      • 1 to enable
      • +
      • 0 to disable (default)
      • +
      +
    • +
    +
  • +
  • 2 URL — The URL to call with log information
      +
    • Accepts a string
    • +
    +
  • +
  • 3 CA Cert Filename — The File to load the certificate from
      +
    • Accepts a string
    • +
    +
  • +
  • b Back
  • +
+

IoT Services: Arduino Cloud

+
+

Arduino

+

At the time of writing, Arduino's IoT service was referred to as the "Arduino IoT Cloud." Arduino updated the service with a different UI and is now referring to the service as the "Arduino Cloud"." When referencing the Arduino IoT or Arduino IoT Cloud in this tutorial, we are referring to the Arduino Cloud.

+
+

In the Settings menu, send an 16 to adjust settings for Arduino Cloud. This feature was added as of firmware v01.01.01.

+
+ Arduino Cloud Menu Options +
+ +
    +
  • 1 Enabled — Enable or Disable the Arduino IoT Client
      +
    • Accepts a boolean value:
        +
      • 1 to enable
      • +
      • 0 to disable (default)
      • +
      +
    • +
    +
  • +
  • 2 Thing Name — The Thing Name to use for the IoT Device connection
      +
    • Accepts a string
    • +
    +
  • +
  • 3 Thing ID — The Thing ID to use for the IoT Device connection
      +
    • Accepts a string
    • +
    +
  • +
  • 4 API Client ID — The Arduino Cloud API Client ID
      +
    • Accepts a string
    • +
    +
  • +
  • 5 API Secret — The Arduino Cloud API Secret
      +
    • Accepts a string
    • +
    +
  • +
  • 6 Device Secret — The Arduino IoT Device Secret
      +
    • Accepts a string
    • +
    +
  • +
  • 7 Device ID — The Arduino IoT Cloud Device ID
      +
    • Accepts a string
    • +
    +
  • +
  • b Back
  • +
+

IoT Web Server

+

As of firmware v01.02.00, log files can be viewed and downloaded using the IoT Web Server feature if mDNS (multicast DNS) is supported on your network. This functionality is accessed via the Settings Menu, Type 17 to enter the System Update menu. Once this menu entry is selected, the following menu options are presented:

+
+ IoT Web Server Options +
+ +
    +
  • 1 Enabled — Enabled or Disable the Web Server
      +
    • Accepts a boolean value
        +
      • 1 to enable
      • +
      • 0 to disable (default)
      • +
      +
    • +
    +
  • +
  • 2 Username — Web access control. Leave empty to disable authentication
      +
    • Accepts a string
    • +
    +
  • +
  • 3 Password — Web access control.
      +
    • Accepts a string
    • +
    +
  • +
  • 4 mDNS Support — Enable a name for the web address this device
      +
    • Accepts a boolean value
        +
      • 1 to enable
      • +
      • 0 to disable (default)
      • +
      +
    • +
    +
  • +
  • 5 mDNS Name — mDNS Name used for this device address
      +
    • Accepts a string
        +
      • dataloggerXXXXX, where XXXXX is the taken from the last 5x characters from your DataLogger IoT's board ID (default)
      • +
      +
    • +
    +
  • +
  • b Back
  • +
+
+

Note

+

You will need to make sure that the ESP32 is on the same network as your computer in order to access the log files.

+
+
+

Note

+

When authentication is enabled, some browsers might require a second login depending on user settings.

+
+
+

Note

+

The SparkFun Datalogger IoT requires restarting if the web interface is enabled.

+
+

For more information on how to use this feature, check out the section on viewing and downloading log files using the IoT web server.

+ + +

Advanced: System Update

+

New sensors and features are being added all the time and we've made it really easy for you to keep your DataLogger IoT up to date. The System Update option provides the following functionality to the end user:

+
    +
  • Restart the device
  • +
  • Performing a Factory Reset on the device
  • +
  • Updated the device firmware from a file on an SD Card.
  • +
+
+

Note

+

What's going on here?!? This tutorial was updated for firmware version 01.02.00!!! You will notice this menu option has changed to 18 !!!

+
+

This functionality is accessed via the Settings Menu, which is required to use this capability. Type 18 to enter the System Update menu. Once this menu entry is selected, the following menu options are presented:

+
+ System Update Menu Options +
+ +
    +
  • 1 Device Restart — Restart/reboot the device
      +
    • Accepts the following values:
        +
      • Y or Y to restart or reboot the device using the current firmware and system preferences
      • +
      • N or n to cancel
      • +
      +
    • +
    +
  • +
  • 2 Factory Reset — Erase all settings and revert to original firmware
      +
    • Accepts the following values:
        +
      • Y or Y to factory reset the device
      • +
      • N or n to cancel
      • +
      +
    • +
    +
  • +
  • 3 Update Firmware - SD Card — Update the firmware from the SD card
      +
    • Accepts firmware in the /root directory of the microSD card with the file naming pattern SparkFunDataLoggerIoT*.bin, where the asterisk * is the firmware version number (i.e. SparkFunDataLoggerIoT_01.00.01.bin).
    • +
    +
  • +
  • 4 Update Firmware - OTA — Update the firmware over-the-air
      +
    • Connects to a server and searches for the latest firmware that is available. Note that you must be connected to a WiFi network to be able to update the board over-the-air.
    • +
    • Accepts the following values if there is new firmware available.
        +
      • Y or Y to update over-the-air
      • +
      • N or n to cancel
      • +
      +
    • +
    +
  • +
  • b Back
  • +
+

When finished, you will need to exit the menus so that the DataLogger IoT saves the changes. Send a b to exit out this menu, b to exit out of the DataLogger IoT settings, and x to exit out of the main menu.

+
+ Output Save Settings Confirmation +
+ +

For more information on how to update firmware manually or over-the-air, check out the section under Examples: Updating Firmware.

+ + +

Device Settings

+

In the Main Menu, send a 2 through the serial terminal to adjust the devices settings.

+
+ Device Settings Menu Options +
+ +

This will bring up the connected devices that are currently available. You can configure each device and enable/disable each output. Below is a sample of the on-board devices available for the DataLogger IoT - 9DoF when only the MAX17048, ISM330, and MMC5983 are connected. As the DataLogger IoT - 9DoF initializes, the board will populate additional devices in this window if they are detected. Your mileage will vary depending on what is connected. On the DataLogger IoT you will not see the ISM330 or MMC5983 as an option since the 6DoF IMU and magnetometer are not populated on that version of the board.

+
    +
  • 1 MAX17048 — MAX17048 LiPo Battery Fuel Gauge
      +
    • 1 Voltage (V) — Battery voltage (Volts)
        +
      • 1 to enable Voltage (V) (default)
      • +
      • 2 to disable Voltage (V)
      • +
      +
    • +
    • 2 State of Charge (%) — Battery state of charge (%)
        +
      • 1 to enable state of charge (%) (default)
      • +
      • 2 to disable state of charge (%)
      • +
      +
    • +
    • 3 Charge Rate (%/hr) — Battery charge change rate (%/hr)
        +
      • 1 to enable change rate (%/hr) (default)
      • +
      • 2 to disable change rate (%/hr)
      • +
      +
    • +
    +
  • +
  • 2 ISM330 — ISM330 Inertial Measurement Unit
      +
    • 1 Accel Data Rate (HZ) — Accelerometer Data Rate (Hz)
        +
      • 1 for Off
      • +
      • 2 for 12.5 Hz
      • +
      • 3 for 26 Hz
      • +
      • 4 for 52 Hz
      • +
      • 5 for 104 Hz (default)
      • +
      • 6 for 208 Hz
      • +
      • 7 for 416 Hz
      • +
      • 8 for 833 Hz
      • +
      • 9 for 1666 Hz
      • +
      • 10 for 3332 Hz
      • +
      • 11 for 6667 Hz
      • +
      • 12 for 1.6 Hz
      • +
      +
    • +
    • 2 Accel Full Scale (g) — Accelerometer Full Scall (g)
        +
      • 1 for 2 g
      • +
      • 2 for 16 g
      • +
      • 3 for 4 g (default)
      • +
      • 4 for 8 g
      • +
      +
    • +
    • 3 Gyro Data Rate (Hz) — Gyro Data Rate (Hz)
        +
      • 1 for Off
      • +
      • 2 for 12.5 Hz
      • +
      • 3 for 26 Hz
      • +
      • 4 for 52 Hz
      • +
      • 5 for 104 Hz (default)
      • +
      • 6 for 208 Hz
      • +
      • 7 for 416 Hz
      • +
      • 8 for 833 Hz
      • +
      • 9 for 1666 Hz
      • +
      • 10 for 3332 Hz
      • +
      • 11 for 6667 Hz
      • +
      +
    • +
    • 4 Gyro Full Scale (dps) — Gyro Full Scale (dps)
        +
      • 1 for 125 dps
      • +
      • 2 for 250 dps
      • +
      • 3 for 500 dps (default)
      • +
      • 4 for 1000 dps
      • +
      • 5 for 2000 dps
      • +
      • 6 for 4000 dps
      • +
      +
    • +
    • 5 Accel Filter LP2 — Accelerometer Filter LP2
        +
      • 1 to enable (default)
      • +
      • 2 to disable
      • +
      +
    • +
    • 6 Gyro Filter LP1 — Gyro Filter LP1
        +
      • 1 to enable (default)
      • +
      • 2 to disable
      • +
      +
    • +
    • 7 Accel Slope Filter — Accelerometer Slope Filter
        +
      • 1 for ODR/4
      • +
      • 2 for ODR/10
      • +
      • 3 for for ODR/20
      • +
      • 4 for ODR/45
      • +
      • 5 for ODR/100 (default)
      • +
      • 6 for ODR/200
      • +
      • 7 for ODR/400
      • +
      • 8 for ODR/800
      • +
      +
    • +
    • 8 Gyro LP1 Filter Bandwidth — Gyro LP1 Filter Bandwidth
        +
      • 1 Ultra Light
      • +
      • 2 Very Light
      • +
      • 3 Light
      • +
      • 4 Medium (default)
      • +
      • 5 Strong
      • +
      • 6 Very Strong
      • +
      • 7 Aggressive
      • +
      • 8 Extreme
      • +
      +
    • +
    • 9 Accel X (milli-g) — Accelerometer X (milli-g)
        +
      • 1 to enable
      • +
      • 2 to disable
      • +
      +
    • +
    • 10 Accel Y (milli-g) — Accelerometer Y (milli-g)
        +
      • 1 to enable
      • +
      • 2 to disable
      • +
      +
    • +
    • 11 Accel Z (milli-g) — Accelerometer Z (milli-g)
        +
      • 1 to enable
      • +
      • 2 to disable
      • +
      +
    • +
    • 12 Gyro X (milli-dps) — Gyro X (milli-g)
        +
      • 1 to enable
      • +
      • 2 to disable
      • +
      +
    • +
    • 13 Gyro Y (milli-dps) — Gyro Y (milli-g)
        +
      • 1 to enable
      • +
      • 2 to disable
      • +
      +
    • +
    • 14 Gyro Z (milli-dps) — Gyro Z (milli-g)
        +
      • 1 to enable
      • +
      • 2 to disable
      • +
      +
    • +
    • 15 Temperature (C) — The temperature in degrees C
        +
      • 1 to enable
      • +
      • 2 to disable
      • +
      +
    • +
    +
  • +
  • 3 MMC5983 — MMC5983 Magnetometer
      +
    • 1 Filter Bandwidth (Hz) — The filter bandwidth in Hz
        +
      • 1 100 Hz (default)
      • +
      • 2 200 Hz
      • +
      • 3 400 Hz
      • +
      • 4 800 Hz
      • +
      +
    • +
    • 2 Auto-Reset — Auto-Reset
        +
      • 1 to enable
      • +
      • 2 to disable
      • +
      +
    • +
    • 3 X Field (Gauss) — The X Field strength in Gauss
        +
      • 1 to enable
      • +
      • 2 to disable
      • +
      +
    • +
    • 4 Y Field (Gauss) — The Y Field strength in Gauss
        +
      • 1 to enable
      • +
      • 2 to disable
      • +
      +
    • +
    • 5 Z Field (Gauss) — The Z Field strength in Gauss
        +
      • 1 to enable
      • +
      • 2 to disable
      • +
      +
    • +
    • 6 Temperature (C) — The ambient temperature in degrees C
        +
      • 1 to enable
      • +
      • 2 to disable
      • +
      +
    • +
    +
  • +
  • b Back
  • +
+

When finished, you will need to exit the menus so that the DataLogger IoT saves the changes. Send a b to exit out this menu, b to exit out of the DataLogger IoT settings, and x to exit out of the main menu.

+
+  Output Save Settings Confirmation +
+ +
+

Warning

+

As you connect additional devices to the DataLogger IoT, the values associated with each device in this menu will change! Make sure to check your device settings menu after additional devices are attached should you decide to configure the additional devices and enable/disable their outputs. +

+ Additional Devices Connected and Showing up as Menu Options +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/contribute/index.html b/contribute/index.html new file mode 100644 index 0000000..a5a684d --- /dev/null +++ b/contribute/index.html @@ -0,0 +1,1899 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Contribute - SparkFun DataLogger IoT Hookup Guide + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + + + + + + + + + + +
+ +
+ + + + +
+
+ + + +
+
+
+ + + + + + + + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +

Contribute: Help Fix our Mistake!

+

Spot something wrong? Feel free to contribute our documentation.

+

Improve our Documentation

+

All of this documentation can be modified by you! Please help us make it better.

+ +

Submit a Correction

+
    +
  1. Fork this repo
  2. +
  3. Add your corrections or improvements to the markdown file
  4. +
  5. File a pull request with your changes, and enjoy making the words worlds world a better place.
      +
    • Once received, the documentation specialist will automatically be notified.
    • +
    • We will review your suggested improvements to make sure they are correct and fit within our documentation standards.
    • +
    +
  6. +
+

Improve our Hardware Design

+

This hardware design is open-source! Please help us make it better.

+ +

Submit a Design Improvement

+
    +
  1. Fork this repo
  2. +
  3. Add your design improvements
  4. +
  5. File a pull request with your changes, and enjoy making the words worlds world a better place.
      +
    • Once received, the engineer in charge of the original design will automatically be notified.
    • +
    • We will review your suggested improvements, if they are within our board design standards and meet our product design requirements, we will flag these changes for our next board revision. (Please note, that even if your suggestion is accepted, these changes may not be immediate. We may have to cycle through our current product inventory first.)
    • +
    +
  6. +
+

Contributors

+

Let's provided some recognition to the contributors for this project!

+

GitHub Contributors Image +
+

+ + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/css/timeago.css b/css/timeago.css new file mode 100644 index 0000000..f7ab7d6 --- /dev/null +++ b/css/timeago.css @@ -0,0 +1,15 @@ +/* + timeago output is dynamic, which breaks when you print a page. + + This CSS is only included when type: timeago + and ensures fallback to type "iso_date" when printing. + + */ + +.git-revision-date-localized-plugin-iso_date { display: none } + +@media print { + .git-revision-date-localized-plugin-iso_date { display: inline } + .git-revision-date-localized-plugin-timeago { display: none } +} + diff --git a/example_CSV_to_spreadsheet/index.html b/example_CSV_to_spreadsheet/index.html new file mode 100644 index 0000000..48065f1 --- /dev/null +++ b/example_CSV_to_spreadsheet/index.html @@ -0,0 +1,1960 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + How to Convert Comma Separated Values (CSV) to a Spreadsheet - SparkFun DataLogger IoT Hookup Guide + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + + + + + + + + + + +
+ +
+ + + + +
+
+ + + +
+
+
+ + + + + + + + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+ + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +

How to Convert Comma Separated Values (CSV) to a Spreadsheet

+ +

The DataLogger IoT is great at displaying real time data with an IoT service whenever there is an Internet connection available. For those that want to use the DataLogger IoT without a WiFi connection and/or you just want to save data to a microSD card, you can import the comma separated values (CSV) from the text file into a spreadsheet to graph the values.

+

There are a few spreadsheet programs available that can take text files with CSV but for the scope of this tutorial, we will be using Google Sheets™ to convert the CSV output to a graph.

+
+ Google Sheets Logo +
+ Image Courtesy of Google Sheets. +
+ +
+

Note

+

Google and Google Sheets are trademarks of Google LLC. This tutorial is not endorsed by or affiliated with Google in any way. We just thought it was a sweet tool to visualize all the data that was collected by your snazzy DataLogger IoT. 😉

+
+

Log Some Data!

+

At this point, we will assume that you have configured connected your devices to the DataLogger IoT and configured its settings. Insert the microSD card into it's socket. Power the DataLogger IoT up and start logging some data! In this case, we were using the DataLogger IoT using the Qwiic Environmental Combo Breakout - ENS160/BME280. of course, you could have other compatible Qwiic-enabled devices connected depending on your setup. For simplicity, a WiFi connection was used to synchronize the clock to the NTP server and a computer's USB port was used to power everything.

+
+

Tip

+

For users without an Internet connection to sync the clock to the NTP server, you may want to consider using a compatible Qwiic-enabled device like the Qwiic Real Time Clock (RTC) Module - RV-8803 or a Qwiic-enabled u-blox GNSS module. Note that you will need to configure the time to your area before logging any data. U-blox GNSS modules would also need to be able to view a few satellites before being able to synchronize to the UTC.

+
+
+

Note

+

For users that require a timestamp with their datasets, make sure to enable timestamp.

+
+

Download the Log Files

+

Users can download the log files to your computer with the IoT Web Server. You will need to update firmware to v01.02.00 and enable this feature. For more information, check out the previous example to view and download log files using the IoT web server.

+ + +

Of course, users can follow the old school method and manually grab the files using a microSD card reader. When ready, remove power from the DataLogger IoT and eject the microSD card from the socket. Insert the microSD card into an adapter and connect to your computer.

+
+ + Memory Card Adapter and USB Reader for microSD cards +
+ +

Importing CSV to a Spreadsheet

+

Log into your Google account and open Google Sheets to create a new spreadsheet.

+ + +

Head to the menu and select: File > Import.

+
+ Importing Google Sheets +
+ +

A window will pop up with some options to import a file. Click the Upload tab. Click on the Browse button to choose the file. Or drag and drop the file into the upload area. In this case, the DataLogger IoT saved the comma separated values to a text file called sfe0003.txt.

+
+ Select CSV File +
+ +
+

Note

+

Not seeing any data in the file or even a text file saved in the root directory? Make sure that the microSD card is formatted correctly and the DataLogger is configured properly. In the menu, make sure to have the SD Card Format enabled and set to the correct format. In this case, we are using the default CSV format.

+
+

Another window will pop up asking how to import the file. From the drop down menu, select: Import location > Create new spreadsheet and Separator Type > Detect automatically. Since the file will include commas to separate each reading, Google Sheets should automatically separate text and values into each cell. Otherwise, you can select comma as the separator type.

+
+ Separator Type +
+ +
+

Note

+

If you have the file open, you can also manually paste the CSV data into the spreadsheet. Select all the contents of the text file and copy the contents onto your clipboard. Right click the cell closest to the top and farthest to the left of the spreadsheet (i.e. A1). Then paste the data. One caveat is that Google Sheets may have problems where it only pastes the title of each column.

+

+ Titles of Each Header Rows Only Pasted +

+

If you see this happen, you will need to select all but the header row from the text file. Then copy the contents onto your clipboard again. Right click on the next row the titles (i.e. A2) and paste the data.

+

+ Manual Paste +

+
+
+

Tip

+

To separate the values to each column, highlight everything in the column. Then head to the menu and select: Data > Split text into columns

+

+ Splitting Text to Columns +

+
+

Graphing Your Datasets

+

Hold down the Shift button on your keyboard and select the columns that you would like to graph using your mouse. Once the data is highlighted, head to the menu and select: Insert > Chart.

+
+ Select Datasets to Graph and then select Insert > Chart from menu +
+ +

The values that were selected will be graphed. You will want to be careful about including too many datasets on the graph as it can be hard to read when they are not in the same range.

+
+ Unformatted Chart +
+ +

At this point, try formatting the data based on your preferences and export the graph. The graph below was formatted and exported to a PNG. Note that the values for the AQI were moved to the right of the graph for a better viewing since they were smaller than the datasets for TVOC and eCO2.

+
+ Exported and Formatted Chart +
+ +
+

Note

+

There are additional features to help format your data based on your personal preferences! Select the column that you would like to format. Then head to the menu: Format > Number. Select the format that you would like to apply to the dataset. In this case, we adjusted the General Time with Custom Date and Time to show a 12-hour format before creating a new graph.

+

+ Exported and Formatted Chart with 12-hour Format +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/example_arduino_iot_cloud/index.html b/example_arduino_iot_cloud/index.html new file mode 100644 index 0000000..b1a0bed --- /dev/null +++ b/example_arduino_iot_cloud/index.html @@ -0,0 +1,2298 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Arduino Cloud - SparkFun DataLogger IoT Hookup Guide + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + + + + + + + + + + +
+ +
+ + + + +
+
+ + + +
+
+
+ + + + + + + + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+ + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +
+

Arduino

+

At the time of writing, Arduino's IoT service was referred to as the "Arduino IoT Cloud." Arduino updated the service with a different UI and is now referring to the service as the "Arduino Cloud"." When referencing the Arduino IoT or Arduino IoT Cloud in this tutorial, we are referring to the Arduino Cloud.

+
+

Creating and Connecting to an Arduino Cloud Device

+

One of the key features of the SparkFun DataLogger IoT is it's simplified access to IoT service providers. This document outlines how an Arduino Cloud Device is used by the DataLogger IoT.

+

The following is covered by this document:

+
    +
  • Structure of the Arduino Cloud Devices
  • +
  • Device creation in the Arduino Cloud
  • +
  • Setup of the Arduino Driver
  • +
  • How data is posted from the DataLogger IoT to the Arduino Device
  • +
+

Currently, the Arduino Cloud device connection is a single direction - used to post data from the DataLogger IoT to an Arduino Cloud device.

+
+ Arduino Logo +
+ Image Courtesy of Arduino +
+ +
+

Note

+

To take advantage of the API's in Arduino Cloud, you will also need to have a service plan with your account.

+
+

General Operation

+

The Arduino Cloud enables connectivity between an IoT/Edge Arduino enabled device and the cloud. The edge device updates data in the Arduino Cloud by updating variables or parameters attached to a cloud device.

+

In the Arduino Cloud, the edge device is represented by a Device which has a virtual Thing attached/associated with it. The Thing acts as a container for a list of parameters or variables which represent the data values received from the edge device. As values on the edge device update, they are transmitted to the Arduino Cloud.

+

For a SparkFun DataLogger IoT connected to an Arduino Cloud device, the output parameters of a device connected to the DataLogger are mapped to variables within the Arduino Cloud Device's Thing using a simple pattern of DeviceName_ParameterName for the name of the variable in the Arduino Cloud.

+
+ Arduino Cloud Overview +
+ +

Creating a Device in Arduino Cloud

+

The first step connecting to the Arduino Cloud is setting up a device within the cloud. A device is a logical element that represents a physical device.

+

Log into your Arduino Cloud account.

+ + +

Click on the expand menu icon on the upper left (e.g. the three lines stacked like a "hamburger"; ☰) and select Devices. If your window is big enough, then it will show up on the navigation bar.

+
+ Select Devices +
+ +

This page lists your currently defined devices. If there are no defined devices, select the Add Device button. This will probably be at the bottom of the page. The location of this button will change once the page has a device (or if there is an update to Arduino's user interface).

+
+ Add a Device +
+ +

A device type selection dialog is then shown. Since we are connecting a DataLogger IoT board to the system, and not connected a known device, select DIY - Any Device to manually include the DataLogger IoT.

+
+ Select DIY Device +
+ +

Once selected, another dialog is presented. Just select Continue. At this point you can provide a name for your device. In this case we named it as "MyDataLoggerIoT."

+
+ Name Device +
+ +

The next screen is the critical step of the device creation process. This step is the one time the Device Secret Key is available. The provided Device ID and Device Secret Key values are needed to connect to the Arduino Cloud. Once this step is completed, the Secret Key is no longer available.

+
+ Device Secret +
+ +

The easiest way to capture these values is by downloading as a PDF file, which is offered on the setup page. Click on the download the PDF and save it to a safe location. When ready, click on the check box indicating that you have saved the values and select the Continue button.

+

Arduino Cloud API Keys

+

In addition to creating a device, to access the Arduino Cloud, the driver requires an API Key. This allows the DataLogger IoT's Arduino Cloud driver to access the web API of the Arduino Cloud. This API is used to setup the connection to the Arduino Cloud.

+

To create an API key, click on the menu bar to expand and select your Arduino account profile > Personal Settings.

+
+ API Keys +
+ +

This menu takes you to a list of existing API Keys. If you have not created one yet, the list will have nothing in it like the image below. From this page, select the CREATE API KEY button.

+
+ Create Key +
+ +
+

Note

+

Users will need a service plan in order to take advantage of the API.

+
+

In the presented dialog, enter a name for the API key. In this case, we named it "MyDataLoggerKey".

+
+ API Key Name +
+ +

Once the name is entered, click CONTINUE. A page with the new API key is presented. Like in Device Creation, this page contains a secret that is only available on this page during this process.

+
+ API KEY NOW +
+ +

Make note of the Client ID and Client Secret values on this page. The best method to capture these values is to download the PDF file offered on this page. Click on the download the PDF and save it to a safe location. When ready, click on the check box indicating that you have saved the values and select the DONE button.

+

At this point, the Arduino Cloud is setup for connection by the driver.

+

Arduino Cloud Configuration

+

To add an Arduino Cloud Device as a destination DataLogger IoT, the Arduino Cloud connection is enabled via the DataLogger menu system and the connection values, obtained from the Arduino Cloud (see above), are set in the connection properties.

+

The specifics for the Arduino Cloud must be configured. This includes the following:

+
    +
  • Thing Name
  • +
  • Thing ID
  • +
  • API Client ID
  • +
  • API Secret
  • +
  • Device Secret
  • +
  • Device ID
  • +
+
+

Note

+

The Thing Name does not necessarily need to be configured. However, there will be less confusion if you set this up before connecting the DataLogger IoT to the Cloud. The Thing ID will automatically be generated and saved once there is a connection available.

+
+

Thing Name

+

The name of the Arduino Cloud Thing to use. If the Thing doesn't exist on startup, the driver will create a Thing and be named "Untitled" if you do not provide a name.

+
+

Note

+

Note satisfied with the default "Untitled" as the Thing's name? You can rename the Thing Name after creating the Thing. Note that you will need to manually rename the Thing Name on the Arduino Cloud and DataLogger IoT.

+
+

Thing ID

+

This is the ID of the Thing being used. This value is obtained by the following methods:

+
    +
  • If the driver creates a new Thing, the ID is obtained and used.
  • +
  • If an existing Thing is connected to the DataLogger IoT, the driver retrieves it's ID.
  • +
+
+

Note

+

In this case, the driver cannot create any new variables until the system is restarted.

+
+
    +
  • The user creates a new Thing using the web interface of Arduino Cloud, and provides the Thing Name and Thing ID .
  • +
+

API Client ID and Secret

+

These values are used to provide API access by the driver. This access allows for the creation/use of a Thing and Variables within the Arduino Cloud. These are obtained via the steps outlined earlier in this document.

+

Device Secret and ID

+

These values are used to identify the Arduino device that is connected to. These are obtained via the steps outlined earlier in this document.

+

Setting Properties

+

The above property values must be set in the DataLogger's Arduino Cloud driver before use. They can be manually by using the menu system like the previous MQTT example.

+

For users that are interested in using the menu system, open a Serial Terminal, connect to the COM port that your DataLogger enumerated to, and set it to 115200 baud. In this case, we connected to COM13. Press any key to enter the Main Menu. Type 1 to enter the Settings menu. Then type 16 to enter the Arduino IoT Menu. When the menu system for the Arduino IoT is presented, you will need to configure the property values as listed in the JSON file. Saving the values through the menu system will save the credentials to the ESP32's persistent memory. The following options are displayed:

+
+ Arduino IoT Cloud Menu Options +
+ +

The alternative to using the menu system is a JSON file. These values can be set using a JSON file that is loaded by the system at startup. For the DataLogger Arduino Cloud example outlined in this document, the entries in the setting's JSON file are as follows:

+
"Arduino IoT": {
+    "Enabled": true,
+    "Thing Name": "SparkFunThing1",
+    "API Client ID": "MY_API_ID",
+    "API Secret": "MY_API_SECRET",
+    "Device Secret": "MY_DEVICE_SECRET",
+    "Device ID": "MY_DEVICE_ID"            
+  },
+
+

You will need to update the API Client ID, API Secret, Device Secret, and Device ID with the values that were obtained earlier. Don't forget to enable Arduino Cloud service by setting the value to true. If the JSON file is saved in the microSD card, you will need to load the credentials to the DataLogger IoT.

+
+

Tip

+

To load the values by the system at startup using a JSON file and microSD card, you will need to configure the Save Settings. This JSON file will be created with the "Save to Fallback" option. Make sure to enable the Arduino IoT as well.

+
+

Operation

+

On startup, when the first values are written from the DataLogger IoT, the connection to the Arduino Cloud is made. During this connection, the system connects to the specified Thing and variables are mapped between the DataLogger Device values and Arduino Cloud Variables. If needed, variables can be created manually in the cloud.

+

While this initial setup takes seconds to complete, updates to the values on the Arduino Cloud are rapid as soon as there is a connection available.

+

Viewing Values

+

Once the DataLogger IoT device is configured and running, updates in Arduino Cloud are listed in the Things tab of the Arduino Cloud page. Clicking the target Thing provides access to the current variable values that are connected to the DataLogger IoT. Your mileage may vary depending on the compatible device that is connected to the DataLogger IoT. In this case, we were able to see the built-in sensors that were connected on the DataLogger IoT - 9DoF.

+
+ Cloud Variables +
+ +
+

Note

+

Not seeing certain variables on your list? Check your connections to make sure that the compatible device is connected to the DataLogger IoT. You may also have certain outputs disabled (like the connected sensors or timestamp).

+
+
+

Note

+

Having problems connecting new variables with the DataLogger IoT? When swapping out compatible Qwiic enabled devices, you may need to delete previous cloud variables so that the DataLogger IoT is able re-initialize them on the next power cycle.

+
+

Create a Dashboard

+

With the data now available in the Arduino Cloud as variables, it is a simple step create a dashboard that plots the data values.

+

The general steps to create a simple dashboard include:

+
    +
  • Select the Dashboards section of the Arduino Cloud.
  • +
  • Select the Build Dashboard button. If you have a dashboard already built, the location of the button will change and the button will be renamed: Create.
  • +
  • Click the edit button (i.e. the icon that looks like a paper and pencil, this is next to the eye).
  • +
  • Add an element to the dashboard -- for this example select ADD ^ > Advanced Chart.
  • +
  • On the Chart's Widget Settings select Link Variables to add readings.
  • +
  • The DataLogger IoT Variables are listed - select the variable to link.
  • +
  • Continue this step until all the desired variables are linked to the chart. You can select up to 5x variables at a time. Click on the Link Variables button after selecting the variables.
  • +
  • This will bring you back to the Chart's Widget Settings window. Configure any preferences that to display (i.e. variable colors, labels, etc.). When all variables are linked and the Chart Widget Settings is configured, select Done.
  • +
+
+ Linked Variables +
+ +

The created dashboard then displays the values posted from the SparkFun DataLogger IoT. You can continue adding additional readings on the dashboard that you were not able to fit on graph or even rename the Dashboard view. In this case, we displayed accelerometer values and temperature in degrees Celsius from the DataLogger IoT - 9DoF.

+
+ DataLogger IoT - 9DoF Dashboard +
+ +
+

Note

+

Not seeing any values on the LIVE view? Try clicking on the other time periods to see the values posted.

+
+

Using compatible Qwiic enabled devices, you can also display additional readings that are not available with the built-in sensors. In this case, we were able to display humidity, temperature in degrees Fahrenheit, equivalent CO2, TVOC, and AQI with the DataLogger IoT and Environmental Combo Breakout (ENS160/BME280).

+
+ DataLogger IoT with Qwiic Environmental Combo Breakout (ENS160/BME280) Dashboard +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/example_aws/index.html b/example_aws/index.html new file mode 100644 index 0000000..1f11ec2 --- /dev/null +++ b/example_aws/index.html @@ -0,0 +1,2298 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + AWS - SparkFun DataLogger IoT Hookup Guide + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + + + + + + + + + + +
+ +
+ + + + +
+
+ + + +
+
+
+ + + + + + + + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+ + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +

Creating and Connecting to an AWS IoT Device (Thing)

+

One of the key features of the DataLogger IoT is it's simplified access to IoT service providers. This document outlines how an AWS IoT device is used by the DataLogger IoT.

+
+ Powered by AWS IoT +
+ Image Courtesy of Amazon Web Services (AWS) +
+ +

The following is covered by this document:

+
    +
  • Device (Thing) creation in AWS
  • +
  • Securely connecting the device
  • +
  • How data is posted from the DataLogger IoT to the AWS Device via it's Shadow
  • +
+

Currently, the AWS IoT device connection is a single direction - used to post data from the hardware to the IoT AWS Device via the AWS IoT devices shadow. Configuration information from AWS IoT to the DataLogger IoT is currently not implemented.

+

General Operation

+

AWS IoT enables connectivity between an IoT / Edge device and the AWS Cloud Platform, implementing secure endpoints and device models within the AWs infrastructure. This infrastructure allows edge devices to post updates, status and state to the AWS infrastructure for analytics, monitoring and reporting.

+

In AWS IoT, an virtual representation of an actual device is created and referred to as a Thing. The virtual device/Thing is allocated a connection endpoint, security certificates and a device shadow - a JSON document used to persist, communicate and manage device state within AWS.

+

The actual IoT device communicates with it's AWS representation via a secure MQTT connection, posting JSON document payloads to a set of pre-defined topics. Updates are posted to the AWS IoT device shadow, which is then accessed within AWS for further process as defined by the users particular cloud implementation.

+
+ MQTT Menu +
+ +

Creating a Device in AWS IoT

+

The following discussion outlines the basic steps taken to create a Thing in AWS IoT that the DataLogger IoT can connect to. First step is to log into your AWS account and create a thing.

+ + +

Once logged into your AWS account, select IoT Core from the menu of services.

+
+ AWS IoT Core +
+ +

From the IoT Core console page, under the Manage section, select All Devices > Things

+

On the resultant Things Page, select the Create Things button.

+
+ AWS IoT Thing Create +
+ +

AWS IoT will then take you through the steps to create a device. Selections made for a demo Thing are:

+
    +
  • Create single thing
  • +
  • Thing Properties
  • +
  • Enter a name for your thing - for this example TestThing23
  • +
  • Device Shadow - select Unnamed shadow (classic)
  • +
  • Auto-generate a new certificate
  • +
  • Attach policies to certificate - This is discussed later in this document
  • +
  • Select Create thing
  • +
+

Upon creation, AWS IoT presents you with a list of downloadable certificates and keys. Some of these are only available at this step. The best option is to download everything presented - three of these are used by the DataLogger IoT. The following should be downloaded:

+
    +
  • Device Certificate
  • +
  • Public Key File
  • +
  • Private Key File
  • +
  • Root CA certificates - (for example: Amazon Root CA 1 )
  • +
+

At this point, the new AWS IoT thing is created and listed on the AWS IoT Things Console

+
+ New Thing Listed +
+ +

Security Policy

+

To write to the IoT device, a security policy that enables this is needed, and the policy needs to be assigned to the devices certificate.

+

To create a Policy, select the Manage > Security > Policies menu item from the left side menu of the AWS IoT panel. Once on this page, select the Create policy button to create a new policy.

+
+ New Policy +
+ +

When entering the policy, provide a name that fits your need. For this example, the name NewThing23Policy is used. For the Policy document, you can manually enter the security entires, or enter them as a JSON document. The JSON document used for this example is:

+
{
+  "Version": "2012-10-17",
+  "Statement": [
+    {
+      "Effect": "Allow",
+      "Action": "iot:Connect",
+      "Resource": "*"
+    },
+    {
+      "Effect": "Allow",
+      "Action": "iot:Subscribe",
+      "Resource": "*"
+    },
+    {
+      "Effect": "Allow",
+      "Action": "iot:Receive",
+      "Resource": "*"
+    },
+    {
+      "Effect": "Allow",
+      "Action": "iot:Publish",
+      "Resource": "*"
+    },
+    {
+      "Effect": "Allow",
+      "Action": "iot:GetThingShadow",
+      "Resource": "*"
+    },
+    {
+      "Effect": "Allow",
+      "Action": "iot:UpdateThingShadow",
+      "Resource": "*"
+    }
+  ]
+}
+
+
+ Create Policy +
+ +

Once the policy is created, go back to the IoT Device/Thing created above and associate this policy to the device Certificate.

+
    +
  • Go to your device Manage > All devices > Things
  • +
  • Select the device - TestThing23 for this example
  • +
  • Select the Certificates tab
  • +
  • Select the listed Certificate (it's a very long hex number)
  • +
  • At the bottom right of the page, select the Attach policies button and select the Policy created above.
  • +
+
+ Attach Policy +
+ +

At this point, AWS IoT is ready for a device to connect and receive data.

+

AWS Configuration

+

The specifics for the AWS IoT Thing must be configured. This includes the following:

+
    +
  • Server name/host
  • +
  • MQTT topic to update
  • +
  • Client Name - The AWS IoT Thing Name
  • +
  • CA Certificate Chain
  • +
  • Client Certificate
  • +
  • Client Key
  • +
+

Server Name/Hostname

+

This value is obtained from the AWS IoT Device page for the created device. When on this page, select the Device Shadows tab, and then select the Classic Shadow shadow, which is listed. Note a secure connection is used, so the port for the connection is 8883.

+
+ Shadow Details +
+ +

Selecting the Classic Shadow entry provides the Server Name/Hostname for the device, as well as the MQTT topic for this device.

+
+ Shadow Details +
+ +
+

Note

+

The server name is obtained from the Device Shadow URL entry

+
+

MQTT Topic

+

The MQTT topic value is based uses the MQTT topic prefix from above, and has the value update added to it. So for this example, the MQTT topic is:

+
$aws/things/TestThing23/shadow/update
+
+

Client Name

+

This is the AWS IoT name of the thing. For the provided example, the value is TestThing23

+

CA Certificate Chain

+

This value was downloaded as a file during the creation process. The contents of this file can be passed on to the DataLogger IoT by copying the file containing the data onto a devices SD Card and setting the filename property for the DataLogger IoT.

+

Client Certificate

+

This value was downloaded as a file during the creation process. The contents of this file can be passed on to the DataLogger IoT by copying the file containing the data onto a devices SD Card and setting the filename property for the DataLogger IoT.

+

Client Key

+

This value was downloaded as a file during the creation process. The contents of this file can be passed on to the DataLogger IoT by copying the file containing the data onto a devices SD Card and setting the filename property for the DataLogger IoT.

+

Setting Properties

+

The above property values must be set on the DataLogger before use. They can be set manually by using the menu system like the previous MQTT example.

+

For users that are interested in using the menu system, you will need to open a Serial Terminal, connect to the COM port that your DataLogger enumerated to, and set it to 115200 baud. In this case, we connected to COM13. Press any key to enter the Main Menu. Type 1 to enter the Settings menu. Then type 11 to enter the AWS IoT Menu. When the menu system for the AWS IoT connection is presented, you will need to configure the property values as listed in the JSON file. Saving the values through the menu system will save the credentials to the ESP32's persistent memory. The following options are displayed:

+
+ AWS IoT Menu +
+ +

The alternative to using the menu system is a JSON file. These values can be set using a JSON file that is loaded by the system at startup. For the DataLogger IoT example outlined in this document, the entries in the settings JSON file are as follows:

+
"AWS IoT": {
+    "Enabled": true,
+    "Port": 8883,
+    "Server": "avgpd2wdr5s6u-ats.iot.us-east-1.amazonaws.com",
+    "MQTT Topic": "$aws/things/TestThing23/shadow/update",
+    "Client Name": "TestThing23",
+    "Buffer Size": 0,
+    "Username": "",
+    "Password": "",
+    "CA Certificate": "",
+    "Client Certificate": "",
+    "Client Key": "",
+    "CA Cert Filename": "AmazonRootCA1.pem",
+    "Client Cert Filename": "TestThing23_DevCert.crt",
+    "Client Key Filename": "TestThing23_Private.key"
+  },
+
+

Besides updating the Server, MQTT Topic, Client Name, CA Cert Filename, Client Cert Filename, and Client Key Filename, you will need to also ensure that the port is set to 8883. The default in previous firmware versions was 1883. As of firmware v01.00.04, the default is 8883. You will need to adjust the port value to properly connect to the AWS IoT service. Don't forget to enable AWS IoT service by setting the value to true. If the JSON file is saved in the microSD card, you can load the credentials to the DataLogger IoT.

+
+

Tip

+

To load the values by the system at startup using a JSON file and microSD card, you will need to configure the Save Settings. This JSON file will be created with the "Save to Fallback" option. Make sure to enable the AWS IoT as well.

+
+

Operation

+

Once the device is configured and running, updates in AWS IoT are listed in the Activity tab of the devices page. For the test device in this document, this page looks like:

+
+ Shadow Activity +
+ +

Opening up an update, you can see the data being set to AWS IoT in a JSON format.

+
+ Shadow Data +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/example_azure/index.html b/example_azure/index.html new file mode 100644 index 0000000..407da74 --- /dev/null +++ b/example_azure/index.html @@ -0,0 +1,2307 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Azure - SparkFun DataLogger IoT Hookup Guide + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + + + + + + + + + + +
+ +
+ + + + +
+
+ + + +
+
+
+ + + + + + + + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+ + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +

Creating and Connecting to an Azure IoT Device

+

One of the key features of the DataLogger IoT is it's simplified access to IoT service providers. This document outlines how an Azure IoT device is used by the DataLogger IoT.

+
+ Microsoft Azure Logo +
+ Image Courtesy of Microsoft Azure +
+ +

The following is covered by this document:

+
    +
  • Device creation Azure
  • +
  • Securely connecting the device
  • +
  • How data is posted from the DataLogger IoT to the Azure Device
  • +
+

Currently, the Azure IoT device connection is a single direction - it is used to post data from the hardware to the Azure IoT Device. Configuration information from Azure IoT to the DataLogger IoT is currently not implemented.

+

General Operation

+

Azure IoT enables connectivity between an IoT / Edge device and the Azure Cloud Platform, implementing secure endpoints and device models within the Azure infrastructure. This infrastructure allows edge devices to post updates, status and state to the Azure infrastructure for analytics, monitoring and reporting.

+

In Azure IoT, an virtual representation of an actual device is created and referred to as a Device. The virtual device is allocated a connection endpoint, security certificates and a device digital twin - a JSON document used to persist, communicate and manage device state within Azure. Unlike AWS IoT, data from the device isn't posted to the devices digital twin (AWS Shadow), but to the device directly.

+

The actual IoT device communicates with it's Azure representation via a secure MQTT connection, posting JSON document payloads to a set of pre-defined topics. Updates are posted directly to the Azure device, which is then accessed within Azure for further process as defined by the users particular cloud implementation.

+
+ Azure IoT Overview +
+ +

Creating a Device in Azure IoT

+

The following discussion outlines the basic steps taken to create a Device in Azure IoT that the DataLogger IoT can connect to. First step is to log into your Azure account and create an IoT Hub for your device.

+ + +

Once logged into your Microsoft Azure account, select Internet of Things > IoT Hub from the menu of services.

+
+ Azure IoT Hub +
+ +

Create an IoT Hub

+

This IoT Hub page lists all the IoT hubs available for your account. To add a device, you need to create a new IoT Hub.

+

Follow the Hub Creation workflow - key settings used for a DataLogger demo device:

+
    +
  • Used the "Free Tier" for testing and development.
  • +
  • Networking
      +
    • Connectivity - Public Access
    • +
    • Minimum TLS Version - 1.0
    • +
    +
  • +
+

The remaining settings were set at their default values.

+

Create a Device

+

Once the IoT Hub is created, a Device needs to be created within the hub. The device represents the connection to the actual DataLogger IoT device.

+

To create a device, select the Device management > Devices from the IoT Hub menu and the select the + Add Device menu item

+
+ Azure IoT Device Create +
+ +

In the create device dialog:

+
    +
  • Enter a name for the device
  • +
  • Select an Authentication type of Symmetric key
  • +
  • Auto-generate keys enabled
  • +
+
+ Azure IoT Device Create Form +
+ +

Once created, the device is listed in the Devices list of the IoT Hub. Selecting the device gives you the device ID and keys used to communicate with the device. Note, when connecting to the device with the DataLogger IoT, the Primary Key value is used.

+
+ Azure IoT Device Details +
+ +

Azure Configuration

+

Once the DataLogger IoT is integrated into the application, the specifics for the Azure IoT Thing must be configured. This includes the following:

+
    +
  • Server Name/Hostname
  • +
  • Device Key
  • +
  • Device ID
  • +
  • CA Certificate Chain
  • +
+

Server Name/Hostname

+

This value is hostname of the created IoT Hub and is obtained from the Overview page of the IoT Hub. Note a secure connection is used, so the port for the connection is 8883.

+
+ Hub Details +
+ +

Device ID

+

The Device ID is obtained from the device detail page. This page is accessible via the Device listing page, which is accessed via the Device management > Devices menu item. The selected device of interest (TestDevice2023 for this example) provides the device ID and Primary Key.

+
+ Azure IoT Device Details +
+ +

Device Primary Key

+

This is obtained via the Device details page, as outlined in the previous section.

+
+

Note

+

You view and copy the key via the icons on the right of the key entry line.

+
+

Root Certificate Authority - CA file

+

The Certificate Authority file for Azure is downloaded from this page:

+ + +

The file to download is the Baltimore CyberTrust Root entry in the Root Certificate Authorities section of the page.

+
+ Azure Root CA +
+ +

Setting Properties

+

The above property values must be set on the DataLogger IoT before use. They can be set manually by using the menu system like the previous MQTT example.

+

For users that are interested in using the menu system, you will need to open a Serial Terminal, connect to the COM port that your DataLogger enumerated to, and set it to 115200 baud. In this case, we connected to COM13. Press any key to enter the Main Menu. Type 1 to enter the Settings menu. Then type 13 to enter the Azure IoT Menu. When the menu system for the Azure IoT connection is presented, you will need to configure the property values as listed in the JSON file. Saving the values through the menu system will save the credentials to the ESP32's persistent memory. The following options are displayed:

+
+ Azure IoT Menu +
+ +

The alternative to using the menu system is a JSON file. These values can be set using a JSON file that is loaded by the system at startup. For the example outlined in this document, the entries in the settings JSON file are as follows:

+
"Azure IoT": {
+    "Enabled": true,
+    "Port": 8883,
+    "Server": "sparkfun-datalogger-hub.azure-devices.net",
+    "MQTT Topic": "",
+    "Client Name": "",
+    "Buffer Size": 0,
+    "Username": "",
+    "Password": "",
+    "Device Key" : "My-Super-Secret-Device-Key",
+    "Device ID"  : "TestDevice2023",
+    "CA Cert Filename": "AzureRootCA.pem"
+  },
+
+

Besides updating the Server, Device Key, Device ID, and CA Cert Filename, you will need to also ensure that the port is set to 8883. The default in previous firmware versions was 1883. As of firmware v01.00.04, the default is 8883. You will need to adjust the port value to properly connect to the Azure IoT service. Don't forget to enable Azure IoT service by setting the value to true. If the JSON file is saved in the microSD card, you can load the credentials to the DataLogger IoT.

+
+

Tip

+

To load the values by the system at startup using a JSON file and microSD card, you will need to configure the Save Settings. This JSON file will be created with the "Save to Fallback" option. Make sure to enable the Azure IoT as well.

+
+

Operation and Monitoring

+

Once the DataLogger IoT device is configured and running, the Azure IoT capability in the DataLogger IoT posts messages via MQTT to the connected Azure Device via it's IoT Hub. Messages to the device are posted as Telemetry Data for the device.

+

The easiest method to view the Telemetry data being sent to an Azure Iot Device is via the Azure IoT Hub extension for the Visual Studio Code editor.

+
+ Azure IoT Hub Extension +
+ +

Once installed, and connected to Azure via the Azure Account extension, you can connect to the target IoT Hub, and monitor telemetry data for a IoT device.

+

Connect to Your Azure IoT Hub

+

On the Explorer panel of Visual Studio Code, click on the ... menu of the AZURE IOT HUB section. In the popup menu, select the Select IoT Hub menu entry.

+
+ Select IoT Hub +
+ +

The available IoT Hubs are displayed in the editors command prompt. Select the desired hub and press Enter (or click).

+
+ Select IoT Hub +
+ +

The hub is then displayed in the AZURE IOT HUB section of the editor Explorer. Expanding the Devices section of the Hub will list the example device created above.

+
+ Select IoT Hub +
+ +

Monitoring

+

To monitor the telemetry data send to a device, right click on the device, TestDevice2023 in this example, select the menu entry Start Monitoring Build-in Event Endpoint.

+
+ Start Monitoring +
+ +

Once selected, the editor output console will start displaying output for the selected device. For the above example, with a device that has environmental sensors attached, the output appears as follows:

+
+ Monitor Output +
+ +

To stop monitoring, click the Stop Monitoring build-in event endpoint item that is displayed in the status bar of the editor.

+
+ Stop Monitoring +
+ +

A menu option to stop monitoring is also available from the ... menu of the AZURE IOT HUB section in the editor Explorer panel.

+ + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/example_http/index.html b/example_http/index.html new file mode 100644 index 0000000..637d4b6 --- /dev/null +++ b/example_http/index.html @@ -0,0 +1,2167 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + HTTP - SparkFun DataLogger IoT Hookup Guide + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + + + + + + + + + + +
+ +
+ + + + +
+
+ + + +
+
+
+ + + + + + + + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+ + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +

Connecting and Sending Output to an HTTP Server

+

One of the key features of the DataLogger IoT is it's simplified access to IoT service providers and servers. This document outlines how output from a DataLogger IoT device is sent to an HTTP server.

+

The following is covered by this document:

+
    +
  • Overview of the HTTP connection
  • +
  • How a user configures and uses the HTTP connection
  • +
  • Use examples
  • +
+

General Operation

+

HTTP connectivity allows data generated by the DataLogger IoT to be sent to an HTTP server. An HTTP endpoint is provided to the HTTP action within the DataLogger IoT, and when data is output, a JSON representation of the data is published to the endpoint via an HTTP POST operation. The body of the POST operation contains the a JSON document that encapsulates the sent DataLogger IoT data.

+
+ HTTP Overview +
+ +

Data Structure

+

Data is sent to the HTTP server as a JSON object, which contains a collection of sub-object. Each sub-object represents a data source in the sensor, and contains the current readings from that source.

+

The following is an example of the data posted - note, this representation was "pretty printed" for readability.

+
{
+  "MAX17048": {
+    "Voltage (V)": 4.304999828,
+    "State Of Charge (%)": 115.0625,
+    "Change Rate (%/hr)": 0
+  },
+  "CCS811": {
+    "CO2": 620,
+    "VOC": 33
+  },
+  "BME280": {
+    "Humidity": 25.03613281,
+    "TemperatureF": 79.64599609,
+    "TemperatureC": 26.46999931,
+    "Pressure": 85280.23438,
+    "AltitudeM": 1430.44104,
+    "AltitudeF": 4693.04834
+  },
+  "ISM330": {
+    "Accel X (milli-g)": -53.31399918,
+    "Accel Y (milli-g)": -34.03800201,
+    "Accel Z (milli-g)": 1017.236023,
+    "Gyro X (milli-dps)": 542.5,
+    "Gyro Y (milli-dps)": -1120,
+    "Gyro Z (milli-dps)": 262.5,
+    "Temperature (C)": 26
+  },
+  "MMC5983": {
+    "X Field (Gauss)": -0.200622559,
+    "Y Field (Gauss)": 0.076416016,
+    "Z Field (Gauss)": 0.447570801,
+    "Temperature (C)": 29
+  }
+}
+
+

HTTP Connection Setup

+

To connect to an HTTP server endpoint, the following information is needed:

+
    +
  • The URL of the endpoint
  • +
  • The SSL certificate for the target server, if the connection is secure (HTTPS)
  • +
+

These values are set using the standard DataLogger methods - the interactive menu system, or a JSON file.

+ +

For users that are interested in using the menu system, you will need to open a Serial Terminal, connect to the COM port that your DataLogger enumerated to, and set it to 115200 baud. In this case, we connected to COM13. Press any key to enter the Main Menu. Type 1 to enter the Settings menu. Then type 14 to enter the HTTP IoT Menu. When the menu system for the HTTP IoT connection is presented, you will need to configure the property values as listed in the JSON file. Saving the values through the menu system will save the credentials to the ESP32's persistent memory. The following options are displayed:

+
+ HTTP Menu +
+ +

The options are:

+
    +
  • Enable/Disable the connection
  • +
  • Set the URL for the endpoint
  • +
  • Set the name of the CA Cert file for a secure connection (HTTP)
  • +
+

To set the HTTP URL/endpoint - select two (2) in the menu, and enter the URL. For this example, we'll enter: http://mysparkfunexample.com:8091 .

+
+ Enter a URL +
+ +

In the above example, the URL/HTTP Endpoint is on a server called mysparkfunexample.com, on port 8091. Once set, the system will post data to this URL.

+

If the endpoint is a secure ssl (HTTPS) connection, the certificate for the server is required. Because of the size of the certificates, the value is provided as a file that is loaded into the system by the attached SD card.

+
+ Cert Filename +
+ +

The above example show providing a certificate filename of example.cer.

+

Once all these values are set, the system will post data to the specified HTTP endpoint, following the JSON information structure noted earlier in this document.

+

JSON File Entries

+

If a JSON file is being used as an option to import settings into the DataLogger IoT, the following entries are used for the HTTP IoT connection:

+
"HTTP IoT": {
+    "Enabled": false,
+    "URL": "<the URL>",
+    "CA Cert Filename": "<certificate filename>"
+  }
+
+

Where:

+
    +
  • Enabled - Set to true to enable the connection.
  • +
  • URL - Set to the URL for the connection.
  • +
  • CA Cert Filename - Set to the cert filename on the SD card if being used.
  • +
+

If the JSON file is saved in the microSD card, you can load the credentials to the DataLogger IoT.

+
+

Tip

+

To load the values by the system at startup using a JSON file and microSD card, you will need to configure the Save Settings. This JSON file will be created with the "Save to Fallback" option. Make sure to enable the HTTP IoT as well.

+
+

Example - Connecting to a HTTP Server

+

In this example, a simple HTTP Server is creating using Node JS, and the HTTP connection in the DataLogger IoT is used to post data to this server. The received data is output to the console from there server.

+

The Server

+

The following javascript/node code creates a HTTP server on port 8090, and outputs received data to the console.

+
var http = require('http');
+
+// Setup the endpoint server
+var myServer = http.createServer(function (req, res) {
+
+  // Initialize our body string
+  var body="";
+
+  // on data callback, append chunk to our body string
+  req.on('data', function(chunk){
+    body += chunk;
+  });
+
+  // On end callback, output the body to the console
+  req.on('end', function(){
+    // parse json string, then stringify it back for 'pretty printing'
+    console.log("payload: " + JSON.stringify(JSON.parse(body),null,2));
+  });
+
+  // send a reply
+  res.writeHead(200, {'Content-Type': 'text/plain'});
+  res.end('n');
+  // Just listen on our port
+}).listen(8090);
+
+

The setup and use of node js is system dependant is beyond the scope of this document. However, Node JS is easily installed with your systems package manager (brew on macOS, Linux distribution package manager (apt, yum, ...etc), on Windows, the WSL is recommended).

+

Once Node is setup, the above server is run via the following command (assuming the implementation is in a file called simple_http.js):

+
node ./simple_http.js
+
+

As data is sent by the DataLogger IoT, the following is output to the console from the server:

+
+ HTTP Output +
+ +

Obtaining a Sites Security Certificate

+

Accessing a sites SSL/Secure Certificate is done via a web browser. The method for each browser is different. The following example uses Edge, which is similar to the operation in Chrome.

+

First, browse to the desired site/server. Click the Secure/Security area/button next to the URL to bring up the security detail page. On this page, select the Connection is secure menu option

+
+ cert step 1 +
+ +

Next, on the page shown, select the certificate button on the upper right of the dialog.

+
+ cert step 2 +
+ +

When you select this button, the certificate details dialog is displayed. On this page, select the Details tab, and select the Export... button on the lower right of the dialog. This will save the sites SSL/Security certificate to a location you specify.

+
+ cert step 3 +
+ +

Once saved, place this file on the SD card your system/DataLogger is using, and set the filename in the HTTP connection menu or settings JSON file.

+ + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/example_iot_web_server/index.html b/example_iot_web_server/index.html new file mode 100644 index 0000000..b043dfe --- /dev/null +++ b/example_iot_web_server/index.html @@ -0,0 +1,1955 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Viewing and Downloading Log Files using the IoT Web Server - SparkFun DataLogger IoT Hookup Guide + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + + + + + + + + + + +
+ +
+ + + + +
+
+ + + +
+
+
+ + + + + + + + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+ + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +

Viewing and Downloading Log Files using the IoT Web Server

+ +

As of firmware v01.02.00, log files can be viewed and downloaded over a WiFi network! This saves you time by allowing you to download the files without the need to disconnect the DataLogger IoT and manually remove microSD card.

+

The following is covered by this document:

+
    +
  • How a user configures and uses the HTTP connection
  • +
  • Use examples
  • +
+

IoT Web Server Connection Setup

+

To connect to the ESP32's IoT Web Server, the following information is needed:

+
    +
  • The server name/address
  • +
  • [optional] A username - if required
  • +
  • [optional] A password - if required
  • +
+

IoT Web Server Menu System

+

We'll need to adjust the settings for the IoT Web Server.

+

For users that are interested in using the menu system, open a Serial Terminal, connect to the COM port that your DataLogger enumerated to, and set it to 115200 baud. In this case, we connected to COM13. Press any key to enter the Main Menu. Type 1 to enter the Settings menu. Then type 17 to enter the IoT Web Server Menu. When the menu system for the IoT Web Server is presented, the following options are displayed:

+
+ IoT Web Server Options +
+ +

The options are:

+
    +
  • Enable/Disable the connection
  • +
  • Username
  • +
  • Password
  • +
  • Enable/Disable mDNS support
  • +
  • mDNS name
  • +
+

At a minimum, you will just need to enable the connection. However, we recommend enabling mDNS support if it is supported in your network.

+

Once all these values are set, the system will serve the log files in your local 2.4GHz WiFi network following the JSON information structure noted below in this document.

+

JSON File Entries

+

If a JSON file is being used as an option to import settings into the DataLogger IoT, the following entries are used for the IoT web server:

+
"IoT Web Server": {
+  "Enabled": false,
+  "Username": "",
+  "Password": "",
+  "mDNS Support": false,
+  "mDNS Name": "dataloggerAD6B8"
+},
+
+

Where:

+
    +
  • Enabled - Set to true to enable the connection.
  • +
  • Username - Web server user name if being used.
  • +
  • Password - Web server password if being used.
  • +
  • mDNS Support - Set to true if multicast DNS is supported. This allows you to enter the address as "http://dataloggerXXXXX.local" (where XXXXX is generated from the last 5x characters from your board ID) rather than typing the exact IP address of the ESP32.
  • +
  • mDNS Name - Multicast DNS name. In this case, the default name was set to dataloggerAD6B8. This name will be different depending on your DataLogger IoT's board ID so AD6B8 will be different for your board.
  • +
+
+

Tip

+

To load the values by the system at startup using a JSON file and microSD card, you will need to configure the Save Settings. This JSON file will be created with the "Save to Fallback" option. Make sure to enable the MQTT Client as well.

+
+

Connect and Download Log File

+
+

Note

+

You will need to make sure that the ESP32 is on the same network as your computer in order to access the log files.

+
+
+

Note

+

When authentication is enabled, some browsers might require a second login depending on user settings.

+
+

Once the web server is enabled and the settings are saved, you will need to reboot the DataLogger IoT. As the DatLogger initializes, it will connect to your WiFi Network. Take note of the mDNS address, in this case, it was "http://dataloggerAD6B8.local".

+
+ DataLogger IoT Initializing, WiFi Connected, Web Server Enabled +
+ +

Once the DataLogger IoT has finished initializing, open web browser. Connect the DataLogger IoT by entering the address "http://dataloggerXXXXX.local", where XXXX is the last 5x characters of your board ID. You will be presented with the log files available on the microSD card. Click on a log file to download and save it to your computer.

+
+ Viewing Available Log Files through a Web Browser +
+ +
+

Note

+

If mDNS is not supported, you can also enter the IP address of the Datalogger IoT into a web browser to view and download the log files. You can view the IP address when the DataLogger IoT is initializing. If you have administrative privileges to the WiFi Network, you can also view the IP address through your WiFi router as well.

+

+ Viewing Available Log Files through a Web Browser using IP Address +

+
+

Now that you have downloaded the log files, try graphing the data on a spreadsheet!

+ + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/example_mqtt/index.html b/example_mqtt/index.html new file mode 100644 index 0000000..0d8d3db --- /dev/null +++ b/example_mqtt/index.html @@ -0,0 +1,2117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + MQTT - SparkFun DataLogger IoT Hookup Guide + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + + + + + + + + + + +
+ +
+ + + + +
+
+ + + +
+
+
+ + + + + + + + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+ + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +

MQTT

+ +

Connecting and Publishing Data to MQTT

+

One of the key features of the DataLogger IoT is it's simplified access to IoT service providers and servers. This document outlines how output from a DataLogger device is sent to an MQTT Broker.

+
+ MQTT Logo +
+ Image Courtesy of MQTT +
+ +

The following is covered by this document:

+
    +
  • Overview of the MQTT connection
  • +
  • How a user configures and uses the MQTT connection
  • +
  • MQTT examples
  • +
+

General Operation

+

MQTT connectivity allows data generated by the DataLogger IoT to be published to an MQTT Broker under a user configured topic. MQTT is an extremely flexible and low overhead data protocol that is widely used in the IoT field.

+

The general use pattern for MQTT is that data is published to a topic on a MQTT broker. The data is then sent to any MQTT client that has subscribed to the specified topic.

+
+ MQTT Overview +
+ +

The DataLogger IoT supports MQTT connections, allowing an end user to enter the parameters for the particular MQTT Broker for the application to publish data to. When the application outputs data to the broker, the DataLogger IoT publishes the available information to the specified "topic" with the payload that is a JSON document.

+

Data Structure

+

Data is published to the MQTT broker as a JSON object, which contains a collection of sub-objects. Each sub-object represents a data source in the sensor, and contains the current readings from that source.

+

The following is an example of the data posted - note, this representation was "pretty printed" for readability.

+
{
+  "MAX17048": {
+    "Voltage (V)": 4.304999828,
+    "State Of Charge (%)": 115.0625,
+    "Change Rate (%/hr)": 0
+  },
+  "CCS811": {
+    "CO2": 620,
+    "VOC": 33
+  },
+  "BME280": {
+    "Humidity": 25.03613281,
+    "TemperatureF": 79.64599609,
+    "TemperatureC": 26.46999931,
+    "Pressure": 85280.23438,
+    "AltitudeM": 1430.44104,
+    "AltitudeF": 4693.04834
+  },
+  "ISM330": {
+    "Accel X (milli-g)": -53.31399918,
+    "Accel Y (milli-g)": -34.03800201,
+    "Accel Z (milli-g)": 1017.236023,
+    "Gyro X (milli-dps)": 542.5,
+    "Gyro Y (milli-dps)": -1120,
+    "Gyro Z (milli-dps)": 262.5,
+    "Temperature (C)": 26
+  },
+  "MMC5983": {
+    "X Field (Gauss)": -0.200622559,
+    "Y Field (Gauss)": 0.076416016,
+    "Z Field (Gauss)": 0.447570801,
+    "Temperature (C)": 29
+  }
+}
+
+

MQTT Broker Connection Setup

+

To connect to a MQTT Broker, the following information is needed:

+
    +
  • The server name/address
  • +
  • The server port
  • +
  • The topic to post to
  • +
  • [optional] The name of the device/Client name publishing the data
  • +
  • [optional] A username - if required
  • +
  • [optional] A password - if required
  • +
+

These values are set using the standard DataLogger methods - the interactive menu system, or a JSON file.

+

MQTT Menu System

+

We'll need to adjust the settings for the MQTT Client using the MQTT Menu System.

+

For users that are interested in using the menu system, open a Serial Terminal, connect to the COM port that your DataLogger enumerated to, and set it to 115200 baud. In this case, we connected to COM13. Press any key to enter the Main Menu. Type 1 to enter the Settings menu. Then type 9 to enter the MQTT Client Menu. When the menu system for the MQTT connection is presented, the following options are displayed:

+
+ MQTT Menu +
+ +

The options are:

+
    +
  • Enable/Disable the connection
  • +
  • Broker Port - The standard port for mqtt is 1883
  • +
  • Broker Server - This is just the name of the server
  • +
  • MQTT Topic - A string
  • +
  • Client Name
  • +
  • Username
  • +
  • Password
  • +
  • Buffer Size
  • +
+

At a minimum, the Broker Port, Broker Server Name, and MQTT Topic need to be set. What parameters are required depends on the settings of the broker being used.

+
+

Note

+

If a secure connection is being used with the MQTT broker, use the MQTT Secure Client option of the DataLogger IoT. This option supports secure connectivity.

+
+
+

Note

+

The Buffer Size option is dynamic by default, adapting to the size of the payload being sent. If runtime memory is a concern, set this value to a static size that supports the device operation.

+
+

Once all these values are set, the system will publish data to the specified MQTT Broker, following the JSON information structure noted earlier in this document.

+

JSON File Entries

+

If a JSON file is being used as an option to import settings into the DataLogger IoT, the following entries are used for the MQTT IoT connection:

+
"MQTT Client": {
+    "Enabled": false,
+    "Port": 1883,
+    "Server": "my-mqttserver.com",
+    "MQTT Topic": "/sparkfun/datalogger1",
+    "Client Name": "mysensor system",
+    "Buffer Size": 0,
+    "Username": "",
+    "Password": ""
+  },
+
+

Where:

+
    +
  • Enabled - Set to true to enable the connection.
  • +
  • Port - Set to the broker port.
  • +
  • Server - The MQTT broker server.
  • +
  • MQTT Topic - The topic to publish to.
  • +
  • Client Name - Optional client name.
  • +
  • Buffer Size - Internal transfer buffer size.
  • +
  • Username - Broker user name if being used.
  • +
  • Password - Broker password if being used.
  • +
+
+

Tip

+

To load the values by the system at startup using a JSON file and microSD card, you will need to configure the Save Settings. This JSON file will be created with the "Save to Fallback" option. Make sure to enable the MQTT Client as well.

+
+

Testing the MQTT Connection

+

Use of a MQTT connection is fairly straightforward - just requiring the entry of broker details into the connection settings.

+

To test the connection, you need a MQTT broker available. A quick method to setup a broker is by installing the mosquitto package on a Raspberry Pi computer. Our basic MQTT Tutorial provides some basic setup for a broker.

+
+ + + + + + + +
+
Introduction to MQTT +
+
+
+ +

This MQTT Broker Tutorial has more details, covering the setup needed for modern mosquitto configurations.

+ + +

And once the broker is setup, the messages published by the IoT sensor are visible using the mosquitto_sub command as outlined. For example, to view messages posted to a the topic "/sparkfun/datalogger1", the following command is used:

+
mosquitto_sub -t "/sparkfun/datalogger1"
+
+

This assumes the MQTT broker is running on the same machine, and using the default port number.

+ + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/example_thingspeak/index.html b/example_thingspeak/index.html new file mode 100644 index 0000000..7a17c30 --- /dev/null +++ b/example_thingspeak/index.html @@ -0,0 +1,2417 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ThingSpeak - SparkFun DataLogger IoT Hookup Guide + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + + + + + + + + + + +
+ +
+ + + + +
+
+ + + +
+
+
+ + + + + + + + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+ + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +

Creating and Connecting to ThingSpeak

+

One of the key features of the DataLogger IoT is it's simplified access to IoT service providers. This document outlines how a ThinkSpeak output is used by the DataLogger IoT.

+
+ ThingSpeak Logo +
+ Image Courtesy of ThingSpeak +
+ +

The following is covered by this document:

+
    +
  • Creating a ThingSpeak Channel and MQTT Connection
  • +
  • Securely connecting the ThingSpeak
  • +
  • How data is posted from the DataLogger IoT to ThingSpeak
  • +
+

General Operation

+

ThingSpeak Structure

+

The structure of ThingSpeak is based off of the concept of Channels, with each channel supporting up to eight fields for data specific to the data source. Each channel is named, and has a unique ID associated with it. One what to think of it is that a Channel is a grouping of associated data values or fields.

+

The fields of a channel are enumerated as Field1, Field2, ..., Field8, but each field can be named to simplify data access and understanding.

+

As data is reported to a ThingSpeak channel, the field values are accessible for further processing or visualization output.

+

Data Structure

+

The DataLogger IoT is constructed around the concept of Devices which are often a type of sensor that can output a set of data values per observation or sample.

+

Mapping Data to ThingSpeak

+

The concept of Channels that contain Fields in ThingSpeak is similar to the Devices that contain Data within the DataLogger IoT, and this similarity is the mapping model used by the DataLogger IoT. Specifically:

+
    +
  • Devices == Channels
  • +
  • Data == Fields
  • +
+
+ DataLogger to ThingSpeak Mapping +
+ +

During configuration of the DataLogger IoT, the mapping between the Device and ThingSpeak channel is specified. The data to field mapping is automatically created by the DataLogger IoT following the data reporting order from the specific device driver.

+

Creating a Device to a ThingSpeak Channel

+

The following discussion outlines the basic steps taken to create a Channel in ThingSpeak and then connect it to the DataLogger's Device. First step is to log into your ThingSpeak and create a Channel.

+ + +

Once logged into your ThingSpeak account, select Channels > My Channels menu item and on the My Channel page, select the New Channel button.

+
+ New Channel +
+ +

On the presented channel page, name the channel and fill in the specific channel fields. The fields should map to the data fields reported from the Device being linked to this channel. Order is important, and is determined by looking at output of a device to the serial device (or reviewing the device driver code).

+
+ New Channel +
+ +

Once the values are entered, select Save Channel. ThingSpeak will now show list of Channel Stats, made up of line plots for each field specified for the channel.

+
+

Note

+

Key note - at the top of this page is listed the Channel ID. Note this number - it is used to map a Device to a ThingSpeak Channel.

+
+

Setting Up ThingSpeak MQTT

+

The DataLogger IoT uses MQTT to post data to a channel. From the ThingSpeak menu, select Devices > MQTT, which displays a list of your MQTT devices. From this page, select the Add a new device button.

+

On the presented dialog, enter a name for the MQTT connection and in the Authorize channels to access, select the channel created earlier. Once you select a channel, click the Add Channel button.

+
+

Note

+

More channels can be added later.

+
+
+ MQTT on ThingSpeak +
+ +
+

Note

+

When the MQTT device is created, a set of credentials (Client ID, Username, and Password) is provided. Copy or download these values, since the password in not accessible after this step.

+
+

The selected Channel is then listed in the Authorized Channel table. Ensure that the Allow Publish and Allow Subscribe attributes are enabled for the added channel.

+
+ MQTT Channel Authorization on ThingSpeak +
+ +

At this point, the ThingSpeak Channel is setup for access by the DataLogger IoT.

+

ThingSpeak Configuration

+

Once the device is integrated into the application, the specifics for the ThingSpeak Channel(s) must be configured. This includes the following:

+
    +
  • Server Name/Hostname
  • +
  • Client Name
  • +
  • User Name
  • +
  • Password
  • +
  • Device to Channel mapping
  • +
  • CA Certificate Chain
  • +
+

Server Name/Hostname

+

This value is hostname of the ThingSpeak mqtt connection, which is mqtt3.thingspeak.com as note at ThingSpeakMQTT Basics page. Note a secure connection is used, so the port for the connection is 8883.

+

Client Name/ID

+

The Client Name/ID is found under MQTT connection details listed in the Devices > MQTT section of ThingSpeak.

+

Username

+

The Username is found under MQTT connection details listed in the Devices > MQTT section of ThingSpeak.

+

Password

+

The connection password was provided when the MQTT device was created. If you lost this value, you can regenerate a password on the MQTT Device information page.

+

Certificate File

+

You can download the cert file for ThingSpeak.com page using a web-browser. Click on the security details of this page, and navigate the dialog (browser dependent) to download the certificate. The downloaded file is the made available for the DataLogger IoT to use as a file that is loaded at runtime)

+

Setting Properties

+

The above property values must be set on the DataLogger IoT before use. They can be manually by using the menu system like the previous MQTT example.

+

For users that are interested in using the menu system, you will need to open a Serial Terminal, connect to the COM port that your DataLogger enumerated to, and set it to 115200 baud. In this case, we connected to COM13. Press any key to enter the Main Menu. Type 1 to enter the Settings menu. Then type 12 to enter the ThingSpeak MQTT Menu. When the menu system for the ThingSpeak MQTT connection is presented, you will need to configure the property values as listed in the JSON file. Saving the values through the menu system will save the credentials to the ESP32's persistent memory. The following options are displayed:

+
+ ThingSpeak MQTT Menu +
+ +

The alternative to using the menu system is a JSON file. These values can be set using a JSON file that is loaded by the system at startup. For the ThingSpeak example outlined in this document, the entries in the settings JSON file are as follows:

+
"ThingSpeak MQTT": {
+    "Enabled": true,
+    "Port": 8883,
+    "Server": "mqtt3.thingspeak.com",
+    "MQTT Topic": "",
+    "Client Name": "MQTT_Device_Client_ID",
+    "Buffer Size": 0,
+    "Username": "MQTT_Device_Username",
+    "Password": "MQTT_Device_Password",
+    "CA Cert Filename": "ThingspeakCA.cer",
+    "Channels" : "BME280=2054891"
+  }
+
+
+

Note

+

The Channels value is a list of [DEVICE NAME]=[Channel ID] pairs. Each pair is separated by a comma. In this case, the device name BME280 and the channel ID was 2054891. Make sure to match the device name that was loaded on start up with the unique channel ID that was generated when creating a ThingSpeak Channel.

+
+

Besides updating the Server, Client Name, Username, Password, CA Cert Filename, and Channels, you will need to also ensure that the port is set to 8883. The default in previous firmware versions was 1883. As of firmware v01.00.04, the default is 8883. You will need to adjust the port value to properly connect to the ThingSpeak service. Don't forget to enable ThingSpeak MQTT service by setting the value to true. If the JSON file is saved in the microSD card, you can load the credentials to the DataLogger IoT.

+
+

Tip

+

To load the values by the system at startup using a JSON file and microSD card, you will need to configure the Save Settings. This JSON file will be created with the "Save to Fallback" option. Make sure to enable the ThingSpeak MQTT as well.

+
+

Monitoring Output

+

Once the connector is configured and the DataLogger IoT is connected to ThingSpeak, as data is posted, the results are show on the Channel Stats page for your Channel. For the above example, the output of a SparkFun BME280 sensor produces the following output:

+
+ ThingSpeak Stats +
+ +

Setting Up 2x or More Devices

+

For users that are setting up 2x or more devices on the DataLogger IoT, you will need to ensure that each device has their own ThingSpeak Channel. Unfortunately, you are not able to plot sensor readings from two devices in the same channel.

+

The following example demonstrates how to set up two devices for ThingSpeak on the DataLogger IoT. In this case, we will use the BME688 and BME680 and their respective default I2C address. This is also a good example of what to do when two devices use the same device driver. Head to ThingSpeak to create a channel for each device connected to the DataLogger IoT. Include a field for each device data that the DataLogger provides. The name of the channel does not need to match the device name or I2C address.

+
+ + + + + + + + + +
Creating a Channel for the BME688Creating a Channel for the BME680
Creating a Channel for the BME688Creating a Channel for the BME680
+
+ +

Once the channels are created, you will be provided with a unique channel ID for each channel. Make sure to take note of the number as explained earlier.

+
+

Note

+

The alternative I2C address for the BME688 and BME680 uses the same address as the default of the other sensor:

+
    +
  • BME680: 0x77 (Default) or 0x76
  • +
  • BME688: 0x76 (Default) or 0x77
  • +
+

Make sure to avoid using the same address when connecting the sensors to the same DataLogger IoT.

+
+

When setting up the connection, you will need to authorize both channels. In this example, we included the channels for the BME688 [x076] and BME680 [x077].

+
+ + + + + + + +
Authorizing 2x Channels through the Same Connection
Authorizing 2x Channels through the Same Connection
+
+ +

Now that the ThingSpeak MQTT connection is setup, adjust the ThingSpeak configuration for the DataLogger IoT by including the credentials (i.e. Client Name, Username, and Password) and channels. We will assume that you have included the ThingSpeak CA certificate file in the root directory of the microSD card already. When including the device name with their respective channel, ensure that the device name matches the name that was loaded on startup. For example, the BME688 and BME680 were loaded on startup as BME68x and BME68x [x77], respectively. Since we are only interested in plotting the BME688 and BME680, we will ignore the MAX17048 that was loaded on startup as well. Under /Settings/ThingSpeak MQTT/Channels, you will enter the string for the device names, each of their respective channel IDs, and a comma separating the two channels like so: BME68x=2613826, BME68x [x77]=2613825.

+
+ + + + + + + + + +
DataLogger IoT Device Name Loaded during StartupDevice Name and Channel for Both Sensors
DataLogger IoT Device Name Loaded during StartupDevice Name and Channel for Both Sensors
+
+ +
+

Note

+

Whenever there are multiple devices using the same device driver (each with unique I2C addresses), the DataLogger IoT will display the device address for each additional device that is loading the same driver. As shown above, the first device name did not include the device's I2C address. The second device name using the same driver included its I2C address. Of course, there is an configuration that enables you to always include the address of all the device names.

+
+

The alternative to using the menu system is the JSON file. In this case, we updated channels for the BME688 and BME680. Not shown are the ThingSpeak Client Name, Username, and Password.

+
"ThingSpeak MQTT": {
+    "Enabled": true,
+    "Port": 8883,
+    "Server": "mqtt3.thingspeak.com",
+    "MQTT Topic": "",
+    "Client Name": "MQTT_Device_Client_ID",
+    "Buffer Size": 0,
+    "Username": "MQTT_Device_Username",
+    "Password": "MQTT_Device_Password",
+    "CA Cert Filename": "ThingspeakCA.cer",
+    "Channels" : "BME68x=2613826, BME68x [x77]=2613825"
+  }
+
+
+

Note

+

If users configure the DataLogger IoT to always include the device address with the device names (i.e. /Settings/Application Settings with Device Names=1), you will need to match the device names for BME688 and BME680 that were loaded on startup as BME68x [x76] and BME68x [x77], respectively. Note the BME688 device name included a space and [x76] in this case. Remember, we are only interested in plotting hte BME688 and BME680 in this case so we will ignore the MAX17048 that was loaded on startup.

+

+ + + + + + + + + +
DataLogger IoT Device Names Loaded during StartupDevice Name and Channel for Both Sensors with Respective Addresses
DataLogger IoT Device Names Loaded during StartupDevice Name and Channel for Both Sensors with Respective Addresses
+

+

Again, the alternative to using the menu system is the JSON file. In this case, we updated channels for the BME688 and BME680. We also included the address name for the BME688 like the configuration menu. Not shown are the ThingSpeak Client Name, Username, and Password.

+
"ThingSpeak MQTT": {
+    "Enabled": true,
+    "Port": 8883,
+    "Server": "mqtt3.thingspeak.com",
+    "MQTT Topic": "",
+    "Client Name": "MQTT_Device_Client_ID",
+    "Buffer Size": 0,
+    "Username": "MQTT_Device_Username",
+    "Password": "MQTT_Device_Password",
+    "CA Cert Filename": "ThingspeakCA.cer",
+    "Channels" : "BME68x [x76]=2613826, BME68x [x77]=2613825"
+  }
+
+
+

Save the configuration to persistent memory and exit out of the configuration menu. Wait a few seconds for the DataLogger IoT to read the sensors and output the readings to the Serial Terminal. Open ThingSpeak channels in separate browser windows. In this case, we had the BME688 and the BME680 in private view. You should see sensor readings update and plot on the charts.

+
+ + + + + + + +
ThingSpeak Graphing the BME688 and BME680 in Seperate Channels on Two Browser Windows
ThingSpeak Graphing the BME688 and BME680 in Seperate Channels on Two Browser Windows
+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/example_timestamp/index.html b/example_timestamp/index.html new file mode 100644 index 0000000..33c5239 --- /dev/null +++ b/example_timestamp/index.html @@ -0,0 +1,1729 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Adding a Timestamp to Data - SparkFun DataLogger IoT Hookup Guide + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ + + + + + +
+ + + + + + + + + + + +
+ +
+ + + + +
+
+ + + +
+
+
+ + + + + + + + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+ + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +

Adding a Timestamp to Data

+ +

Open a Serial Terminal, connect to the COM port that your DataLogger enumerated to, and set it to 115200 baud. In this case, we connected to COM13. Press any key to enter the Main Menu. Type 1 to enter the Settings menu. Then send a 6 to adjust how data is logged.

+
+ Logger Menu Options +
+ +

Send a 1 to configure the timestamp for each log entry. The settings in this menu relate to the system clock and is dependent on the reference clock. You'll be prompted with different formats. In this example, we sent a a 4 to have a timestamp with the USA date format.

+
+ + Configure Timestamp +
+ +

Follow the prompts to exit out of the menu properly so that the DataLogger IoT saves the settings. Once you see the message [I] Saving System Settings, the DataLogger IoT will add a timestamp with your preferred format to each log entry. Assuming that you have the output set to the serial terminal, you should see the timestamp attached to the output after the system settings are saved like the image below.

+
+ + Timestamped Data +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/factory_reset/index.html b/factory_reset/index.html new file mode 100644 index 0000000..6b859d8 --- /dev/null +++ b/factory_reset/index.html @@ -0,0 +1,1732 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Factory Reset - SparkFun DataLogger IoT Hookup Guide + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ + + + + + +
+ + + + + + + + + + + +
+ +
+ + + + +
+
+ + + +
+
+
+ + + + + + + + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+ + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +

Factory Reset

+ +

A factory reset will move the boot firmware of the device to the firmware imaged installed at the factory and erase any on-board stored settings on the device. This is helpful if an update fails, or an update has issues that prevent proper operations.

+

This option is available on ESP32 devices that contained a factory firmware partition that contains a bootable firmware image. Consult the specific product's production and build system for further details.

+

Open a Serial Terminal, connect to the COM port that your DataLogger enumerated to, and set it to 115200 baud. In this case, we connected to COM13. Press any key to enter the Main Menu. Type 1 to enter the Settings menu. Then type 16 to enter the System Update Menu. Finally, type 2 to enter the Factory Reset option.

+

The user is presented a prompt to continue. To launch a factory reset, the value of Y should be entered. To abort the update, enter n or press the Esc key.

+
+ + Reset Prompt +
+ +

When a Y is entered, the system performs the following:

+
    +
  • Set the boot image to the Factory installed firmware
  • +
  • Erase any settings stored in the on-board flash memory
  • +
  • Reboot the device
  • +
+
+ + Reset Reboot +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/file_issue/index.html b/file_issue/index.html new file mode 100644 index 0000000..11adac5 --- /dev/null +++ b/file_issue/index.html @@ -0,0 +1,1976 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Submit Issues - SparkFun DataLogger IoT Hookup Guide + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + + + + + + + + + + +
+ +
+ + + + +
+
+ + + +
+
+
+ + + + + + + + + + + + + +
+
+
+ + + + + + + +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +

Did we make a mistake?

+

Spot something wrong? Please let us know.

+
+

Attention

+

This is not where customers should seek assistance on a product. If you require technical assistance or have questions about a product that is not working as expected, please head over to the SparkFun Technical Assistance page for some initial troubleshooting. +

+SparkFun Technical Assistance Page +

+

If you can't find what you need there, you'll need a Forum Account to search product forums and post questions.

+
+

Discrepancies in the Documentation

+

All of this documentation can be modified by you! Please help us make it better.

+ +

Spot something wrong?

+

If a section of the documentation is incorrect, please open an issue and let us know.

+

Do you have a suggested correction?

+
    +
  1. With a GitHub account, fork this repo
  2. +
  3. Add your correction(s) or improvement(s) to the markdown file(s)
  4. +
  5. File a pull request with your changes, and enjoy making the words worlds world a better place.
      +
    • Once received, the documentation specialist will automatically be notified.
    • +
    • We will review your suggested improvement(s) to make sure they are correct and fit within our documentation standards.
    • +
    +
  6. +
+

Problems in the Hardware Design

+

All of our designs are open-source! Please help us make it better.

+ +

Does something not make sense?

+

If part of the design is confusing, please open an issue and let us know.

+

Did we forget to include an important function of the board?

+
    +
  • Please keep in mind that we may intentionally exclude certain functions of the board to meet our product design requirements. (For example, our Qwiic Micro boards are intended to fit on a small board layout and only use I2C communication; therefore, we may not have the SPI and interrupt pins available for users.)
  • +
  • If part of the board's functionality is missing, please open an issue and file a feature request.
  • +
+

Do you wish to contribute directly to improving the board design?

+
    +
  1. With a GitHub account, Fork this repo
  2. +
  3. Add your design improvement(s)
  4. +
  5. File a pull request with your changes, and enjoy making the words worlds world a better place.
      +
    • Once received, the engineer in charge of the original design will automatically be notified.
    • +
    • We will review your suggested improvement(s), if they are within our board design standards and meet our product design requirements, we will flag these changes for our next board revision. (Please note, that even if your suggestion is accepted, these changes may not be immediate. We may have to cycle through our current product inventory first.)
    • +
    +
  6. +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/hard_copy/index.html b/hard_copy/index.html new file mode 100644 index 0000000..f6b4481 --- /dev/null +++ b/hard_copy/index.html @@ -0,0 +1,1621 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + Hard copy - SparkFun DataLogger IoT Hookup Guide + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ + + + + + +
+ + + + + + + + + + + +
+ +
+ + + + +
+
+ + + +
+
+
+ + + + + + + + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +

Hard copy

+ +

Need to download or print our hookup guide?

+
    +
  • Print (Single-Page View)
      +
    • To save as a *.pdf file, select the Printer or Destination labeled Save as PDF. (Instructions will vary based on the browser)
    • +
    +
  • +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/hardware_hookup/index.html b/hardware_hookup/index.html new file mode 100644 index 0000000..fbb78b6 --- /dev/null +++ b/hardware_hookup/index.html @@ -0,0 +1,2005 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Hardware Hookup - SparkFun DataLogger IoT Hookup Guide + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + + + + + + + + + + +
+ +
+ + + + +
+
+ + + +
+
+
+ + + + + + + + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +

Hardware Hookup

+ +

In this section, we will go over how to connect to the SparkFun DataLogger IoT. At the time of writing, we used the DataLogger IoT - 9DoF. This hardware hookup explained in this section also applies for the DataLogger IoT.

+

Soldering to the PTHs

+
+

Note

+

The UART, SPI, analog, and digital I/O pins are not currently supported in the firmware for data logging.

+
+

For users that are interested in soldering to the edge of the board, we recommend soldering headers to the PTHs on the breakout for a permanent connection and using jumper wires. Of course, you could also solder wires to the breakout board as well. For a temporary connection during prototyping, you can use IC hooks like these.

+ +

MicroSD Card

+

If all you want to do is display your sensor readings in a serial terminal or monitor (connected via USB-C) then, strictly, you don’t need to add a microSD card. But of course the whole point of the DataLogger IoT is that it can log readings from whatever sensors you have attached to microSD card. The data is logged in easy-to-read Comma Separated Value (CSV) text format by default. You can also set the format as JSON.

+

You probably already have a microSD card laying around but if you need any additional units, we have plenty in the store. The DataLogger IoT can use any size microSD card as long as it is formatted correctly. Please ensure your SD card is formatted correctly. There are different software tools available. Some are built into your operating system. We recommend using the Raspberry Pi Imager Tool to easily format the memory card as FAT32 using the GUI. Flip over the DataLogger IoT and you’ll see the latching microSD card socket. Slide in your formatted SD card and it will click neatly into place. Part of the edge of the SD card will stick out when fully inserted in the microSD card socket.

+
+ + + + +
Inserting MicroSD card
+
+ +

You should only insert or remove the SD card while the power is turned off or disconnected. Removing the card while the DataLogger IoT is logging will almost certainly corrupt your data. You can tell when the board has just logged by observing the addressable RGB LED. When enabled, the LED will blink blue after it has logged one data point.

+

After you’ve logged some data, you will find a new file on your SD card. There may also be additional files if you manually saved the firmware or preferences to the memory card.

+
    +
  • sfe0001.txt: This is the file that contains the CSV or JSON sensor data. The format will depend on how you configured the DataLogger's output. We use .TXT as the file type so that your computer can open it in a simple text editor. The contents are all human-readable. But, if you want to, you can rename it as .CSV or .JSON instead. The file number is incremented for the next logging session.
  • +
  • datalogger.json: This file only appears when you save the settings as your fallback storage. The file will include all preferences saved for any connected device, WiFi credentials, certificates, and keys.
  • +
  • SparkFun_DataLoggerIoT*.bin: This file only appears when you save the firmware to the microSD card. Note that the asterisk (*) is the firmware version number (i.e. SparkFunDataLoggerIoT_01.00.01.bin).
  • +
+

To remove the microSD card, make sure power is disconnected from the DataLogger IoT. Then press the microSD card into the microSD socket. The memory card will be ejected and you will hear a click again. Once the card is ejected, you can insert it into a microSD card adapter or USB reader to be read on a computer.

+
+ + + + +
Memory Card Adapter and USB Reader for microSD cards
+
+ +

Qwiic Sensors

+

If you are going to attach extra sensors or any Qwiic-enabled device to the DataLogger IoT, then those need to be connected first before attaching a USB cable. It is a good idea to only attach or disconnect Qwiic sensors when the power is turned off or disconnected. The Qwiic bus is pretty tolerant to “hot swapping”, but: disconnecting a sensor while it is in use will confuse the DataLogger IoT software (most likely each value associated with the device will remain constant); and a new sensor won’t be detected until the firmware restarts.

+

Plug one end of your Qwiic cable into the DataLogger IoT and plug the other end into your sensor. If you want to add extra sensors, you can simply connect them to each other in a daisy chain. You will need a Qwiic cable for each sensor. Our Qwiic Cable Kit covers all the options.

+
+ + + + + + + + + +
DataLogger IoT and a Qwiic DeviceDataLogger and several Qwiic-Enabled Devices Daisy Chained
DataLogger IoT and a Qwiic-Enabled DeviceDataLogger IoT and several Qwiic-Enabled Devices Daisy Chained
+
+ +

Our Qwiic sensors usually all have power indicator LEDs and I2C pull-up resistors. Depending on your application, you may want or need to disable these by cutting the jumper links on the sensor circuit boards. We have a tutorial that will show you how to do that safely.

+

Sometimes you might want to connect more than one of the same type of sensor to the DataLogger IoT. On the I2C bus, each device needs to have a unique address. On many of our boards, there are jumpers links which you can use to change the address and some have addresses that can be configured in software. But there are some where you cannot change the address - the NAU7802 Qwiic Scale being one example.

+

Typically one would use a multiplexor. However, we currently do not have the DataLogger IoT configured to work with any multiplexors (i.e. Qwiic Mux Breakout).

+
+

Note

+

Currently the Qwiic Mux is not compatible with the DataLogger IoT.

+
+

LiPo Battery

+
+

Battery Polarity

+

Please make sure that you use one of our recommended Lithium Ion batteries. Some batteries use the same JST connector as ours but have the opposite polarity. Connecting one of these to your DataLogger IoT will destroy it. If you are going to use your own battery, it is up to you to ensure it has the correct polarity.

+
+

Now is a good time to attach a LiPo battery, if you want the DataLogger IoT to keep logging when you disconnect USB-C.

+
+ + + + +
LiPo Battery Inserted
+
+ +

You can connect one of our standard single cell LiPo batteries to the DataLogger IoT and power it for hours, days or weeks depending on what sensors you have attached and how often you log data. The DataLogger IoT has a built-in charger too which will charge your battery at 500mA when USB-C is connected. Please make sure your battery capacity is at least 500mAh (0.5Ah); bad things will happen if you try to charge our smallest batteries at 500mA. The yellow CHG charging LED will light up while the battery is charging and will go out once charging is complete.

+
+

Warning

+

The MCP73831 charge IC on the board is used on a few SparkFun products. For more information about the CHG status LED, we recommend taking look at the Hardware Overview. We also recommend taking a look at this tutorial for Single Cell LiPo Battery Care.

+
+

USB Cable

+

The USB-C connector provides power to the DataLogger IoT and acts as a serial interface for configuration and data display.

+
+ + + + +
Insert USB
+
+ +

If you are going to use a microSD card to store your data, and why wouldn’t you, then insert that first before attaching your USB cable. You should only insert or remove the SD card while the power is turned off or disconnected. Removing the card while the DataLogger IoT is logging will almost certainly corrupt your data.

+

Likewise, it is a good idea to only attach or disconnect Qwiic sensors when the power is turned off or disconnected. The Qwiic bus is pretty tolerant to “hot swapping”, but: disconnecting a sensor while it is in use will confuse the DataLogger IoT software; and a new sensor won’t be detected until the firmware restarts.

+

Depending on what ports your computer has available, you will need one of the following cables:

+ +

Use the cable to connect your DataLogger IoT to your computer and you will see the LEDs light as the DataLogger IoT starts up. The addressable RGB RGB LED will light up green for a second or two while the DataLogger IoT configures itself. It will flash blue while data is being logged to the microSD card. If you have jumped the gun and have a LiPo battery already connected, the yellow CHG charging LED may light up too.

+

If the addressable RGB LED does not light up, your DataLogger IoT is probably in deep sleep following a previous logging session. Pressing the RST reset button will wake it.

+

You’ll find full instructions on how to configure the DataLogger IoT later in this tutorial.

+

Standoffs

+

For users interested in stacking the Qwiic-enabled device on the DataLogger IoT or mounting in an enclosure, you will need some standoffs to mount the boards. When mounting, note that all four mounting holes are not positioned exactly for a 1.0"x1.0" sized Qwiic board. Only two of the four mounting holes are compatible for a 1.0"x1.0" sized Qwiic board. For example the image below shows the boards stacked on each side of the DataLogger IoT. On top, the Qwiic GPS (SAM-M10Q) breakout was also able to stack by rotating the board slightly and aligning the mounting holes on the 1.6"x1.6" sized board to the other mounting holes

+
+ + + + +
Qwiic-enabled boards connected and stacked on the DataLogger Using Standoffs
+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/hardware_overview/index.html b/hardware_overview/index.html new file mode 100644 index 0000000..8a29251 --- /dev/null +++ b/hardware_overview/index.html @@ -0,0 +1,2581 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Hardware Overview - SparkFun DataLogger IoT Hookup Guide + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + + + + + + + + + + +
+ +
+ + + + +
+
+ + + +
+
+
+ + + + + + + + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +

Hardware Overview

+ +

In this section, we will highlight the hardware and pins that are broken out on the SparkFun DataLogger IoT. At the time of writing, we highlighted the SparkFun DataLogger IoT - 9DoF. However, this also applies for the SparkFun DataLogger IoT.

+
+ + + + + + + + + +
DataLogger IoT - 9DoF (Top View)DataLogger IoT - 9DoF (Bottom View)
DataLogger IoT - 9DoF (Top View)DataLogger IoT - 9DoF (Bottom View)
+
+ +

The SparkFun DataLogger is pretty much the same with the exception of the following features listed below. We'll include notes highlighting the differences in each section.

+
    +
  • No built - in 6DoF IMU - ISM330DHCX
  • +
  • No built - in magnetometer - MMC5983
  • +
  • The addressable RGB LED - WS2812 is replaced with the side emitting addressable RGB LED - B3DQ3BRG
  • +
  • No IMU INT2 jumper
  • +
  • No Mag INT jumper
  • +
  • Included MEAS PTH Jumper
  • +
  • The "35 / A7" PTH on the edge of the board is replace with a "5" PTH.
  • +
+
+ + + + + + + + + +
DataLogger IoT (Top View) + DataLogger IoT (Bottom View) +
DataLogger IoT (Top View)DataLogger IoT (Bottom View)
+
+ +

ESP32-WROOM Module

+

The DataLogger IoT is populated with Espressif's ESP32-WROOM-32E module. Espressif's ESP32 WROOM ubiquitous IoT microcontroller is a powerful WiFi, BT, and BLE MCU module that targets a wide variety of applications. For the DataLogger IoT, the firmware currently utilizes the WiFi feature.

+
+ + + + +
ESP32-WROOM Highlighted on the DataLogger IoT - 9DoF
+
+ +
+

Note

+

Currently the DataLogger IoT does not have BT or BLE. However, BT or BLE is being considered on a future firmware build to include this as a feature.

+
+

Power

+

There are a variety of power and power-related nets broken out to connectors and through hole pads. Below list a few methods of powering the board up. There are protection diodes for the USB-C, 5V pin, and single cell LiPo battery. Power is regulated down to 3.3V for the system voltage. Depending on the settings and what is connected to the DataLogger IoT, the board can pull a minimum of 200µA in low power mode by itself.

+
    +
  • USB-C
  • +
  • 5V Pin
  • +
  • Single Cell LiPo Battery
  • +
  • 3V3 Pin
  • +
+

USB-C and 5V

+

The DataLogger IoT comes equipped with a USB type C socket which you can use to connect it to your computer to view the output and configuration through the serial terminal, or plug in a USB-C power supply. The DataLogger IoT includes the configuration channel resistors needed to tell the power supply to deliver 5V. You can use your USB-C laptop charger as the power source should you need to, even though it normally delivers a much higher voltage.

+

There is also a 5V power input pin. You can use this to feed in 5V power from an external source. The maximum voltage is 6.0V. The 5V pin is diode-protected and so is the USB-C power input, so it is OK to have both connected at the same time. This pin is ideal if you want to power your DataLogger from regulated solar power or a different type of power supply. You can not use the 5V pin as an output.

+
+ + + + + +
+ USB C Connector, 3.3V Voltage regulator, 5V PTH + 5V PTH
+
+ +

Voltage from the USB is regulated down to the XC6222 3.3V/700mA voltage regulators for the system voltage and Qwiic-enabled devices. USB power is also connected to the MCP73831 to charge a single cell LiPo battery at a default rate of 500mA.

+

For customers in North America, our NEMA Raspberry Pi Wall Adapter is a perfect choice. You can power the DataLogger IoT from our USB Battery Pack / Power Bank - TOL-15204 but you will need a USB-C cable too:

+ +

LiPo Battery Input, Charger, and Fuel Gauge

+
+

Warning

+

Battery Polarity: Please make sure that you use one of our recommended Lithium Ion batteries. Some batteries use the same JST connector as ours but have the opposite polarity. Connecting one of these to your DataLogger IoT will destroy it. If you are going to use your own battery, it is up to you to ensure it has the correct polarity.

+
+

But of course you’re going to want to use the DataLogger IoT to log sensor data while on the move too. You can connect one of our standard single cell LiPo batteries to the DataLogger IoT and power it for hours, days or weeks depending on what sensors you have attached and how often you log data. The DataLogger IoT uses the built-in MCP73831 charger too which will charge your battery at 500mA when USB-C is connected. Please make sure your battery capacity is at least 500mAh (0.5Ah); bad things will happen if you try to charge our smallest batteries at 500mA. The board also includes the MAX17048 LiPo Fuel Gauge which allows you to determine how much power your LiPo battery has available. The 2-pin JST connector pins are broken out to PTHs on the edge of the board if you decide to solder a single cell LiPo battery directly to the board or power another device.

+
+ + + + + +
LiPo Charger and Fuel Gauge Circuit Highlighted on teh DataLogger IoT -  9DoF (Top View)LiPo Charger and Fuel Gauge Circuit Highlighted on teh DataLogger IoT -  9DoF (Bottom View)
+
+ +

3V3 Pins

+

For those going the old school route, you can also bypass the voltage regulators by soldering directly to the 3V3 and GND pin to provide power if your application has a regulated 3.3V. Note that this is only connected to the system voltage. You will also need to provide power to the 3V3 SWCH or Qwiic-enabled devices should you decide to bypass the voltage regulator.

+
+ + + + + +
3.3V Voltage Regulators, PTHs, and Qwiic Connectors Highlighted on the DataLogger IoT - 9DoF (Top View)3.3V Voltage Regulators, PTHs, and Qwiic Connectors Highlighted on the DataLogger IoT - 9DoF (Bottom View)
+
+ +

CH340 USB-to-Serial Converter

+

The top side of the board includes a CH340 USB-to-Serial Converter. The chip can be used to send serial data between the device and computer. You can view the output or configure the device through a serial terminal.

+
+ + + + +
CH340 Highlighted on the DataLogger IoT - 9DoF (Top View)
+
+ +

The driver should automatically install on most operating systems. However, there is a wide range of operating systems out there. You may need to install drivers the first time you connect the chip to your computer's USB port or when there are operating system updates. For more information, check out our How to Install CH340 Drivers Tutorial.

+ + +

UART

+

The hardware serial UART pins are broken out on the edge of the board. For more information about Serial UART, check out the tutorial about Serial Communication for more information.

+
    +
  • TXD: UART transmit pin. This is connected to pin 16.
  • +
  • RXD: UART receive pin. This is connected to pin 17.
  • +
+
+

Note

+

The UART pins are not currently supported in the firmware for data logging.

+
+
+ + + + + +
UART Pins Highlighted on the DataLogger IoT - 9DoF (Top View)UART Pins Highlighted on the DataLogger IoT - 9DoF (Bottom View)
+
+ +

Qwiic and I2C

+
+

Note

+

You may notice a thin film over the vertial Qwiic connector. This is used by a pick-and-place machine when populating the component on the board before it goes through the reflow oven. This can be removed if you decide to use the vertical Qwiic connector with Qwiic-enabled devices.

+
+

SparkFun's Qwiic Connect System uses 4-pin JST style connectors to quickly interface development boards with I2C sensors and more. No soldering required and there's no need to worry about accidentally swapping the SDA and SCL wires. The Qwiic connector is polarized so you know you’ll have it wired correctly every time, right from the start. Qwiic boards are daisy chain-able too so you can connect multiple sensors to the DataLogger IoT and log readings from all of them.

+

The board is populated with vertical and horizontal Qwiic connectors. These are also broken out to PTHs on the edge of the board.

+
+ + + + + +
Qwiic Connector and I2C PTH Highlighted on the DataLogger IoT - 9DoF (Top View)Qwiic Connector and I2C PTH Highlighted on the DataLogger IoT - 9DoF (Bottom View)
+
+ +
    +
  • SCL: I2C clock pin. This is connected to pin 22 and a 2.2kΩ pull-up resistor.
  • +
  • SDA: I2C data pin. This is connected to pin 21 and a 2.2kΩ pull-up resistor.
  • +
  • 3V3 SW: The 3.3v pin is connected to the XC6222 voltage regulator's output to power the Qwiic devices.
  • +
  • GND: Common, ground voltage (0V reference) for the system
  • +
+

Connected to the line I2C line is the MAX17048 LiPo fuel gauge (7-bit unshifted address = 0x36).

+

Sometimes you might want to connect more than one of the same type of sensor to the DataLogger IoT. On the I2C bus, each device needs to have a unique address. On many of our boards, there are jumpers links which you can use to change the address and some have addresses that can be configured in software. But there are some where you cannot change the address. Typically, one would use a multiplexor. However, we currently do not have the DataLogger IoT configured to work with any multiplexors (i.e. Qwiic Mux Breakout).

+
+

Note

+

Currently the Qwiic Mux is not compatible with the DataLogger IoT.

+
+

The DataLogger IoT includes a dedicated 3.3V regulator for the Qwiic connector. This has several advantages including:

+
    +
  • The DataLogger IoT can completely power-down the I2C sensors during sleep to prolong your battery life
  • +
  • There’s no risk of the Qwiic bus gulping too much current and causing problems for the ESP32
  • +
+

SPI

+
+

Note

+

Besides the built-in ISM330DHCX and MMC5983MA, the SPI pins are not currently supported in the firmware for data logging.

+
+

The SPI pins are broken out on the edge of the board. For those that are unfamiliar to PICO and POCI, check out the SPI tutorial for more information.

+
    +
  • SCK: SPI clock pin. This is connect to pin 18.
  • +
  • PICO: SPI Peripheral In Controller Out. This is connected to pin 23.
  • +
  • POCI: SPI Peripheral Out Controller In. This is connected to pin 19.
  • +
+
+ + + + + +
SPI Pins Highlighted on the DataLogger IoT -9DoF (Top View)SPI Pins Highlighted on the DataLogger IoT - 9DoF (Bottom View)
+
+ +

Not shown in the image are the chip select (CS) pins. The 6DoF IMU's CS pin is connected to pin 5. The magnetometer's CS pin is connected to pin 27 which is not broken out.

+
+

Note

+

On the DataLogger IoT, the IMU and magnetometer are not connected to the SPI port since they are not included on the board. Instead of pin "35 / A7" being broken out, pin "5" is broken out on the edge of the board.

+

+ + + + + +
Pin 5 Highlighted on the DataLogger IoT (Top View)Pin 5 Highlighted on the DataLogger IoT (Bottom View)
+

+
+

MicroSD Card Socket

+

The DataLogger IoT supports full 4-bit SDIO for fast logging and uses common microSD cards to record clear text, comma separated files. Flip over the DataLogger IoT and you'll see the latching microSD card socket. You probably already have a microSD card laying around. However, if you need any additional units, we have plenty in the SparkFun catalog. The DataLogger can use any size microSD card and supports FAT32 cards in addition to FAT16. Please ensure that your SD card is formatted correctly; we recommend the Raspberry Pi Imager Tool.

+
+ + + + +
MicroSD Card
+
+ +

Slide in your formatted SD card and it will click neatly into place. The edge of the SD card will stick out on the edge of the circuit board when it is inserted correctly.

+
+

Warning

+

You should only insert or remove the SD card while the power is turned off or disconnected. Removing the card while the DataLogger IoT is logging will almost certainly corrupt your data.

+
+

9 Degrees of Freedom (9DOF)

+

As stated earlier, included on every DataLogger IoT - 9DoF is a 6DoF Inertial Measurement Unit (IMU) for built-in logging of triple-axis accelerometer and gyro. There is also a built-in triple-axis magnetometer for a complete 9 degrees of freedom. Beside each IC is a silkcreen showing the reference axis. Both are connected to the ESP32 via the SPI port. Combined, you have 9 degrees of inertial measurement! Whereas the original 9DOF Razor used the old MPU-9250, this uses the ISM330DHCX and MMC5983MA. Oh, and if that wasn’t enough, it comes with a built-in temperature sensor on each IC too. So if you want to use the DataLogger IoT as a transportation logger, it will do that straight out of the anti-static bag!

+
+ + + + +
Accelerometer, Gyro, and Magnetometer
+
+ +
+

Note

+

For users using the SparkFun DataLogger, there 6DoF IMU and magnetometer is not populated on the board. The associated silkscreen and jumpers for the sensors are also not included on the board.

+

+ + +
+ + +
DataLogger IoT (Top View) + DataLogger IoT (Bottom View) +
+

+
+

Analog Pins

+
+

Note

+

The analog pins are not currently supported in the firmware for data logging.

+
+

There are three 12-bit analog pins available and broken out on edge of the board.

+
    +
  • 36 / A0: Analog A0. This is connected to pin 36.
  • +
  • 39 / A3: Analog A3. This is connected to pin 39.
  • +
  • 35 / A7: Analog A7. This is connected to pin 35.
  • +
+
+ + + + + +
Analog Pins Highlighted on the DataLogger IoT (Top View)Analog Pins Highlighted on the DataLogger IoT (Bottom View)
+
+ +
+

Note

+

Instead of pin "35 / A7" being broken out on the DataLogger IoT, pin "5" is broken out on the edge of the board.

+

+ + + + + +
Pin 5 Highlighted on the DataLogger IoT (Top View)Pin 5 Highlighted on the DataLogger IoT (Bottom View)
+

+
+

Reset and Boot Buttons

+
+

Note

+

You may notice a thin film over buttons. This is used by a pick-and-place machine when populating the component on the board before it goes through the reflow oven. This can be removed.

+
+

There are two buttons available on the board for reset and boot. These are also broken out on the edge of the board as PTHs. If you have your DataLogger IoT mounted in an enclosure, you can also attach an external boot or reset switch too. Any Single Pole Normally-Open Push-To-Close momentary switch will do. Solder pin headers or wires to the RST and GND breakout pins and connect your external switch to those.

+
    +
  • RESET: Pressing this button will pull the pin LOW and reset the program running on the ESP32 without unplugging the board.
  • +
  • BOOT: The boot button usually allows users to force the ESP32 into bootloader mode to manually flash new firmware to the ESP32. The ESP32 will remain in this mode until there is a power cycle or the reset button is pressed. As of firmware v01.00.02, this button has an extra function: pressing down on the user button for 20 seconds will erase on-board preference storage and restart the board. This is connected to pin 0 on the ESP32.
  • +
+
+ + + + + +
Reset and Boot Buttons (and associated PTHs) Highlighted on the DataLogger IoT - 9DoF (Top View)Reset and Boot Buttons (and associated PTHs) Highlighted on the DataLogger IoT - 9DoF (Bottom View)
+
+ +

Like other ESP32 development boards, these buttons are populated so that users can place the ESP32 module in bootloader mode. For users that need to place the board in bootloader mode when powered, you will need to:

+
    +
  • Press the BOOT button.
  • +
  • While holding on the BOOT button, press the RESET button momentarily.
  • +
  • Finally, release the BOOT button.
  • +
+

Most of the time, users will simply have the board executing the firmware that is loaded on the ESP32 module and updating through the configuration menu either through the microSD card or OTA.

+
+

Danger

+

Please think very carefully before uploading any Arduino sketches to your DataLogger IoT.

+

You will overwrite the DataLogger IoT firmware, leaving it unable to update or restore itself.

+

The DataLogger IoT comes pre-programmed with amazing firmware which can do so much. It is designed to be able to update itself and restore itself if necessary. But it can not do that if you overwrite the firmware with any Arduino sketch. It is just like erasing the restore partition on your computer hard drive. Do not do it - unless you really know what you are doing.

+

Really. We mean it.

+
+

LEDs

+

There are three LEDs populated on the board. These can be disabled with their respective jumpers on the back of the board.

+
    +
  • STAT: The status LED is connected to pin 25.
  • +
  • RGB: The WS2812-2020 RGB addressable LED is connected to pin 26. In addition to being disabled through the jumper on the back, you can also disable the LED through software. The following colors represent different states that the board is in.
      +
    • White: A solid white LED indicates that the board is currently being configured through the configuration menu.
    • +
    • Green: A solid green LED indicates that the board is initializing. As of firmware v01.00.02, the LED blinks green when on battery power indicating that the battery level is VBATT > 50%.
    • +
    • Blue: A blinking blue LED indicates that the board is reading sensor data and logging the values.
    • +
    • Yellow: A solid yellow LED indicates that a firmware update is in progress. As of firmware v01.00.02, the LED blinks yellow when on battery power indicating that the battery level is between 50% > VBATT > 10%.
    • +
    • Red: As of firmware v01.00.02, the LED blinks red when on battery power indicating that the battery level is VBATT < 10%.
    • +
    +
  • +
  • CHG: The on-board yellow CHG LED can be used to get an indication of the charge status of your battery. Below is a table of other status indicators depending on the state of the charge IC.
  • +
+
+ + + + + + + + + + + + + + + + + + + + + +
Charge StateLED status
No BatteryFloating (should be OFF, but may flicker)
ShutdownFloating (should be OFF, but may flicker)
ChargingON
Charge CompleteOFF
+
+ +
+ + + + + +
LEDsPTHS connected to LEDs
+
+ +
+

Note

+

On the DataLogger IoT, we included the B3DQ3BRG addressable RGB LED instead of the WS2812 with the light emitting from the top of the IC. This side emitting LED uses the same protocol as the WS2812 and was a design choice for users placing the board in an enclosure.

+

+ + + + +
side emitting B3DQ3BRG Addressable RGB LED
+

+
+

Jumpers

+

There are seven jumpers on the back of the DataLogger IoT - 9DoF. For more information, check out our tutorial on working with jumper pads and PCB traces should you decide to cut the traces with a hobby knife.

+
    +
  • SHLD: This jumper connects the USB Type C connector's shield pin to GND. Cut this to isolate the USB Type C connector's shield pin.
  • +
  • I2C: This three way jumper labeled as I2C are closed by default. By cutting the jumpers, it will disconnect the 2.2kΩ pull-up resistors for the I2C bus. Most of the time you can leave these alone unless your project requires you to disconnect the pull-up resistors.
  • +
  • STAT: This jumper connects the status LED to pin 25 and it is closed by default. Open the jumper to disable the LED.
  • +
  • RGB: This jumper connects the WS2812-2020 RGB addressable LED to pin 26 and it is closed by default. Open the jumper to disable the LED.
  • +
  • CHG: This jumper connects the charge LED on the MCP73831 charge IC and it is closed by default. Open the jumper to disable the LED.
  • +
  • IMU INT2: This jumper connects the ISM330DHCX IMU's interrupt pin to pin 35 and it is open by default. Add a solder jumper to connect.
  • +
  • MAG INT: This jumper connects the MMC5983MA magnetometer's interrupt pin to pin 35 and it is open by default. Add a solder jumper to connect.
  • +
+
+ + + + +
Jumpers
+
+ +
+

Note

+

On the DataLogger IoT, the IMU INT2 or MAG INT jumpers are not included since it does not have the built in 6DoF IMU or magnetometer. With the extra real estate on the board, we were able to include a MEAS PTH and jumper on the board. By default, the jumper is closed. You can cut this jumper on the bottom side of the board to measure the DataLogger IoT’s current draw from external power.

+

+ + + + + +
MEAS Jumper on the DataLogger IoT (Top View)MEAS Jumper on the DataLogger IoT (Bottom View))
+

+
+

Board Dimensions

+

The overall length and width with the antenna connector is about 1.66" x 2.00". There are four mounting holes in the center of the board. Due to the size of the board and the ESP32 module, the mounting holes are positioned in a way for users to add two Qwiic enabled boards with a width of 1.0" instead of one Qwiic board.

+
+ + + + + + + + + +
+ DataLogger IoT - 9DoF Board Dimensions + + DataLogger IoT Board Dimensions +
DataLogger IoT - 9DoFDataLogger IoT
+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/img/Applications_DataLogger_Banner.jpg b/img/Applications_DataLogger_Banner.jpg new file mode 100644 index 0000000..65a59df Binary files /dev/null and b/img/Applications_DataLogger_Banner.jpg differ diff --git a/img/datalogger_banner - Old.png b/img/datalogger_banner - Old.png new file mode 100644 index 0000000..97a5018 Binary files /dev/null and b/img/datalogger_banner - Old.png differ diff --git a/index.html b/index.html new file mode 100644 index 0000000..c09e823 --- /dev/null +++ b/index.html @@ -0,0 +1,14 @@ + + + + + + Redirecting... + + + + + +You're being redirected to a new destination. + + diff --git a/introduction/index.html b/introduction/index.html new file mode 100644 index 0000000..52679b2 --- /dev/null +++ b/introduction/index.html @@ -0,0 +1,2132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Introduction - SparkFun DataLogger IoT Hookup Guide + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + + + + + + + + + + +
+ +
+ + + + +
+
+ + + +
+
+
+ + + + + + + + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +

Introduction

+ +

The SparkFun DataLogger IoT is a data logger that comes preprogrammed to automatically log IMU, GPS, and various pressure, humidity, and distance sensors. All without writing a single line of code! They come in two flavors: The SparkFun DataLogger IoT - 9DoF and the SparkFun DataLogger IoT. Both versions of the DataLogger IoT automatically detects, configures, and logs Qwiic sensors. It was specifically designed for users who just need to capture a lot of data to a CSV or JSON file, and get back to their larger project. Save the data to a microSD card or send it wirelessly to your preferred Internet of Things (IoT) service!

+ +
+ + + + + +
+ + + +
+
+ +

Required Materials

+
+

Battery Polarity

+

Please make sure that you use one of our recommended Lithium Ion batteries. Some batteries use the same JST connector as ours but have the opposite polarity. Connecting one of these to your DataLogger IoT will destroy it. If you are going to use your own battery, it is up to you to ensure it has the correct polarity.

+
+

To follow along with this tutorial, you will need the following materials. You may not need everything though depending on what you have. Add it to your cart, read through the guide, and adjust the cart as necessary.

+ + +

The Sensors

+

Straight out of the box anti-static bag, the DataLogger IoT is ready to log data from its built-in ISM330DHCX Inertial Measurement Unit (IMU) and MMC5983MA magnetometer. Only want to log magnetometer, accelerometer, gyro or temperature data? You’re good to go! But the fun is only just beginning…

+

The DataLogger IoT is preprogrammed to automatically log data from all of the following sensors, so you may wish to add one or more of these to your shopping cart too. (More sensors are being added all the time and it is really easy to upgrade the DataLogger IoT to support them. But we'll get to that in a moment!). Currently, auto-detection is supported on the following Qwiic-enabled products (with the exception of the ISM330DHCX and MMC5983 which is built-in on the SPI port):

+
+

Note

+

For a list of supported devices based on the firmware, you can check out the list of supported Qwiic Devices in the appendix. We simply categorized the supported devices below based on the type.

+
+
    +
  • Any u-Blox GNSS Modules (Lat/Long, Altitude, Velocity, SIV, Time, Date) such as: +
  • +
  • Inertial Measurement Unit (Accelerometer and Gyro):
      +
    • ISM330DHCX IMU (Built-in for the 9DoF version via SPI)
    • +
    +
  • +
  • Magnetometer:
      +
    • MMC5983 (Built-in for the 9DoF version via SPI)
    • +
    +
  • +
  • Distance: +
  • +
  • Pressure, Altitude, Humidity and Temperature Data: +
  • +
  • Air Quality and Environmental Sensors:
      +
    • CCS811 Air Quality (CO2 and VOC)
    • +
    • ENS160 Indoor Air Quality (AQI, eCO2, and TVOC)
    • +
    • PASCO2V01 Air Quality (CO2)
    • +
    • SGP30 Air Quality (TVOC, CO2, H2, Ethanol)
    • +
    • SGP40 Air Quality (VOC, Humidity, Temperature)
    • +
    • SCD30 CO2, Humidity, and Temperature
    • +
    • SCD40 CO2, Humidity, and Temperature
    • +
    • BME680 Air Quality (Pressure, Humidity, Temperature, Gas, VOCs)
    • +
    • BME688 Air Quality (Pressure, Humidity, Temperature, Gas, VOCs, VSC, CO, Gas)
    • +
    • FS3000 Air Velocity
    • +
    • SEN54 Environmental Sensor Node (Particle, VOC, Humidity, and Temperature)
    • +
    • STC31 CO2 and Temperature sensor
    • +
    • VEML6075 UV
    • +
    • VEML7700 Ambient Light and Lux
    • +
    • OPT4048DTSR Tristimulus Color
    • +
    • AS7265x Triad Spectroscopy
    • +
    +
  • +
  • Temperature: +
  • +
  • Power: +
  • +
  • Real Time Clock: +
  • +
  • NFC/RFID: +
  • +
  • Weight:
      +
    • NAU7802 Qwiic Scale Load Cell Amplifier
    • +
    +
  • +
  • Miscellaneous: +
  • +
  • Analog Voltage:
      +
    • ADS1015 12-bit 4-channel Differential ADC
    • +
    • ADS122C04 24-bit Differential ADC found on the PT100
    • +
    +
  • +
+

Suggested Reading

+

If you aren't familiar with the Qwiic system, we recommend reading here for an overview.

+
+ + + + + + + +
+
Qwiic Connect System +
+
+
+ +

If you aren’t familiar with the following concepts, we also recommend checking out a few of these tutorials before continuing.

+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/javascript/index.html b/javascript/index.html new file mode 100644 index 0000000..6239911 --- /dev/null +++ b/javascript/index.html @@ -0,0 +1,1620 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + javascript directory - SparkFun DataLogger IoT Hookup Guide + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + + + + + + + + + + +
+ +
+ + + + +
+
+ + + +
+
+
+ + + + + + + + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +

javascript directory

+

This folder should contain the files for the custom javascript that is enabled in the product documentation

+ + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/javascript/mathjax.js b/javascript/mathjax.js new file mode 100644 index 0000000..4bd5221 --- /dev/null +++ b/javascript/mathjax.js @@ -0,0 +1,19 @@ +window.MathJax = { + tex: { + inlineMath: [["\\(", "\\)"]], + displayMath: [["\\[", "\\]"]], + processEscapes: true, + processEnvironments: true, + // No Equation Numbering + tags: 'none' + }, + options: { + ignoreHtmlClass: ".*|", + processHtmlClass: "arithmatex" + } + }; + + document$.subscribe(() => { + MathJax.typesetPromise() + }) + \ No newline at end of file diff --git a/js/timeago.min.js b/js/timeago.min.js new file mode 100644 index 0000000..a8530a5 --- /dev/null +++ b/js/timeago.min.js @@ -0,0 +1,2 @@ +/* Taken from https://cdnjs.cloudflare.com/ajax/libs/timeago.js/4.0.2/timeago.min.js */ +!function(s,n){"object"==typeof exports&&"undefined"!=typeof module?n(exports):"function"==typeof define&&define.amd?define(["exports"],n):n((s=s||self).timeago={})}(this,function(s){"use strict";var a=["second","minute","hour","day","week","month","year"];function n(s,n){if(0===n)return["just now","right now"];var e=a[Math.floor(n/2)];return 1=m[t]&&t=m[e]&&e 0) { + var locale = getLocale(nodes[0]); + timeago.render(nodes, locale); + } + }) +} else { + var nodes = document.querySelectorAll('.timeago'); + if (nodes.length > 0) { + var locale = getLocale(nodes[0]); + timeago.render(nodes, locale); + } +} diff --git a/prepare_your_microsd_card/index.html b/prepare_your_microsd_card/index.html new file mode 100644 index 0000000..effc816 --- /dev/null +++ b/prepare_your_microsd_card/index.html @@ -0,0 +1,1928 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Preparing Your MicroSD Card - SparkFun DataLogger IoT Hookup Guide + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + + + + + + + + + + +
+ +
+ + + + +
+
+ + + +
+
+
+ + + + + + + + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +

Preparing Your MicroSD Card

+ +

Not all microSD cards are created equal. The capacity, read/write speed, and format vary depending on the manufacturer. In order to log data to the microSD card, you will need to ensure that your memory card is formatted as FAT32. You can also use FAT16. If the memory card is formatted as a different memory card, the DataLogger IoT will not be able to recognize the microSD card.

+

Checking MicroSD Card Format

+

While you can simply insert the microSD card into your DataLogger IoT and start logging, there may be a chance that the it will not recognize the memory card due to the format.

+

Checking MicroSD Card Format - Windows

+

To check to see if it is the correct format on a Windows you could head to the drive, right click, and select Properties.

+
+ + microSD Card Properties +
+ +

Once the properties are open, you should be able to tell what file system that the memory card uses. In this case, it was exFAT which is not compatible with the DataLogger IoT. You will need to reformat the memory card since it is not formatted as FAT32.

+
+ + Check File System Windows +
+ +

Checking MicroSD Card Format - macOS

+

To check to see if it is the correct format on a macOS, you could head to the drive on your desktop. Then right click, and select Get Info.

+
+ + Get Info on MicroSD Card +
+ +

A window will pop up indicating the microSD card properties. Under General: > Format:, you should be able to tell what file system that the memory card uses. In this case, it was exFAT which is not compatible with the DataLogger IoT. You will need to reformat the memory card since it is not formatted as FAT32.

+
+ + exFAT +
+ +

Download Raspberry Pi Imager

+

There are a few methods and programs available to reformat your microSD card as a FAT32. We found it easier to use the Raspberry Pi Imager Tool. Of course, you will only be using the tool to erase the contents of the microSD card and formatting it as a FAT32 system. You will not actually flash any image to the memory card. Click on the button below to download the tool from the Raspberry Pi Foundation. It is supported on Windows, macOS, and Ubuntu for x86.

+ + +

Formatting as FAT32 using the Raspberry Pi Imager

+

After downloading and installing the software, open the Raspberry Pi Imager.

+
+ + Raspberry Pi Imager +
+ +

Under "Operating System", select "Erase" to "format card as FAT32."

+
+ + Raspberry Pi Imager - Erase : Format as FAT32 +
+ +

Under "Storage", select the drive that the microSD card appeared as on your computer.

+
+ + Raspberry Pi Imager - Select Storage Drive +
+ +

When ready, select "Write". After a few minutes, the microSD card should be formatted with FAT32.

+
+ + Raspberry Pi Imager - Write +
+ +

Once the memory card has finished formatting, eject the microSD from your computer. To check to see if the microSD card is formatted as FAT32, you can check its properties as explained earlier with your operating system. Below shows a screenshot from a Windows and macOS showing that the microSD card reformatted as a FAT32 file system.

+
+ + + + + +
+ Check File System Windows + Check File System macOS
+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/relnotes/index.html b/relnotes/index.html new file mode 100644 index 0000000..9725cb5 --- /dev/null +++ b/relnotes/index.html @@ -0,0 +1,1622 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + Latest DataLogger IoT Firmware Release Notes - SparkFun DataLogger IoT Hookup Guide + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + + + + + + + + + + +
+ +
+ + + + +
+
+ + + +
+
+
+ + + + + + + + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +

Latest DataLogger IoT Firmware Release Notes

+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/relnotes/res/v010101_aiot.png b/relnotes/res/v010101_aiot.png new file mode 100644 index 0000000..6f19643 Binary files /dev/null and b/relnotes/res/v010101_aiot.png differ diff --git a/relnotes/res/v010200_startup.png b/relnotes/res/v010200_startup.png new file mode 100644 index 0000000..d6577fe Binary files /dev/null and b/relnotes/res/v010200_startup.png differ diff --git a/relnotes/res/v010200_webI.png b/relnotes/res/v010200_webI.png new file mode 100644 index 0000000..542e088 Binary files /dev/null and b/relnotes/res/v010200_webI.png differ diff --git a/relnotes/rn_v010101/index.html b/relnotes/rn_v010101/index.html new file mode 100644 index 0000000..d636851 --- /dev/null +++ b/relnotes/rn_v010101/index.html @@ -0,0 +1,1751 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + Rn v010101 - SparkFun DataLogger IoT Hookup Guide + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + + + + + + + + + + +
+ +
+ + + + +
+
+ + + +
+
+
+ + + + + + + + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +

Rn v010101

+ +

SparkFun DataLogger Iot Firmware - Version 1.1

+

November 11th, 2023

+

Today we’re releasing the latest firmware update for our line of DataLogger IoT products. While we’ve released a services of small defect correct releases over the base 6 months, this release, officially version v1.01.00, is the first to include major new functionality.

+

And with the release, we feel we’ve covered all aspects of a 1.1 release – new functionality, feature enhancements, and a wide variety of bug fixes.

+

New Feature – Arduino IoT Cloud Connectivity

+

Being an IoT focused product, one of our goals is to enable easy access to leading IoT data services. With this in mind, for this release we worked closely with the Arduino Team to enabled easy access to the Arduino IoT Cloud directly from a DataLogger IoT Device.

+

Setup a device in the Arduino IoT Cloud, add the credentials to your DataLogger IoT and you have a IoT based dashboard up and running in minutes. Want to add an additional device to your dashboard – add the device to the DataLogger IoT and the combined system automatically adds the additional data to the Arduino Cloud – ready for use in your dashboard.

+

DataLogger Iot Data on Arduino IoT Cloud

+

The above image shows data from a DataLogger IoT with an Environmental Combo attached to it and mapping data to a plot graphic on an Arduino IoT Cloud dashboard.

+

And like all DataLogger IoT functionality, no programming is required to produces these plots – all settings are provided via the intuitive serial console menu interface or set via a settings JSON file.

+

With the combination of the DataLogger IoT, qwiic and Arduino IoT Cloud, creating a IoT dashboard is as easy as Plug-in, Connect, Plot!

+

New qwiic Sensor Board Support

+

The original release of the DataLogger IoT firmware supported around 50 SparkFun qwiic development boards and with this release we’ve added six boards additional qwiic boards.

+

Selecting from our recently added qwiic products as well as from the list of popular products, the release of version 1.1 adds support for the following qwiic development boards.

+
    +
  • SparkFun Indoor Air Quality Sensor - ENS160
  • +
  • SparkFun Photoacoustic Spectroscopy CO2 Sensor - PASCO2V01
  • +
  • SparkFun Human Presence and Motion Sensor - STHS34PF80
  • +
  • SparkFun Tristimulus Color Sensor - OPT4048DTSR
  • +
  • SparkFun Triad Spectroscopy Sensor - AS7265x
  • +
  • SparkFun 6DoF IMU Breakout - BMI270
  • +
+

Like previously supported qwiic boards, the DataLogger IoT firmware automatically detects when a device is connected, making the new device automatically available to the logging, menu and IoT systems of the DataLogger IoT.

+

All these new sensors are great adds on their own, but the ENS160 has the additional feature of supporting temperature and humidity calibration inputs. Connect an BME280 or an SHTC3 qwiic board along with an ENS160, and the DataLogger automatically connects the two devices!

+

Feature Improvements

+

In addition to the new functionality, we also took input from our customers (and our own use) to expand and enhance existing features. While a wide variety of small additions were made, a few notable additions include:

+

Improved Reference Clock Management – the internal logic was reviewed and improved. Additionally, time zone support was moved out of the NTP system, and into the overall clock management subsystem.

+

Internal JSON Buffer Limits – The initial firmware had fixed sizes for the internal JSON data buffers used for saving settings and log data output. These values are now user settable in the settings menu. This allows for larger buffer support, as well as reducing the internal buffers to what you need.

+

Add Device IDs to the Device Names – As you add more devices to a DataLogger, it’s difficult to match the device to the actual devices address. Addresses are now included in device listings and the option to include the address in the device name is now available.

+

Board Information in the Log Stream – To improve identification of a data source within the data log stream, the Board Name and Board ID can now be added if desired. As part of this functionality, a configurable name is now available for each DataLogger IoT.

+

Bug Fixes

+

We also squashed a variety of defects in the firmware. Some of the more notable issued fixed in this release:

+
    +
  • Incorporated a fix in the RV8803 RTC Clock Arduino Library that resolved force ti* shifts based on time zone. This was a “real nasty one” to resolve!
  • +
  • Improved runtime memory (ram) consumption
  • +
  • Resolved issues with devices having the same name. Now the second device adds it* address to the name.
  • +
  • Improvements to the Machinechat IoT driver
  • +
+

In Summary

+

With the release of DataLogger IoT firmware version 1.1.0 we continue to enhance the capabilities of our DataLogger IoT line – adding to our IOT service, supported devices as well as improving the overall quality of the system.

+

And this new functionality is available today at the DataLogger repo. The update is free, available as an over-the-air upgrade, or as a file uploaded via an SD Card. Just select the “System Update” option within the DataLogger IoT menu system and select your desired upgrade option.

+ + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/relnotes/rn_v010200/index.html b/relnotes/rn_v010200/index.html new file mode 100644 index 0000000..aa1d03d --- /dev/null +++ b/relnotes/rn_v010200/index.html @@ -0,0 +1,1934 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + Rn v010200 - SparkFun DataLogger IoT Hookup Guide + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + + + + + + + + + + +
+ +
+ + + + +
+
+ + + +
+
+
+ + + + + + + + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +

Rn v010200

+ +

SparkFun DataLogger IoT Firmware - Version 1.2

+

April 15th, 2024_

+

With the release of our version 1.2 software for our DataLogger IoT products, we continue to add additional functionally to the products capability, as well as fix a number of issues.

+

And with the release, we feel we’ve covered all aspects of a 1.1 release – new functionality, feature enhancements, and a wide variety of bug fixes.

+

New Features and Enhancements

+

Log File Download via a Web Interface

+

To allow access to log files located on the DataLogger IoT device, without requiring the removal of the SD card, a new Web Interface is provided. Once enabled, you can browse the on-board log files of the DataLogger. Clicking on a a filename will download the file.

+

Web Interface

+

Currently file browse and download options are available, but we plan on expanding this feature in the future.

+

Additionally, this feature has the following options:

+
    +
  • mDNS functionality allowing you to set a network name for a device if mDNS is supported on your network
  • +
  • Username/Password authentication for the web interface.
  • +
+
+

Note: For authentication use - currently some browsers might require a second login depending on settings.

+

Note: +The datalogger requires restarting if the web interface is enabled

+
+

This feature is enabled in settings under the Preview heading.

+

Startup Command Menu and Delay

+

To allow start-up time configuration and delay, a Startup Menu was added to the system. Now, at startup a short menu is presented for a brief period, allowing modification of the startup options of the DataLogger.

+

Startup Menu options:

+

Pressing the highlighted letter while the menu is active, will change the behavior of the system. This change only affects the current system session.

+

The options include:

+
    +
  • 'n' - Normal startup
  • +
  • 'a' - Disable I2C device auto load on startup
  • +
  • 'l' - List the I2C devices supported. This device table is discarded after auto-load
  • +
  • 'w' - Disable WiFi
  • +
  • 's' - Disable preference restore during startup
  • +
+

In addition, the amount of time the menu is displayed is adjustable. This settings is on the Settings/Application Settings page, under the Advanced section.

+

Quick (!) Commands

+

The addition of a quick (!) command system that allows for the direct execution of commands directly from the serial console, bypassing the serial menu system.

+

An example of this is the display of the "about" page for the system. Normally this would require navigating the serial menu system. With the quick command system, entering the value of "!about" at the serial console will display the about page.

+

The following commands are supported:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
commandDescription
!aboutDisplay the system about page
!clear-settingsClear the on board system preferences with a yes/no prompt
!clear-settings-forcedClear the on board system preferences with no prompt
!devicesList the currently connected devices
!factory-resetPerform a factory reset - presents a Y/N prompt
!heapDisplay the current system heap memory usage
!helpList the available quick commands
!json-settingsFor setting the device settings via a serial connection. When this command is sent, the system expects to receive a JSON settings file
!log-nowPerform a log observation event
!log-rateIf log rate measurement is enabled, the current log rate is printed
!reset-deviceReset the device - erasing any saved settings and restarting the device
!reset-device-forcedReset the device, but without a Y/N prompt
!restartRestart the device
!restart-forcedRestart the device without a Y/N prompt
!save-settingsSave the current settings to on-board flash
!sdcardOutput the current SD card usage statistics
!systimeOutput current system time
!uptimeThe uptime of the device
!device-idThe ID for the device
!versionThe version of the firmware
!wifiOutput current system WiFi state
+

Log Data Rate

+

The DataLogger system can now measure the data logging rate. Once this feature is enabled, the system will monitor the time between log events. This value is averaged over the latest 10 log events.

+

System Info in the log stream

+

The system operational parameters can now be added to log stream. This is useful to monitor system resource uses over time, or just perform general debugging.

+

Currently the following information is provided:

+
    +
  • WiFi SSID
  • +
  • WiFi RSSI
  • +
  • Memory Heap free space in bytes
  • +
  • SD Card free space in bytes
  • +
  • Uptime in MS
  • +
+

Feature Improvements

+

In addition to the new functionality, we also took input from our customers (and our own use) to expand and enhance existing features. While a wide variety of small additions were made, a few notable additions include:

+

Serial Console - Value Display – The serial console now shows the current setting value in the menu system. Previously this value was only show once that item was selected.

+

Serial Console Color – Text highlighting and color were added to the serial console output. If your serial console application/command supports it, the menu system highlights key values. This setting is controlled in the Settings/Application Settings section of the settings menu.

+

Startup Messages – Normally a verbose log of startup options and settings are displayed at system startup. The about of information is now controllable - with values of Normal, Compact, Disabled.

+

Improved Device Auto-Load – A major update to the I2C auto-load device detection logic that improves device detection and address collision prevention.

+

General System Enhancements – Internal system job dispatch subsystem update to increase performance throughput. Overall decrease in static and dynamic memory usage.

+

Bug Fixes

+

We also squashed a variety of defects in the firmware. Some of the more notable issued fixed in this release:

+
    +
  • Fixed issue with the LED display logic that caused a system crash if the log interval was less than 100ms
  • +
  • Incorporate driver updates for greater NAU7802 device output value stability
  • +
  • Incorporate driver update for the MMC5983MA device
  • +
+

In Summary

+

With the release of DataLogger IoT firmware version 1.2.0 we continue to enhance the capabilities of our DataLogger IoT line – adding to our IOT service, supported devices as well as improving the overall quality of the system.

+

And this new functionality is available today at the DataLogger repo. The update is free, available as an over-the-air upgrade, or as a file uploaded via an SD Card. Just select the “System Update” option within the DataLogger IoT menu system and select your desired upgrade option.

+ + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/resources/index.html b/resources/index.html new file mode 100644 index 0000000..f9c233c --- /dev/null +++ b/resources/index.html @@ -0,0 +1,1737 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Resources - SparkFun DataLogger IoT Hookup Guide + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ + + + + + +
+ + + + + + + + + + + +
+ +
+ + + + +
+
+ + + +
+
+
+ + + + + + + + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +

Resources

+ +

Now that you've successfully got your DataLogger IoT up and running, it's time to incorporate it into your own project! For more information, check out the resources below:

+ +

Or check out these related blog posts.

+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/search/search_index.json b/search/search_index.json new file mode 100644 index 0000000..8e90746 --- /dev/null +++ b/search/search_index.json @@ -0,0 +1 @@ +{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"],"fields":{"title":{"boost":1000.0},"text":{"boost":1.0},"tags":{"boost":1000000.0}}},"docs":[{"location":"","title":"Home","text":"

Placeholder file for index redirect functionality.

"},{"location":"configuration/","title":"Configuration","text":"

Configuring the settings is as easy as opening a serial menu. You can use any serial monitor or terminal emulator to quickly and easily change and store the DataLogger IoT settings via its USB-C interface.

There are plenty of free alternatives out there to configure the DataLogger IoT. For the scope of this tutorial we will be using Tera Term.

  • Tera Term (Windows)
  • RealTerm (Windows)
  • Minicom (Linux, Unix, MacOS)
  • Screen (Linux, Unix, MacOS)

Note

You will need a serial terminal client that supports edit characters. Most if not all modern serial terminal programs will have the ability to support interactive edits. Unfortunately, we have not had any success with CoolTerm. We have tested the DataLogger IoT with Tera Term, Minicom, and Screen.

If this is the your first time using a terminal window, We recommend checking out the tutorial below for more information on serial terminal basics.

Serial Terminal Basics

The above guides will show you how to open the correct port for the DataLogger IoT and how to set the baud rate to 115200 baud. You can change the DataLogger IoT's baud rate through the configuration menus too should you need to.

Note

For users with an Arduino IDE, you could also use the Arduino Serial Monitor by setting the line ending to Newline. Users will also need to CTRL + Enter when sending any character to the DataLogger IoT. However, we recommend using one of the terminals mentioned earlier.

"},{"location":"configuration/#initialization-and-serial-output","title":"Initialization and Serial Output","text":"

Connect the DataLogger IoT to a USB cable and connect to your computer. The addressable RGB LED will light up green as it initializes. As of firmware v1.0.2.00 - build 00013e, a Startup Menu was added to the system. This allows you to change the behavior of the DataLogger at start-up. This change only affects the current system session.

  • 'n' \u2014 Normal startup
  • 'a' \u2014 Disable I2C device auto load on startup
  • 'l' \u2014 List the I2C devices supported. This device table is discarded after auto-load
  • 'w' \u2014 Disable WiFi
  • 's' \u2014 Disable preference restore during startup

Note

The amount of time the start-up menu is displayed is adjustable. This settings can be configured in the Settings/Application Settings page, under the Advanced section.

You should see the following output when the board initializes:

The messages in the serial terminal provide us with the DataLogger's configuration and will vary depending on the firmware version that is loaded on the board.

  • The DataLogger IoT software version (in this case is v01.02.00 - build 00013e).
  • As the DataLogger IoT is initializing, the system settings are being restored from the last saved preference.
  • There no WiFi credentials and the board has failed to connect. This output will change once you provide the WiFi credentials and are able to connect to the network.
  • There are 3x devices currently detected and they are connected through I2C through the Qwiic port and SPI. These are the on-board sensors for the DataLogger IoT. There may be more devices that are detected depending on the firmware and what is connected to the ports. Since these were recognized, they were loaded onto the DataLogger IoT.
  • The current date and time is shown (by default), the date and time is set to 1-1-1970 and 00:00:00). This value will change depending on the clock source through NTP, RTC, or a u-blox GNSS module.
  • The time the board has been running will be shown in the uptime.
  • The primary external time source that the board syncs is currently through the NTP client. This can be configured depending on your clock source.
  • The board name (in this case, it was SparkFun DataLogger IoT - 9DoF)
  • The board ID (in this case, it was SFD16C8F0D1AD6B8)
  • The microSD card has been found, the type of memory card it is, the size of the memory card, how much memory is used, and how much is available.
  • If there is a WiFi network name saved, the SSID will be shown along with information indicating whether the board was able to connect to the WiFi network. By default there is no SSID saved in memory.
  • If there is a battery connected, the LiPo Battery Fuel Guage will indicate if there is one attached to the board.
  • Parameters for low power mode will be provided indicating if deep sleep is enabled, sleep interval, and wake interval.
  • Parameters for logging are also provided for the logging interval, the format for the serial output, format for the microSD card, current saved filename, and file rotation period.
  • The board will also show the available IoT services that are enabled for the DataLogger IoT.
  • Current settings to download log files via a web interface (included in firmware v01.02.00)
  • Supported devices through Qwiic or SPI will be listed if they are connected.
  • The output will finish by telling you what devices are connected to the DataLogger IoT again.

Note

As of firmware v01.02.00, there is also a compact mode! By adjusting the setting, the ESP32 will output less at startup. This settings can be configured in the Settings/Application Settings page, under the Advanced section.

Once the DataLogger IoT has initialized, the DataLogger IoT will begin outputting comma separated values (CSV). This is the default output that is set for the DataLogger IoT - 9DoF. Of course, you will not have as many readings on the DataLogger IoT since the 6DoF IMU and magnetometer are not populated on that version of the board.

Note

Depending on your DataLogger IoT preferences, your device may output as a JSON format like the image shown below.

The data scrolling up the screen show what each device's output is along with their associated unit if it is available. Your mileage will vary depending on the board version that you have and what device is connected:

  • MAX17048.Voltage (V)
  • MAX17048.State of Charge (%)
  • MAX17048.Charge Rate (%/hr)
  • ISM330.Accel X (milli-g)
  • ISM330.Accel Y (milli-g)
  • ISM330.Accel Z (milli-g)
  • ISM330.Gyro X (milli-dps)
  • ISM330.Gyro Y (milli-dps)
  • ISM330.Gyro Z (milli-dps)
  • ISM330.Temperature (C)
  • MMC5983.X Field (Gauss)
  • MMC5983.Y Field (Gauss)
  • MMC5983.Z Field (Gauss)
  • MMC5983.Temperature (C)

The output will vary depending on what is connected so you may get additional readings in the output and it may not be in the following order listed above. The logging rate defaults to about 0.067Hz (or 15000ms), so as the data scrolls past, you will see the last value settle at about 0.067Hz.

"},{"location":"configuration/#main-menu","title":"Main Menu","text":"

Right! Let's open the main menu by pressing on any key in the serial terminal program.

You will be prompted with a few options. Once in the configuration menu, all three colors of the addressable RGB LED will turn on to produce the color white indicating that you are navigating through the menu. Before we dive into the settings, lets check out a few commands and saving settings.

"},{"location":"configuration/#quick-command-reference","title":"Quick (!) Command Reference","text":"

As of firmware v01.02.00, commands can be executed directly from the serial console thus bypassing the serial menu system! The following commands are supported.

Quick Command Command Description !about Display the system about page !clear-settings Clear the on board system preferences with a yes/no prompt !clear-settings-forced Clear the on board system preferences with no prompt !devices List the currently connected devices !factory-reset Perform a factory reset - presents a Y/N prompt !heap Display the current system heap memory usage !help List the available quick commands !json-settings For setting the device settings via a serial connection. When this command is sent, the system expects to receive a JSON settings file !log-now Perform a log observation event !log-rate If log rate measurement is enabled, the current log rate is printed !reset-device Reset the device - erasing any saved settings and restarting the device !reset-device-forced Reset the device, but without a Y/N prompt !restart Restart the device !restart-forced Restart the device without a Y/N prompt !save-settings Save the current settings to on-board flash !sdcard Output the current SD card usage statistics !systime Output current system time !uptime The uptime of the device !device-id The ID for the device !version The version of the firmware !wifi Output current system WiFi state

Typing a quick command and hitting the Enter button will result in the DataLogger IoT executing the command without the need to go through the menu system. Below is an example showing the !about quick command being sent and then executing the command as the DataLogger IoT is outputting CSV values to the serial terminal.

"},{"location":"configuration/#exiting-and-saving","title":"Exiting and Saving","text":"

When exiting the menus, you will be prompted with either an x or b. You can use either character when exiting the menus as well as X or B. Note that you will need to use either of these keys when making a change in order for the DataLogger IoT to save any changes in memory. Make sure that you receive the following message indicating that the settings were saved: [I] Saving System Settings. The DataLogger IoT will the continue reading the devices and outputting the readings through the serial terminal.

"},{"location":"configuration/#cancelling-changes","title":"Cancelling Changes","text":"

You can also use any of your Esc or arrow keys (i.e. \u2191, \u2193, \u2190, \u2192) to exit. However, using the escape or arrow keys will not save any changes in memory once the reset button is hit or whenever power is cycled.

"},{"location":"configuration/#timeout-from-inactivity","title":"Timeout from Inactivity","text":"

The menus will slowly exit out after 2 minutes of inactivity, so if you do not press a key the DataLogger IoT will return to its previous menu. It will continue to move back until it reaches the main menu. After another additional 2 minutes of inactivity, the board will exit begin logging data again. When the menu exits from inactivity, any changes will not be saved in memory as well.

"},{"location":"configuration/#settings","title":"Settings","text":"

Let's start by configuring the DataLogger's system settings. Send a 1 through the serial terminal. You will have the option to adjust various settings ranging from the your preferences, time source to synchronize the date and time, WiFi network, how the device logs data, which IoT service to use, and firmware updates.

Note

You may notice after entering a 1 that there is a slight delay before the DataLogger IoT responds. The delay was added to allow some time for the DataLogger IoT to receive an additional digit for any option greater than 9. If you want to head to option 1 immediately without the slight delay, you can hit the Enter key to enter the Application Settings.

We'll go over each of these options below.

"},{"location":"configuration/#general-application-settings","title":"General: Application Settings","text":"

In the Settings Menu, send a 1 to adjust the Application Settings. As of firmware v01.00.02, users can now adjust the baud rate of the serial console output and the menu system's timeout value.

In the Application Settings Menu, users will be able to configure the addressable RGB's LED through software, menu timeout, microSD card's output format, serial console's output format, terminal's baud rate, deep sleep parameters, and view the current settings of the DataLogger IoT similar to when the board was initialized. Depending on your preference and how you are logging data, you can adjust the data as CSV or JSON.

  • 1 LED Enabled \u2014 Enable/Disable the on-board RGB LED activity
    • Accepts a boolean value:
      • 1 to enable (default)
      • 0 to disable
  • 2 Menu Timeout \u2014 Inactivity timeout period for the menu system
    • Accepts the following values:
      • 1 30 Seconds = 30
      • 2 60 Seconds = 60 (default)
      • 3 2 Minutes = 120
      • 4 5 Minutes = 300
      • 5 10 Minutes = 600
      • b Back
  • 3 Color Output \u2014 Use color output with the Serial console. (added as of firmware v01.02.00)
    • Accepts a boolean value:
      • 1 to enable (default)
      • 0 to disable
  • 4 Board Name \u2014 A specific name for this DataLogger
    • Accepts a string
  • 5 SD Card Format \u2014 Enable and set the output format
    • Accepts the following values:
      • 1 to disable = 0
      • 2 CSV format (default) = 1
      • 3 JSON format = 2
  • 6 Serial Console Format \u2014 Enable and set the output format
    • Accepts the following values:
      • 1 to disable = 0
      • 2 CSV format (default) = 1
      • 3 JSON format = 2
  • 7 JSON Buffer Size \u2014 Output buffer size in bytes
    • Accepts an integer between 100 to 5000 :
      • 1600 bytes (default)
  • 8 Terminal Baud Rate \u2014 Update terminal baud rate. Changes take effect on restart.
    • Accepts an unsigned integer between 1200 to 50000:
      • 115200 (default)
  • 9 Enable System Sleep \u2014 If enabled, sleep the system
    • Accepts a boolean value:
      • 1 to enable
      • 0 to disable (default)
  • 10 Sleep Interval (sec) \u2014 The interval the system will sleep for
    • Accepts an integer between 5 to 86400 :
      • 30 seconds (default)
  • 11 Wake Interval (sec) \u2014 The interval the system will operate between sleep period
    • Accepts an unsigned integer between 60 to 86400 :
      • 120 seconds (default)
  • 12 Startup Messages Level of message output at startup
    • Accepts a value between 1 to 3 :
    • 1 Normal = 0 (default)
    • 2 Compact = 1
    • 3 Disabled = 2
  • 13 Startup Delay Startup Menu Delay in Seconds
    • Accepts a value between 0 to 60 :
      • 2 seconds (default)
  • 14 Device Names Name always includes the device address
    • Accepts a boolean value:
      • 1 to enable (default)
      • 0 to disable
  • 15 About... \u2014 Details about the system
  • b Back

Note

Once the baud rate is changed and saved, make sure to adjust the baud rate of your serial terminal when the board is reset. If you forgot the baud rate, you can hold the BOOT button down for 20 seconds to erase the on-board preferences (besides the baud rate, this also includes any other settings that were saved) and restart the board.

When finished, you will need to exit the menus so that the DataLogger IoT saves the changes. Send a b to exit out this menu, b to exit out of the DataLogger IoT settings, and x to exit out of the main menu.

"},{"location":"configuration/#general-save-settings","title":"General: Save Settings","text":"

In the Settings menu, send a 2 to adjust the Save Settings. As of firmware v01.01.01, the JSON output buffer size is now user configurable. This will be under option \"JSON File Buffer Size\" when in the Save Settings Menu.

In the Save Settings Menu, users will be able to save, restore, or clear any preferences in memory (i.e. persistent storage) or a saved file to a fallback device (i.e. microSD card). Note that any passwords and secret keys are not saved in the save settings file. You will need to manually enter those values in the file saved on the microSD card.

  • 1 Fallback Restore \u2014 If unable to restore settings, use the fallback source (JSON File)
    • Accepts a boolean value:
      • 1 to enable (default)
      • 0 to disable
  • 2 Fallback Save \u2014 Save settings also saves on the fallback storage (JSON File)
    • Accepts a boolean value:
      • 1 to enable
      • 0 to disable (default)
  • 3 JSON File Buffer Size \u2014 The size in bytes used for the internal I/O buffer
    • Accepts an unsigned integer:
    • 6400 (default, as of firmware v01.01.01)
  • 4 Save Settings \u2014 Save current settings to persistent storage
    • Accepts a yes/no:
      • Y or y for yes
      • N or n for no
  • 5 Restore Settings \u2014 Restore saved settings
    • Accepts a yes/no:
      • Y or y for yes
      • N or n for no
  • 6 Clear Settings \u2014 Erase the saved settings on the device
    • Accepts a yes/no:
      • Y or y for yes
      • N or n for no
  • 7 Save to Fallback \u2014 Save System Settings to the fallback storage (JSON File)
    • Accepts a yes/no:
      • Y or y for yes
      • N or n for no
  • 8 Restore from Fallback \u2014 Restore system settings from the fallback storage (JSON File)
    • Accepts a yes/no:
      • Y or y for yes
      • N or n for no
  • b Back

If you have the Fallback Save enabled or selected the option Save to Fallback, you will notice an additional file called datalogger.json saved in the microSD card. This is the fallback file that is saved. Using a text editor, you can edit this file to adjust the settings or provide WiFi credentials, certificates, and keys. You can use option 7 to restore the settings on your DataLogger IoT.

When finished, you will need to exit the menus so that the DataLogger IoT saves the changes. Send a b to exit out this menu, b to exit out of the DataLogger IoT settings, and x to exit out of the main menu.

"},{"location":"configuration/#general-time-sources","title":"General: Time Sources","text":"

Note

Make sure to connect the ESP32-WROOM to a 2.4GHz WiFi network and ensure that is not a guest network that requires you to sign in. Unfortunately, 5GHz WiFi is not supported on the ESP32-WROOM module.

In the Settings Menu, send 3 to manage the time reference sources. As of firmware v01.01.01, time zone support is at the clock level, not tied to NTP. The option to adjust the Time Zone is moved to the Time Sources menu.

In this menu, you will have options to update the primary reference clock, update interval, add a secondary reference clock, and update it's interval. By default, the primary reference clock is set to use the Network Time Protocol (NTP). To synchronization the time, you will need to connect to a 2.4GHz WiFi network in order to update the time. To add a secondary clock, make sure to connect a compatible Qwiic-enabled devices that can keep track of time (i.e. Qwiic Real Time Clock Module - RV-8803 or a Qwiic-enabled u-blox GNSS module).

Note

To adjust the time zone, you will need to enter a POSIX timezone string variable. Try checking out this CSV in this GitHub repo and searching for the timezone string variable in your area. For more information about POSIX format specification check out this article from IBM.

  • 1 The Time Zone \u2014 Time zone setting string for the device
    • Accepts a string:
      • MST7MDT,M3.2.0,M11.1.0 (default, as of firmware v01.01.01)
  • 2 Reference Clock \u2014 The current reference clock source
    • Accepts the following values:
      • 1 for no clock
      • 2 for NTP Client (default)
  • 3 Update Interval \u2014 Main clock update interval in minutes. 0 = No update
    • Accepts an unsigned integer:
      • 0 = No update
      • 60 seconds (default)
  • 4 Enable Clock Fallback \u2014 Use a valid reference clock if the primary is not available
    • Accepts a boolean value:
      • 1 to enable (default)
      • 0 to disable
  • 5 Dependant Interval \u2014 Connected depedant clock update interval in minutes. 0 = No update
    • Accepts an unsigned integer:
      • 0 = No update
      • 60 seconds (default)
  • 6 Update Connected \u2014 Update connected clocks on main clock update
    • Accepts a boolean value:
      • 1 to enable (default)
      • 0 to disable
  • b Back

Note

As an alternative to using the NTP, users can also add a compatible Qwiic-enabled device that can keep track of time (i.e. Qwiic Real Time Clock Module - RV-8803 or a Qwiic-enabled u-blox GNSS module). These can be set as the primary or secondary clock.

Once attached, you will be prompted with additional options to select a primary reference clock.

If you are using a u-blox GNSS module, make sure that you have enough satellites in view. The option to add or configure the GNSS will not be available if there are not enough satellites in view. If you are using the Qwiic Real Time Clock Module - RV-8803, you may need to go into the device settings to manually adjust the date and time.

When finished, you will need to exit the menus so that the DataLogger IoT saves the changes. Send a b to exit out this menu, b to exit out of the DataLogger IoT settings, and x to exit out of the main menu.

"},{"location":"configuration/#network-wifi-network","title":"Network: WiFi Network","text":"

Note

The ESP32-WROOM can only connect to a 2.4GHz WiFi network. Unfortunately, 5GHz is not supported on the ESP32-WROOM module.

In the Settings Menu, send a 4 to configure the WiFi settings. As of firmware v01.00.02, up to 4 sets of WiFi credentials can be saved.

Once you are in the WiFi Network menu, you can enable/disable WiFi and save the WiFi network credentials. Once connected to a 2.4GHz WiFi network, you can synchronize the date and time, connect to an IoT service to log data, and update the latest firmware over-the-air. Since the WiFi is turned on by default, you will simply need to save the WiFi network's name and password.

  • 1 Enabled \u2014 Enable or Disable the WiFi Network connection
    • Accepts a boolean value:
      • 1 to enable (default)
      • 0 to disable
  • 2 Network Name \u2014 The SSID of the WiFi network
    • Accepts a string:
      • For example, if my network name is \"MY_NETWORK_NAME\", you would manually type MY_NETWORK_NAME. When finished hit the ENTER key
  • 3 Password \u2014 The Password to connect to the WiFi network
    • Accepts a string:
      • For example, if my network name is \"MY_SUPER_SECRET_PASSWORD\", you would manually type MY_SUPER_SECRET_PASSWORD. Note that as you type the password, each character will be replaced with an asterisk (*). When finished hit the ENTER key.
  • 4 Network 2 Name \u2014 Alternative network 2 SSID
    • Accepts a string:
      • For example, if my network name is \"MY_NETWORK_NAME_2\", you would manually type MY_NETWORK_NAME_2. When finished hit the ENTER key
  • 5 Network 2 Password \u2014 Alternative network 2 Password
    • Accepts a string:
      • For example, if my network name is \"MY_SUPER_SECRET_PASSWORD_2\", you would manually type MY_SUPER_SECRET_PASSWORD_2. Note that as you type the password, each character will be replaced with an asterisk (*). When finished hit the ENTER key.
  • 6 Network 3 Name \u2014 Alternative network 2 SSID
    • Accepts a string:
      • For example, if my network name is \"MY_NETWORK_NAME_3\", you would manually type MY_NETWORK_NAME_3. When finished hit the ENTER key
  • 7 Network 3 Password \u2014 Alternative network 3 Password
    • Accepts a string:
      • For example, if my network name is \"MY_SUPER_SECRET_PASSWORD_3\", you would manually type MY_SUPER_SECRET_PASSWORD_3. Note that as you type the password, each character will be replaced with an asterisk (*). When finished hit the ENTER key.
  • 8 Network 4 Name \u2014 Alternative network 2 SSID
    • Accepts a string:
      • For example, if my network name is \"MY_NETWORK_NAME_4\", you would manually type MY_NETWORK_NAME_4. When finished hit the ENTER key
  • 9 Network 4 Password \u2014 Alternative network 4 Password
    • Accepts a string:
      • For example, if my network name is \"MY_SUPER_SECRET_PASSWORD_4\", you would manually type MY_SUPER_SECRET_PASSWORD_4. Note that as you type the password, each character will be replaced with an asterisk (*). When finished hit the ENTER key.
  • b Back

When finished, you will need to exit the menus so that the DataLogger IoT saves the changes. Send a b to exit out this menu, b to exit out of the DataLogger IoT settings, and x to exit out of the main menu.

Press the reset button or cycle power to restart the DataLogger IoT. You can also go through the menu and reset the device through software as well. Once the board is reset, the DataLogger will attempt to connect to a WiFi network. If you are successful, the output will indicate that the board connected to a WiFi network and will update the current time through a NTP Client.

Note

If you have a Qwiic Dynamic NFC/RFID Tag connected to the board's Qwiic connector, you can easily update your WiFi credentials! Just make sure to save the WiFi credentials to the tag.

Note

If you saved your preferences to a JSON file on your microSD card's root directory, you can also save your WiFi credentials and load the system settings from the menu as well!

"},{"location":"configuration/#network-ntp-client","title":"Network: NTP Client","text":"

In the Settings menu, send a 5 to adjust the NTP Client settings. As of firmware v01.01.01, time zone support is at the clock level, not tied to the NTP. The option to adjust the Time Zone is moved to the Time Sources menu.

In this menu, users will have the option to enable/disable the NTP client, select the primary/secondary server, or adjust the time zone for your area.

  • 1 Enabled \u2014 Enable or Disable the NTP Client
    • Accepts a boolean value:
      • 1 to enable (default)
      • 0 to disable
  • 2 NTP Server One \u2014 The primary NTP Server to use
    • Accepts a string:
      • time.nist.gov (default)
  • 3 NTP Server Two \u2014 The secondary NTP Server to use
    • Accepts a string:
      • pool.ntp.org (default)
  • b Back

When finished, you will need to exit the menus so that the DataLogger IoT saves the changes. Send a b to exit out this menu, b to exit out of the DataLogger IoT settings, and x to exit out of the main menu.

"},{"location":"configuration/#logging-logger","title":"Logging: Logger","text":"

In the Settings menu, send a 6 to adjust how data is logged.

In the Logger menu, users will have the option to add a timestamp, increment sample numbering, data format, or reset the sample counter. Note that the timestamp is the system clock and syncs with the reference clock that was chosen. Data from the Qwiic-enabled devices that keep track of time can also be included for each data entry by default.

  • 1 Timestamp Mode \u2014 Enable timestamp output and set the format of a log entry timestamp
    • 1 for no timestamp (default) = 0
    • 2 for milliseconds since program start = 1
    • 3 for seconds since Epoch = 2
    • 4 for Date Time - USA Date format = 3
    • 5 for Date Time = 4
    • 6 for ISO08601 Timestamp = 5
    • 7 for ISO08601 Timestamp with Time Zone = 6
  • 2 Sample Numbering \u2014 An incremental count of the current log entry
    • Accepts a boolean value:
      • 1 to enable
      • 0 to disable (default)
  • 3 Numbering Increment \u2014 Increment amount for Sample Numbering
    • Accepts an unsigned integer between 1 to 10000:
      • 1 (default)
  • 4 Output ID \u2014 Include the Board ID in the log output (added as of firmware v01.02.00)
    • Accepts a boolean value:
      • 1 to enable
      • 0 to disable (default)
  • 5 Output Name \u2014 Include the Board Name in the log output (added as of firmware v01.02.00)
    • Accepts a boolean value:
      • 1 to enable
      • 0 to disable (default)
  • 6 Rate Metric \u2014 Enable to record the logging rate data (added as of firmware v01.02.00)
    • Accepts a boolean value:
      • 1 to enable
      • 0 to disable (default)
  • 7 SD Card Format \u2014 Enable and set the output format
    • Accepts an integer:
      • 1 to disable = 0
      • 2 CSV format = 1 (default)
      • 3 JSON format = 2
  • 8 Serial Console Format \u2014 Enable and set the output format
    • Accepts an integer:
      • 1 to disable = 0
      • 2 CSV format = 1 (default)
      • 3 JSON format = 2
  • 9 System Info \u2014 Log system information (added as of firmware v01.02.00)
    • Accepts a boolean value:
      • 1 to enable
      • 0 to disable (default)
  • 10 Reset Sample Counter \u2014 Reset the sample number counter to the provided value
    • Accepts an unsigned integer between 0 to 10000:
      • 0 (default)
  • b Back

When finished, you will need to exit the menus so that the DataLogger IoT saves the changes. Send a b to exit out this menu, b to exit out of the DataLogger IoT settings, and x to exit out of the main menu.

Press the reset button or cycle power to restart the DataLogger IoT. You can also go through the menu and reset the device through software as well. Below is an example with the ISO08601 time that was added to the output.

"},{"location":"configuration/#logging-logging-timer","title":"Logging: Logging Timer","text":"

In the Settings menu, send an 7 to adjust the Logging Timer.

Adjusting the interval for the Logging Timer will change the amount of time between log entries.

  • 1 Interval \u2014 The timer interval in milliseconds
    • Accepts an integer:
      • 15000 milliseconds (default)
  • b Back

When finished, you will need to exit the menus so that the DataLogger IoT saves the changes. Send a b to exit out this menu, b to exit out of the DataLogger IoT settings, and x to exit out of the main menu.

"},{"location":"configuration/#logging-data-file","title":"Logging: Data File","text":"

In the Settings menu, send an 8 to adjust the Logging Data File.

Adjusting these parameters allows you to change the filename prefix, the number the files starts at, and how often the DataLogger will create a new file on the microSD card. For example, the default file will be saved as sfe0001.txt. After 1 day, the DataLogger will rotate files by creating a new file named sfe0002.txt. The DataLogger will begin logging data in this new file. The purpose of this log rotation is to limit the size of each file prevent issues when opening large files.

  • 1 Rotate Period \u2014 Time between file rotation
    • Accepts the following values:
      • 1 for 6 hours = 6
      • 2 for 12 hours = 12
      • 3 for 1 day (24 hours) = 24 (default)
      • 4 for 2 days (48 hours) = 48
      • 5 for 1 week (168 hours) = 168
  • 2 File Start Number \u2014 The number the filename rotation starts with
    • Accepts an unsigned integer:
      • 1 (default)
  • 3 Filename Prefix \u2014 The prefix string for the generated filenames
    • Accepts a string:
      • sfe (default)
  • b Back

When finished, you will need to exit the menus so that the DataLogger IoT saves the changes. Send a b to exit out this menu, b to exit out of the DataLogger IoT settings, and x to exit out of the main menu.

The contents of the file will depend on how the data was saved (either CSV or JSON). Make sure that the SD Card format is enabled to either CSV or JSON with your desired device outputs turned on so that the DataLogger can save the readings.

When removing the microSD card, make sure to remove your power source. Then insert into it into microSD card adapter or USB reader. When connecting the memory card to your computer, you can use a text editor to view the saved readings. In this case, a Windows operating system was viewing the file sfe0000.txt and it was only file available in the microSD card.

"},{"location":"configuration/#iot-services-mqtt-client","title":"IoT Services: MQTT Client","text":"

In the Settings menu, send an 9 to adjust settings for the MQTT Client.

  • 1 Enabled \u2014 Enable or Disable MQTT Client
    • Accepts a boolean value:
      • 1 to enable
      • 0 to disable (default)
  • 2 Port \u2014 The MQTT broker port to connect to
    • Accepts an unsigned integer:
      • 1883 (default)
  • 3 Server \u2014 The MQTT server to connect to
    • Accepts a string
  • 4 MQTT Topic \u2014 The MQTT topic to publish to
    • Accepts a string
  • 5 Client Name \u2014 Name of this device used for MQTT Communications
    • Accepts a string
  • 6 Username \u2014 Username to connect to an MQTT broker, if required.
    • Accepts a string
  • 7 Password \u2014 Password to connect to an MQTT broker, if required.
    • Accepts a string
  • 8 Buffer Size \u2014 MQTT payload buffer size. If 0, the buffer size is dynamic
    • Accepts an unsigned int16:
      • 0 for dynamic buffer size (default)
  • b Back
"},{"location":"configuration/#iot-services-mqtt-secure-client","title":"IoT Services: MQTT Secure Client","text":"

In the Settings menu, send an 10 to adjust settings for the MQTT Secure Client.

  • 1 Enabled \u2014 Enable or Disable MQTT Secure Client
    • Accepts a boolean value:
      • 1 to enable
      • 0 to disable (default)
  • 2 Port \u2014 The MQTT broker port to connect to
    • Accepts an unsigned integer:
      • 8883 (default, as of firmware v01.00.04)
  • 3 Server \u2014 The MQTT server to connect to
    • Accepts a string
  • 4 MQTT Topic \u2014 The MQTT topic to publish to
    • Accepts a string
  • 5 Client Name \u2014 Name of this device used for MQTT Communications
    • Accepts a string
  • 6 Username \u2014 Username to connect to an MQTT broker, if required.
    • Accepts a string
  • 7 Password \u2014 Password to connect to an MQTT broker, if required.
    • Accepts a string
  • 8 Buffer Size \u2014 MQTT payload buffer size. If 0, the buffer size is dynamic
    • Accepts an unsigned int16:
      • 0 for dynamic buffer size (default)
  • 9 CA Cert Filename \u2014 The File to load the certificate from
    • Accepts a string
  • 10 Client Cert Filename \u2014 The File to load the client certificate from
    • Accepts a string
  • 11 Client Key Filename \u2014 The File to load the client key from
    • Accepts a string
  • b Back
"},{"location":"configuration/#iot-services-aws-iot","title":"IoT Services: AWS IoT","text":"

In the Settings menu, send an 11 to adjust settings for the AWS IoT.

  • 1 Enabled \u2014 Enable or Disable AWS IoT
    • Accepts a boolean value:
      • 1 to enable
      • 0 to disable (default)
  • 2 Port \u2014 The MQTT broker port to connect to
    • Accepts an unsigned integer:
      • 8883 (default, as of firmware v01.00.04)
  • 3 Server \u2014 The MQTT server to connect to
    • Accepts a string
  • 4 MQTT Topic \u2014 The MQTT topic to publish to
    • Accepts a string
      • $aws/things//shadow/update (default)
  • 5 Client Name \u2014 Name of this device used for MQTT Communications
    • Accepts a string
  • 6 Username \u2014 Username to connect to an MQTT broker, if required.
    • Accepts a string
  • 7 Password \u2014 Password to connect to an MQTT broker, if required.
    • Accepts a string
  • 8 Buffer Size \u2014 MQTT payload buffer size. If 0, the buffer size is dynamic
    • Accepts an unsigned int16:
      • 0 for dynamic buffer size (default)
  • 9 CA Cert Filename \u2014 The File to load the certificate from
    • Accepts a string
  • 10 Client Cert Filename \u2014 The File to load the client certificate from
    • Accepts a string
  • 11 Client Key Filename \u2014 The File to load the client key from
    • Accepts a string
  • b Back
"},{"location":"configuration/#iot-services-thingspeak-mqtt","title":"IoT Services: ThingSpeak MQTT","text":"

In the Settings menu, send an 12 to adjust settings for ThingSpeak MQTT

  • 1 Enabled \u2014 Enable or Disable ThingSpeak MQTT
    • Accepts a boolean value:
      • 1 to enable
      • 0 to disable (default)
  • 2 Port \u2014 The MQTT broker port to connect to
    • Accepts an unsigned integer:
      • 8883 (default, as of firmware v01.00.04)
  • 3 Server \u2014 The MQTT server to connect to
    • Accepts a string
  • 4 MQTT Topic \u2014 The MQTT topic to publish to
    • Accepts a string
  • 5 Client Name \u2014 Name of this device used for MQTT Communications
    • Accepts a string
  • 6 Username \u2014 Username to connect to an MQTT broker, if required.
    • Accepts a string
  • 7 Password \u2014 Password to connect to an MQTT broker, if required.
    • Accepts a string
  • 8 Buffer Size \u2014 MQTT payload buffer size. If 0, the buffer size is dynamic
    • Accepts an unsigned int16:
      • 0 for dynamic buffer size (default)
  • 9 CA Cert Filename \u2014 The File to load the certificate from
    • Accepts a string
  • 10 Client Cert Filename \u2014 The File to load the client certificate from
    • Accepts a string
  • 11 Client Key Filename \u2014 The File to load the client key from
    • Accepts a string
  • 12 Channels \u2014 Comma separated list of =
    • Accepts a string
  • b Back
  • "},{"location":"configuration/#iot-services-azure-iot","title":"IoT Services: Azure IoT","text":"

    In the Settings menu, send an 13 to adjust settings for the Azure IoT.

    • 1 Enabled \u2014 Enable or Disable Azure IoT
      • Accepts a boolean value:
        • 1 to enable
        • 0 to disable (default)
    • 2 Port \u2014 The MQTT broker port to connect to
      • Accepts an unsigned integer:
        • 8883 (default, as of firmware v01.00.04)
    • 3 Server \u2014 The MQTT server to connect to
      • Accepts a string
    • 4 MQTT Topic \u2014 The MQTT topic to publish to
      • Accepts a string
    • 5 Client Name \u2014 Name of this device used for MQTT Communications
      • Accepts a string
    • 6 Username \u2014 Username to connect to an MQTT broker, if required.
      • Accepts a string
    • 7 Password \u2014 Password to connect to an MQTT broker, if required.
      • Accepts a string
    • 8 Buffer Size \u2014 MQTT payload buffer size. If 0, the buffer size is dynamic
      • Accepts an unsigned int16:
        • 0 for dynamic buffer size (default)
    • 9 CA Cert Filename \u2014 The File to load the certificate from
      • Accepts a string
    • 10 Client Cert Filename \u2014 The File to load the client certificate from
      • Accepts a string
    • 11 Client Key Filename \u2014 The File to load the client key from
      • Accepts a string
    • 11 Device ID \u2014 The device id for the Azure IoT device
      • Accepts a string
    • 12 Device Key \u2014 The device key for the Azure IoT device
      • Accepts a string
    • b Back
    "},{"location":"configuration/#iot-services-http-iot","title":"IoT Services: HTTP IoT","text":"

    In the Settings menu, send an 14 to adjust settings for the Azure IoT.

    • 1 Enabled \u2014 Enable or Disable the HTTP Client
      • Accepts a boolean value:
        • 1 to enable
        • 0 to disable (default)
    • 2 URL \u2014 The URL to call with log information
      • Accepts a string
    • 3 CA Cert Filename \u2014 The File to load the certificate from
      • Accepts a string
    • b Back
    "},{"location":"configuration/#iot-services-machinechat","title":"IoT Services: MachineChat","text":"

    In the Settings menu, send an 15 to adjust settings for MachineChat.

    • 1 Enabled \u2014 Enable or Disable the HTTP Client
      • Accepts a boolean value:
        • 1 to enable
        • 0 to disable (default)
    • 2 URL \u2014 The URL to call with log information
      • Accepts a string
    • 3 CA Cert Filename \u2014 The File to load the certificate from
      • Accepts a string
    • b Back
    "},{"location":"configuration/#iot-services-arduino-cloud","title":"IoT Services: Arduino Cloud","text":"

    Arduino

    At the time of writing, Arduino's IoT service was referred to as the \"Arduino IoT Cloud.\" Arduino updated the service with a different UI and is now referring to the service as the \"Arduino Cloud\".\" When referencing the Arduino IoT or Arduino IoT Cloud in this tutorial, we are referring to the Arduino Cloud.

    In the Settings menu, send an 16 to adjust settings for Arduino Cloud. This feature was added as of firmware v01.01.01.

    • 1 Enabled \u2014 Enable or Disable the Arduino IoT Client
      • Accepts a boolean value:
        • 1 to enable
        • 0 to disable (default)
    • 2 Thing Name \u2014 The Thing Name to use for the IoT Device connection
      • Accepts a string
    • 3 Thing ID \u2014 The Thing ID to use for the IoT Device connection
      • Accepts a string
    • 4 API Client ID \u2014 The Arduino Cloud API Client ID
      • Accepts a string
    • 5 API Secret \u2014 The Arduino Cloud API Secret
      • Accepts a string
    • 6 Device Secret \u2014 The Arduino IoT Device Secret
      • Accepts a string
    • 7 Device ID \u2014 The Arduino IoT Cloud Device ID
      • Accepts a string
    • b Back
    "},{"location":"configuration/#iot-web-server","title":"IoT Web Server","text":"

    As of firmware v01.02.00, log files can be viewed and downloaded using the IoT Web Server feature if mDNS (multicast DNS) is supported on your network. This functionality is accessed via the Settings Menu, Type 17 to enter the System Update menu. Once this menu entry is selected, the following menu options are presented:

    • 1 Enabled \u2014 Enabled or Disable the Web Server
      • Accepts a boolean value
        • 1 to enable
        • 0 to disable (default)
    • 2 Username \u2014 Web access control. Leave empty to disable authentication
      • Accepts a string
    • 3 Password \u2014 Web access control.
      • Accepts a string
    • 4 mDNS Support \u2014 Enable a name for the web address this device
      • Accepts a boolean value
        • 1 to enable
        • 0 to disable (default)
    • 5 mDNS Name \u2014 mDNS Name used for this device address
      • Accepts a string
        • dataloggerXXXXX, where XXXXX is the taken from the last 5x characters from your DataLogger IoT's board ID (default)
    • b Back

    Note

    You will need to make sure that the ESP32 is on the same network as your computer in order to access the log files.

    Note

    When authentication is enabled, some browsers might require a second login depending on user settings.

    Note

    The SparkFun Datalogger IoT requires restarting if the web interface is enabled.

    For more information on how to use this feature, check out the section on viewing and downloading log files using the IoT web server.

    Examples: Viewing and Downloading Log Files using the IoT Web Server"},{"location":"configuration/#advanced-system-update","title":"Advanced: System Update","text":"

    New sensors and features are being added all the time and we've made it really easy for you to keep your DataLogger IoT up to date. The System Update option provides the following functionality to the end user:

    • Restart the device
    • Performing a Factory Reset on the device
    • Updated the device firmware from a file on an SD Card.

    Note

    What's going on here?!? This tutorial was updated for firmware version 01.02.00!!! You will notice this menu option has changed to 18 !!!

    This functionality is accessed via the Settings Menu, which is required to use this capability. Type 18 to enter the System Update menu. Once this menu entry is selected, the following menu options are presented:

    • 1 Device Restart \u2014 Restart/reboot the device
      • Accepts the following values:
        • Y or Y to restart or reboot the device using the current firmware and system preferences
        • N or n to cancel
    • 2 Factory Reset \u2014 Erase all settings and revert to original firmware
      • Accepts the following values:
        • Y or Y to factory reset the device
        • N or n to cancel
    • 3 Update Firmware - SD Card \u2014 Update the firmware from the SD card
      • Accepts firmware in the /root directory of the microSD card with the file naming pattern SparkFunDataLoggerIoT*.bin, where the asterisk * is the firmware version number (i.e. SparkFunDataLoggerIoT_01.00.01.bin).
    • 4 Update Firmware - OTA \u2014 Update the firmware over-the-air
      • Connects to a server and searches for the latest firmware that is available. Note that you must be connected to a WiFi network to be able to update the board over-the-air.
      • Accepts the following values if there is new firmware available.
        • Y or Y to update over-the-air
        • N or n to cancel
    • b Back

    When finished, you will need to exit the menus so that the DataLogger IoT saves the changes. Send a b to exit out this menu, b to exit out of the DataLogger IoT settings, and x to exit out of the main menu.

    For more information on how to update firmware manually or over-the-air, check out the section under Examples: Updating Firmware.

    Examples: Updating Firmware"},{"location":"configuration/#device-settings","title":"Device Settings","text":"

    In the Main Menu, send a 2 through the serial terminal to adjust the devices settings.

    This will bring up the connected devices that are currently available. You can configure each device and enable/disable each output. Below is a sample of the on-board devices available for the DataLogger IoT - 9DoF when only the MAX17048, ISM330, and MMC5983 are connected. As the DataLogger IoT - 9DoF initializes, the board will populate additional devices in this window if they are detected. Your mileage will vary depending on what is connected. On the DataLogger IoT you will not see the ISM330 or MMC5983 as an option since the 6DoF IMU and magnetometer are not populated on that version of the board.

    • 1 MAX17048 \u2014 MAX17048 LiPo Battery Fuel Gauge
      • 1 Voltage (V) \u2014 Battery voltage (Volts)
        • 1 to enable Voltage (V) (default)
        • 2 to disable Voltage (V)
      • 2 State of Charge (%) \u2014 Battery state of charge (%)
        • 1 to enable state of charge (%) (default)
        • 2 to disable state of charge (%)
      • 3 Charge Rate (%/hr) \u2014 Battery charge change rate (%/hr)
        • 1 to enable change rate (%/hr) (default)
        • 2 to disable change rate (%/hr)
    • 2 ISM330 \u2014 ISM330 Inertial Measurement Unit
      • 1 Accel Data Rate (HZ) \u2014 Accelerometer Data Rate (Hz)
        • 1 for Off
        • 2 for 12.5 Hz
        • 3 for 26 Hz
        • 4 for 52 Hz
        • 5 for 104 Hz (default)
        • 6 for 208 Hz
        • 7 for 416 Hz
        • 8 for 833 Hz
        • 9 for 1666 Hz
        • 10 for 3332 Hz
        • 11 for 6667 Hz
        • 12 for 1.6 Hz
      • 2 Accel Full Scale (g) \u2014 Accelerometer Full Scall (g)
        • 1 for 2 g
        • 2 for 16 g
        • 3 for 4 g (default)
        • 4 for 8 g
      • 3 Gyro Data Rate (Hz) \u2014 Gyro Data Rate (Hz)
        • 1 for Off
        • 2 for 12.5 Hz
        • 3 for 26 Hz
        • 4 for 52 Hz
        • 5 for 104 Hz (default)
        • 6 for 208 Hz
        • 7 for 416 Hz
        • 8 for 833 Hz
        • 9 for 1666 Hz
        • 10 for 3332 Hz
        • 11 for 6667 Hz
      • 4 Gyro Full Scale (dps) \u2014 Gyro Full Scale (dps)
        • 1 for 125 dps
        • 2 for 250 dps
        • 3 for 500 dps (default)
        • 4 for 1000 dps
        • 5 for 2000 dps
        • 6 for 4000 dps
      • 5 Accel Filter LP2 \u2014 Accelerometer Filter LP2
        • 1 to enable (default)
        • 2 to disable
      • 6 Gyro Filter LP1 \u2014 Gyro Filter LP1
        • 1 to enable (default)
        • 2 to disable
      • 7 Accel Slope Filter \u2014 Accelerometer Slope Filter
        • 1 for ODR/4
        • 2 for ODR/10
        • 3 for for ODR/20
        • 4 for ODR/45
        • 5 for ODR/100 (default)
        • 6 for ODR/200
        • 7 for ODR/400
        • 8 for ODR/800
      • 8 Gyro LP1 Filter Bandwidth \u2014 Gyro LP1 Filter Bandwidth
        • 1 Ultra Light
        • 2 Very Light
        • 3 Light
        • 4 Medium (default)
        • 5 Strong
        • 6 Very Strong
        • 7 Aggressive
        • 8 Extreme
      • 9 Accel X (milli-g) \u2014 Accelerometer X (milli-g)
        • 1 to enable
        • 2 to disable
      • 10 Accel Y (milli-g) \u2014 Accelerometer Y (milli-g)
        • 1 to enable
        • 2 to disable
      • 11 Accel Z (milli-g) \u2014 Accelerometer Z (milli-g)
        • 1 to enable
        • 2 to disable
      • 12 Gyro X (milli-dps) \u2014 Gyro X (milli-g)
        • 1 to enable
        • 2 to disable
      • 13 Gyro Y (milli-dps) \u2014 Gyro Y (milli-g)
        • 1 to enable
        • 2 to disable
      • 14 Gyro Z (milli-dps) \u2014 Gyro Z (milli-g)
        • 1 to enable
        • 2 to disable
      • 15 Temperature (C) \u2014 The temperature in degrees C
        • 1 to enable
        • 2 to disable
    • 3 MMC5983 \u2014 MMC5983 Magnetometer
      • 1 Filter Bandwidth (Hz) \u2014 The filter bandwidth in Hz
        • 1 100 Hz (default)
        • 2 200 Hz
        • 3 400 Hz
        • 4 800 Hz
      • 2 Auto-Reset \u2014 Auto-Reset
        • 1 to enable
        • 2 to disable
      • 3 X Field (Gauss) \u2014 The X Field strength in Gauss
        • 1 to enable
        • 2 to disable
      • 4 Y Field (Gauss) \u2014 The Y Field strength in Gauss
        • 1 to enable
        • 2 to disable
      • 5 Z Field (Gauss) \u2014 The Z Field strength in Gauss
        • 1 to enable
        • 2 to disable
      • 6 Temperature (C) \u2014 The ambient temperature in degrees C
        • 1 to enable
        • 2 to disable
    • b Back

    When finished, you will need to exit the menus so that the DataLogger IoT saves the changes. Send a b to exit out this menu, b to exit out of the DataLogger IoT settings, and x to exit out of the main menu.

    Warning

    As you connect additional devices to the DataLogger IoT, the values associated with each device in this menu will change! Make sure to check your device settings menu after additional devices are attached should you decide to configure the additional devices and enable/disable their outputs.

    "},{"location":"contribute/","title":"Contribute: Help Fix our Mistake!","text":"

    Spot something wrong? Feel free to contribute our documentation.

    "},{"location":"contribute/#improve-our-documentation","title":"Improve our Documentation","text":"

    All of this documentation can be modified by you! Please help us make it better.

    • These pages are contained in the docs folder of the SparkFun DataLogger IoT repository.
    "},{"location":"contribute/#submit-a-correction","title":"Submit a Correction","text":"
    1. Fork this repo
    2. Add your corrections or improvements to the markdown file
    3. File a pull request with your changes, and enjoy making the words worlds world a better place.
      • Once received, the documentation specialist will automatically be notified.
      • We will review your suggested improvements to make sure they are correct and fit within our documentation standards.
    "},{"location":"contribute/#improve-our-hardware-design","title":"Improve our Hardware Design","text":"

    This hardware design is open-source! Please help us make it better.

    • Our board design files are contained in the Hardware folder of the SparkFun DataLogger IoT repository.
    "},{"location":"contribute/#submit-a-design-improvement","title":"Submit a Design Improvement","text":"
    1. Fork this repo
    2. Add your design improvements
    3. File a pull request with your changes, and enjoy making the words worlds world a better place.
      • Once received, the engineer in charge of the original design will automatically be notified.
      • We will review your suggested improvements, if they are within our board design standards and meet our product design requirements, we will flag these changes for our next board revision. (Please note, that even if your suggestion is accepted, these changes may not be immediate. We may have to cycle through our current product inventory first.)
    "},{"location":"contribute/#contributors","title":"Contributors","text":"

    Let's provided some recognition to the contributors for this project!

    "},{"location":"example_CSV_to_spreadsheet/","title":"How to Convert Comma Separated Values (CSV) to a Spreadsheet","text":"

    The DataLogger IoT is great at displaying real time data with an IoT service whenever there is an Internet connection available. For those that want to use the DataLogger IoT without a WiFi connection and/or you just want to save data to a microSD card, you can import the comma separated values (CSV) from the text file into a spreadsheet to graph the values.

    There are a few spreadsheet programs available that can take text files with CSV but for the scope of this tutorial, we will be using Google Sheets\u2122 to convert the CSV output to a graph.

    Image Courtesy of Google Sheets.

    Note

    Google and Google Sheets are trademarks of Google LLC. This tutorial is not endorsed by or affiliated with Google in any way. We just thought it was a sweet tool to visualize all the data that was collected by your snazzy DataLogger IoT. \ud83d\ude09

    "},{"location":"example_CSV_to_spreadsheet/#log-some-data","title":"Log Some Data!","text":"

    At this point, we will assume that you have configured connected your devices to the DataLogger IoT and configured its settings. Insert the microSD card into it's socket. Power the DataLogger IoT up and start logging some data! In this case, we were using the DataLogger IoT using the Qwiic Environmental Combo Breakout - ENS160/BME280. of course, you could have other compatible Qwiic-enabled devices connected depending on your setup. For simplicity, a WiFi connection was used to synchronize the clock to the NTP server and a computer's USB port was used to power everything.

    Tip

    For users without an Internet connection to sync the clock to the NTP server, you may want to consider using a compatible Qwiic-enabled device like the Qwiic Real Time Clock (RTC) Module - RV-8803 or a Qwiic-enabled u-blox GNSS module. Note that you will need to configure the time to your area before logging any data. U-blox GNSS modules would also need to be able to view a few satellites before being able to synchronize to the UTC.

    Note

    For users that require a timestamp with their datasets, make sure to enable timestamp.

    "},{"location":"example_CSV_to_spreadsheet/#download-the-log-files","title":"Download the Log Files","text":"

    Users can download the log files to your computer with the IoT Web Server. You will need to update firmware to v01.02.00 and enable this feature. For more information, check out the previous example to view and download log files using the IoT web server.

    Examples: Viewing and Downloading Log Files using the IoT Web Server

    Of course, users can follow the old school method and manually grab the files using a microSD card reader. When ready, remove power from the DataLogger IoT and eject the microSD card from the socket. Insert the microSD card into an adapter and connect to your computer.

    "},{"location":"example_CSV_to_spreadsheet/#importing-csv-to-a-spreadsheet","title":"Importing CSV to a Spreadsheet","text":"

    Log into your Google account and open Google Sheets to create a new spreadsheet.

    Click Here to Create a New Google Spreadsheet

    Head to the menu and select: File > Import.

    A window will pop up with some options to import a file. Click the Upload tab. Click on the Browse button to choose the file. Or drag and drop the file into the upload area. In this case, the DataLogger IoT saved the comma separated values to a text file called sfe0003.txt.

    Note

    Not seeing any data in the file or even a text file saved in the root directory? Make sure that the microSD card is formatted correctly and the DataLogger is configured properly. In the menu, make sure to have the SD Card Format enabled and set to the correct format. In this case, we are using the default CSV format.

    Another window will pop up asking how to import the file. From the drop down menu, select: Import location > Create new spreadsheet and Separator Type > Detect automatically. Since the file will include commas to separate each reading, Google Sheets should automatically separate text and values into each cell. Otherwise, you can select comma as the separator type.

    Note

    If you have the file open, you can also manually paste the CSV data into the spreadsheet. Select all the contents of the text file and copy the contents onto your clipboard. Right click the cell closest to the top and farthest to the left of the spreadsheet (i.e. A1). Then paste the data. One caveat is that Google Sheets may have problems where it only pastes the title of each column.

    If you see this happen, you will need to select all but the header row from the text file. Then copy the contents onto your clipboard again. Right click on the next row the titles (i.e. A2) and paste the data.

    Tip

    To separate the values to each column, highlight everything in the column. Then head to the menu and select: Data > Split text into columns

    "},{"location":"example_CSV_to_spreadsheet/#graphing-your-datasets","title":"Graphing Your Datasets","text":"

    Hold down the Shift button on your keyboard and select the columns that you would like to graph using your mouse. Once the data is highlighted, head to the menu and select: Insert > Chart.

    The values that were selected will be graphed. You will want to be careful about including too many datasets on the graph as it can be hard to read when they are not in the same range.

    At this point, try formatting the data based on your preferences and export the graph. The graph below was formatted and exported to a PNG. Note that the values for the AQI were moved to the right of the graph for a better viewing since they were smaller than the datasets for TVOC and eCO2.

    Note

    There are additional features to help format your data based on your personal preferences! Select the column that you would like to format. Then head to the menu: Format > Number. Select the format that you would like to apply to the dataset. In this case, we adjusted the General Time with Custom Date and Time to show a 12-hour format before creating a new graph.

    "},{"location":"example_arduino_iot_cloud/","title":"Arduino Cloud","text":"

    Arduino

    At the time of writing, Arduino's IoT service was referred to as the \"Arduino IoT Cloud.\" Arduino updated the service with a different UI and is now referring to the service as the \"Arduino Cloud\".\" When referencing the Arduino IoT or Arduino IoT Cloud in this tutorial, we are referring to the Arduino Cloud.

    "},{"location":"example_arduino_iot_cloud/#creating-and-connecting-to-an-arduino-cloud-device","title":"Creating and Connecting to an Arduino Cloud Device","text":"

    One of the key features of the SparkFun DataLogger IoT is it's simplified access to IoT service providers. This document outlines how an Arduino Cloud Device is used by the DataLogger IoT.

    The following is covered by this document:

    • Structure of the Arduino Cloud Devices
    • Device creation in the Arduino Cloud
    • Setup of the Arduino Driver
    • How data is posted from the DataLogger IoT to the Arduino Device

    Currently, the Arduino Cloud device connection is a single direction - used to post data from the DataLogger IoT to an Arduino Cloud device.

    Image Courtesy of Arduino

    Note

    To take advantage of the API's in Arduino Cloud, you will also need to have a service plan with your account.

    "},{"location":"example_arduino_iot_cloud/#general-operation","title":"General Operation","text":"

    The Arduino Cloud enables connectivity between an IoT/Edge Arduino enabled device and the cloud. The edge device updates data in the Arduino Cloud by updating variables or parameters attached to a cloud device.

    In the Arduino Cloud, the edge device is represented by a Device which has a virtual Thing attached/associated with it. The Thing acts as a container for a list of parameters or variables which represent the data values received from the edge device. As values on the edge device update, they are transmitted to the Arduino Cloud.

    For a SparkFun DataLogger IoT connected to an Arduino Cloud device, the output parameters of a device connected to the DataLogger are mapped to variables within the Arduino Cloud Device's Thing using a simple pattern of DeviceName_ParameterName for the name of the variable in the Arduino Cloud.

    "},{"location":"example_arduino_iot_cloud/#creating-a-device-in-arduino-cloud","title":"Creating a Device in Arduino Cloud","text":"

    The first step connecting to the Arduino Cloud is setting up a device within the cloud. A device is a logical element that represents a physical device.

    Log into your Arduino Cloud account.

    Click Here to Log into Arduino Cloud

    Click on the expand menu icon on the upper left (e.g. the three lines stacked like a \"hamburger\"; \u2630) and select Devices. If your window is big enough, then it will show up on the navigation bar.

    This page lists your currently defined devices. If there are no defined devices, select the Add Device button. This will probably be at the bottom of the page. The location of this button will change once the page has a device (or if there is an update to Arduino's user interface).

    A device type selection dialog is then shown. Since we are connecting a DataLogger IoT board to the system, and not connected a known device, select DIY - Any Device to manually include the DataLogger IoT.

    Once selected, another dialog is presented. Just select Continue. At this point you can provide a name for your device. In this case we named it as \"MyDataLoggerIoT.\"

    The next screen is the critical step of the device creation process. This step is the one time the Device Secret Key is available. The provided Device ID and Device Secret Key values are needed to connect to the Arduino Cloud. Once this step is completed, the Secret Key is no longer available.

    The easiest way to capture these values is by downloading as a PDF file, which is offered on the setup page. Click on the download the PDF and save it to a safe location. When ready, click on the check box indicating that you have saved the values and select the Continue button.

    "},{"location":"example_arduino_iot_cloud/#arduino-cloud-api-keys","title":"Arduino Cloud API Keys","text":"

    In addition to creating a device, to access the Arduino Cloud, the driver requires an API Key. This allows the DataLogger IoT's Arduino Cloud driver to access the web API of the Arduino Cloud. This API is used to setup the connection to the Arduino Cloud.

    To create an API key, click on the menu bar to expand and select your Arduino account profile > Personal Settings.

    This menu takes you to a list of existing API Keys. If you have not created one yet, the list will have nothing in it like the image below. From this page, select the CREATE API KEY button.

    Note

    Users will need a service plan in order to take advantage of the API.

    In the presented dialog, enter a name for the API key. In this case, we named it \"MyDataLoggerKey\".

    Once the name is entered, click CONTINUE. A page with the new API key is presented. Like in Device Creation, this page contains a secret that is only available on this page during this process.

    Make note of the Client ID and Client Secret values on this page. The best method to capture these values is to download the PDF file offered on this page. Click on the download the PDF and save it to a safe location. When ready, click on the check box indicating that you have saved the values and select the DONE button.

    At this point, the Arduino Cloud is setup for connection by the driver.

    "},{"location":"example_arduino_iot_cloud/#arduino-cloud-configuration","title":"Arduino Cloud Configuration","text":"

    To add an Arduino Cloud Device as a destination DataLogger IoT, the Arduino Cloud connection is enabled via the DataLogger menu system and the connection values, obtained from the Arduino Cloud (see above), are set in the connection properties.

    The specifics for the Arduino Cloud must be configured. This includes the following:

    • Thing Name
    • Thing ID
    • API Client ID
    • API Secret
    • Device Secret
    • Device ID

    Note

    The Thing Name does not necessarily need to be configured. However, there will be less confusion if you set this up before connecting the DataLogger IoT to the Cloud. The Thing ID will automatically be generated and saved once there is a connection available.

    "},{"location":"example_arduino_iot_cloud/#thing-name","title":"Thing Name","text":"

    The name of the Arduino Cloud Thing to use. If the Thing doesn't exist on startup, the driver will create a Thing and be named \"Untitled\" if you do not provide a name.

    Note

    Note satisfied with the default \"Untitled\" as the Thing's name? You can rename the Thing Name after creating the Thing. Note that you will need to manually rename the Thing Name on the Arduino Cloud and DataLogger IoT.

    "},{"location":"example_arduino_iot_cloud/#thing-id","title":"Thing ID","text":"

    This is the ID of the Thing being used. This value is obtained by the following methods:

    • If the driver creates a new Thing, the ID is obtained and used.
    • If an existing Thing is connected to the DataLogger IoT, the driver retrieves it's ID.

    Note

    In this case, the driver cannot create any new variables until the system is restarted.

    • The user creates a new Thing using the web interface of Arduino Cloud, and provides the Thing Name and Thing ID .
    "},{"location":"example_arduino_iot_cloud/#api-client-id-and-secret","title":"API Client ID and Secret","text":"

    These values are used to provide API access by the driver. This access allows for the creation/use of a Thing and Variables within the Arduino Cloud. These are obtained via the steps outlined earlier in this document.

    "},{"location":"example_arduino_iot_cloud/#device-secret-and-id","title":"Device Secret and ID","text":"

    These values are used to identify the Arduino device that is connected to. These are obtained via the steps outlined earlier in this document.

    "},{"location":"example_arduino_iot_cloud/#setting-properties","title":"Setting Properties","text":"

    The above property values must be set in the DataLogger's Arduino Cloud driver before use. They can be manually by using the menu system like the previous MQTT example.

    For users that are interested in using the menu system, open a Serial Terminal, connect to the COM port that your DataLogger enumerated to, and set it to 115200 baud. In this case, we connected to COM13. Press any key to enter the Main Menu. Type 1 to enter the Settings menu. Then type 16 to enter the Arduino IoT Menu. When the menu system for the Arduino IoT is presented, you will need to configure the property values as listed in the JSON file. Saving the values through the menu system will save the credentials to the ESP32's persistent memory. The following options are displayed:

    The alternative to using the menu system is a JSON file. These values can be set using a JSON file that is loaded by the system at startup. For the DataLogger Arduino Cloud example outlined in this document, the entries in the setting's JSON file are as follows:

    \"Arduino IoT\": {\n    \"Enabled\": true,\n    \"Thing Name\": \"SparkFunThing1\",\n    \"API Client ID\": \"MY_API_ID\",\n    \"API Secret\": \"MY_API_SECRET\",\n    \"Device Secret\": \"MY_DEVICE_SECRET\",\n    \"Device ID\": \"MY_DEVICE_ID\"            \n  },\n

    You will need to update the API Client ID, API Secret, Device Secret, and Device ID with the values that were obtained earlier. Don't forget to enable Arduino Cloud service by setting the value to true. If the JSON file is saved in the microSD card, you will need to load the credentials to the DataLogger IoT.

    Tip

    To load the values by the system at startup using a JSON file and microSD card, you will need to configure the Save Settings. This JSON file will be created with the \"Save to Fallback\" option. Make sure to enable the Arduino IoT as well.

    "},{"location":"example_arduino_iot_cloud/#operation","title":"Operation","text":"

    On startup, when the first values are written from the DataLogger IoT, the connection to the Arduino Cloud is made. During this connection, the system connects to the specified Thing and variables are mapped between the DataLogger Device values and Arduino Cloud Variables. If needed, variables can be created manually in the cloud.

    While this initial setup takes seconds to complete, updates to the values on the Arduino Cloud are rapid as soon as there is a connection available.

    "},{"location":"example_arduino_iot_cloud/#viewing-values","title":"Viewing Values","text":"

    Once the DataLogger IoT device is configured and running, updates in Arduino Cloud are listed in the Things tab of the Arduino Cloud page. Clicking the target Thing provides access to the current variable values that are connected to the DataLogger IoT. Your mileage may vary depending on the compatible device that is connected to the DataLogger IoT. In this case, we were able to see the built-in sensors that were connected on the DataLogger IoT - 9DoF.

    Note

    Not seeing certain variables on your list? Check your connections to make sure that the compatible device is connected to the DataLogger IoT. You may also have certain outputs disabled (like the connected sensors or timestamp).

    Note

    Having problems connecting new variables with the DataLogger IoT? When swapping out compatible Qwiic enabled devices, you may need to delete previous cloud variables so that the DataLogger IoT is able re-initialize them on the next power cycle.

    "},{"location":"example_arduino_iot_cloud/#create-a-dashboard","title":"Create a Dashboard","text":"

    With the data now available in the Arduino Cloud as variables, it is a simple step create a dashboard that plots the data values.

    The general steps to create a simple dashboard include:

    • Select the Dashboards section of the Arduino Cloud.
    • Select the Build Dashboard button. If you have a dashboard already built, the location of the button will change and the button will be renamed: Create.
    • Click the edit button (i.e. the icon that looks like a paper and pencil, this is next to the eye).
    • Add an element to the dashboard -- for this example select ADD ^ > Advanced Chart.
    • On the Chart's Widget Settings select Link Variables to add readings.
    • The DataLogger IoT Variables are listed - select the variable to link.
    • Continue this step until all the desired variables are linked to the chart. You can select up to 5x variables at a time. Click on the Link Variables button after selecting the variables.
    • This will bring you back to the Chart's Widget Settings window. Configure any preferences that to display (i.e. variable colors, labels, etc.). When all variables are linked and the Chart Widget Settings is configured, select Done.

    The created dashboard then displays the values posted from the SparkFun DataLogger IoT. You can continue adding additional readings on the dashboard that you were not able to fit on graph or even rename the Dashboard view. In this case, we displayed accelerometer values and temperature in degrees Celsius from the DataLogger IoT - 9DoF.

    Note

    Not seeing any values on the LIVE view? Try clicking on the other time periods to see the values posted.

    Using compatible Qwiic enabled devices, you can also display additional readings that are not available with the built-in sensors. In this case, we were able to display humidity, temperature in degrees Fahrenheit, equivalent CO2, TVOC, and AQI with the DataLogger IoT and Environmental Combo Breakout (ENS160/BME280).

    "},{"location":"example_aws/","title":"Creating and Connecting to an AWS IoT Device (Thing)","text":"

    One of the key features of the DataLogger IoT is it's simplified access to IoT service providers. This document outlines how an AWS IoT device is used by the DataLogger IoT.

    Image Courtesy of Amazon Web Services (AWS)

    The following is covered by this document:

    • Device (Thing) creation in AWS
    • Securely connecting the device
    • How data is posted from the DataLogger IoT to the AWS Device via it's Shadow

    Currently, the AWS IoT device connection is a single direction - used to post data from the hardware to the IoT AWS Device via the AWS IoT devices shadow. Configuration information from AWS IoT to the DataLogger IoT is currently not implemented.

    "},{"location":"example_aws/#general-operation","title":"General Operation","text":"

    AWS IoT enables connectivity between an IoT / Edge device and the AWS Cloud Platform, implementing secure endpoints and device models within the AWs infrastructure. This infrastructure allows edge devices to post updates, status and state to the AWS infrastructure for analytics, monitoring and reporting.

    In AWS IoT, an virtual representation of an actual device is created and referred to as a Thing. The virtual device/Thing is allocated a connection endpoint, security certificates and a device shadow - a JSON document used to persist, communicate and manage device state within AWS.

    The actual IoT device communicates with it's AWS representation via a secure MQTT connection, posting JSON document payloads to a set of pre-defined topics. Updates are posted to the AWS IoT device shadow, which is then accessed within AWS for further process as defined by the users particular cloud implementation.

    "},{"location":"example_aws/#creating-a-device-in-aws-iot","title":"Creating a Device in AWS IoT","text":"

    The following discussion outlines the basic steps taken to create a Thing in AWS IoT that the DataLogger IoT can connect to. First step is to log into your AWS account and create a thing.

    Click Here to Log into AWS

    Once logged into your AWS account, select IoT Core from the menu of services.

    From the IoT Core console page, under the Manage section, select All Devices > Things

    On the resultant Things Page, select the Create Things button.

    AWS IoT will then take you through the steps to create a device. Selections made for a demo Thing are:

    • Create single thing
    • Thing Properties
    • Enter a name for your thing - for this example TestThing23
    • Device Shadow - select Unnamed shadow (classic)
    • Auto-generate a new certificate
    • Attach policies to certificate - This is discussed later in this document
    • Select Create thing

    Upon creation, AWS IoT presents you with a list of downloadable certificates and keys. Some of these are only available at this step. The best option is to download everything presented - three of these are used by the DataLogger IoT. The following should be downloaded:

    • Device Certificate
    • Public Key File
    • Private Key File
    • Root CA certificates - (for example: Amazon Root CA 1 )

    At this point, the new AWS IoT thing is created and listed on the AWS IoT Things Console

    "},{"location":"example_aws/#security-policy","title":"Security Policy","text":"

    To write to the IoT device, a security policy that enables this is needed, and the policy needs to be assigned to the devices certificate.

    To create a Policy, select the Manage > Security > Policies menu item from the left side menu of the AWS IoT panel. Once on this page, select the Create policy button to create a new policy.

    When entering the policy, provide a name that fits your need. For this example, the name NewThing23Policy is used. For the Policy document, you can manually enter the security entires, or enter them as a JSON document. The JSON document used for this example is:

    {\n  \"Version\": \"2012-10-17\",\n  \"Statement\": [\n    {\n      \"Effect\": \"Allow\",\n      \"Action\": \"iot:Connect\",\n      \"Resource\": \"*\"\n    },\n    {\n      \"Effect\": \"Allow\",\n      \"Action\": \"iot:Subscribe\",\n      \"Resource\": \"*\"\n    },\n    {\n      \"Effect\": \"Allow\",\n      \"Action\": \"iot:Receive\",\n      \"Resource\": \"*\"\n    },\n    {\n      \"Effect\": \"Allow\",\n      \"Action\": \"iot:Publish\",\n      \"Resource\": \"*\"\n    },\n    {\n      \"Effect\": \"Allow\",\n      \"Action\": \"iot:GetThingShadow\",\n      \"Resource\": \"*\"\n    },\n    {\n      \"Effect\": \"Allow\",\n      \"Action\": \"iot:UpdateThingShadow\",\n      \"Resource\": \"*\"\n    }\n  ]\n}\n

    Once the policy is created, go back to the IoT Device/Thing created above and associate this policy to the device Certificate.

    • Go to your device Manage > All devices > Things
    • Select the device - TestThing23 for this example
    • Select the Certificates tab
    • Select the listed Certificate (it's a very long hex number)
    • At the bottom right of the page, select the Attach policies button and select the Policy created above.

    At this point, AWS IoT is ready for a device to connect and receive data.

    "},{"location":"example_aws/#aws-configuration","title":"AWS Configuration","text":"

    The specifics for the AWS IoT Thing must be configured. This includes the following:

    • Server name/host
    • MQTT topic to update
    • Client Name - The AWS IoT Thing Name
    • CA Certificate Chain
    • Client Certificate
    • Client Key
    "},{"location":"example_aws/#server-namehostname","title":"Server Name/Hostname","text":"

    This value is obtained from the AWS IoT Device page for the created device. When on this page, select the Device Shadows tab, and then select the Classic Shadow shadow, which is listed. Note a secure connection is used, so the port for the connection is 8883.

    Selecting the Classic Shadow entry provides the Server Name/Hostname for the device, as well as the MQTT topic for this device.

    Note

    The server name is obtained from the Device Shadow URL entry

    "},{"location":"example_aws/#mqtt-topic","title":"MQTT Topic","text":"

    The MQTT topic value is based uses the MQTT topic prefix from above, and has the value update added to it. So for this example, the MQTT topic is:

    $aws/things/TestThing23/shadow/update\n
    "},{"location":"example_aws/#client-name","title":"Client Name","text":"

    This is the AWS IoT name of the thing. For the provided example, the value is TestThing23

    "},{"location":"example_aws/#ca-certificate-chain","title":"CA Certificate Chain","text":"

    This value was downloaded as a file during the creation process. The contents of this file can be passed on to the DataLogger IoT by copying the file containing the data onto a devices SD Card and setting the filename property for the DataLogger IoT.

    "},{"location":"example_aws/#client-certificate","title":"Client Certificate","text":"

    This value was downloaded as a file during the creation process. The contents of this file can be passed on to the DataLogger IoT by copying the file containing the data onto a devices SD Card and setting the filename property for the DataLogger IoT.

    "},{"location":"example_aws/#client-key","title":"Client Key","text":"

    This value was downloaded as a file during the creation process. The contents of this file can be passed on to the DataLogger IoT by copying the file containing the data onto a devices SD Card and setting the filename property for the DataLogger IoT.

    "},{"location":"example_aws/#setting-properties","title":"Setting Properties","text":"

    The above property values must be set on the DataLogger before use. They can be set manually by using the menu system like the previous MQTT example.

    For users that are interested in using the menu system, you will need to open a Serial Terminal, connect to the COM port that your DataLogger enumerated to, and set it to 115200 baud. In this case, we connected to COM13. Press any key to enter the Main Menu. Type 1 to enter the Settings menu. Then type 11 to enter the AWS IoT Menu. When the menu system for the AWS IoT connection is presented, you will need to configure the property values as listed in the JSON file. Saving the values through the menu system will save the credentials to the ESP32's persistent memory. The following options are displayed:

    The alternative to using the menu system is a JSON file. These values can be set using a JSON file that is loaded by the system at startup. For the DataLogger IoT example outlined in this document, the entries in the settings JSON file are as follows:

    \"AWS IoT\": {\n    \"Enabled\": true,\n    \"Port\": 8883,\n    \"Server\": \"avgpd2wdr5s6u-ats.iot.us-east-1.amazonaws.com\",\n    \"MQTT Topic\": \"$aws/things/TestThing23/shadow/update\",\n    \"Client Name\": \"TestThing23\",\n    \"Buffer Size\": 0,\n    \"Username\": \"\",\n    \"Password\": \"\",\n    \"CA Certificate\": \"\",\n    \"Client Certificate\": \"\",\n    \"Client Key\": \"\",\n    \"CA Cert Filename\": \"AmazonRootCA1.pem\",\n    \"Client Cert Filename\": \"TestThing23_DevCert.crt\",\n    \"Client Key Filename\": \"TestThing23_Private.key\"\n  },\n

    Besides updating the Server, MQTT Topic, Client Name, CA Cert Filename, Client Cert Filename, and Client Key Filename, you will need to also ensure that the port is set to 8883. The default in previous firmware versions was 1883. As of firmware v01.00.04, the default is 8883. You will need to adjust the port value to properly connect to the AWS IoT service. Don't forget to enable AWS IoT service by setting the value to true. If the JSON file is saved in the microSD card, you can load the credentials to the DataLogger IoT.

    Tip

    To load the values by the system at startup using a JSON file and microSD card, you will need to configure the Save Settings. This JSON file will be created with the \"Save to Fallback\" option. Make sure to enable the AWS IoT as well.

    "},{"location":"example_aws/#operation","title":"Operation","text":"

    Once the device is configured and running, updates in AWS IoT are listed in the Activity tab of the devices page. For the test device in this document, this page looks like:

    Opening up an update, you can see the data being set to AWS IoT in a JSON format.

    "},{"location":"example_azure/","title":"Creating and Connecting to an Azure IoT Device","text":"

    One of the key features of the DataLogger IoT is it's simplified access to IoT service providers. This document outlines how an Azure IoT device is used by the DataLogger IoT.

    Image Courtesy of Microsoft Azure

    The following is covered by this document:

    • Device creation Azure
    • Securely connecting the device
    • How data is posted from the DataLogger IoT to the Azure Device

    Currently, the Azure IoT device connection is a single direction - it is used to post data from the hardware to the Azure IoT Device. Configuration information from Azure IoT to the DataLogger IoT is currently not implemented.

    "},{"location":"example_azure/#general-operation","title":"General Operation","text":"

    Azure IoT enables connectivity between an IoT / Edge device and the Azure Cloud Platform, implementing secure endpoints and device models within the Azure infrastructure. This infrastructure allows edge devices to post updates, status and state to the Azure infrastructure for analytics, monitoring and reporting.

    In Azure IoT, an virtual representation of an actual device is created and referred to as a Device. The virtual device is allocated a connection endpoint, security certificates and a device digital twin - a JSON document used to persist, communicate and manage device state within Azure. Unlike AWS IoT, data from the device isn't posted to the devices digital twin (AWS Shadow), but to the device directly.

    The actual IoT device communicates with it's Azure representation via a secure MQTT connection, posting JSON document payloads to a set of pre-defined topics. Updates are posted directly to the Azure device, which is then accessed within Azure for further process as defined by the users particular cloud implementation.

    "},{"location":"example_azure/#creating-a-device-in-azure-iot","title":"Creating a Device in Azure IoT","text":"

    The following discussion outlines the basic steps taken to create a Device in Azure IoT that the DataLogger IoT can connect to. First step is to log into your Azure account and create an IoT Hub for your device.

    Click Here to Log into Microsoft Azure

    Once logged into your Microsoft Azure account, select Internet of Things > IoT Hub from the menu of services.

    "},{"location":"example_azure/#create-an-iot-hub","title":"Create an IoT Hub","text":"

    This IoT Hub page lists all the IoT hubs available for your account. To add a device, you need to create a new IoT Hub.

    Follow the Hub Creation workflow - key settings used for a DataLogger demo device:

    • Used the \"Free Tier\" for testing and development.
    • Networking
      • Connectivity - Public Access
      • Minimum TLS Version - 1.0

    The remaining settings were set at their default values.

    "},{"location":"example_azure/#create-a-device","title":"Create a Device","text":"

    Once the IoT Hub is created, a Device needs to be created within the hub. The device represents the connection to the actual DataLogger IoT device.

    To create a device, select the Device management > Devices from the IoT Hub menu and the select the + Add Device menu item

    In the create device dialog:

    • Enter a name for the device
    • Select an Authentication type of Symmetric key
    • Auto-generate keys enabled

    Once created, the device is listed in the Devices list of the IoT Hub. Selecting the device gives you the device ID and keys used to communicate with the device. Note, when connecting to the device with the DataLogger IoT, the Primary Key value is used.

    "},{"location":"example_azure/#azure-configuration","title":"Azure Configuration","text":"

    Once the DataLogger IoT is integrated into the application, the specifics for the Azure IoT Thing must be configured. This includes the following:

    • Server Name/Hostname
    • Device Key
    • Device ID
    • CA Certificate Chain
    "},{"location":"example_azure/#server-namehostname","title":"Server Name/Hostname","text":"

    This value is hostname of the created IoT Hub and is obtained from the Overview page of the IoT Hub. Note a secure connection is used, so the port for the connection is 8883.

    "},{"location":"example_azure/#device-id","title":"Device ID","text":"

    The Device ID is obtained from the device detail page. This page is accessible via the Device listing page, which is accessed via the Device management > Devices menu item. The selected device of interest (TestDevice2023 for this example) provides the device ID and Primary Key.

    "},{"location":"example_azure/#device-primary-key","title":"Device Primary Key","text":"

    This is obtained via the Device details page, as outlined in the previous section.

    Note

    You view and copy the key via the icons on the right of the key entry line.

    "},{"location":"example_azure/#root-certificate-authority-ca-file","title":"Root Certificate Authority - CA file","text":"

    The Certificate Authority file for Azure is downloaded from this page:

    Microsoft: Azure Certificate Authority details

    The file to download is the Baltimore CyberTrust Root entry in the Root Certificate Authorities section of the page.

    "},{"location":"example_azure/#setting-properties","title":"Setting Properties","text":"

    The above property values must be set on the DataLogger IoT before use. They can be set manually by using the menu system like the previous MQTT example.

    For users that are interested in using the menu system, you will need to open a Serial Terminal, connect to the COM port that your DataLogger enumerated to, and set it to 115200 baud. In this case, we connected to COM13. Press any key to enter the Main Menu. Type 1 to enter the Settings menu. Then type 13 to enter the Azure IoT Menu. When the menu system for the Azure IoT connection is presented, you will need to configure the property values as listed in the JSON file. Saving the values through the menu system will save the credentials to the ESP32's persistent memory. The following options are displayed:

    The alternative to using the menu system is a JSON file. These values can be set using a JSON file that is loaded by the system at startup. For the example outlined in this document, the entries in the settings JSON file are as follows:

    \"Azure IoT\": {\n    \"Enabled\": true,\n    \"Port\": 8883,\n    \"Server\": \"sparkfun-datalogger-hub.azure-devices.net\",\n    \"MQTT Topic\": \"\",\n    \"Client Name\": \"\",\n    \"Buffer Size\": 0,\n    \"Username\": \"\",\n    \"Password\": \"\",\n    \"Device Key\" : \"My-Super-Secret-Device-Key\",\n    \"Device ID\"  : \"TestDevice2023\",\n    \"CA Cert Filename\": \"AzureRootCA.pem\"\n  },\n

    Besides updating the Server, Device Key, Device ID, and CA Cert Filename, you will need to also ensure that the port is set to 8883. The default in previous firmware versions was 1883. As of firmware v01.00.04, the default is 8883. You will need to adjust the port value to properly connect to the Azure IoT service. Don't forget to enable Azure IoT service by setting the value to true. If the JSON file is saved in the microSD card, you can load the credentials to the DataLogger IoT.

    Tip

    To load the values by the system at startup using a JSON file and microSD card, you will need to configure the Save Settings. This JSON file will be created with the \"Save to Fallback\" option. Make sure to enable the Azure IoT as well.

    "},{"location":"example_azure/#operation-and-monitoring","title":"Operation and Monitoring","text":"

    Once the DataLogger IoT device is configured and running, the Azure IoT capability in the DataLogger IoT posts messages via MQTT to the connected Azure Device via it's IoT Hub. Messages to the device are posted as Telemetry Data for the device.

    The easiest method to view the Telemetry data being sent to an Azure Iot Device is via the Azure IoT Hub extension for the Visual Studio Code editor.

    Once installed, and connected to Azure via the Azure Account extension, you can connect to the target IoT Hub, and monitor telemetry data for a IoT device.

    "},{"location":"example_azure/#connect-to-your-azure-iot-hub","title":"Connect to Your Azure IoT Hub","text":"

    On the Explorer panel of Visual Studio Code, click on the ... menu of the AZURE IOT HUB section. In the popup menu, select the Select IoT Hub menu entry.

    The available IoT Hubs are displayed in the editors command prompt. Select the desired hub and press Enter (or click).

    The hub is then displayed in the AZURE IOT HUB section of the editor Explorer. Expanding the Devices section of the Hub will list the example device created above.

    "},{"location":"example_azure/#monitoring","title":"Monitoring","text":"

    To monitor the telemetry data send to a device, right click on the device, TestDevice2023 in this example, select the menu entry Start Monitoring Build-in Event Endpoint.

    Once selected, the editor output console will start displaying output for the selected device. For the above example, with a device that has environmental sensors attached, the output appears as follows:

    To stop monitoring, click the Stop Monitoring build-in event endpoint item that is displayed in the status bar of the editor.

    A menu option to stop monitoring is also available from the ... menu of the AZURE IOT HUB section in the editor Explorer panel.

    "},{"location":"example_http/","title":"Connecting and Sending Output to an HTTP Server","text":"

    One of the key features of the DataLogger IoT is it's simplified access to IoT service providers and servers. This document outlines how output from a DataLogger IoT device is sent to an HTTP server.

    The following is covered by this document:

    • Overview of the HTTP connection
    • How a user configures and uses the HTTP connection
    • Use examples
    "},{"location":"example_http/#general-operation","title":"General Operation","text":"

    HTTP connectivity allows data generated by the DataLogger IoT to be sent to an HTTP server. An HTTP endpoint is provided to the HTTP action within the DataLogger IoT, and when data is output, a JSON representation of the data is published to the endpoint via an HTTP POST operation. The body of the POST operation contains the a JSON document that encapsulates the sent DataLogger IoT data.

    "},{"location":"example_http/#data-structure","title":"Data Structure","text":"

    Data is sent to the HTTP server as a JSON object, which contains a collection of sub-object. Each sub-object represents a data source in the sensor, and contains the current readings from that source.

    The following is an example of the data posted - note, this representation was \"pretty printed\" for readability.

    {\n  \"MAX17048\": {\n    \"Voltage (V)\": 4.304999828,\n    \"State Of Charge (%)\": 115.0625,\n    \"Change Rate (%/hr)\": 0\n  },\n  \"CCS811\": {\n    \"CO2\": 620,\n    \"VOC\": 33\n  },\n  \"BME280\": {\n    \"Humidity\": 25.03613281,\n    \"TemperatureF\": 79.64599609,\n    \"TemperatureC\": 26.46999931,\n    \"Pressure\": 85280.23438,\n    \"AltitudeM\": 1430.44104,\n    \"AltitudeF\": 4693.04834\n  },\n  \"ISM330\": {\n    \"Accel X (milli-g)\": -53.31399918,\n    \"Accel Y (milli-g)\": -34.03800201,\n    \"Accel Z (milli-g)\": 1017.236023,\n    \"Gyro X (milli-dps)\": 542.5,\n    \"Gyro Y (milli-dps)\": -1120,\n    \"Gyro Z (milli-dps)\": 262.5,\n    \"Temperature (C)\": 26\n  },\n  \"MMC5983\": {\n    \"X Field (Gauss)\": -0.200622559,\n    \"Y Field (Gauss)\": 0.076416016,\n    \"Z Field (Gauss)\": 0.447570801,\n    \"Temperature (C)\": 29\n  }\n}\n
    "},{"location":"example_http/#http-connection-setup","title":"HTTP Connection Setup","text":"

    To connect to an HTTP server endpoint, the following information is needed:

    • The URL of the endpoint
    • The SSL certificate for the target server, if the connection is secure (HTTPS)

    These values are set using the standard DataLogger methods - the interactive menu system, or a JSON file.

    "},{"location":"example_http/#menu-system","title":"Menu System","text":"

    For users that are interested in using the menu system, you will need to open a Serial Terminal, connect to the COM port that your DataLogger enumerated to, and set it to 115200 baud. In this case, we connected to COM13. Press any key to enter the Main Menu. Type 1 to enter the Settings menu. Then type 14 to enter the HTTP IoT Menu. When the menu system for the HTTP IoT connection is presented, you will need to configure the property values as listed in the JSON file. Saving the values through the menu system will save the credentials to the ESP32's persistent memory. The following options are displayed:

    The options are:

    • Enable/Disable the connection
    • Set the URL for the endpoint
    • Set the name of the CA Cert file for a secure connection (HTTP)

    To set the HTTP URL/endpoint - select two (2) in the menu, and enter the URL. For this example, we'll enter: http://mysparkfunexample.com:8091 .

    In the above example, the URL/HTTP Endpoint is on a server called mysparkfunexample.com, on port 8091. Once set, the system will post data to this URL.

    If the endpoint is a secure ssl (HTTPS) connection, the certificate for the server is required. Because of the size of the certificates, the value is provided as a file that is loaded into the system by the attached SD card.

    The above example show providing a certificate filename of example.cer.

    Once all these values are set, the system will post data to the specified HTTP endpoint, following the JSON information structure noted earlier in this document.

    "},{"location":"example_http/#json-file-entries","title":"JSON File Entries","text":"

    If a JSON file is being used as an option to import settings into the DataLogger IoT, the following entries are used for the HTTP IoT connection:

    \"HTTP IoT\": {\n    \"Enabled\": false,\n    \"URL\": \"<the URL>\",\n    \"CA Cert Filename\": \"<certificate filename>\"\n  }\n

    Where:

    • Enabled - Set to true to enable the connection.
    • URL - Set to the URL for the connection.
    • CA Cert Filename - Set to the cert filename on the SD card if being used.

    If the JSON file is saved in the microSD card, you can load the credentials to the DataLogger IoT.

    Tip

    To load the values by the system at startup using a JSON file and microSD card, you will need to configure the Save Settings. This JSON file will be created with the \"Save to Fallback\" option. Make sure to enable the HTTP IoT as well.

    "},{"location":"example_http/#example-connecting-to-a-http-server","title":"Example - Connecting to a HTTP Server","text":"

    In this example, a simple HTTP Server is creating using Node JS, and the HTTP connection in the DataLogger IoT is used to post data to this server. The received data is output to the console from there server.

    "},{"location":"example_http/#the-server","title":"The Server","text":"

    The following javascript/node code creates a HTTP server on port 8090, and outputs received data to the console.

    var http = require('http');\n\n// Setup the endpoint server\nvar myServer = http.createServer(function (req, res) {\n\n  // Initialize our body string\n  var body=\"\";\n\n  // on data callback, append chunk to our body string\n  req.on('data', function(chunk){\n    body += chunk;\n  });\n\n  // On end callback, output the body to the console\n  req.on('end', function(){\n    // parse json string, then stringify it back for 'pretty printing'\n    console.log(\"payload: \" + JSON.stringify(JSON.parse(body),null,2));\n  });\n\n  // send a reply\n  res.writeHead(200, {'Content-Type': 'text/plain'});\n  res.end('n');\n  // Just listen on our port\n}).listen(8090);\n

    The setup and use of node js is system dependant is beyond the scope of this document. However, Node JS is easily installed with your systems package manager (brew on macOS, Linux distribution package manager (apt, yum, ...etc), on Windows, the WSL is recommended).

    Once Node is setup, the above server is run via the following command (assuming the implementation is in a file called simple_http.js):

    node ./simple_http.js\n

    As data is sent by the DataLogger IoT, the following is output to the console from the server:

    "},{"location":"example_http/#obtaining-a-sites-security-certificate","title":"Obtaining a Sites Security Certificate","text":"

    Accessing a sites SSL/Secure Certificate is done via a web browser. The method for each browser is different. The following example uses Edge, which is similar to the operation in Chrome.

    First, browse to the desired site/server. Click the Secure/Security area/button next to the URL to bring up the security detail page. On this page, select the Connection is secure menu option

    Next, on the page shown, select the certificate button on the upper right of the dialog.

    When you select this button, the certificate details dialog is displayed. On this page, select the Details tab, and select the Export... button on the lower right of the dialog. This will save the sites SSL/Security certificate to a location you specify.

    Once saved, place this file on the SD card your system/DataLogger is using, and set the filename in the HTTP connection menu or settings JSON file.

    "},{"location":"example_iot_web_server/","title":"Viewing and Downloading Log Files using the IoT Web Server","text":"

    As of firmware v01.02.00, log files can be viewed and downloaded over a WiFi network! This saves you time by allowing you to download the files without the need to disconnect the DataLogger IoT and manually remove microSD card.

    The following is covered by this document:

    • How a user configures and uses the HTTP connection
    • Use examples
    "},{"location":"example_iot_web_server/#iot-web-server-connection-setup","title":"IoT Web Server Connection Setup","text":"

    To connect to the ESP32's IoT Web Server, the following information is needed:

    • The server name/address
    • [optional] A username - if required
    • [optional] A password - if required
    "},{"location":"example_iot_web_server/#iot-web-server-menu-system","title":"IoT Web Server Menu System","text":"

    We'll need to adjust the settings for the IoT Web Server.

    For users that are interested in using the menu system, open a Serial Terminal, connect to the COM port that your DataLogger enumerated to, and set it to 115200 baud. In this case, we connected to COM13. Press any key to enter the Main Menu. Type 1 to enter the Settings menu. Then type 17 to enter the IoT Web Server Menu. When the menu system for the IoT Web Server is presented, the following options are displayed:

    The options are:

    • Enable/Disable the connection
    • Username
    • Password
    • Enable/Disable mDNS support
    • mDNS name

    At a minimum, you will just need to enable the connection. However, we recommend enabling mDNS support if it is supported in your network.

    Once all these values are set, the system will serve the log files in your local 2.4GHz WiFi network following the JSON information structure noted below in this document.

    "},{"location":"example_iot_web_server/#json-file-entries","title":"JSON File Entries","text":"

    If a JSON file is being used as an option to import settings into the DataLogger IoT, the following entries are used for the IoT web server:

    \"IoT Web Server\": {\n  \"Enabled\": false,\n  \"Username\": \"\",\n  \"Password\": \"\",\n  \"mDNS Support\": false,\n  \"mDNS Name\": \"dataloggerAD6B8\"\n},\n

    Where:

    • Enabled - Set to true to enable the connection.
    • Username - Web server user name if being used.
    • Password - Web server password if being used.
    • mDNS Support - Set to true if multicast DNS is supported. This allows you to enter the address as \"http://dataloggerXXXXX.local\" (where XXXXX is generated from the last 5x characters from your board ID) rather than typing the exact IP address of the ESP32.
    • mDNS Name - Multicast DNS name. In this case, the default name was set to dataloggerAD6B8. This name will be different depending on your DataLogger IoT's board ID so AD6B8 will be different for your board.

    Tip

    To load the values by the system at startup using a JSON file and microSD card, you will need to configure the Save Settings. This JSON file will be created with the \"Save to Fallback\" option. Make sure to enable the MQTT Client as well.

    "},{"location":"example_iot_web_server/#connect-and-download-log-file","title":"Connect and Download Log File","text":"

    Note

    You will need to make sure that the ESP32 is on the same network as your computer in order to access the log files.

    Note

    When authentication is enabled, some browsers might require a second login depending on user settings.

    Once the web server is enabled and the settings are saved, you will need to reboot the DataLogger IoT. As the DatLogger initializes, it will connect to your WiFi Network. Take note of the mDNS address, in this case, it was \"http://dataloggerAD6B8.local\".

    Once the DataLogger IoT has finished initializing, open web browser. Connect the DataLogger IoT by entering the address \"http://dataloggerXXXXX.local\", where XXXX is the last 5x characters of your board ID. You will be presented with the log files available on the microSD card. Click on a log file to download and save it to your computer.

    Note

    If mDNS is not supported, you can also enter the IP address of the Datalogger IoT into a web browser to view and download the log files. You can view the IP address when the DataLogger IoT is initializing. If you have administrative privileges to the WiFi Network, you can also view the IP address through your WiFi router as well.

    Now that you have downloaded the log files, try graphing the data on a spreadsheet!

    "},{"location":"example_mqtt/","title":"MQTT","text":""},{"location":"example_mqtt/#connecting-and-publishing-data-to-mqtt","title":"Connecting and Publishing Data to MQTT","text":"

    One of the key features of the DataLogger IoT is it's simplified access to IoT service providers and servers. This document outlines how output from a DataLogger device is sent to an MQTT Broker.

    Image Courtesy of MQTT

    The following is covered by this document:

    • Overview of the MQTT connection
    • How a user configures and uses the MQTT connection
    • MQTT examples
    "},{"location":"example_mqtt/#general-operation","title":"General Operation","text":"

    MQTT connectivity allows data generated by the DataLogger IoT to be published to an MQTT Broker under a user configured topic. MQTT is an extremely flexible and low overhead data protocol that is widely used in the IoT field.

    The general use pattern for MQTT is that data is published to a topic on a MQTT broker. The data is then sent to any MQTT client that has subscribed to the specified topic.

    The DataLogger IoT supports MQTT connections, allowing an end user to enter the parameters for the particular MQTT Broker for the application to publish data to. When the application outputs data to the broker, the DataLogger IoT publishes the available information to the specified \"topic\" with the payload that is a JSON document.

    "},{"location":"example_mqtt/#data-structure","title":"Data Structure","text":"

    Data is published to the MQTT broker as a JSON object, which contains a collection of sub-objects. Each sub-object represents a data source in the sensor, and contains the current readings from that source.

    The following is an example of the data posted - note, this representation was \"pretty printed\" for readability.

    {\n  \"MAX17048\": {\n    \"Voltage (V)\": 4.304999828,\n    \"State Of Charge (%)\": 115.0625,\n    \"Change Rate (%/hr)\": 0\n  },\n  \"CCS811\": {\n    \"CO2\": 620,\n    \"VOC\": 33\n  },\n  \"BME280\": {\n    \"Humidity\": 25.03613281,\n    \"TemperatureF\": 79.64599609,\n    \"TemperatureC\": 26.46999931,\n    \"Pressure\": 85280.23438,\n    \"AltitudeM\": 1430.44104,\n    \"AltitudeF\": 4693.04834\n  },\n  \"ISM330\": {\n    \"Accel X (milli-g)\": -53.31399918,\n    \"Accel Y (milli-g)\": -34.03800201,\n    \"Accel Z (milli-g)\": 1017.236023,\n    \"Gyro X (milli-dps)\": 542.5,\n    \"Gyro Y (milli-dps)\": -1120,\n    \"Gyro Z (milli-dps)\": 262.5,\n    \"Temperature (C)\": 26\n  },\n  \"MMC5983\": {\n    \"X Field (Gauss)\": -0.200622559,\n    \"Y Field (Gauss)\": 0.076416016,\n    \"Z Field (Gauss)\": 0.447570801,\n    \"Temperature (C)\": 29\n  }\n}\n
    "},{"location":"example_mqtt/#mqtt-broker-connection-setup","title":"MQTT Broker Connection Setup","text":"

    To connect to a MQTT Broker, the following information is needed:

    • The server name/address
    • The server port
    • The topic to post to
    • [optional] The name of the device/Client name publishing the data
    • [optional] A username - if required
    • [optional] A password - if required

    These values are set using the standard DataLogger methods - the interactive menu system, or a JSON file.

    "},{"location":"example_mqtt/#mqtt-menu-system","title":"MQTT Menu System","text":"

    We'll need to adjust the settings for the MQTT Client using the MQTT Menu System.

    For users that are interested in using the menu system, open a Serial Terminal, connect to the COM port that your DataLogger enumerated to, and set it to 115200 baud. In this case, we connected to COM13. Press any key to enter the Main Menu. Type 1 to enter the Settings menu. Then type 9 to enter the MQTT Client Menu. When the menu system for the MQTT connection is presented, the following options are displayed:

    The options are:

    • Enable/Disable the connection
    • Broker Port - The standard port for mqtt is 1883
    • Broker Server - This is just the name of the server
    • MQTT Topic - A string
    • Client Name
    • Username
    • Password
    • Buffer Size

    At a minimum, the Broker Port, Broker Server Name, and MQTT Topic need to be set. What parameters are required depends on the settings of the broker being used.

    Note

    If a secure connection is being used with the MQTT broker, use the MQTT Secure Client option of the DataLogger IoT. This option supports secure connectivity.

    Note

    The Buffer Size option is dynamic by default, adapting to the size of the payload being sent. If runtime memory is a concern, set this value to a static size that supports the device operation.

    Once all these values are set, the system will publish data to the specified MQTT Broker, following the JSON information structure noted earlier in this document.

    "},{"location":"example_mqtt/#json-file-entries","title":"JSON File Entries","text":"

    If a JSON file is being used as an option to import settings into the DataLogger IoT, the following entries are used for the MQTT IoT connection:

    \"MQTT Client\": {\n    \"Enabled\": false,\n    \"Port\": 1883,\n    \"Server\": \"my-mqttserver.com\",\n    \"MQTT Topic\": \"/sparkfun/datalogger1\",\n    \"Client Name\": \"mysensor system\",\n    \"Buffer Size\": 0,\n    \"Username\": \"\",\n    \"Password\": \"\"\n  },\n

    Where:

    • Enabled - Set to true to enable the connection.
    • Port - Set to the broker port.
    • Server - The MQTT broker server.
    • MQTT Topic - The topic to publish to.
    • Client Name - Optional client name.
    • Buffer Size - Internal transfer buffer size.
    • Username - Broker user name if being used.
    • Password - Broker password if being used.

    Tip

    To load the values by the system at startup using a JSON file and microSD card, you will need to configure the Save Settings. This JSON file will be created with the \"Save to Fallback\" option. Make sure to enable the MQTT Client as well.

    "},{"location":"example_mqtt/#testing-the-mqtt-connection","title":"Testing the MQTT Connection","text":"

    Use of a MQTT connection is fairly straightforward - just requiring the entry of broker details into the connection settings.

    To test the connection, you need a MQTT broker available. A quick method to setup a broker is by installing the mosquitto package on a Raspberry Pi computer. Our basic MQTT Tutorial provides some basic setup for a broker.

    Introduction to MQTT

    This MQTT Broker Tutorial has more details, covering the setup needed for modern mosquitto configurations.

    Random Nerd Tutorials: Install Mosquitto Broker on Raspberry Pi

    And once the broker is setup, the messages published by the IoT sensor are visible using the mosquitto_sub command as outlined. For example, to view messages posted to a the topic \"/sparkfun/datalogger1\", the following command is used:

    mosquitto_sub -t \"/sparkfun/datalogger1\"\n

    This assumes the MQTT broker is running on the same machine, and using the default port number.

    "},{"location":"example_thingspeak/","title":"Creating and Connecting to ThingSpeak","text":"

    One of the key features of the DataLogger IoT is it's simplified access to IoT service providers. This document outlines how a ThinkSpeak output is used by the DataLogger IoT.

    Image Courtesy of ThingSpeak

    The following is covered by this document:

    • Creating a ThingSpeak Channel and MQTT Connection
    • Securely connecting the ThingSpeak
    • How data is posted from the DataLogger IoT to ThingSpeak
    "},{"location":"example_thingspeak/#general-operation","title":"General Operation","text":""},{"location":"example_thingspeak/#thingspeak-structure","title":"ThingSpeak Structure","text":"

    The structure of ThingSpeak is based off of the concept of Channels, with each channel supporting up to eight fields for data specific to the data source. Each channel is named, and has a unique ID associated with it. One what to think of it is that a Channel is a grouping of associated data values or fields.

    The fields of a channel are enumerated as Field1, Field2, ..., Field8, but each field can be named to simplify data access and understanding.

    As data is reported to a ThingSpeak channel, the field values are accessible for further processing or visualization output.

    "},{"location":"example_thingspeak/#data-structure","title":"Data Structure","text":"

    The DataLogger IoT is constructed around the concept of Devices which are often a type of sensor that can output a set of data values per observation or sample.

    "},{"location":"example_thingspeak/#mapping-data-to-thingspeak","title":"Mapping Data to ThingSpeak","text":"

    The concept of Channels that contain Fields in ThingSpeak is similar to the Devices that contain Data within the DataLogger IoT, and this similarity is the mapping model used by the DataLogger IoT. Specifically:

    • Devices == Channels
    • Data == Fields

    During configuration of the DataLogger IoT, the mapping between the Device and ThingSpeak channel is specified. The data to field mapping is automatically created by the DataLogger IoT following the data reporting order from the specific device driver.

    "},{"location":"example_thingspeak/#creating-a-device-to-a-thingspeak-channel","title":"Creating a Device to a ThingSpeak Channel","text":"

    The following discussion outlines the basic steps taken to create a Channel in ThingSpeak and then connect it to the DataLogger's Device. First step is to log into your ThingSpeak and create a Channel.

    Click Here to Log into ThingSpeak

    Once logged into your ThingSpeak account, select Channels > My Channels menu item and on the My Channel page, select the New Channel button.

    On the presented channel page, name the channel and fill in the specific channel fields. The fields should map to the data fields reported from the Device being linked to this channel. Order is important, and is determined by looking at output of a device to the serial device (or reviewing the device driver code).

    Once the values are entered, select Save Channel. ThingSpeak will now show list of Channel Stats, made up of line plots for each field specified for the channel.

    Note

    Key note - at the top of this page is listed the Channel ID. Note this number - it is used to map a Device to a ThingSpeak Channel.

    "},{"location":"example_thingspeak/#setting-up-thingspeak-mqtt","title":"Setting Up ThingSpeak MQTT","text":"

    The DataLogger IoT uses MQTT to post data to a channel. From the ThingSpeak menu, select Devices > MQTT, which displays a list of your MQTT devices. From this page, select the Add a new device button.

    On the presented dialog, enter a name for the MQTT connection and in the Authorize channels to access, select the channel created earlier. Once you select a channel, click the Add Channel button.

    Note

    More channels can be added later.

    Note

    When the MQTT device is created, a set of credentials (Client ID, Username, and Password) is provided. Copy or download these values, since the password in not accessible after this step.

    The selected Channel is then listed in the Authorized Channel table. Ensure that the Allow Publish and Allow Subscribe attributes are enabled for the added channel.

    At this point, the ThingSpeak Channel is setup for access by the DataLogger IoT.

    "},{"location":"example_thingspeak/#thingspeak-configuration","title":"ThingSpeak Configuration","text":"

    Once the device is integrated into the application, the specifics for the ThingSpeak Channel(s) must be configured. This includes the following:

    • Server Name/Hostname
    • Client Name
    • User Name
    • Password
    • Device to Channel mapping
    • CA Certificate Chain
    "},{"location":"example_thingspeak/#server-namehostname","title":"Server Name/Hostname","text":"

    This value is hostname of the ThingSpeak mqtt connection, which is mqtt3.thingspeak.com as note at ThingSpeakMQTT Basics page. Note a secure connection is used, so the port for the connection is 8883.

    "},{"location":"example_thingspeak/#client-nameid","title":"Client Name/ID","text":"

    The Client Name/ID is found under MQTT connection details listed in the Devices > MQTT section of ThingSpeak.

    "},{"location":"example_thingspeak/#username","title":"Username","text":"

    The Username is found under MQTT connection details listed in the Devices > MQTT section of ThingSpeak.

    "},{"location":"example_thingspeak/#password","title":"Password","text":"

    The connection password was provided when the MQTT device was created. If you lost this value, you can regenerate a password on the MQTT Device information page.

    "},{"location":"example_thingspeak/#certificate-file","title":"Certificate File","text":"

    You can download the cert file for ThingSpeak.com page using a web-browser. Click on the security details of this page, and navigate the dialog (browser dependent) to download the certificate. The downloaded file is the made available for the DataLogger IoT to use as a file that is loaded at runtime)

    "},{"location":"example_thingspeak/#setting-properties","title":"Setting Properties","text":"

    The above property values must be set on the DataLogger IoT before use. They can be manually by using the menu system like the previous MQTT example.

    For users that are interested in using the menu system, you will need to open a Serial Terminal, connect to the COM port that your DataLogger enumerated to, and set it to 115200 baud. In this case, we connected to COM13. Press any key to enter the Main Menu. Type 1 to enter the Settings menu. Then type 12 to enter the ThingSpeak MQTT Menu. When the menu system for the ThingSpeak MQTT connection is presented, you will need to configure the property values as listed in the JSON file. Saving the values through the menu system will save the credentials to the ESP32's persistent memory. The following options are displayed:

    The alternative to using the menu system is a JSON file. These values can be set using a JSON file that is loaded by the system at startup. For the ThingSpeak example outlined in this document, the entries in the settings JSON file are as follows:

    \"ThingSpeak MQTT\": {\n    \"Enabled\": true,\n    \"Port\": 8883,\n    \"Server\": \"mqtt3.thingspeak.com\",\n    \"MQTT Topic\": \"\",\n    \"Client Name\": \"MQTT_Device_Client_ID\",\n    \"Buffer Size\": 0,\n    \"Username\": \"MQTT_Device_Username\",\n    \"Password\": \"MQTT_Device_Password\",\n    \"CA Cert Filename\": \"ThingspeakCA.cer\",\n    \"Channels\" : \"BME280=2054891\"\n  }\n

    Note

    The Channels value is a list of [DEVICE NAME]=[Channel ID] pairs. Each pair is separated by a comma. In this case, the device name BME280 and the channel ID was 2054891. Make sure to match the device name that was loaded on start up with the unique channel ID that was generated when creating a ThingSpeak Channel.

    Besides updating the Server, Client Name, Username, Password, CA Cert Filename, and Channels, you will need to also ensure that the port is set to 8883. The default in previous firmware versions was 1883. As of firmware v01.00.04, the default is 8883. You will need to adjust the port value to properly connect to the ThingSpeak service. Don't forget to enable ThingSpeak MQTT service by setting the value to true. If the JSON file is saved in the microSD card, you can load the credentials to the DataLogger IoT.

    Tip

    To load the values by the system at startup using a JSON file and microSD card, you will need to configure the Save Settings. This JSON file will be created with the \"Save to Fallback\" option. Make sure to enable the ThingSpeak MQTT as well.

    "},{"location":"example_thingspeak/#monitoring-output","title":"Monitoring Output","text":"

    Once the connector is configured and the DataLogger IoT is connected to ThingSpeak, as data is posted, the results are show on the Channel Stats page for your Channel. For the above example, the output of a SparkFun BME280 sensor produces the following output:

    "},{"location":"example_thingspeak/#setting-up-2x-or-more-devices","title":"Setting Up 2x or More Devices","text":"

    For users that are setting up 2x or more devices on the DataLogger IoT, you will need to ensure that each device has their own ThingSpeak Channel. Unfortunately, you are not able to plot sensor readings from two devices in the same channel.

    The following example demonstrates how to set up two devices for ThingSpeak on the DataLogger IoT. In this case, we will use the BME688 and BME680 and their respective default I2C address. This is also a good example of what to do when two devices use the same device driver. Head to ThingSpeak to create a channel for each device connected to the DataLogger IoT. Include a field for each device data that the DataLogger provides. The name of the channel does not need to match the device name or I2C address.

    Creating a Channel for the BME688 Creating a Channel for the BME680

    Once the channels are created, you will be provided with a unique channel ID for each channel. Make sure to take note of the number as explained earlier.

    Note

    The alternative I2C address for the BME688 and BME680 uses the same address as the default of the other sensor:

    • BME680: 0x77 (Default) or 0x76
    • BME688: 0x76 (Default) or 0x77

    Make sure to avoid using the same address when connecting the sensors to the same DataLogger IoT.

    When setting up the connection, you will need to authorize both channels. In this example, we included the channels for the BME688 [x076] and BME680 [x077].

    Authorizing 2x Channels through the Same Connection

    Now that the ThingSpeak MQTT connection is setup, adjust the ThingSpeak configuration for the DataLogger IoT by including the credentials (i.e. Client Name, Username, and Password) and channels. We will assume that you have included the ThingSpeak CA certificate file in the root directory of the microSD card already. When including the device name with their respective channel, ensure that the device name matches the name that was loaded on startup. For example, the BME688 and BME680 were loaded on startup as BME68x and BME68x [x77], respectively. Since we are only interested in plotting the BME688 and BME680, we will ignore the MAX17048 that was loaded on startup as well. Under /Settings/ThingSpeak MQTT/Channels, you will enter the string for the device names, each of their respective channel IDs, and a comma separating the two channels like so: BME68x=2613826, BME68x [x77]=2613825.

    DataLogger IoT Device Name Loaded during Startup Device Name and Channel for Both Sensors

    Note

    Whenever there are multiple devices using the same device driver (each with unique I2C addresses), the DataLogger IoT will display the device address for each additional device that is loading the same driver. As shown above, the first device name did not include the device's I2C address. The second device name using the same driver included its I2C address. Of course, there is an configuration that enables you to always include the address of all the device names.

    The alternative to using the menu system is the JSON file. In this case, we updated channels for the BME688 and BME680. Not shown are the ThingSpeak Client Name, Username, and Password.

    \"ThingSpeak MQTT\": {\n    \"Enabled\": true,\n    \"Port\": 8883,\n    \"Server\": \"mqtt3.thingspeak.com\",\n    \"MQTT Topic\": \"\",\n    \"Client Name\": \"MQTT_Device_Client_ID\",\n    \"Buffer Size\": 0,\n    \"Username\": \"MQTT_Device_Username\",\n    \"Password\": \"MQTT_Device_Password\",\n    \"CA Cert Filename\": \"ThingspeakCA.cer\",\n    \"Channels\" : \"BME68x=2613826, BME68x [x77]=2613825\"\n  }\n

    Note

    If users configure the DataLogger IoT to always include the device address with the device names (i.e. /Settings/Application Settings with Device Names=1), you will need to match the device names for BME688 and BME680 that were loaded on startup as BME68x [x76] and BME68x [x77], respectively. Note the BME688 device name included a space and [x76] in this case. Remember, we are only interested in plotting hte BME688 and BME680 in this case so we will ignore the MAX17048 that was loaded on startup.

    DataLogger IoT Device Names Loaded during Startup Device Name and Channel for Both Sensors with Respective Addresses

    Again, the alternative to using the menu system is the JSON file. In this case, we updated channels for the BME688 and BME680. We also included the address name for the BME688 like the configuration menu. Not shown are the ThingSpeak Client Name, Username, and Password.

    \"ThingSpeak MQTT\": {\n    \"Enabled\": true,\n    \"Port\": 8883,\n    \"Server\": \"mqtt3.thingspeak.com\",\n    \"MQTT Topic\": \"\",\n    \"Client Name\": \"MQTT_Device_Client_ID\",\n    \"Buffer Size\": 0,\n    \"Username\": \"MQTT_Device_Username\",\n    \"Password\": \"MQTT_Device_Password\",\n    \"CA Cert Filename\": \"ThingspeakCA.cer\",\n    \"Channels\" : \"BME68x [x76]=2613826, BME68x [x77]=2613825\"\n  }\n

    Save the configuration to persistent memory and exit out of the configuration menu. Wait a few seconds for the DataLogger IoT to read the sensors and output the readings to the Serial Terminal. Open ThingSpeak channels in separate browser windows. In this case, we had the BME688 and the BME680 in private view. You should see sensor readings update and plot on the charts.

    ThingSpeak Graphing the BME688 and BME680 in Seperate Channels on Two Browser Windows"},{"location":"example_timestamp/","title":"Adding a Timestamp to Data","text":"

    Open a Serial Terminal, connect to the COM port that your DataLogger enumerated to, and set it to 115200 baud. In this case, we connected to COM13. Press any key to enter the Main Menu. Type 1 to enter the Settings menu. Then send a 6 to adjust how data is logged.

    Send a 1 to configure the timestamp for each log entry. The settings in this menu relate to the system clock and is dependent on the reference clock. You'll be prompted with different formats. In this example, we sent a a 4 to have a timestamp with the USA date format.

    Follow the prompts to exit out of the menu properly so that the DataLogger IoT saves the settings. Once you see the message [I] Saving System Settings, the DataLogger IoT will add a timestamp with your preferred format to each log entry. Assuming that you have the output set to the serial terminal, you should see the timestamp attached to the output after the system settings are saved like the image below.

    "},{"location":"factory_reset/","title":"Factory Reset","text":"

    A factory reset will move the boot firmware of the device to the firmware imaged installed at the factory and erase any on-board stored settings on the device. This is helpful if an update fails, or an update has issues that prevent proper operations.

    This option is available on ESP32 devices that contained a factory firmware partition that contains a bootable firmware image. Consult the specific product's production and build system for further details.

    Open a Serial Terminal, connect to the COM port that your DataLogger enumerated to, and set it to 115200 baud. In this case, we connected to COM13. Press any key to enter the Main Menu. Type 1 to enter the Settings menu. Then type 16 to enter the System Update Menu. Finally, type 2 to enter the Factory Reset option.

    The user is presented a prompt to continue. To launch a factory reset, the value of Y should be entered. To abort the update, enter n or press the Esc key.

    When a Y is entered, the system performs the following:

    • Set the boot image to the Factory installed firmware
    • Erase any settings stored in the on-board flash memory
    • Reboot the device
    "},{"location":"file_issue/","title":"Did we make a mistake?","text":"

    Spot something wrong? Please let us know.

    Attention

    This is not where customers should seek assistance on a product. If you require technical assistance or have questions about a product that is not working as expected, please head over to the SparkFun Technical Assistance page for some initial troubleshooting. SparkFun Technical Assistance Page

    If you can't find what you need there, you'll need a Forum Account to search product forums and post questions.

    "},{"location":"file_issue/#discrepancies-in-the-documentation","title":"Discrepancies in the Documentation","text":"

    All of this documentation can be modified by you! Please help us make it better.

    • The documentation files for these pages are contained in the docs folder of the SparkFun DataLogger IoT repository.
    "},{"location":"file_issue/#spot-something-wrong","title":"Spot something wrong?","text":"

    If a section of the documentation is incorrect, please open an issue and let us know.

    "},{"location":"file_issue/#do-you-have-a-suggested-correction","title":"Do you have a suggested correction?","text":"
    1. With a GitHub account, fork this repo
    2. Add your correction(s) or improvement(s) to the markdown file(s)
    3. File a pull request with your changes, and enjoy making the words worlds world a better place.
      • Once received, the documentation specialist will automatically be notified.
      • We will review your suggested improvement(s) to make sure they are correct and fit within our documentation standards.
    "},{"location":"file_issue/#problems-in-the-hardware-design","title":"Problems in the Hardware Design","text":"

    All of our designs are open-source! Please help us make it better.

    • Our board design files are contained in the Hardware folder of the SparkFun DataLogger IoT repository.
    "},{"location":"file_issue/#does-something-not-make-sense","title":"Does something not make sense?","text":"

    If part of the design is confusing, please open an issue and let us know.

    "},{"location":"file_issue/#did-we-forget-to-include-an-important-function-of-the-board","title":"Did we forget to include an important function of the board?","text":"
    • Please keep in mind that we may intentionally exclude certain functions of the board to meet our product design requirements. (For example, our Qwiic Micro boards are intended to fit on a small board layout and only use I2C communication; therefore, we may not have the SPI and interrupt pins available for users.)
    • If part of the board's functionality is missing, please open an issue and file a feature request.
    "},{"location":"file_issue/#do-you-wish-to-contribute-directly-to-improving-the-board-design","title":"Do you wish to contribute directly to improving the board design?","text":"
    1. With a GitHub account, Fork this repo
    2. Add your design improvement(s)
    3. File a pull request with your changes, and enjoy making the words worlds world a better place.
      • Once received, the engineer in charge of the original design will automatically be notified.
      • We will review your suggested improvement(s), if they are within our board design standards and meet our product design requirements, we will flag these changes for our next board revision. (Please note, that even if your suggestion is accepted, these changes may not be immediate. We may have to cycle through our current product inventory first.)
    "},{"location":"hard_copy/","title":"Hard copy","text":"

    Need to download or print our hookup guide?

    • Print (Single-Page View)
      • To save as a *.pdf file, select the Printer or Destination labeled Save as PDF. (Instructions will vary based on the browser)
    "},{"location":"hardware_hookup/","title":"Hardware Hookup","text":"

    In this section, we will go over how to connect to the SparkFun DataLogger IoT. At the time of writing, we used the DataLogger IoT - 9DoF. This hardware hookup explained in this section also applies for the DataLogger IoT.

    "},{"location":"hardware_hookup/#soldering-to-the-pths","title":"Soldering to the PTHs","text":"

    Note

    The UART, SPI, analog, and digital I/O pins are not currently supported in the firmware for data logging.

    For users that are interested in soldering to the edge of the board, we recommend soldering headers to the PTHs on the breakout for a permanent connection and using jumper wires. Of course, you could also solder wires to the breakout board as well. For a temporary connection during prototyping, you can use IC hooks like these.

    • How to Solder: Through-Hole Soldering

    • Working with Wire

    "},{"location":"hardware_hookup/#microsd-card","title":"MicroSD Card","text":"

    If all you want to do is display your sensor readings in a serial terminal or monitor (connected via USB-C) then, strictly, you don\u2019t need to add a microSD card. But of course the whole point of the DataLogger IoT is that it can log readings from whatever sensors you have attached to microSD card. The data is logged in easy-to-read Comma Separated Value (CSV) text format by default. You can also set the format as JSON.

    You probably already have a microSD card laying around but if you need any additional units, we have plenty in the store. The DataLogger IoT can use any size microSD card as long as it is formatted correctly. Please ensure your SD card is formatted correctly. There are different software tools available. Some are built into your operating system. We recommend using the Raspberry Pi Imager Tool to easily format the memory card as FAT32 using the GUI. Flip over the DataLogger IoT and you\u2019ll see the latching microSD card socket. Slide in your formatted SD card and it will click neatly into place. Part of the edge of the SD card will stick out when fully inserted in the microSD card socket.

    You should only insert or remove the SD card while the power is turned off or disconnected. Removing the card while the DataLogger IoT is logging will almost certainly corrupt your data. You can tell when the board has just logged by observing the addressable RGB LED. When enabled, the LED will blink blue after it has logged one data point.

    After you\u2019ve logged some data, you will find a new file on your SD card. There may also be additional files if you manually saved the firmware or preferences to the memory card.

    • sfe0001.txt: This is the file that contains the CSV or JSON sensor data. The format will depend on how you configured the DataLogger's output. We use .TXT as the file type so that your computer can open it in a simple text editor. The contents are all human-readable. But, if you want to, you can rename it as .CSV or .JSON instead. The file number is incremented for the next logging session.
    • datalogger.json: This file only appears when you save the settings as your fallback storage. The file will include all preferences saved for any connected device, WiFi credentials, certificates, and keys.
    • SparkFun_DataLoggerIoT*.bin: This file only appears when you save the firmware to the microSD card. Note that the asterisk (*) is the firmware version number (i.e. SparkFunDataLoggerIoT_01.00.01.bin).

    To remove the microSD card, make sure power is disconnected from the DataLogger IoT. Then press the microSD card into the microSD socket. The memory card will be ejected and you will hear a click again. Once the card is ejected, you can insert it into a microSD card adapter or USB reader to be read on a computer.

    "},{"location":"hardware_hookup/#qwiic-sensors","title":"Qwiic Sensors","text":"

    If you are going to attach extra sensors or any Qwiic-enabled device to the DataLogger IoT, then those need to be connected first before attaching a USB cable. It is a good idea to only attach or disconnect Qwiic sensors when the power is turned off or disconnected. The Qwiic bus is pretty tolerant to \u201chot swapping\u201d, but: disconnecting a sensor while it is in use will confuse the DataLogger IoT software (most likely each value associated with the device will remain constant); and a new sensor won\u2019t be detected until the firmware restarts.

    Plug one end of your Qwiic cable into the DataLogger IoT and plug the other end into your sensor. If you want to add extra sensors, you can simply connect them to each other in a daisy chain. You will need a Qwiic cable for each sensor. Our Qwiic Cable Kit covers all the options.

    DataLogger IoT and a Qwiic-Enabled Device DataLogger IoT and several Qwiic-Enabled Devices Daisy Chained

    Our Qwiic sensors usually all have power indicator LEDs and I2C pull-up resistors. Depending on your application, you may want or need to disable these by cutting the jumper links on the sensor circuit boards. We have a tutorial that will show you how to do that safely.

    Sometimes you might want to connect more than one of the same type of sensor to the DataLogger IoT. On the I2C bus, each device needs to have a unique address. On many of our boards, there are jumpers links which you can use to change the address and some have addresses that can be configured in software. But there are some where you cannot change the address - the NAU7802 Qwiic Scale being one example.

    Typically one would use a multiplexor. However, we currently do not have the DataLogger IoT configured to work with any multiplexors (i.e. Qwiic Mux Breakout).

    Note

    Currently the Qwiic Mux is not compatible with the DataLogger IoT.

    "},{"location":"hardware_hookup/#lipo-battery","title":"LiPo Battery","text":"

    Battery Polarity

    Please make sure that you use one of our recommended Lithium Ion batteries. Some batteries use the same JST connector as ours but have the opposite polarity. Connecting one of these to your DataLogger IoT will destroy it. If you are going to use your own battery, it is up to you to ensure it has the correct polarity.

    Now is a good time to attach a LiPo battery, if you want the DataLogger IoT to keep logging when you disconnect USB-C.

    You can connect one of our standard single cell LiPo batteries to the DataLogger IoT and power it for hours, days or weeks depending on what sensors you have attached and how often you log data. The DataLogger IoT has a built-in charger too which will charge your battery at 500mA when USB-C is connected. Please make sure your battery capacity is at least 500mAh (0.5Ah); bad things will happen if you try to charge our smallest batteries at 500mA. The yellow CHG charging LED will light up while the battery is charging and will go out once charging is complete.

    Warning

    The MCP73831 charge IC on the board is used on a few SparkFun products. For more information about the CHG status LED, we recommend taking look at the Hardware Overview. We also recommend taking a look at this tutorial for Single Cell LiPo Battery Care.

    "},{"location":"hardware_hookup/#usb-cable","title":"USB Cable","text":"

    The USB-C connector provides power to the DataLogger IoT and acts as a serial interface for configuration and data display.

    If you are going to use a microSD card to store your data, and why wouldn\u2019t you, then insert that first before attaching your USB cable. You should only insert or remove the SD card while the power is turned off or disconnected. Removing the card while the DataLogger IoT is logging will almost certainly corrupt your data.

    Likewise, it is a good idea to only attach or disconnect Qwiic sensors when the power is turned off or disconnected. The Qwiic bus is pretty tolerant to \u201chot swapping\u201d, but: disconnecting a sensor while it is in use will confuse the DataLogger IoT software; and a new sensor won\u2019t be detected until the firmware restarts.

    Depending on what ports your computer has available, you will need one of the following cables:

    • USB 2.0 A to C Cable
    • USB 3.1 A to C Cable
    • USB 2.0 C to C Cable

    Use the cable to connect your DataLogger IoT to your computer and you will see the LEDs light as the DataLogger IoT starts up. The addressable RGB RGB LED will light up green for a second or two while the DataLogger IoT configures itself. It will flash blue while data is being logged to the microSD card. If you have jumped the gun and have a LiPo battery already connected, the yellow CHG charging LED may light up too.

    If the addressable RGB LED does not light up, your DataLogger IoT is probably in deep sleep following a previous logging session. Pressing the RST reset button will wake it.

    You\u2019ll find full instructions on how to configure the DataLogger IoT later in this tutorial.

    "},{"location":"hardware_hookup/#standoffs","title":"Standoffs","text":"

    For users interested in stacking the Qwiic-enabled device on the DataLogger IoT or mounting in an enclosure, you will need some standoffs to mount the boards. When mounting, note that all four mounting holes are not positioned exactly for a 1.0\"x1.0\" sized Qwiic board. Only two of the four mounting holes are compatible for a 1.0\"x1.0\" sized Qwiic board. For example the image below shows the boards stacked on each side of the DataLogger IoT. On top, the Qwiic GPS (SAM-M10Q) breakout was also able to stack by rotating the board slightly and aligning the mounting holes on the 1.6\"x1.6\" sized board to the other mounting holes

    "},{"location":"hardware_overview/","title":"Hardware Overview","text":"

    In this section, we will highlight the hardware and pins that are broken out on the SparkFun DataLogger IoT. At the time of writing, we highlighted the SparkFun DataLogger IoT - 9DoF. However, this also applies for the SparkFun DataLogger IoT.

    DataLogger IoT - 9DoF (Top View) DataLogger IoT - 9DoF (Bottom View)

    The SparkFun DataLogger is pretty much the same with the exception of the following features listed below. We'll include notes highlighting the differences in each section.

    • No built - in 6DoF IMU - ISM330DHCX
    • No built - in magnetometer - MMC5983
    • The addressable RGB LED - WS2812 is replaced with the side emitting addressable RGB LED - B3DQ3BRG
    • No IMU INT2 jumper
    • No Mag INT jumper
    • Included MEAS PTH Jumper
    • The \"35 / A7\" PTH on the edge of the board is replace with a \"5\" PTH.
    DataLogger IoT (Top View) DataLogger IoT (Bottom View)"},{"location":"hardware_overview/#esp32-wroom-module","title":"ESP32-WROOM Module","text":"

    The DataLogger IoT is populated with Espressif's ESP32-WROOM-32E module. Espressif's ESP32 WROOM ubiquitous IoT microcontroller is a powerful WiFi, BT, and BLE MCU module that targets a wide variety of applications. For the DataLogger IoT, the firmware currently utilizes the WiFi feature.

    Note

    Currently the DataLogger IoT does not have BT or BLE. However, BT or BLE is being considered on a future firmware build to include this as a feature.

    "},{"location":"hardware_overview/#power","title":"Power","text":"

    There are a variety of power and power-related nets broken out to connectors and through hole pads. Below list a few methods of powering the board up. There are protection diodes for the USB-C, 5V pin, and single cell LiPo battery. Power is regulated down to 3.3V for the system voltage. Depending on the settings and what is connected to the DataLogger IoT, the board can pull a minimum of 200\u00b5A in low power mode by itself.

    • USB-C
    • 5V Pin
    • Single Cell LiPo Battery
    • 3V3 Pin
    "},{"location":"hardware_overview/#usb-c-and-5v","title":"USB-C and 5V","text":"

    The DataLogger IoT comes equipped with a USB type C socket which you can use to connect it to your computer to view the output and configuration through the serial terminal, or plug in a USB-C power supply. The DataLogger IoT includes the configuration channel resistors needed to tell the power supply to deliver 5V. You can use your USB-C laptop charger as the power source should you need to, even though it normally delivers a much higher voltage.

    There is also a 5V power input pin. You can use this to feed in 5V power from an external source. The maximum voltage is 6.0V. The 5V pin is diode-protected and so is the USB-C power input, so it is OK to have both connected at the same time. This pin is ideal if you want to power your DataLogger from regulated solar power or a different type of power supply. You can not use the 5V pin as an output.

    Voltage from the USB is regulated down to the XC6222 3.3V/700mA voltage regulators for the system voltage and Qwiic-enabled devices. USB power is also connected to the MCP73831 to charge a single cell LiPo battery at a default rate of 500mA.

    For customers in North America, our NEMA Raspberry Pi Wall Adapter is a perfect choice. You can power the DataLogger IoT from our USB Battery Pack / Power Bank - TOL-15204 but you will need a USB-C cable too:

    • Our USB 2.0 A to C Cable - CAB-15092 will do nicely
    • Our USB 3.1 A to C Cable - CAB-14743 is a good choice too
    "},{"location":"hardware_overview/#lipo-battery-input-charger-and-fuel-gauge","title":"LiPo Battery Input, Charger, and Fuel Gauge","text":"

    Warning

    Battery Polarity: Please make sure that you use one of our recommended Lithium Ion batteries. Some batteries use the same JST connector as ours but have the opposite polarity. Connecting one of these to your DataLogger IoT will destroy it. If you are going to use your own battery, it is up to you to ensure it has the correct polarity.

    But of course you\u2019re going to want to use the DataLogger IoT to log sensor data while on the move too. You can connect one of our standard single cell LiPo batteries to the DataLogger IoT and power it for hours, days or weeks depending on what sensors you have attached and how often you log data. The DataLogger IoT uses the built-in MCP73831 charger too which will charge your battery at 500mA when USB-C is connected. Please make sure your battery capacity is at least 500mAh (0.5Ah); bad things will happen if you try to charge our smallest batteries at 500mA. The board also includes the MAX17048 LiPo Fuel Gauge which allows you to determine how much power your LiPo battery has available. The 2-pin JST connector pins are broken out to PTHs on the edge of the board if you decide to solder a single cell LiPo battery directly to the board or power another device.

    "},{"location":"hardware_overview/#3v3-pins","title":"3V3 Pins","text":"

    For those going the old school route, you can also bypass the voltage regulators by soldering directly to the 3V3 and GND pin to provide power if your application has a regulated 3.3V. Note that this is only connected to the system voltage. You will also need to provide power to the 3V3 SWCH or Qwiic-enabled devices should you decide to bypass the voltage regulator.

    "},{"location":"hardware_overview/#ch340-usb-to-serial-converter","title":"CH340 USB-to-Serial Converter","text":"

    The top side of the board includes a CH340 USB-to-Serial Converter. The chip can be used to send serial data between the device and computer. You can view the output or configure the device through a serial terminal.

    The driver should automatically install on most operating systems. However, there is a wide range of operating systems out there. You may need to install drivers the first time you connect the chip to your computer's USB port or when there are operating system updates. For more information, check out our How to Install CH340 Drivers Tutorial.

    How to Install CH340 Drivers"},{"location":"hardware_overview/#uart","title":"UART","text":"

    The hardware serial UART pins are broken out on the edge of the board. For more information about Serial UART, check out the tutorial about Serial Communication for more information.

    • TXD: UART transmit pin. This is connected to pin 16.
    • RXD: UART receive pin. This is connected to pin 17.

    Note

    The UART pins are not currently supported in the firmware for data logging.

    "},{"location":"hardware_overview/#qwiic-and-i2c","title":"Qwiic and I2C","text":"

    Note

    You may notice a thin film over the vertial Qwiic connector. This is used by a pick-and-place machine when populating the component on the board before it goes through the reflow oven. This can be removed if you decide to use the vertical Qwiic connector with Qwiic-enabled devices.

    SparkFun's Qwiic Connect System uses 4-pin JST style connectors to quickly interface development boards with I2C sensors and more. No soldering required and there's no need to worry about accidentally swapping the SDA and SCL wires. The Qwiic connector is polarized so you know you\u2019ll have it wired correctly every time, right from the start. Qwiic boards are daisy chain-able too so you can connect multiple sensors to the DataLogger IoT and log readings from all of them.

    The board is populated with vertical and horizontal Qwiic connectors. These are also broken out to PTHs on the edge of the board.

    • SCL: I2C clock pin. This is connected to pin 22 and a 2.2k\u03a9 pull-up resistor.
    • SDA: I2C data pin. This is connected to pin 21 and a 2.2k\u03a9 pull-up resistor.
    • 3V3 SW: The 3.3v pin is connected to the XC6222 voltage regulator's output to power the Qwiic devices.
    • GND: Common, ground voltage (0V reference) for the system

    Connected to the line I2C line is the MAX17048 LiPo fuel gauge (7-bit unshifted address = 0x36).

    Sometimes you might want to connect more than one of the same type of sensor to the DataLogger IoT. On the I2C bus, each device needs to have a unique address. On many of our boards, there are jumpers links which you can use to change the address and some have addresses that can be configured in software. But there are some where you cannot change the address. Typically, one would use a multiplexor. However, we currently do not have the DataLogger IoT configured to work with any multiplexors (i.e. Qwiic Mux Breakout).

    Note

    Currently the Qwiic Mux is not compatible with the DataLogger IoT.

    The DataLogger IoT includes a dedicated 3.3V regulator for the Qwiic connector. This has several advantages including:

    • The DataLogger IoT can completely power-down the I2C sensors during sleep to prolong your battery life
    • There\u2019s no risk of the Qwiic bus gulping too much current and causing problems for the ESP32
    "},{"location":"hardware_overview/#spi","title":"SPI","text":"

    Note

    Besides the built-in ISM330DHCX and MMC5983MA, the SPI pins are not currently supported in the firmware for data logging.

    The SPI pins are broken out on the edge of the board. For those that are unfamiliar to PICO and POCI, check out the SPI tutorial for more information.

    • SCK: SPI clock pin. This is connect to pin 18.
    • PICO: SPI Peripheral In Controller Out. This is connected to pin 23.
    • POCI: SPI Peripheral Out Controller In. This is connected to pin 19.

    Not shown in the image are the chip select (CS) pins. The 6DoF IMU's CS pin is connected to pin 5. The magnetometer's CS pin is connected to pin 27 which is not broken out.

    Note

    On the DataLogger IoT, the IMU and magnetometer are not connected to the SPI port since they are not included on the board. Instead of pin \"35 / A7\" being broken out, pin \"5\" is broken out on the edge of the board.

    "},{"location":"hardware_overview/#microsd-card-socket","title":"MicroSD Card Socket","text":"

    The DataLogger IoT supports full 4-bit SDIO for fast logging and uses common microSD cards to record clear text, comma separated files. Flip over the DataLogger IoT and you'll see the latching microSD card socket. You probably already have a microSD card laying around. However, if you need any additional units, we have plenty in the SparkFun catalog. The DataLogger can use any size microSD card and supports FAT32 cards in addition to FAT16. Please ensure that your SD card is formatted correctly; we recommend the Raspberry Pi Imager Tool.

    Slide in your formatted SD card and it will click neatly into place. The edge of the SD card will stick out on the edge of the circuit board when it is inserted correctly.

    Warning

    You should only insert or remove the SD card while the power is turned off or disconnected. Removing the card while the DataLogger IoT is logging will almost certainly corrupt your data.

    "},{"location":"hardware_overview/#9-degrees-of-freedom-9dof","title":"9 Degrees of Freedom (9DOF)","text":"

    As stated earlier, included on every DataLogger IoT - 9DoF is a 6DoF Inertial Measurement Unit (IMU) for built-in logging of triple-axis accelerometer and gyro. There is also a built-in triple-axis magnetometer for a complete 9 degrees of freedom. Beside each IC is a silkcreen showing the reference axis. Both are connected to the ESP32 via the SPI port. Combined, you have 9 degrees of inertial measurement! Whereas the original 9DOF Razor used the old MPU-9250, this uses the ISM330DHCX and MMC5983MA. Oh, and if that wasn\u2019t enough, it comes with a built-in temperature sensor on each IC too. So if you want to use the DataLogger IoT as a transportation logger, it will do that straight out of the anti-static bag!

    Note

    For users using the SparkFun DataLogger, there 6DoF IMU and magnetometer is not populated on the board. The associated silkscreen and jumpers for the sensors are also not included on the board.

    "},{"location":"hardware_overview/#analog-pins","title":"Analog Pins","text":"

    Note

    The analog pins are not currently supported in the firmware for data logging.

    There are three 12-bit analog pins available and broken out on edge of the board.

    • 36 / A0: Analog A0. This is connected to pin 36.
    • 39 / A3: Analog A3. This is connected to pin 39.
    • 35 / A7: Analog A7. This is connected to pin 35.

    Note

    Instead of pin \"35 / A7\" being broken out on the DataLogger IoT, pin \"5\" is broken out on the edge of the board.

    "},{"location":"hardware_overview/#reset-and-boot-buttons","title":"Reset and Boot Buttons","text":"

    Note

    You may notice a thin film over buttons. This is used by a pick-and-place machine when populating the component on the board before it goes through the reflow oven. This can be removed.

    There are two buttons available on the board for reset and boot. These are also broken out on the edge of the board as PTHs. If you have your DataLogger IoT mounted in an enclosure, you can also attach an external boot or reset switch too. Any Single Pole Normally-Open Push-To-Close momentary switch will do. Solder pin headers or wires to the RST and GND breakout pins and connect your external switch to those.

    • RESET: Pressing this button will pull the pin LOW and reset the program running on the ESP32 without unplugging the board.
    • BOOT: The boot button usually allows users to force the ESP32 into bootloader mode to manually flash new firmware to the ESP32. The ESP32 will remain in this mode until there is a power cycle or the reset button is pressed. As of firmware v01.00.02, this button has an extra function: pressing down on the user button for 20 seconds will erase on-board preference storage and restart the board. This is connected to pin 0 on the ESP32.

    Like other ESP32 development boards, these buttons are populated so that users can place the ESP32 module in bootloader mode. For users that need to place the board in bootloader mode when powered, you will need to:

    • Press the BOOT button.
    • While holding on the BOOT button, press the RESET button momentarily.
    • Finally, release the BOOT button.

    Most of the time, users will simply have the board executing the firmware that is loaded on the ESP32 module and updating through the configuration menu either through the microSD card or OTA.

    Danger

    Please think very carefully before uploading any Arduino sketches to your DataLogger IoT.

    You will overwrite the DataLogger IoT firmware, leaving it unable to update or restore itself.

    The DataLogger IoT comes pre-programmed with amazing firmware which can do so much. It is designed to be able to update itself and restore itself if necessary. But it can not do that if you overwrite the firmware with any Arduino sketch. It is just like erasing the restore partition on your computer hard drive. Do not do it - unless you really know what you are doing.

    Really. We mean it.

    "},{"location":"hardware_overview/#leds","title":"LEDs","text":"

    There are three LEDs populated on the board. These can be disabled with their respective jumpers on the back of the board.

    • STAT: The status LED is connected to pin 25.
    • RGB: The WS2812-2020 RGB addressable LED is connected to pin 26. In addition to being disabled through the jumper on the back, you can also disable the LED through software. The following colors represent different states that the board is in.
      • White: A solid white LED indicates that the board is currently being configured through the configuration menu.
      • Green: A solid green LED indicates that the board is initializing. As of firmware v01.00.02, the LED blinks green when on battery power indicating that the battery level is VBATT > 50%.
      • Blue: A blinking blue LED indicates that the board is reading sensor data and logging the values.
      • Yellow: A solid yellow LED indicates that a firmware update is in progress. As of firmware v01.00.02, the LED blinks yellow when on battery power indicating that the battery level is between 50% > VBATT > 10%.
      • Red: As of firmware v01.00.02, the LED blinks red when on battery power indicating that the battery level is VBATT < 10%.
    • CHG: The on-board yellow CHG LED can be used to get an indication of the charge status of your battery. Below is a table of other status indicators depending on the state of the charge IC.
    Charge State LED status No Battery Floating (should be OFF, but may flicker) Shutdown Floating (should be OFF, but may flicker) Charging ON Charge Complete OFF

    Note

    On the DataLogger IoT, we included the B3DQ3BRG addressable RGB LED instead of the WS2812 with the light emitting from the top of the IC. This side emitting LED uses the same protocol as the WS2812 and was a design choice for users placing the board in an enclosure.

    "},{"location":"hardware_overview/#jumpers","title":"Jumpers","text":"

    There are seven jumpers on the back of the DataLogger IoT - 9DoF. For more information, check out our tutorial on working with jumper pads and PCB traces should you decide to cut the traces with a hobby knife.

    • SHLD: This jumper connects the USB Type C connector's shield pin to GND. Cut this to isolate the USB Type C connector's shield pin.
    • I2C: This three way jumper labeled as I2C are closed by default. By cutting the jumpers, it will disconnect the 2.2k\u03a9 pull-up resistors for the I2C bus. Most of the time you can leave these alone unless your project requires you to disconnect the pull-up resistors.
    • STAT: This jumper connects the status LED to pin 25 and it is closed by default. Open the jumper to disable the LED.
    • RGB: This jumper connects the WS2812-2020 RGB addressable LED to pin 26 and it is closed by default. Open the jumper to disable the LED.
    • CHG: This jumper connects the charge LED on the MCP73831 charge IC and it is closed by default. Open the jumper to disable the LED.
    • IMU INT2: This jumper connects the ISM330DHCX IMU's interrupt pin to pin 35 and it is open by default. Add a solder jumper to connect.
    • MAG INT: This jumper connects the MMC5983MA magnetometer's interrupt pin to pin 35 and it is open by default. Add a solder jumper to connect.

    Note

    On the DataLogger IoT, the IMU INT2 or MAG INT jumpers are not included since it does not have the built in 6DoF IMU or magnetometer. With the extra real estate on the board, we were able to include a MEAS PTH and jumper on the board. By default, the jumper is closed. You can cut this jumper on the bottom side of the board to measure the DataLogger IoT\u2019s current draw from external power.

    "},{"location":"hardware_overview/#board-dimensions","title":"Board Dimensions","text":"

    The overall length and width with the antenna connector is about 1.66\" x 2.00\". There are four mounting holes in the center of the board. Due to the size of the board and the ESP32 module, the mounting holes are positioned in a way for users to add two Qwiic enabled boards with a width of 1.0\" instead of one Qwiic board.

    DataLogger IoT - 9DoF DataLogger IoT"},{"location":"introduction/","title":"Introduction","text":"

    The SparkFun DataLogger IoT is a data logger that comes preprogrammed to automatically log IMU, GPS, and various pressure, humidity, and distance sensors. All without writing a single line of code! They come in two flavors: The SparkFun DataLogger IoT - 9DoF and the SparkFun DataLogger IoT. Both versions of the DataLogger IoT automatically detects, configures, and logs Qwiic sensors. It was specifically designed for users who just need to capture a lot of data to a CSV or JSON file, and get back to their larger project. Save the data to a microSD card or send it wirelessly to your preferred Internet of Things (IoT) service!

    • SparkFun DataLogger IoT - 9DoF DEV-20594 Purchase from SparkFun

    • SparkFun DataLogger IoT DEV-22462 Purchase from SparkFun

    "},{"location":"introduction/#required-materials","title":"Required Materials","text":"

    Battery Polarity

    Please make sure that you use one of our recommended Lithium Ion batteries. Some batteries use the same JST connector as ours but have the opposite polarity. Connecting one of these to your DataLogger IoT will destroy it. If you are going to use your own battery, it is up to you to ensure it has the correct polarity.

    To follow along with this tutorial, you will need the following materials. You may not need everything though depending on what you have. Add it to your cart, read through the guide, and adjust the cart as necessary.

    • 1x SparkFun DataLogger IoT
      • SparkFun DataLogger IoT - 9DoF [DEV-20594]
      • SparkFun DataLogger IoT [DEV-22462]
    • 1x microSD card formatted with FAT32 [COM-15107]
    • 1x A USB-C cable for configuring and LiPo charging
      • Our USB 2.0 A to C Cable [CAB-15092] will do nicely
      • Our USB 3.1 A to C Cable [CAB-14743] is a good choice too
    • 1x Lithium Ion Battery
    • At least one Qwiic cable
      • A single 50mm Cable is all you need to get going
      • Our Qwiic Cable Kit covers all the options
    • At least one Qwiic enabled devices that is compatible that you may need
    • SparkFun DataLogger IoT - 9DoF DEV-20594

    • USB 3.1 Cable A to C - 3 Foot CAB-14743

    • microSD Card - 1GB (Class 4) COM-15107

    • Qwiic Cable - 50mm PRT-14426

    • Lithium Ion Battery - 1250mAh (IEC62133 Certified) PRT-18286

    "},{"location":"introduction/#the-sensors","title":"The Sensors","text":"

    Straight out of the box anti-static bag, the DataLogger IoT is ready to log data from its built-in ISM330DHCX Inertial Measurement Unit (IMU) and MMC5983MA magnetometer. Only want to log magnetometer, accelerometer, gyro or temperature data? You\u2019re good to go! But the fun is only just beginning\u2026

    The DataLogger IoT is preprogrammed to automatically log data from all of the following sensors, so you may wish to add one or more of these to your shopping cart too. (More sensors are being added all the time and it is really easy to upgrade the DataLogger IoT to support them. But we'll get to that in a moment!). Currently, auto-detection is supported on the following Qwiic-enabled products (with the exception of the ISM330DHCX and MMC5983 which is built-in on the SPI port):

    Note

    For a list of supported devices based on the firmware, you can check out the list of supported Qwiic Devices in the appendix. We simply categorized the supported devices below based on the type.

    • Any u-Blox GNSS Modules (Lat/Long, Altitude, Velocity, SIV, Time, Date) such as:
      • ZED-F9P 1cm High Precision GPS
      • NEO-M8P 2.5cm High Precision GPS
      • SAM-M10Q 1.5m GPS
      • SAM-M8Q 1.5m 72 Channel GPS
      • ZOE-M8Q 1.5m Compact GPS
      • NEO-M9N 1.5m GPS
      • MAX-M10S 1.5m Ultra-Low Power GPS
    • Inertial Measurement Unit (Accelerometer and Gyro):
      • ISM330DHCX IMU (Built-in for the 9DoF version via SPI)
    • Magnetometer:
      • MMC5983 (Built-in for the 9DoF version via SPI)
    • Distance:
      • STHS34PF80] Human Presence Sensor - 4 Meter
      • TMF8820 dToF Imager
      • TMF8821 dToF Imager
      • VCNL4040 Proximity and Lux
      • VL53L1X Distance - 4 Meter
      • VL53L4 Distance - 1.3 Meter
      • VL53L5 ToF Imager
    • Pressure, Altitude, Humidity and Temperature Data:
      • BME280 Atmospheric
      • LPS25HB Absolute Pressure
      • MPR Series - MPRLS0025PA00001A MicroPressure
      • MS8607 Pressure, Humidity, and Temperature
      • MS5637 Barometric Pressure and Temperature
      • AHT20 Humidity and Temperature
      • SHTC3 Humidity and Temperature
      • SDP31 Differential Pressure
      • BMP384 Pressure and Temperature
      • BMP581 Pressure and Temperature
    • Air Quality and Environmental Sensors:
      • CCS811 Air Quality (CO2 and VOC)
      • ENS160 Indoor Air Quality (AQI, eCO2, and TVOC)
      • PASCO2V01 Air Quality (CO2)
      • SGP30 Air Quality (TVOC, CO2, H2, Ethanol)
      • SGP40 Air Quality (VOC, Humidity, Temperature)
      • SCD30 CO2, Humidity, and Temperature
      • SCD40 CO2, Humidity, and Temperature
      • BME680 Air Quality (Pressure, Humidity, Temperature, Gas, VOCs)
      • BME688 Air Quality (Pressure, Humidity, Temperature, Gas, VOCs, VSC, CO, Gas)
      • FS3000 Air Velocity
      • SEN54 Environmental Sensor Node (Particle, VOC, Humidity, and Temperature)
      • STC31 CO2 and Temperature sensor
      • VEML6075 UV
      • VEML7700 Ambient Light and Lux
      • OPT4048DTSR Tristimulus Color
      • AS7265x Triad Spectroscopy
    • Temperature:
      • AMG8833 Grid-EYE Infrared Array
      • MCP9600 Thermocouple Amplifier
      • PT100 ADS122C04 PR Temperature
      • TMP117 High Precision Temperature
    • Power:
      • ACS37800 Power Meter
      • MAX17048 Li-Po Battery Fuel Gauge (Built-in via I2C)
    • Real Time Clock:
      • RV8803 RTC Module
    • NFC/RFID:
      • ST25DVxxKC Dynamic NFC/RFID Tag
    • Weight:
      • NAU7802 Qwiic Scale Load Cell Amplifier
    • Miscellaneous:
      • Qwiic Button
      • Qwiic Twist RGB Rotary Encoder
    • Analog Voltage:
      • ADS1015 12-bit 4-channel Differential ADC
      • ADS122C04 24-bit Differential ADC found on the PT100
    "},{"location":"introduction/#suggested-reading","title":"Suggested Reading","text":"

    If you aren't familiar with the Qwiic system, we recommend reading here for an overview.

    Qwiic Connect System

    If you aren\u2019t familiar with the following concepts, we also recommend checking out a few of these tutorials before continuing.

    • Accelerometer Basics

    • Gyroscope

    • Qwiic 9DoF - ISM330DHCX, MMC5983MA Hookup Guide

    • Serial Terminal Basics

    • How to Work with Jumper Pads and PCB Traces

    • I2C

    • Battery Technologies

    • Single Cell LiPo Battery Care

    "},{"location":"prepare_your_microsd_card/","title":"Preparing Your MicroSD Card","text":"

    Not all microSD cards are created equal. The capacity, read/write speed, and format vary depending on the manufacturer. In order to log data to the microSD card, you will need to ensure that your memory card is formatted as FAT32. You can also use FAT16. If the memory card is formatted as a different memory card, the DataLogger IoT will not be able to recognize the microSD card.

    "},{"location":"prepare_your_microsd_card/#checking-microsd-card-format","title":"Checking MicroSD Card Format","text":"

    While you can simply insert the microSD card into your DataLogger IoT and start logging, there may be a chance that the it will not recognize the memory card due to the format.

    "},{"location":"prepare_your_microsd_card/#checking-microsd-card-format-windows","title":"Checking MicroSD Card Format - Windows","text":"

    To check to see if it is the correct format on a Windows you could head to the drive, right click, and select Properties.

    Once the properties are open, you should be able to tell what file system that the memory card uses. In this case, it was exFAT which is not compatible with the DataLogger IoT. You will need to reformat the memory card since it is not formatted as FAT32.

    "},{"location":"prepare_your_microsd_card/#checking-microsd-card-format-macos","title":"Checking MicroSD Card Format - macOS","text":"

    To check to see if it is the correct format on a macOS, you could head to the drive on your desktop. Then right click, and select Get Info.

    A window will pop up indicating the microSD card properties. Under General: > Format:, you should be able to tell what file system that the memory card uses. In this case, it was exFAT which is not compatible with the DataLogger IoT. You will need to reformat the memory card since it is not formatted as FAT32.

    "},{"location":"prepare_your_microsd_card/#download-raspberry-pi-imager","title":"Download Raspberry Pi Imager","text":"

    There are a few methods and programs available to reformat your microSD card as a FAT32. We found it easier to use the Raspberry Pi Imager Tool. Of course, you will only be using the tool to erase the contents of the microSD card and formatting it as a FAT32 system. You will not actually flash any image to the memory card. Click on the button below to download the tool from the Raspberry Pi Foundation. It is supported on Windows, macOS, and Ubuntu for x86.

    Raspberry Pi Imager Tool"},{"location":"prepare_your_microsd_card/#formatting-as-fat32-using-the-raspberry-pi-imager","title":"Formatting as FAT32 using the Raspberry Pi Imager","text":"

    After downloading and installing the software, open the Raspberry Pi Imager.

    Under \"Operating System\", select \"Erase\" to \"format card as FAT32.\"

    Under \"Storage\", select the drive that the microSD card appeared as on your computer.

    When ready, select \"Write\". After a few minutes, the microSD card should be formatted with FAT32.

    Once the memory card has finished formatting, eject the microSD from your computer. To check to see if the microSD card is formatted as FAT32, you can check its properties as explained earlier with your operating system. Below shows a screenshot from a Windows and macOS showing that the microSD card reformatted as a FAT32 file system.

    "},{"location":"resources/","title":"Resources","text":"

    Now that you've successfully got your DataLogger IoT up and running, it's time to incorporate it into your own project! For more information, check out the resources below:

    • DataLogger IoT - 9DoF
      • Schematic (PDF)
      • Eagle Files (ZIP)
      • Board Dimensions (PNG)
      • Fritzing Part (FZPZ)
    • DataLogger IoT
      • Schematic (PDF)
      • Eagle Files (ZIP)
      • Board Dimensions (PNG)
      • Fritzing Part (FZPZ)
    • CH340 Drivers
    • Firmware
    • GitHub Hardware Repo
      • SparkFun DataLogger IoT - 9DoF
      • SparkFun DataLogger IoT
    • SFE Showcase
      • DataLogger IoT - 9DoF
      • DataLogger IoT

    Or check out these related blog posts.

    • IoT Platforms and Protocols

    • Extending the Reach of Data Logging

    • Send Sensor Data to AWS All In Under 15 Minutes

    "},{"location":"single_page/","title":"Introduction","text":"

    The SparkFun DataLogger IoT is a data logger that comes preprogrammed to automatically log IMU, GPS, and various pressure, humidity, and distance sensors. All without writing a single line of code! They come in two flavors: The SparkFun DataLogger IoT - 9DoF and the SparkFun DataLogger IoT. Both versions of the DataLogger IoT automatically detects, configures, and logs Qwiic sensors. It was specifically designed for users who just need to capture a lot of data to a CSV or JSON file, and get back to their larger project. Save the data to a microSD card or send it wirelessly to your preferred Internet of Things (IoT) service!

    • SparkFun DataLogger IoT - 9DoF DEV-20594 Purchase from SparkFun

    • SparkFun DataLogger IoT DEV-22462 Purchase from SparkFun

    "},{"location":"single_page/#required-materials","title":"Required Materials","text":"

    Battery Polarity

    Please make sure that you use one of our recommended Lithium Ion batteries. Some batteries use the same JST connector as ours but have the opposite polarity. Connecting one of these to your DataLogger IoT will destroy it. If you are going to use your own battery, it is up to you to ensure it has the correct polarity.

    To follow along with this tutorial, you will need the following materials. You may not need everything though depending on what you have. Add it to your cart, read through the guide, and adjust the cart as necessary.

    • 1x SparkFun DataLogger IoT
      • SparkFun DataLogger IoT - 9DoF [DEV-20594]
      • SparkFun DataLogger IoT [DEV-22462]
    • 1x microSD card formatted with FAT32 [COM-15107]
    • 1x A USB-C cable for configuring and LiPo charging
      • Our USB 2.0 A to C Cable [CAB-15092] will do nicely
      • Our USB 3.1 A to C Cable [CAB-14743] is a good choice too
    • 1x Lithium Ion Battery
    • At least one Qwiic cable
      • A single 50mm Cable is all you need to get going
      • Our Qwiic Cable Kit covers all the options
    • At least one Qwiic enabled devices that is compatible that you may need
    • SparkFun DataLogger IoT - 9DoF DEV-20594

    • USB 3.1 Cable A to C - 3 Foot CAB-14743

    • microSD Card - 1GB (Class 4) COM-15107

    • Qwiic Cable - 50mm PRT-14426

    • Lithium Ion Battery - 1250mAh (IEC62133 Certified) PRT-18286

    "},{"location":"single_page/#the-sensors","title":"The Sensors","text":"

    Straight out of the box anti-static bag, the DataLogger IoT is ready to log data from its built-in ISM330DHCX Inertial Measurement Unit (IMU) and MMC5983MA magnetometer. Only want to log magnetometer, accelerometer, gyro or temperature data? You\u2019re good to go! But the fun is only just beginning\u2026

    The DataLogger IoT is preprogrammed to automatically log data from all of the following sensors, so you may wish to add one or more of these to your shopping cart too. (More sensors are being added all the time and it is really easy to upgrade the DataLogger IoT to support them. But we'll get to that in a moment!). Currently, auto-detection is supported on the following Qwiic-enabled products (with the exception of the ISM330DHCX and MMC5983 which is built-in on the SPI port):

    Note

    For a list of supported devices based on the firmware, you can check out the list of supported Qwiic Devices in the appendix. We simply categorized the supported devices below based on the type.

    • Any u-Blox GNSS Modules (Lat/Long, Altitude, Velocity, SIV, Time, Date) such as:
      • ZED-F9P 1cm High Precision GPS
      • NEO-M8P 2.5cm High Precision GPS
      • SAM-M10Q 1.5m GPS
      • SAM-M8Q 1.5m 72 Channel GPS
      • ZOE-M8Q 1.5m Compact GPS
      • NEO-M9N 1.5m GPS
      • MAX-M10S 1.5m Ultra-Low Power GPS
    • Inertial Measurement Unit (Accelerometer and Gyro):
      • ISM330DHCX IMU (Built-in for the 9DoF version via SPI)
    • Magnetometer:
      • MMC5983 (Built-in for the 9DoF version via SPI)
    • Distance:
      • STHS34PF80] Human Presence Sensor - 4 Meter
      • TMF8820 dToF Imager
      • TMF8821 dToF Imager
      • VCNL4040 Proximity and Lux
      • VL53L1X Distance - 4 Meter
      • VL53L4 Distance - 1.3 Meter
      • VL53L5 ToF Imager
    • Pressure, Altitude, Humidity and Temperature Data:
      • BME280 Atmospheric
      • LPS25HB Absolute Pressure
      • MPR Series - MPRLS0025PA00001A MicroPressure
      • MS8607 Pressure, Humidity, and Temperature
      • MS5637 Barometric Pressure and Temperature
      • AHT20 Humidity and Temperature
      • SHTC3 Humidity and Temperature
      • SDP31 Differential Pressure
      • BMP384 Pressure and Temperature
      • BMP581 Pressure and Temperature
    • Air Quality and Environmental Sensors:
      • CCS811 Air Quality (CO2 and VOC)
      • ENS160 Indoor Air Quality (AQI, eCO2, and TVOC)
      • PASCO2V01 Air Quality (CO2)
      • SGP30 Air Quality (TVOC, CO2, H2, Ethanol)
      • SGP40 Air Quality (VOC, Humidity, Temperature)
      • SCD30 CO2, Humidity, and Temperature
      • SCD40 CO2, Humidity, and Temperature
      • BME680 Air Quality (Pressure, Humidity, Temperature, Gas, VOCs)
      • BME688 Air Quality (Pressure, Humidity, Temperature, Gas, VOCs, VSC, CO, Gas)
      • FS3000 Air Velocity
      • SEN54 Environmental Sensor Node (Particle, VOC, Humidity, and Temperature)
      • STC31 CO2 and Temperature sensor
      • VEML6075 UV
      • VEML7700 Ambient Light and Lux
      • OPT4048DTSR Tristimulus Color
      • AS7265x Triad Spectroscopy
    • Temperature:
      • AMG8833 Grid-EYE Infrared Array
      • MCP9600 Thermocouple Amplifier
      • PT100 ADS122C04 PR Temperature
      • TMP117 High Precision Temperature
    • Power:
      • ACS37800 Power Meter
      • MAX17048 Li-Po Battery Fuel Gauge (Built-in via I2C)
    • Real Time Clock:
      • RV8803 RTC Module
    • NFC/RFID:
      • ST25DVxxKC Dynamic NFC/RFID Tag
    • Weight:
      • NAU7802 Qwiic Scale Load Cell Amplifier
    • Miscellaneous:
      • Qwiic Button
      • Qwiic Twist RGB Rotary Encoder
    • Analog Voltage:
      • ADS1015 12-bit 4-channel Differential ADC
      • ADS122C04 24-bit Differential ADC found on the PT100
    "},{"location":"single_page/#suggested-reading","title":"Suggested Reading","text":"

    If you aren't familiar with the Qwiic system, we recommend reading here for an overview.

    Qwiic Connect System

    If you aren\u2019t familiar with the following concepts, we also recommend checking out a few of these tutorials before continuing.

    • Accelerometer Basics

    • Gyroscope

    • Qwiic 9DoF - ISM330DHCX, MMC5983MA Hookup Guide

    • Serial Terminal Basics

    • How to Work with Jumper Pads and PCB Traces

    • I2C

    • Battery Technologies

    • Single Cell LiPo Battery Care

    "},{"location":"single_page/#hardware-overview","title":"Hardware Overview","text":"

    In this section, we will highlight the hardware and pins that are broken out on the SparkFun DataLogger IoT. At the time of writing, we highlighted the SparkFun DataLogger IoT - 9DoF. However, this also applies for the SparkFun DataLogger IoT.

    DataLogger IoT - 9DoF (Top View) DataLogger IoT - 9DoF (Bottom View)

    The SparkFun DataLogger is pretty much the same with the exception of the following features listed below. We'll include notes highlighting the differences in each section.

    • No built - in 6DoF IMU - ISM330DHCX
    • No built - in magnetometer - MMC5983
    • The addressable RGB LED - WS2812 is replaced with the side emitting addressable RGB LED - B3DQ3BRG
    • No IMU INT2 jumper
    • No Mag INT jumper
    • Included MEAS PTH Jumper
    • The \"35 / A7\" PTH on the edge of the board is replace with a \"5\" PTH.
    DataLogger IoT (Top View) DataLogger IoT (Bottom View)"},{"location":"single_page/#esp32-wroom-module","title":"ESP32-WROOM Module","text":"

    The DataLogger IoT is populated with Espressif's ESP32-WROOM-32E module. Espressif's ESP32 WROOM ubiquitous IoT microcontroller is a powerful WiFi, BT, and BLE MCU module that targets a wide variety of applications. For the DataLogger IoT, the firmware currently utilizes the WiFi feature.

    Note

    Currently the DataLogger IoT does not have BT or BLE. However, BT or BLE is being considered on a future firmware build to include this as a feature.

    "},{"location":"single_page/#power","title":"Power","text":"

    There are a variety of power and power-related nets broken out to connectors and through hole pads. Below list a few methods of powering the board up. There are protection diodes for the USB-C, 5V pin, and single cell LiPo battery. Power is regulated down to 3.3V for the system voltage. Depending on the settings and what is connected to the DataLogger IoT, the board can pull a minimum of 200\u00b5A in low power mode by itself.

    • USB-C
    • 5V Pin
    • Single Cell LiPo Battery
    • 3V3 Pin
    "},{"location":"single_page/#usb-c-and-5v","title":"USB-C and 5V","text":"

    The DataLogger IoT comes equipped with a USB type C socket which you can use to connect it to your computer to view the output and configuration through the serial terminal, or plug in a USB-C power supply. The DataLogger IoT includes the configuration channel resistors needed to tell the power supply to deliver 5V. You can use your USB-C laptop charger as the power source should you need to, even though it normally delivers a much higher voltage.

    There is also a 5V power input pin. You can use this to feed in 5V power from an external source. The maximum voltage is 6.0V. The 5V pin is diode-protected and so is the USB-C power input, so it is OK to have both connected at the same time. This pin is ideal if you want to power your DataLogger from regulated solar power or a different type of power supply. You can not use the 5V pin as an output.

    Voltage from the USB is regulated down to the XC6222 3.3V/700mA voltage regulators for the system voltage and Qwiic-enabled devices. USB power is also connected to the MCP73831 to charge a single cell LiPo battery at a default rate of 500mA.

    For customers in North America, our NEMA Raspberry Pi Wall Adapter is a perfect choice. You can power the DataLogger IoT from our USB Battery Pack / Power Bank - TOL-15204 but you will need a USB-C cable too:

    • Our USB 2.0 A to C Cable - CAB-15092 will do nicely
    • Our USB 3.1 A to C Cable - CAB-14743 is a good choice too
    "},{"location":"single_page/#lipo-battery-input-charger-and-fuel-gauge","title":"LiPo Battery Input, Charger, and Fuel Gauge","text":"

    Warning

    Battery Polarity: Please make sure that you use one of our recommended Lithium Ion batteries. Some batteries use the same JST connector as ours but have the opposite polarity. Connecting one of these to your DataLogger IoT will destroy it. If you are going to use your own battery, it is up to you to ensure it has the correct polarity.

    But of course you\u2019re going to want to use the DataLogger IoT to log sensor data while on the move too. You can connect one of our standard single cell LiPo batteries to the DataLogger IoT and power it for hours, days or weeks depending on what sensors you have attached and how often you log data. The DataLogger IoT uses the built-in MCP73831 charger too which will charge your battery at 500mA when USB-C is connected. Please make sure your battery capacity is at least 500mAh (0.5Ah); bad things will happen if you try to charge our smallest batteries at 500mA. The board also includes the MAX17048 LiPo Fuel Gauge which allows you to determine how much power your LiPo battery has available. The 2-pin JST connector pins are broken out to PTHs on the edge of the board if you decide to solder a single cell LiPo battery directly to the board or power another device.

    "},{"location":"single_page/#3v3-pins","title":"3V3 Pins","text":"

    For those going the old school route, you can also bypass the voltage regulators by soldering directly to the 3V3 and GND pin to provide power if your application has a regulated 3.3V. Note that this is only connected to the system voltage. You will also need to provide power to the 3V3 SWCH or Qwiic-enabled devices should you decide to bypass the voltage regulator.

    "},{"location":"single_page/#ch340-usb-to-serial-converter","title":"CH340 USB-to-Serial Converter","text":"

    The top side of the board includes a CH340 USB-to-Serial Converter. The chip can be used to send serial data between the device and computer. You can view the output or configure the device through a serial terminal.

    The driver should automatically install on most operating systems. However, there is a wide range of operating systems out there. You may need to install drivers the first time you connect the chip to your computer's USB port or when there are operating system updates. For more information, check out our How to Install CH340 Drivers Tutorial.

    How to Install CH340 Drivers"},{"location":"single_page/#uart","title":"UART","text":"

    The hardware serial UART pins are broken out on the edge of the board. For more information about Serial UART, check out the tutorial about Serial Communication for more information.

    • TXD: UART transmit pin. This is connected to pin 16.
    • RXD: UART receive pin. This is connected to pin 17.

    Note

    The UART pins are not currently supported in the firmware for data logging.

    "},{"location":"single_page/#qwiic-and-i2c","title":"Qwiic and I2C","text":"

    Note

    You may notice a thin film over the vertial Qwiic connector. This is used by a pick-and-place machine when populating the component on the board before it goes through the reflow oven. This can be removed if you decide to use the vertical Qwiic connector with Qwiic-enabled devices.

    SparkFun's Qwiic Connect System uses 4-pin JST style connectors to quickly interface development boards with I2C sensors and more. No soldering required and there's no need to worry about accidentally swapping the SDA and SCL wires. The Qwiic connector is polarized so you know you\u2019ll have it wired correctly every time, right from the start. Qwiic boards are daisy chain-able too so you can connect multiple sensors to the DataLogger IoT and log readings from all of them.

    The board is populated with vertical and horizontal Qwiic connectors. These are also broken out to PTHs on the edge of the board.

    • SCL: I2C clock pin. This is connected to pin 22 and a 2.2k\u03a9 pull-up resistor.
    • SDA: I2C data pin. This is connected to pin 21 and a 2.2k\u03a9 pull-up resistor.
    • 3V3 SW: The 3.3v pin is connected to the XC6222 voltage regulator's output to power the Qwiic devices.
    • GND: Common, ground voltage (0V reference) for the system

    Connected to the line I2C line is the MAX17048 LiPo fuel gauge (7-bit unshifted address = 0x36).

    Sometimes you might want to connect more than one of the same type of sensor to the DataLogger IoT. On the I2C bus, each device needs to have a unique address. On many of our boards, there are jumpers links which you can use to change the address and some have addresses that can be configured in software. But there are some where you cannot change the address. Typically, one would use a multiplexor. However, we currently do not have the DataLogger IoT configured to work with any multiplexors (i.e. Qwiic Mux Breakout).

    Note

    Currently the Qwiic Mux is not compatible with the DataLogger IoT.

    The DataLogger IoT includes a dedicated 3.3V regulator for the Qwiic connector. This has several advantages including:

    • The DataLogger IoT can completely power-down the I2C sensors during sleep to prolong your battery life
    • There\u2019s no risk of the Qwiic bus gulping too much current and causing problems for the ESP32
    "},{"location":"single_page/#spi","title":"SPI","text":"

    Note

    Besides the built-in ISM330DHCX and MMC5983MA, the SPI pins are not currently supported in the firmware for data logging.

    The SPI pins are broken out on the edge of the board. For those that are unfamiliar to PICO and POCI, check out the SPI tutorial for more information.

    • SCK: SPI clock pin. This is connect to pin 18.
    • PICO: SPI Peripheral In Controller Out. This is connected to pin 23.
    • POCI: SPI Peripheral Out Controller In. This is connected to pin 19.

    Not shown in the image are the chip select (CS) pins. The 6DoF IMU's CS pin is connected to pin 5. The magnetometer's CS pin is connected to pin 27 which is not broken out.

    Note

    On the DataLogger IoT, the IMU and magnetometer are not connected to the SPI port since they are not included on the board. Instead of pin \"35 / A7\" being broken out, pin \"5\" is broken out on the edge of the board.

    "},{"location":"single_page/#microsd-card-socket","title":"MicroSD Card Socket","text":"

    The DataLogger IoT supports full 4-bit SDIO for fast logging and uses common microSD cards to record clear text, comma separated files. Flip over the DataLogger IoT and you'll see the latching microSD card socket. You probably already have a microSD card laying around. However, if you need any additional units, we have plenty in the SparkFun catalog. The DataLogger can use any size microSD card and supports FAT32 cards in addition to FAT16. Please ensure that your SD card is formatted correctly; we recommend the Raspberry Pi Imager Tool.

    Slide in your formatted SD card and it will click neatly into place. The edge of the SD card will stick out on the edge of the circuit board when it is inserted correctly.

    Warning

    You should only insert or remove the SD card while the power is turned off or disconnected. Removing the card while the DataLogger IoT is logging will almost certainly corrupt your data.

    "},{"location":"single_page/#9-degrees-of-freedom-9dof","title":"9 Degrees of Freedom (9DOF)","text":"

    As stated earlier, included on every DataLogger IoT - 9DoF is a 6DoF Inertial Measurement Unit (IMU) for built-in logging of triple-axis accelerometer and gyro. There is also a built-in triple-axis magnetometer for a complete 9 degrees of freedom. Beside each IC is a silkcreen showing the reference axis. Both are connected to the ESP32 via the SPI port. Combined, you have 9 degrees of inertial measurement! Whereas the original 9DOF Razor used the old MPU-9250, this uses the ISM330DHCX and MMC5983MA. Oh, and if that wasn\u2019t enough, it comes with a built-in temperature sensor on each IC too. So if you want to use the DataLogger IoT as a transportation logger, it will do that straight out of the anti-static bag!

    Note

    For users using the SparkFun DataLogger, there 6DoF IMU and magnetometer is not populated on the board. The associated silkscreen and jumpers for the sensors are also not included on the board.

    "},{"location":"single_page/#analog-pins","title":"Analog Pins","text":"

    Note

    The analog pins are not currently supported in the firmware for data logging.

    There are three 12-bit analog pins available and broken out on edge of the board.

    • 36 / A0: Analog A0. This is connected to pin 36.
    • 39 / A3: Analog A3. This is connected to pin 39.
    • 35 / A7: Analog A7. This is connected to pin 35.

    Note

    Instead of pin \"35 / A7\" being broken out on the DataLogger IoT, pin \"5\" is broken out on the edge of the board.

    "},{"location":"single_page/#reset-and-boot-buttons","title":"Reset and Boot Buttons","text":"

    Note

    You may notice a thin film over buttons. This is used by a pick-and-place machine when populating the component on the board before it goes through the reflow oven. This can be removed.

    There are two buttons available on the board for reset and boot. These are also broken out on the edge of the board as PTHs. If you have your DataLogger IoT mounted in an enclosure, you can also attach an external boot or reset switch too. Any Single Pole Normally-Open Push-To-Close momentary switch will do. Solder pin headers or wires to the RST and GND breakout pins and connect your external switch to those.

    • RESET: Pressing this button will pull the pin LOW and reset the program running on the ESP32 without unplugging the board.
    • BOOT: The boot button usually allows users to force the ESP32 into bootloader mode to manually flash new firmware to the ESP32. The ESP32 will remain in this mode until there is a power cycle or the reset button is pressed. As of firmware v01.00.02, this button has an extra function: pressing down on the user button for 20 seconds will erase on-board preference storage and restart the board. This is connected to pin 0 on the ESP32.

    Like other ESP32 development boards, these buttons are populated so that users can place the ESP32 module in bootloader mode. For users that need to place the board in bootloader mode when powered, you will need to:

    • Press the BOOT button.
    • While holding on the BOOT button, press the RESET button momentarily.
    • Finally, release the BOOT button.

    Most of the time, users will simply have the board executing the firmware that is loaded on the ESP32 module and updating through the configuration menu either through the microSD card or OTA.

    Danger

    Please think very carefully before uploading any Arduino sketches to your DataLogger IoT.

    You will overwrite the DataLogger IoT firmware, leaving it unable to update or restore itself.

    The DataLogger IoT comes pre-programmed with amazing firmware which can do so much. It is designed to be able to update itself and restore itself if necessary. But it can not do that if you overwrite the firmware with any Arduino sketch. It is just like erasing the restore partition on your computer hard drive. Do not do it - unless you really know what you are doing.

    Really. We mean it.

    "},{"location":"single_page/#leds","title":"LEDs","text":"

    There are three LEDs populated on the board. These can be disabled with their respective jumpers on the back of the board.

    • STAT: The status LED is connected to pin 25.
    • RGB: The WS2812-2020 RGB addressable LED is connected to pin 26. In addition to being disabled through the jumper on the back, you can also disable the LED through software. The following colors represent different states that the board is in.
      • White: A solid white LED indicates that the board is currently being configured through the configuration menu.
      • Green: A solid green LED indicates that the board is initializing. As of firmware v01.00.02, the LED blinks green when on battery power indicating that the battery level is VBATT > 50%.
      • Blue: A blinking blue LED indicates that the board is reading sensor data and logging the values.
      • Yellow: A solid yellow LED indicates that a firmware update is in progress. As of firmware v01.00.02, the LED blinks yellow when on battery power indicating that the battery level is between 50% > VBATT > 10%.
      • Red: As of firmware v01.00.02, the LED blinks red when on battery power indicating that the battery level is VBATT < 10%.
    • CHG: The on-board yellow CHG LED can be used to get an indication of the charge status of your battery. Below is a table of other status indicators depending on the state of the charge IC.
    Charge State LED status No Battery Floating (should be OFF, but may flicker) Shutdown Floating (should be OFF, but may flicker) Charging ON Charge Complete OFF

    Note

    On the DataLogger IoT, we included the B3DQ3BRG addressable RGB LED instead of the WS2812 with the light emitting from the top of the IC. This side emitting LED uses the same protocol as the WS2812 and was a design choice for users placing the board in an enclosure.

    "},{"location":"single_page/#jumpers","title":"Jumpers","text":"

    There are seven jumpers on the back of the DataLogger IoT - 9DoF. For more information, check out our tutorial on working with jumper pads and PCB traces should you decide to cut the traces with a hobby knife.

    • SHLD: This jumper connects the USB Type C connector's shield pin to GND. Cut this to isolate the USB Type C connector's shield pin.
    • I2C: This three way jumper labeled as I2C are closed by default. By cutting the jumpers, it will disconnect the 2.2k\u03a9 pull-up resistors for the I2C bus. Most of the time you can leave these alone unless your project requires you to disconnect the pull-up resistors.
    • STAT: This jumper connects the status LED to pin 25 and it is closed by default. Open the jumper to disable the LED.
    • RGB: This jumper connects the WS2812-2020 RGB addressable LED to pin 26 and it is closed by default. Open the jumper to disable the LED.
    • CHG: This jumper connects the charge LED on the MCP73831 charge IC and it is closed by default. Open the jumper to disable the LED.
    • IMU INT2: This jumper connects the ISM330DHCX IMU's interrupt pin to pin 35 and it is open by default. Add a solder jumper to connect.
    • MAG INT: This jumper connects the MMC5983MA magnetometer's interrupt pin to pin 35 and it is open by default. Add a solder jumper to connect.

    Note

    On the DataLogger IoT, the IMU INT2 or MAG INT jumpers are not included since it does not have the built in 6DoF IMU or magnetometer. With the extra real estate on the board, we were able to include a MEAS PTH and jumper on the board. By default, the jumper is closed. You can cut this jumper on the bottom side of the board to measure the DataLogger IoT\u2019s current draw from external power.

    "},{"location":"single_page/#board-dimensions","title":"Board Dimensions","text":"

    The overall length and width with the antenna connector is about 1.66\" x 2.00\". There are four mounting holes in the center of the board. Due to the size of the board and the ESP32 module, the mounting holes are positioned in a way for users to add two Qwiic enabled boards with a width of 1.0\" instead of one Qwiic board.

    DataLogger IoT - 9DoF DataLogger IoT"},{"location":"single_page/#hardware-hookup","title":"Hardware Hookup","text":"

    In this section, we will go over how to connect to the SparkFun DataLogger IoT. At the time of writing, we used the DataLogger IoT - 9DoF. This hardware hookup explained in this section also applies for the DataLogger IoT.

    "},{"location":"single_page/#soldering-to-the-pths","title":"Soldering to the PTHs","text":"

    Note

    The UART, SPI, analog, and digital I/O pins are not currently supported in the firmware for data logging.

    For users that are interested in soldering to the edge of the board, we recommend soldering headers to the PTHs on the breakout for a permanent connection and using jumper wires. Of course, you could also solder wires to the breakout board as well. For a temporary connection during prototyping, you can use IC hooks like these.

    • How to Solder: Through-Hole Soldering

    • Working with Wire

    "},{"location":"single_page/#microsd-card","title":"MicroSD Card","text":"

    If all you want to do is display your sensor readings in a serial terminal or monitor (connected via USB-C) then, strictly, you don\u2019t need to add a microSD card. But of course the whole point of the DataLogger IoT is that it can log readings from whatever sensors you have attached to microSD card. The data is logged in easy-to-read Comma Separated Value (CSV) text format by default. You can also set the format as JSON.

    You probably already have a microSD card laying around but if you need any additional units, we have plenty in the store. The DataLogger IoT can use any size microSD card as long as it is formatted correctly. Please ensure your SD card is formatted correctly. There are different software tools available. Some are built into your operating system. We recommend using the Raspberry Pi Imager Tool to easily format the memory card as FAT32 using the GUI. Flip over the DataLogger IoT and you\u2019ll see the latching microSD card socket. Slide in your formatted SD card and it will click neatly into place. Part of the edge of the SD card will stick out when fully inserted in the microSD card socket.

    You should only insert or remove the SD card while the power is turned off or disconnected. Removing the card while the DataLogger IoT is logging will almost certainly corrupt your data. You can tell when the board has just logged by observing the addressable RGB LED. When enabled, the LED will blink blue after it has logged one data point.

    After you\u2019ve logged some data, you will find a new file on your SD card. There may also be additional files if you manually saved the firmware or preferences to the memory card.

    • sfe0001.txt: This is the file that contains the CSV or JSON sensor data. The format will depend on how you configured the DataLogger's output. We use .TXT as the file type so that your computer can open it in a simple text editor. The contents are all human-readable. But, if you want to, you can rename it as .CSV or .JSON instead. The file number is incremented for the next logging session.
    • datalogger.json: This file only appears when you save the settings as your fallback storage. The file will include all preferences saved for any connected device, WiFi credentials, certificates, and keys.
    • SparkFun_DataLoggerIoT*.bin: This file only appears when you save the firmware to the microSD card. Note that the asterisk (*) is the firmware version number (i.e. SparkFunDataLoggerIoT_01.00.01.bin).

    To remove the microSD card, make sure power is disconnected from the DataLogger IoT. Then press the microSD card into the microSD socket. The memory card will be ejected and you will hear a click again. Once the card is ejected, you can insert it into a microSD card adapter or USB reader to be read on a computer.

    "},{"location":"single_page/#qwiic-sensors","title":"Qwiic Sensors","text":"

    If you are going to attach extra sensors or any Qwiic-enabled device to the DataLogger IoT, then those need to be connected first before attaching a USB cable. It is a good idea to only attach or disconnect Qwiic sensors when the power is turned off or disconnected. The Qwiic bus is pretty tolerant to \u201chot swapping\u201d, but: disconnecting a sensor while it is in use will confuse the DataLogger IoT software (most likely each value associated with the device will remain constant); and a new sensor won\u2019t be detected until the firmware restarts.

    Plug one end of your Qwiic cable into the DataLogger IoT and plug the other end into your sensor. If you want to add extra sensors, you can simply connect them to each other in a daisy chain. You will need a Qwiic cable for each sensor. Our Qwiic Cable Kit covers all the options.

    DataLogger IoT and a Qwiic-Enabled Device DataLogger IoT and several Qwiic-Enabled Devices Daisy Chained

    Our Qwiic sensors usually all have power indicator LEDs and I2C pull-up resistors. Depending on your application, you may want or need to disable these by cutting the jumper links on the sensor circuit boards. We have a tutorial that will show you how to do that safely.

    Sometimes you might want to connect more than one of the same type of sensor to the DataLogger IoT. On the I2C bus, each device needs to have a unique address. On many of our boards, there are jumpers links which you can use to change the address and some have addresses that can be configured in software. But there are some where you cannot change the address - the NAU7802 Qwiic Scale being one example.

    Typically one would use a multiplexor. However, we currently do not have the DataLogger IoT configured to work with any multiplexors (i.e. Qwiic Mux Breakout).

    Note

    Currently the Qwiic Mux is not compatible with the DataLogger IoT.

    "},{"location":"single_page/#lipo-battery","title":"LiPo Battery","text":"

    Battery Polarity

    Please make sure that you use one of our recommended Lithium Ion batteries. Some batteries use the same JST connector as ours but have the opposite polarity. Connecting one of these to your DataLogger IoT will destroy it. If you are going to use your own battery, it is up to you to ensure it has the correct polarity.

    Now is a good time to attach a LiPo battery, if you want the DataLogger IoT to keep logging when you disconnect USB-C.

    You can connect one of our standard single cell LiPo batteries to the DataLogger IoT and power it for hours, days or weeks depending on what sensors you have attached and how often you log data. The DataLogger IoT has a built-in charger too which will charge your battery at 500mA when USB-C is connected. Please make sure your battery capacity is at least 500mAh (0.5Ah); bad things will happen if you try to charge our smallest batteries at 500mA. The yellow CHG charging LED will light up while the battery is charging and will go out once charging is complete.

    Warning

    The MCP73831 charge IC on the board is used on a few SparkFun products. For more information about the CHG status LED, we recommend taking look at the Hardware Overview. We also recommend taking a look at this tutorial for Single Cell LiPo Battery Care.

    "},{"location":"single_page/#usb-cable","title":"USB Cable","text":"

    The USB-C connector provides power to the DataLogger IoT and acts as a serial interface for configuration and data display.

    If you are going to use a microSD card to store your data, and why wouldn\u2019t you, then insert that first before attaching your USB cable. You should only insert or remove the SD card while the power is turned off or disconnected. Removing the card while the DataLogger IoT is logging will almost certainly corrupt your data.

    Likewise, it is a good idea to only attach or disconnect Qwiic sensors when the power is turned off or disconnected. The Qwiic bus is pretty tolerant to \u201chot swapping\u201d, but: disconnecting a sensor while it is in use will confuse the DataLogger IoT software; and a new sensor won\u2019t be detected until the firmware restarts.

    Depending on what ports your computer has available, you will need one of the following cables:

    • USB 2.0 A to C Cable
    • USB 3.1 A to C Cable
    • USB 2.0 C to C Cable

    Use the cable to connect your DataLogger IoT to your computer and you will see the LEDs light as the DataLogger IoT starts up. The addressable RGB RGB LED will light up green for a second or two while the DataLogger IoT configures itself. It will flash blue while data is being logged to the microSD card. If you have jumped the gun and have a LiPo battery already connected, the yellow CHG charging LED may light up too.

    If the addressable RGB LED does not light up, your DataLogger IoT is probably in deep sleep following a previous logging session. Pressing the RST reset button will wake it.

    You\u2019ll find full instructions on how to configure the DataLogger IoT later in this tutorial.

    "},{"location":"single_page/#standoffs","title":"Standoffs","text":"

    For users interested in stacking the Qwiic-enabled device on the DataLogger IoT or mounting in an enclosure, you will need some standoffs to mount the boards. When mounting, note that all four mounting holes are not positioned exactly for a 1.0\"x1.0\" sized Qwiic board. Only two of the four mounting holes are compatible for a 1.0\"x1.0\" sized Qwiic board. For example the image below shows the boards stacked on each side of the DataLogger IoT. On top, the Qwiic GPS (SAM-M10Q) breakout was also able to stack by rotating the board slightly and aligning the mounting holes on the 1.6\"x1.6\" sized board to the other mounting holes

    "},{"location":"single_page/#preparing-your-microsd-card","title":"Preparing Your MicroSD card","text":"

    Not all microSD cards are created equal. The capacity, read/write speed, and format vary depending on the manufacturer. In order to log data to the microSD card, you will need to ensure that your memory card is formatted as FAT32. You can also use FAT16. If the memory card is formatted as a different memory card, the DataLogger IoT will not be able to recognize the microSD card.

    "},{"location":"single_page/#checking-microsd-card-format","title":"Checking MicroSD Card Format","text":"

    While you can simply insert the microSD card into your DataLogger IoT and start logging, there may be a chance that the it will not recognize the memory card due to the format.

    "},{"location":"single_page/#checking-microsd-card-format-windows","title":"Checking MicroSD Card Format - Windows","text":"

    To check to see if it is the correct format on a Windows you could head to the drive, right click, and select Properties.

    Once the properties are open, you should be able to tell what file system that the memory card uses. In this case, it was exFAT which is not compatible with the DataLogger IoT. You will need to reformat the memory card since it is not formatted as FAT32.

    "},{"location":"single_page/#checking-microsd-card-format-macos","title":"Checking MicroSD Card Format - macOS","text":"

    To check to see if it is the correct format on a macOS, you could head to the drive on your desktop. Then right click, and select Get Info.

    A window will pop up indicating the microSD card properties. Under General: > Format:, you should be able to tell what file system that the memory card uses. In this case, it was exFAT which is not compatible with the DataLogger IoT. You will need to reformat the memory card since it is not formatted as FAT32.

    "},{"location":"single_page/#download-raspberry-pi-imager","title":"Download Raspberry Pi Imager","text":"

    There are a few methods and programs available to reformat your microSD card as a FAT32. We found it easier to use the Raspberry Pi Imager Tool. Of course, you will only be using the tool to erase the contents of the microSD card and formatting it as a FAT32 system. You will not actually flash any image to the memory card. Click on the button below to download the tool from the Raspberry Pi Foundation. It is supported on Windows, macOS, and Ubuntu for x86.

    Raspberry Pi Imager Tool"},{"location":"single_page/#formatting-as-fat32-using-the-raspberry-pi-imager","title":"Formatting as FAT32 using the Raspberry Pi Imager","text":"

    After downloading and installing the software, open the Raspberry Pi Imager.

    Under \"Operating System\", select \"Erase\" to \"format card as FAT32.\"

    Under \"Storage\", select the drive that the microSD card appeared as on your computer.

    When ready, select \"Write\". After a few minutes, the microSD card should be formatted with FAT32.

    Once the memory card has finished formatting, eject the microSD from your computer. To check to see if the microSD card is formatted as FAT32, you can check its properties as explained earlier with your operating system. Below shows a screenshot from a Windows and macOS showing that the microSD card reformatted as a FAT32 file system.

    "},{"location":"single_page/#configuration","title":"Configuration","text":"

    Configuring the settings is as easy as opening a serial menu. You can use any serial monitor or terminal emulator to quickly and easily change and store the DataLogger IoT settings via its USB-C interface.

    There are plenty of free alternatives out there to configure the DataLogger IoT. For the scope of this tutorial we will be using Tera Term.

    • Tera Term (Windows)
    • RealTerm (Windows)
    • Minicom (Linux, Unix, MacOS)
    • Screen (Linux, Unix, MacOS)

    Note

    You will need a serial terminal client that supports edit characters. Most if not all modern serial terminal programs will have the ability to support interactive edits. Unfortunately, we have not had any success with CoolTerm. We have tested the DataLogger IoT with Tera Term, Minicom, and Screen.

    If this is the your first time using a terminal window, We recommend checking out the tutorial below for more information on serial terminal basics.

    Serial Terminal Basics

    The above guides will show you how to open the correct port for the DataLogger IoT and how to set the baud rate to 115200 baud. You can change the DataLogger IoT's baud rate through the configuration menus too should you need to.

    Note

    For users with an Arduino IDE, you could also use the Arduino Serial Monitor by setting the line ending to Newline. Users will also need to CTRL + Enter when sending any character to the DataLogger IoT. However, we recommend using one of the terminals mentioned earlier.

    "},{"location":"single_page/#initialization-and-serial-output","title":"Initialization and Serial Output","text":"

    Connect the DataLogger IoT to a USB cable and connect to your computer. The addressable RGB LED will light up green as it initializes. As of firmware v1.0.2.00 - build 00013e, a Startup Menu was added to the system. This allows you to change the behavior of the DataLogger at start-up. This change only affects the current system session.

    • 'n' \u2014 Normal startup
    • 'a' \u2014 Disable I2C device auto load on startup
    • 'l' \u2014 List the I2C devices supported. This device table is discarded after auto-load
    • 'w' \u2014 Disable WiFi
    • 's' \u2014 Disable preference restore during startup

    Note

    The amount of time the start-up menu is displayed is adjustable. This settings can be configured in the Settings/Application Settings page, under the Advanced section.

    You should see the following output when the board initializes:

    The messages in the serial terminal provide us with the DataLogger's configuration and will vary depending on the firmware version that is loaded on the board.

    • The DataLogger IoT software version (in this case is v01.02.00 - build 00013e).
    • As the DataLogger IoT is initializing, the system settings are being restored from the last saved preference.
    • There no WiFi credentials and the board has failed to connect. This output will change once you provide the WiFi credentials and are able to connect to the network.
    • There are 3x devices currently detected and they are connected through I2C through the Qwiic port and SPI. These are the on-board sensors for the DataLogger IoT. There may be more devices that are detected depending on the firmware and what is connected to the ports. Since these were recognized, they were loaded onto the DataLogger IoT.
    • The current date and time is shown (by default), the date and time is set to 1-1-1970 and 00:00:00). This value will change depending on the clock source through NTP, RTC, or a u-blox GNSS module.
    • The time the board has been running will be shown in the uptime.
    • The primary external time source that the board syncs is currently through the NTP client. This can be configured depending on your clock source.
    • The board name (in this case, it was SparkFun DataLogger IoT - 9DoF)
    • The board ID (in this case, it was SFD16C8F0D1AD6B8)
    • The microSD card has been found, the type of memory card it is, the size of the memory card, how much memory is used, and how much is available.
    • If there is a WiFi network name saved, the SSID will be shown along with information indicating whether the board was able to connect to the WiFi network. By default there is no SSID saved in memory.
    • If there is a battery connected, the LiPo Battery Fuel Guage will indicate if there is one attached to the board.
    • Parameters for low power mode will be provided indicating if deep sleep is enabled, sleep interval, and wake interval.
    • Parameters for logging are also provided for the logging interval, the format for the serial output, format for the microSD card, current saved filename, and file rotation period.
    • The board will also show the available IoT services that are enabled for the DataLogger IoT.
    • Current settings to download log files via a web interface (included in firmware v01.02.00)
    • Supported devices through Qwiic or SPI will be listed if they are connected.
    • The output will finish by telling you what devices are connected to the DataLogger IoT again.

    Note

    As of firmware v01.02.00, there is also a compact mode! By adjusting the setting, the ESP32 will output less at startup. This settings can be configured in the Settings/Application Settings page, under the Advanced section.

    Once the DataLogger IoT has initialized, the DataLogger IoT will begin outputting comma separated values (CSV). This is the default output that is set for the DataLogger IoT - 9DoF. Of course, you will not have as many readings on the DataLogger IoT since the 6DoF IMU and magnetometer are not populated on that version of the board.

    Note

    Depending on your DataLogger IoT preferences, your device may output as a JSON format like the image shown below.

    The data scrolling up the screen show what each device's output is along with their associated unit if it is available. Your mileage will vary depending on the board version that you have and what device is connected:

    • MAX17048.Voltage (V)
    • MAX17048.State of Charge (%)
    • MAX17048.Charge Rate (%/hr)
    • ISM330.Accel X (milli-g)
    • ISM330.Accel Y (milli-g)
    • ISM330.Accel Z (milli-g)
    • ISM330.Gyro X (milli-dps)
    • ISM330.Gyro Y (milli-dps)
    • ISM330.Gyro Z (milli-dps)
    • ISM330.Temperature (C)
    • MMC5983.X Field (Gauss)
    • MMC5983.Y Field (Gauss)
    • MMC5983.Z Field (Gauss)
    • MMC5983.Temperature (C)

    The output will vary depending on what is connected so you may get additional readings in the output and it may not be in the following order listed above. The logging rate defaults to about 0.067Hz (or 15000ms), so as the data scrolls past, you will see the last value settle at about 0.067Hz.

    "},{"location":"single_page/#main-menu","title":"Main Menu","text":"

    Right! Let's open the main menu by pressing on any key in the serial terminal program.

    You will be prompted with a few options. Once in the configuration menu, all three colors of the addressable RGB LED will turn on to produce the color white indicating that you are navigating through the menu. Before we dive into the settings, lets check out a few commands and saving settings.

    "},{"location":"single_page/#quick-command-reference","title":"Quick (!) Command Reference","text":"

    As of firmware v01.02.00, commands can be executed directly from the serial console thus bypassing the serial menu system! The following commands are supported.

    Quick Command Command Description !about Display the system about page !clear-settings Clear the on board system preferences with a yes/no prompt !clear-settings-forced Clear the on board system preferences with no prompt !devices List the currently connected devices !factory-reset Perform a factory reset - presents a Y/N prompt !heap Display the current system heap memory usage !help List the available quick commands !json-settings For setting the device settings via a serial connection. When this command is sent, the system expects to receive a JSON settings file !log-now Perform a log observation event !log-rate If log rate measurement is enabled, the current log rate is printed !reset-device Reset the device - erasing any saved settings and restarting the device !reset-device-forced Reset the device, but without a Y/N prompt !restart Restart the device !restart-forced Restart the device without a Y/N prompt !save-settings Save the current settings to on-board flash !sdcard Output the current SD card usage statistics !systime Output current system time !uptime The uptime of the device !device-id The ID for the device !version The version of the firmware !wifi Output current system WiFi state

    Typing a quick command and hitting the Enter button will result in the DataLogger IoT executing the command without the need to go through the menu system. Below is an example showing the !about quick command being sent and then executing the command as the DataLogger IoT is outputting CSV values to the serial terminal.

    "},{"location":"single_page/#exiting-and-saving","title":"Exiting and Saving","text":"

    When exiting the menus, you will be prompted with either an x or b. You can use either character when exiting the menus as well as X or B. Note that you will need to use either of these keys when making a change in order for the DataLogger IoT to save any changes in memory. Make sure that you receive the following message indicating that the settings were saved: [I] Saving System Settings. The DataLogger IoT will the continue reading the devices and outputting the readings through the serial terminal.

    "},{"location":"single_page/#cancelling-changes","title":"Cancelling Changes","text":"

    You can also use any of your Esc or arrow keys (i.e. \u2191, \u2193, \u2190, \u2192) to exit. However, using the escape or arrow keys will not save any changes in memory once the reset button is hit or whenever power is cycled.

    "},{"location":"single_page/#timeout-from-inactivity","title":"Timeout from Inactivity","text":"

    The menus will slowly exit out after 2 minutes of inactivity, so if you do not press a key the DataLogger IoT will return to its previous menu. It will continue to move back until it reaches the main menu. After another additional 2 minutes of inactivity, the board will exit begin logging data again. When the menu exits from inactivity, any changes will not be saved in memory as well.

    "},{"location":"single_page/#settings","title":"Settings","text":"

    Let's start by configuring the DataLogger's system settings. Send a 1 through the serial terminal. You will have the option to adjust various settings ranging from the your preferences, time source to synchronize the date and time, WiFi network, how the device logs data, which IoT service to use, and firmware updates.

    Note

    You may notice after entering a 1 that there is a slight delay before the DataLogger IoT responds. The delay was added to allow some time for the DataLogger IoT to receive an additional digit for any option greater than 9. If you want to head to option 1 immediately without the slight delay, you can hit the Enter key to enter the Application Settings.

    We'll go over each of these options below.

    "},{"location":"single_page/#general-application-settings","title":"General: Application Settings","text":"

    In the Settings Menu, send a 1 to adjust the Application Settings. As of firmware v01.00.02, users can now adjust the baud rate of the serial console output and the menu system's timeout value.

    In the Application Settings Menu, users will be able to configure the addressable RGB's LED through software, menu timeout, microSD card's output format, serial console's output format, terminal's baud rate, deep sleep parameters, and view the current settings of the DataLogger IoT similar to when the board was initialized. Depending on your preference and how you are logging data, you can adjust the data as CSV or JSON.

    • 1 LED Enabled \u2014 Enable/Disable the on-board RGB LED activity
      • Accepts a boolean value:
        • 1 to enable (default)
        • 0 to disable
    • 2 Menu Timeout \u2014 Inactivity timeout period for the menu system
      • Accepts the following values:
        • 1 30 Seconds = 30
        • 2 60 Seconds = 60 (default)
        • 3 2 Minutes = 120
        • 4 5 Minutes = 300
        • 5 10 Minutes = 600
        • b Back
    • 3 Color Output \u2014 Use color output with the Serial console. (added as of firmware v01.02.00)
      • Accepts a boolean value:
        • 1 to enable (default)
        • 0 to disable
    • 4 Board Name \u2014 A specific name for this DataLogger
      • Accepts a string
    • 5 SD Card Format \u2014 Enable and set the output format
      • Accepts the following values:
        • 1 to disable = 0
        • 2 CSV format (default) = 1
        • 3 JSON format = 2
    • 6 Serial Console Format \u2014 Enable and set the output format
      • Accepts the following values:
        • 1 to disable = 0
        • 2 CSV format (default) = 1
        • 3 JSON format = 2
    • 7 JSON Buffer Size \u2014 Output buffer size in bytes
      • Accepts an integer between 100 to 5000 :
        • 1600 bytes (default)
    • 8 Terminal Baud Rate \u2014 Update terminal baud rate. Changes take effect on restart.
      • Accepts an unsigned integer between 1200 to 50000:
        • 115200 (default)
    • 9 Enable System Sleep \u2014 If enabled, sleep the system
      • Accepts a boolean value:
        • 1 to enable
        • 0 to disable (default)
    • 10 Sleep Interval (sec) \u2014 The interval the system will sleep for
      • Accepts an integer between 5 to 86400 :
        • 30 seconds (default)
    • 11 Wake Interval (sec) \u2014 The interval the system will operate between sleep period
      • Accepts an unsigned integer between 60 to 86400 :
        • 120 seconds (default)
    • 12 Startup Messages Level of message output at startup
      • Accepts a value between 1 to 3 :
      • 1 Normal = 0 (default)
      • 2 Compact = 1
      • 3 Disabled = 2
    • 13 Startup Delay Startup Menu Delay in Seconds
      • Accepts a value between 0 to 60 :
        • 2 seconds (default)
    • 14 Device Names Name always includes the device address
      • Accepts a boolean value:
        • 1 to enable (default)
        • 0 to disable
    • 15 About... \u2014 Details about the system
    • b Back

    Note

    Once the baud rate is changed and saved, make sure to adjust the baud rate of your serial terminal when the board is reset. If you forgot the baud rate, you can hold the BOOT button down for 20 seconds to erase the on-board preferences (besides the baud rate, this also includes any other settings that were saved) and restart the board.

    When finished, you will need to exit the menus so that the DataLogger IoT saves the changes. Send a b to exit out this menu, b to exit out of the DataLogger IoT settings, and x to exit out of the main menu.

    "},{"location":"single_page/#general-save-settings","title":"General: Save Settings","text":"

    In the Settings menu, send a 2 to adjust the Save Settings. As of firmware v01.01.01, the JSON output buffer size is now user configurable. This will be under option \"JSON File Buffer Size\" when in the Save Settings Menu.

    In the Save Settings Menu, users will be able to save, restore, or clear any preferences in memory (i.e. persistent storage) or a saved file to a fallback device (i.e. microSD card). Note that any passwords and secret keys are not saved in the save settings file. You will need to manually enter those values in the file saved on the microSD card.

    • 1 Fallback Restore \u2014 If unable to restore settings, use the fallback source (JSON File)
      • Accepts a boolean value:
        • 1 to enable (default)
        • 0 to disable
    • 2 Fallback Save \u2014 Save settings also saves on the fallback storage (JSON File)
      • Accepts a boolean value:
        • 1 to enable
        • 0 to disable (default)
    • 3 JSON File Buffer Size \u2014 The size in bytes used for the internal I/O buffer
      • Accepts an unsigned integer:
      • 6400 (default, as of firmware v01.01.01)
    • 4 Save Settings \u2014 Save current settings to persistent storage
      • Accepts a yes/no:
        • Y or y for yes
        • N or n for no
    • 5 Restore Settings \u2014 Restore saved settings
      • Accepts a yes/no:
        • Y or y for yes
        • N or n for no
    • 6 Clear Settings \u2014 Erase the saved settings on the device
      • Accepts a yes/no:
        • Y or y for yes
        • N or n for no
    • 7 Save to Fallback \u2014 Save System Settings to the fallback storage (JSON File)
      • Accepts a yes/no:
        • Y or y for yes
        • N or n for no
    • 8 Restore from Fallback \u2014 Restore system settings from the fallback storage (JSON File)
      • Accepts a yes/no:
        • Y or y for yes
        • N or n for no
    • b Back

    If you have the Fallback Save enabled or selected the option Save to Fallback, you will notice an additional file called datalogger.json saved in the microSD card. This is the fallback file that is saved. Using a text editor, you can edit this file to adjust the settings or provide WiFi credentials, certificates, and keys. You can use option 7 to restore the settings on your DataLogger IoT.

    When finished, you will need to exit the menus so that the DataLogger IoT saves the changes. Send a b to exit out this menu, b to exit out of the DataLogger IoT settings, and x to exit out of the main menu.

    "},{"location":"single_page/#general-time-sources","title":"General: Time Sources","text":"

    Note

    Make sure to connect the ESP32-WROOM to a 2.4GHz WiFi network and ensure that is not a guest network that requires you to sign in. Unfortunately, 5GHz WiFi is not supported on the ESP32-WROOM module.

    In the Settings Menu, send 3 to manage the time reference sources. As of firmware v01.01.01, time zone support is at the clock level, not tied to NTP. The option to adjust the Time Zone is moved to the Time Sources menu.

    In this menu, you will have options to update the primary reference clock, update interval, add a secondary reference clock, and update it's interval. By default, the primary reference clock is set to use the Network Time Protocol (NTP). To synchronization the time, you will need to connect to a 2.4GHz WiFi network in order to update the time. To add a secondary clock, make sure to connect a compatible Qwiic-enabled devices that can keep track of time (i.e. Qwiic Real Time Clock Module - RV-8803 or a Qwiic-enabled u-blox GNSS module).

    Note

    To adjust the time zone, you will need to enter a POSIX timezone string variable. Try checking out this CSV in this GitHub repo and searching for the timezone string variable in your area. For more information about POSIX format specification check out this article from IBM.

    • 1 The Time Zone \u2014 Time zone setting string for the device
      • Accepts a string:
        • MST7MDT,M3.2.0,M11.1.0 (default, as of firmware v01.01.01)
    • 2 Reference Clock \u2014 The current reference clock source
      • Accepts the following values:
        • 1 for no clock
        • 2 for NTP Client (default)
    • 3 Update Interval \u2014 Main clock update interval in minutes. 0 = No update
      • Accepts an unsigned integer:
        • 0 = No update
        • 60 seconds (default)
    • 4 Enable Clock Fallback \u2014 Use a valid reference clock if the primary is not available
      • Accepts a boolean value:
        • 1 to enable (default)
        • 0 to disable
    • 5 Dependant Interval \u2014 Connected depedant clock update interval in minutes. 0 = No update
      • Accepts an unsigned integer:
        • 0 = No update
        • 60 seconds (default)
    • 6 Update Connected \u2014 Update connected clocks on main clock update
      • Accepts a boolean value:
        • 1 to enable (default)
        • 0 to disable
    • b Back

    Note

    As an alternative to using the NTP, users can also add a compatible Qwiic-enabled device that can keep track of time (i.e. Qwiic Real Time Clock Module - RV-8803 or a Qwiic-enabled u-blox GNSS module). These can be set as the primary or secondary clock.

    Once attached, you will be prompted with additional options to select a primary reference clock.

    If you are using a u-blox GNSS module, make sure that you have enough satellites in view. The option to add or configure the GNSS will not be available if there are not enough satellites in view. If you are using the Qwiic Real Time Clock Module - RV-8803, you may need to go into the device settings to manually adjust the date and time.

    When finished, you will need to exit the menus so that the DataLogger IoT saves the changes. Send a b to exit out this menu, b to exit out of the DataLogger IoT settings, and x to exit out of the main menu.

    "},{"location":"single_page/#network-wifi-network","title":"Network: WiFi Network","text":"

    Note

    The ESP32-WROOM can only connect to a 2.4GHz WiFi network. Unfortunately, 5GHz is not supported on the ESP32-WROOM module.

    In the Settings Menu, send a 4 to configure the WiFi settings. As of firmware v01.00.02, up to 4 sets of WiFi credentials can be saved.

    Once you are in the WiFi Network menu, you can enable/disable WiFi and save the WiFi network credentials. Once connected to a 2.4GHz WiFi network, you can synchronize the date and time, connect to an IoT service to log data, and update the latest firmware over-the-air. Since the WiFi is turned on by default, you will simply need to save the WiFi network's name and password.

    • 1 Enabled \u2014 Enable or Disable the WiFi Network connection
      • Accepts a boolean value:
        • 1 to enable (default)
        • 0 to disable
    • 2 Network Name \u2014 The SSID of the WiFi network
      • Accepts a string:
        • For example, if my network name is \"MY_NETWORK_NAME\", you would manually type MY_NETWORK_NAME. When finished hit the ENTER key
    • 3 Password \u2014 The Password to connect to the WiFi network
      • Accepts a string:
        • For example, if my network name is \"MY_SUPER_SECRET_PASSWORD\", you would manually type MY_SUPER_SECRET_PASSWORD. Note that as you type the password, each character will be replaced with an asterisk (*). When finished hit the ENTER key.
    • 4 Network 2 Name \u2014 Alternative network 2 SSID
      • Accepts a string:
        • For example, if my network name is \"MY_NETWORK_NAME_2\", you would manually type MY_NETWORK_NAME_2. When finished hit the ENTER key
    • 5 Network 2 Password \u2014 Alternative network 2 Password
      • Accepts a string:
        • For example, if my network name is \"MY_SUPER_SECRET_PASSWORD_2\", you would manually type MY_SUPER_SECRET_PASSWORD_2. Note that as you type the password, each character will be replaced with an asterisk (*). When finished hit the ENTER key.
    • 6 Network 3 Name \u2014 Alternative network 2 SSID
      • Accepts a string:
        • For example, if my network name is \"MY_NETWORK_NAME_3\", you would manually type MY_NETWORK_NAME_3. When finished hit the ENTER key
    • 7 Network 3 Password \u2014 Alternative network 3 Password
      • Accepts a string:
        • For example, if my network name is \"MY_SUPER_SECRET_PASSWORD_3\", you would manually type MY_SUPER_SECRET_PASSWORD_3. Note that as you type the password, each character will be replaced with an asterisk (*). When finished hit the ENTER key.
    • 8 Network 4 Name \u2014 Alternative network 2 SSID
      • Accepts a string:
        • For example, if my network name is \"MY_NETWORK_NAME_4\", you would manually type MY_NETWORK_NAME_4. When finished hit the ENTER key
    • 9 Network 4 Password \u2014 Alternative network 4 Password
      • Accepts a string:
        • For example, if my network name is \"MY_SUPER_SECRET_PASSWORD_4\", you would manually type MY_SUPER_SECRET_PASSWORD_4. Note that as you type the password, each character will be replaced with an asterisk (*). When finished hit the ENTER key.
    • b Back

    When finished, you will need to exit the menus so that the DataLogger IoT saves the changes. Send a b to exit out this menu, b to exit out of the DataLogger IoT settings, and x to exit out of the main menu.

    Press the reset button or cycle power to restart the DataLogger IoT. You can also go through the menu and reset the device through software as well. Once the board is reset, the DataLogger will attempt to connect to a WiFi network. If you are successful, the output will indicate that the board connected to a WiFi network and will update the current time through a NTP Client.

    Note

    If you have a Qwiic Dynamic NFC/RFID Tag connected to the board's Qwiic connector, you can easily update your WiFi credentials! Just make sure to save the WiFi credentials to the tag.

    Note

    If you saved your preferences to a JSON file on your microSD card's root directory, you can also save your WiFi credentials and load the system settings from the menu as well!

    "},{"location":"single_page/#network-ntp-client","title":"Network: NTP Client","text":"

    In the Settings menu, send a 5 to adjust the NTP Client settings. As of firmware v01.01.01, time zone support is at the clock level, not tied to the NTP. The option to adjust the Time Zone is moved to the Time Sources menu.

    In this menu, users will have the option to enable/disable the NTP client, select the primary/secondary server, or adjust the time zone for your area.

    • 1 Enabled \u2014 Enable or Disable the NTP Client
      • Accepts a boolean value:
        • 1 to enable (default)
        • 0 to disable
    • 2 NTP Server One \u2014 The primary NTP Server to use
      • Accepts a string:
        • time.nist.gov (default)
    • 3 NTP Server Two \u2014 The secondary NTP Server to use
      • Accepts a string:
        • pool.ntp.org (default)
    • b Back

    When finished, you will need to exit the menus so that the DataLogger IoT saves the changes. Send a b to exit out this menu, b to exit out of the DataLogger IoT settings, and x to exit out of the main menu.

    "},{"location":"single_page/#logging-logger","title":"Logging: Logger","text":"

    In the Settings menu, send a 6 to adjust how data is logged.

    In the Logger menu, users will have the option to add a timestamp, increment sample numbering, data format, or reset the sample counter. Note that the timestamp is the system clock and syncs with the reference clock that was chosen. Data from the Qwiic-enabled devices that keep track of time can also be included for each data entry by default.

    • 1 Timestamp Mode \u2014 Enable timestamp output and set the format of a log entry timestamp
      • 1 for no timestamp (default) = 0
      • 2 for milliseconds since program start = 1
      • 3 for seconds since Epoch = 2
      • 4 for Date Time - USA Date format = 3
      • 5 for Date Time = 4
      • 6 for ISO08601 Timestamp = 5
      • 7 for ISO08601 Timestamp with Time Zone = 6
    • 2 Sample Numbering \u2014 An incremental count of the current log entry
      • Accepts a boolean value:
        • 1 to enable
        • 0 to disable (default)
    • 3 Numbering Increment \u2014 Increment amount for Sample Numbering
      • Accepts an unsigned integer between 1 to 10000:
        • 1 (default)
    • 4 Output ID \u2014 Include the Board ID in the log output (added as of firmware v01.02.00)
      • Accepts a boolean value:
        • 1 to enable
        • 0 to disable (default)
    • 5 Output Name \u2014 Include the Board Name in the log output (added as of firmware v01.02.00)
      • Accepts a boolean value:
        • 1 to enable
        • 0 to disable (default)
    • 6 Rate Metric \u2014 Enable to record the logging rate data (added as of firmware v01.02.00)
      • Accepts a boolean value:
        • 1 to enable
        • 0 to disable (default)
    • 7 SD Card Format \u2014 Enable and set the output format
      • Accepts an integer:
        • 1 to disable = 0
        • 2 CSV format = 1 (default)
        • 3 JSON format = 2
    • 8 Serial Console Format \u2014 Enable and set the output format
      • Accepts an integer:
        • 1 to disable = 0
        • 2 CSV format = 1 (default)
        • 3 JSON format = 2
    • 9 System Info \u2014 Log system information (added as of firmware v01.02.00)
      • Accepts a boolean value:
        • 1 to enable
        • 0 to disable (default)
    • 10 Reset Sample Counter \u2014 Reset the sample number counter to the provided value
      • Accepts an unsigned integer between 0 to 10000:
        • 0 (default)
    • b Back

    When finished, you will need to exit the menus so that the DataLogger IoT saves the changes. Send a b to exit out this menu, b to exit out of the DataLogger IoT settings, and x to exit out of the main menu.

    Press the reset button or cycle power to restart the DataLogger IoT. You can also go through the menu and reset the device through software as well. Below is an example with the ISO08601 time that was added to the output.

    "},{"location":"single_page/#logging-logging-timer","title":"Logging: Logging Timer","text":"

    In the Settings menu, send an 7 to adjust the Logging Timer.

    Adjusting the interval for the Logging Timer will change the amount of time between log entries.

    • 1 Interval \u2014 The timer interval in milliseconds
      • Accepts an integer:
        • 15000 milliseconds (default)
    • b Back

    When finished, you will need to exit the menus so that the DataLogger IoT saves the changes. Send a b to exit out this menu, b to exit out of the DataLogger IoT settings, and x to exit out of the main menu.

    "},{"location":"single_page/#logging-data-file","title":"Logging: Data File","text":"

    In the Settings menu, send an 8 to adjust the Logging Data File.

    Adjusting these parameters allows you to change the filename prefix, the number the files starts at, and how often the DataLogger will create a new file on the microSD card. For example, the default file will be saved as sfe0001.txt. After 1 day, the DataLogger will rotate files by creating a new file named sfe0002.txt. The DataLogger will begin logging data in this new file. The purpose of this log rotation is to limit the size of each file prevent issues when opening large files.

    • 1 Rotate Period \u2014 Time between file rotation
      • Accepts the following values:
        • 1 for 6 hours = 6
        • 2 for 12 hours = 12
        • 3 for 1 day (24 hours) = 24 (default)
        • 4 for 2 days (48 hours) = 48
        • 5 for 1 week (168 hours) = 168
    • 2 File Start Number \u2014 The number the filename rotation starts with
      • Accepts an unsigned integer:
        • 1 (default)
    • 3 Filename Prefix \u2014 The prefix string for the generated filenames
      • Accepts a string:
        • sfe (default)
    • b Back

    When finished, you will need to exit the menus so that the DataLogger IoT saves the changes. Send a b to exit out this menu, b to exit out of the DataLogger IoT settings, and x to exit out of the main menu.

    The contents of the file will depend on how the data was saved (either CSV or JSON). Make sure that the SD Card format is enabled to either CSV or JSON with your desired device outputs turned on so that the DataLogger can save the readings.

    When removing the microSD card, make sure to remove your power source. Then insert into it into microSD card adapter or USB reader. When connecting the memory card to your computer, you can use a text editor to view the saved readings. In this case, a Windows operating system was viewing the file sfe0000.txt and it was only file available in the microSD card.

    "},{"location":"single_page/#iot-services-mqtt-client","title":"IoT Services: MQTT Client","text":"

    In the Settings menu, send an 9 to adjust settings for the MQTT Client.

    • 1 Enabled \u2014 Enable or Disable MQTT Client
      • Accepts a boolean value:
        • 1 to enable
        • 0 to disable (default)
    • 2 Port \u2014 The MQTT broker port to connect to
      • Accepts an unsigned integer:
        • 1883 (default)
    • 3 Server \u2014 The MQTT server to connect to
      • Accepts a string
    • 4 MQTT Topic \u2014 The MQTT topic to publish to
      • Accepts a string
    • 5 Client Name \u2014 Name of this device used for MQTT Communications
      • Accepts a string
    • 6 Username \u2014 Username to connect to an MQTT broker, if required.
      • Accepts a string
    • 7 Password \u2014 Password to connect to an MQTT broker, if required.
      • Accepts a string
    • 8 Buffer Size \u2014 MQTT payload buffer size. If 0, the buffer size is dynamic
      • Accepts an unsigned int16:
        • 0 for dynamic buffer size (default)
    • b Back
    "},{"location":"single_page/#iot-services-mqtt-secure-client","title":"IoT Services: MQTT Secure Client","text":"

    In the Settings menu, send an 10 to adjust settings for the MQTT Secure Client.

    • 1 Enabled \u2014 Enable or Disable MQTT Secure Client
      • Accepts a boolean value:
        • 1 to enable
        • 0 to disable (default)
    • 2 Port \u2014 The MQTT broker port to connect to
      • Accepts an unsigned integer:
        • 8883 (default, as of firmware v01.00.04)
    • 3 Server \u2014 The MQTT server to connect to
      • Accepts a string
    • 4 MQTT Topic \u2014 The MQTT topic to publish to
      • Accepts a string
    • 5 Client Name \u2014 Name of this device used for MQTT Communications
      • Accepts a string
    • 6 Username \u2014 Username to connect to an MQTT broker, if required.
      • Accepts a string
    • 7 Password \u2014 Password to connect to an MQTT broker, if required.
      • Accepts a string
    • 8 Buffer Size \u2014 MQTT payload buffer size. If 0, the buffer size is dynamic
      • Accepts an unsigned int16:
        • 0 for dynamic buffer size (default)
    • 9 CA Cert Filename \u2014 The File to load the certificate from
      • Accepts a string
    • 10 Client Cert Filename \u2014 The File to load the client certificate from
      • Accepts a string
    • 11 Client Key Filename \u2014 The File to load the client key from
      • Accepts a string
    • b Back
    "},{"location":"single_page/#iot-services-aws-iot","title":"IoT Services: AWS IoT","text":"

    In the Settings menu, send an 11 to adjust settings for the AWS IoT.

    • 1 Enabled \u2014 Enable or Disable AWS IoT
      • Accepts a boolean value:
        • 1 to enable
        • 0 to disable (default)
    • 2 Port \u2014 The MQTT broker port to connect to
      • Accepts an unsigned integer:
        • 8883 (default, as of firmware v01.00.04)
    • 3 Server \u2014 The MQTT server to connect to
      • Accepts a string
    • 4 MQTT Topic \u2014 The MQTT topic to publish to
      • Accepts a string
        • $aws/things//shadow/update (default)
    • 5 Client Name \u2014 Name of this device used for MQTT Communications
      • Accepts a string
    • 6 Username \u2014 Username to connect to an MQTT broker, if required.
      • Accepts a string
    • 7 Password \u2014 Password to connect to an MQTT broker, if required.
      • Accepts a string
    • 8 Buffer Size \u2014 MQTT payload buffer size. If 0, the buffer size is dynamic
      • Accepts an unsigned int16:
        • 0 for dynamic buffer size (default)
    • 9 CA Cert Filename \u2014 The File to load the certificate from
      • Accepts a string
    • 10 Client Cert Filename \u2014 The File to load the client certificate from
      • Accepts a string
    • 11 Client Key Filename \u2014 The File to load the client key from
      • Accepts a string
    • b Back
    "},{"location":"single_page/#iot-services-thingspeak-mqtt","title":"IoT Services: ThingSpeak MQTT","text":"

    In the Settings menu, send an 12 to adjust settings for ThingSpeak MQTT

    • 1 Enabled \u2014 Enable or Disable ThingSpeak MQTT
      • Accepts a boolean value:
        • 1 to enable
        • 0 to disable (default)
    • 2 Port \u2014 The MQTT broker port to connect to
      • Accepts an unsigned integer:
        • 8883 (default, as of firmware v01.00.04)
    • 3 Server \u2014 The MQTT server to connect to
      • Accepts a string
    • 4 MQTT Topic \u2014 The MQTT topic to publish to
      • Accepts a string
    • 5 Client Name \u2014 Name of this device used for MQTT Communications
      • Accepts a string
    • 6 Username \u2014 Username to connect to an MQTT broker, if required.
      • Accepts a string
    • 7 Password \u2014 Password to connect to an MQTT broker, if required.
      • Accepts a string
    • 8 Buffer Size \u2014 MQTT payload buffer size. If 0, the buffer size is dynamic
      • Accepts an unsigned int16:
        • 0 for dynamic buffer size (default)
    • 9 CA Cert Filename \u2014 The File to load the certificate from
      • Accepts a string
    • 10 Client Cert Filename \u2014 The File to load the client certificate from
      • Accepts a string
    • 11 Client Key Filename \u2014 The File to load the client key from
      • Accepts a string
    • 12 Channels \u2014 Comma separated list of =
      • Accepts a string
    • b Back
    • "},{"location":"single_page/#iot-services-azure-iot","title":"IoT Services: Azure IoT","text":"

      In the Settings menu, send an 13 to adjust settings for the Azure IoT.

      • 1 Enabled \u2014 Enable or Disable Azure IoT
        • Accepts a boolean value:
          • 1 to enable
          • 0 to disable (default)
      • 2 Port \u2014 The MQTT broker port to connect to
        • Accepts an unsigned integer:
          • 8883 (default, as of firmware v01.00.04)
      • 3 Server \u2014 The MQTT server to connect to
        • Accepts a string
      • 4 MQTT Topic \u2014 The MQTT topic to publish to
        • Accepts a string
      • 5 Client Name \u2014 Name of this device used for MQTT Communications
        • Accepts a string
      • 6 Username \u2014 Username to connect to an MQTT broker, if required.
        • Accepts a string
      • 7 Password \u2014 Password to connect to an MQTT broker, if required.
        • Accepts a string
      • 8 Buffer Size \u2014 MQTT payload buffer size. If 0, the buffer size is dynamic
        • Accepts an unsigned int16:
          • 0 for dynamic buffer size (default)
      • 9 CA Cert Filename \u2014 The File to load the certificate from
        • Accepts a string
      • 10 Client Cert Filename \u2014 The File to load the client certificate from
        • Accepts a string
      • 11 Client Key Filename \u2014 The File to load the client key from
        • Accepts a string
      • 11 Device ID \u2014 The device id for the Azure IoT device
        • Accepts a string
      • 12 Device Key \u2014 The device key for the Azure IoT device
        • Accepts a string
      • b Back
      "},{"location":"single_page/#iot-services-http-iot","title":"IoT Services: HTTP IoT","text":"

      In the Settings menu, send an 14 to adjust settings for the Azure IoT.

      • 1 Enabled \u2014 Enable or Disable the HTTP Client
        • Accepts a boolean value:
          • 1 to enable
          • 0 to disable (default)
      • 2 URL \u2014 The URL to call with log information
        • Accepts a string
      • 3 CA Cert Filename \u2014 The File to load the certificate from
        • Accepts a string
      • b Back
      "},{"location":"single_page/#iot-services-machinechat","title":"IoT Services: MachineChat","text":"

      In the Settings menu, send an 15 to adjust settings for MachineChat.

      • 1 Enabled \u2014 Enable or Disable the HTTP Client
        • Accepts a boolean value:
          • 1 to enable
          • 0 to disable (default)
      • 2 URL \u2014 The URL to call with log information
        • Accepts a string
      • 3 CA Cert Filename \u2014 The File to load the certificate from
        • Accepts a string
      • b Back
      "},{"location":"single_page/#iot-services-arduino-cloud","title":"IoT Services: Arduino Cloud","text":"

      Arduino

      At the time of writing, Arduino's IoT service was referred to as the \"Arduino IoT Cloud.\" Arduino updated the service with a different UI and is now referring to the service as the \"Arduino Cloud\".\" When referencing the Arduino IoT or Arduino IoT Cloud in this tutorial, we are referring to the Arduino Cloud.

      In the Settings menu, send an 16 to adjust settings for Arduino Cloud. This feature was added as of firmware v01.01.01.

      • 1 Enabled \u2014 Enable or Disable the Arduino IoT Client
        • Accepts a boolean value:
          • 1 to enable
          • 0 to disable (default)
      • 2 Thing Name \u2014 The Thing Name to use for the IoT Device connection
        • Accepts a string
      • 3 Thing ID \u2014 The Thing ID to use for the IoT Device connection
        • Accepts a string
      • 4 API Client ID \u2014 The Arduino Cloud API Client ID
        • Accepts a string
      • 5 API Secret \u2014 The Arduino Cloud API Secret
        • Accepts a string
      • 6 Device Secret \u2014 The Arduino IoT Device Secret
        • Accepts a string
      • 7 Device ID \u2014 The Arduino IoT Cloud Device ID
        • Accepts a string
      • b Back
      "},{"location":"single_page/#iot-web-server","title":"IoT Web Server","text":"

      As of firmware v01.02.00, log files can be viewed and downloaded using the IoT Web Server feature if mDNS (multicast DNS) is supported on your network. This functionality is accessed via the Settings Menu, Type 17 to enter the System Update menu. Once this menu entry is selected, the following menu options are presented:

      • 1 Enabled \u2014 Enabled or Disable the Web Server
        • Accepts a boolean value
          • 1 to enable
          • 0 to disable (default)
      • 2 Username \u2014 Web access control. Leave empty to disable authentication
        • Accepts a string
      • 3 Password \u2014 Web access control.
        • Accepts a string
      • 4 mDNS Support \u2014 Enable a name for the web address this device
        • Accepts a boolean value
          • 1 to enable
          • 0 to disable (default)
      • 5 mDNS Name \u2014 mDNS Name used for this device address
        • Accepts a string
          • dataloggerXXXXX, where XXXXX is the taken from the last 5x characters from your DataLogger IoT's board ID (default)
      • b Back

      Note

      You will need to make sure that the ESP32 is on the same network as your computer in order to access the log files.

      Note

      When authentication is enabled, some browsers might require a second login depending on user settings.

      Note

      The SparkFun Datalogger IoT requires restarting if the web interface is enabled.

      For more information on how to use this feature, check out the section on viewing and downloading log files using the IoT web server.

      Examples: Viewing and Downloading Log Files using the IoT Web Server"},{"location":"single_page/#advanced-system-update","title":"Advanced: System Update","text":"

      New sensors and features are being added all the time and we've made it really easy for you to keep your DataLogger IoT up to date. The System Update option provides the following functionality to the end user:

      • Restart the device
      • Performing a Factory Reset on the device
      • Updated the device firmware from a file on an SD Card.

      Note

      What's going on here?!? This tutorial was updated for firmware version 01.02.00!!! You will notice this menu option has changed to 18 !!!

      This functionality is accessed via the Settings Menu, which is required to use this capability. Type 18 to enter the System Update menu. Once this menu entry is selected, the following menu options are presented:

      • 1 Device Restart \u2014 Restart/reboot the device
        • Accepts the following values:
          • Y or Y to restart or reboot the device using the current firmware and system preferences
          • N or n to cancel
      • 2 Factory Reset \u2014 Erase all settings and revert to original firmware
        • Accepts the following values:
          • Y or Y to factory reset the device
          • N or n to cancel
      • 3 Update Firmware - SD Card \u2014 Update the firmware from the SD card
        • Accepts firmware in the /root directory of the microSD card with the file naming pattern SparkFunDataLoggerIoT*.bin, where the asterisk * is the firmware version number (i.e. SparkFunDataLoggerIoT_01.00.01.bin).
      • 4 Update Firmware - OTA \u2014 Update the firmware over-the-air
        • Connects to a server and searches for the latest firmware that is available. Note that you must be connected to a WiFi network to be able to update the board over-the-air.
        • Accepts the following values if there is new firmware available.
          • Y or Y to update over-the-air
          • N or n to cancel
      • b Back

      When finished, you will need to exit the menus so that the DataLogger IoT saves the changes. Send a b to exit out this menu, b to exit out of the DataLogger IoT settings, and x to exit out of the main menu.

      For more information on how to update firmware manually or over-the-air, check out the section under Examples: Updating Firmware.

      Examples: Updating Firmware"},{"location":"single_page/#device-settings","title":"Device Settings","text":"

      In the Main Menu, send a 2 through the serial terminal to adjust the devices settings.

      This will bring up the connected devices that are currently available. You can configure each device and enable/disable each output. Below is a sample of the on-board devices available for the DataLogger IoT - 9DoF when only the MAX17048, ISM330, and MMC5983 are connected. As the DataLogger IoT - 9DoF initializes, the board will populate additional devices in this window if they are detected. Your mileage will vary depending on what is connected. On the DataLogger IoT you will not see the ISM330 or MMC5983 as an option since the 6DoF IMU and magnetometer are not populated on that version of the board.

      • 1 MAX17048 \u2014 MAX17048 LiPo Battery Fuel Gauge
        • 1 Voltage (V) \u2014 Battery voltage (Volts)
          • 1 to enable Voltage (V) (default)
          • 2 to disable Voltage (V)
        • 2 State of Charge (%) \u2014 Battery state of charge (%)
          • 1 to enable state of charge (%) (default)
          • 2 to disable state of charge (%)
        • 3 Charge Rate (%/hr) \u2014 Battery charge change rate (%/hr)
          • 1 to enable change rate (%/hr) (default)
          • 2 to disable change rate (%/hr)
      • 2 ISM330 \u2014 ISM330 Inertial Measurement Unit
        • 1 Accel Data Rate (HZ) \u2014 Accelerometer Data Rate (Hz)
          • 1 for Off
          • 2 for 12.5 Hz
          • 3 for 26 Hz
          • 4 for 52 Hz
          • 5 for 104 Hz (default)
          • 6 for 208 Hz
          • 7 for 416 Hz
          • 8 for 833 Hz
          • 9 for 1666 Hz
          • 10 for 3332 Hz
          • 11 for 6667 Hz
          • 12 for 1.6 Hz
        • 2 Accel Full Scale (g) \u2014 Accelerometer Full Scall (g)
          • 1 for 2 g
          • 2 for 16 g
          • 3 for 4 g (default)
          • 4 for 8 g
        • 3 Gyro Data Rate (Hz) \u2014 Gyro Data Rate (Hz)
          • 1 for Off
          • 2 for 12.5 Hz
          • 3 for 26 Hz
          • 4 for 52 Hz
          • 5 for 104 Hz (default)
          • 6 for 208 Hz
          • 7 for 416 Hz
          • 8 for 833 Hz
          • 9 for 1666 Hz
          • 10 for 3332 Hz
          • 11 for 6667 Hz
        • 4 Gyro Full Scale (dps) \u2014 Gyro Full Scale (dps)
          • 1 for 125 dps
          • 2 for 250 dps
          • 3 for 500 dps (default)
          • 4 for 1000 dps
          • 5 for 2000 dps
          • 6 for 4000 dps
        • 5 Accel Filter LP2 \u2014 Accelerometer Filter LP2
          • 1 to enable (default)
          • 2 to disable
        • 6 Gyro Filter LP1 \u2014 Gyro Filter LP1
          • 1 to enable (default)
          • 2 to disable
        • 7 Accel Slope Filter \u2014 Accelerometer Slope Filter
          • 1 for ODR/4
          • 2 for ODR/10
          • 3 for for ODR/20
          • 4 for ODR/45
          • 5 for ODR/100 (default)
          • 6 for ODR/200
          • 7 for ODR/400
          • 8 for ODR/800
        • 8 Gyro LP1 Filter Bandwidth \u2014 Gyro LP1 Filter Bandwidth
          • 1 Ultra Light
          • 2 Very Light
          • 3 Light
          • 4 Medium (default)
          • 5 Strong
          • 6 Very Strong
          • 7 Aggressive
          • 8 Extreme
        • 9 Accel X (milli-g) \u2014 Accelerometer X (milli-g)
          • 1 to enable
          • 2 to disable
        • 10 Accel Y (milli-g) \u2014 Accelerometer Y (milli-g)
          • 1 to enable
          • 2 to disable
        • 11 Accel Z (milli-g) \u2014 Accelerometer Z (milli-g)
          • 1 to enable
          • 2 to disable
        • 12 Gyro X (milli-dps) \u2014 Gyro X (milli-g)
          • 1 to enable
          • 2 to disable
        • 13 Gyro Y (milli-dps) \u2014 Gyro Y (milli-g)
          • 1 to enable
          • 2 to disable
        • 14 Gyro Z (milli-dps) \u2014 Gyro Z (milli-g)
          • 1 to enable
          • 2 to disable
        • 15 Temperature (C) \u2014 The temperature in degrees C
          • 1 to enable
          • 2 to disable
      • 3 MMC5983 \u2014 MMC5983 Magnetometer
        • 1 Filter Bandwidth (Hz) \u2014 The filter bandwidth in Hz
          • 1 100 Hz (default)
          • 2 200 Hz
          • 3 400 Hz
          • 4 800 Hz
        • 2 Auto-Reset \u2014 Auto-Reset
          • 1 to enable
          • 2 to disable
        • 3 X Field (Gauss) \u2014 The X Field strength in Gauss
          • 1 to enable
          • 2 to disable
        • 4 Y Field (Gauss) \u2014 The Y Field strength in Gauss
          • 1 to enable
          • 2 to disable
        • 5 Z Field (Gauss) \u2014 The Z Field strength in Gauss
          • 1 to enable
          • 2 to disable
        • 6 Temperature (C) \u2014 The ambient temperature in degrees C
          • 1 to enable
          • 2 to disable
      • b Back

      When finished, you will need to exit the menus so that the DataLogger IoT saves the changes. Send a b to exit out this menu, b to exit out of the DataLogger IoT settings, and x to exit out of the main menu.

      Warning

      As you connect additional devices to the DataLogger IoT, the values associated with each device in this menu will change! Make sure to check your device settings menu after additional devices are attached should you decide to configure the additional devices and enable/disable their outputs.

      "},{"location":"single_page/#example-connecting-to-a-wifi-network","title":"Example - Connecting to a WiFi Network","text":"

      Note

      The ESP32-WROOM can only connect to a 2.4GHz WiFi network. Unfortunately, 5GHz is not supported on the ESP32-WROOM module.

      To take advantage of syncing the DataLogger to the Network Time Protocol (NTP), logging data to an IoT service, or updating firmware OTA, you will need to connect to a 2.4GHz WiFi network.

      Open a Serial Terminal, connect to the COM port that your DataLogger enumerated to, and set it to 115200 baud. In this case, we connected to COM13. Press any key to enter the Main Menu. Type 1 to enter the Settings menu. Then send a 4 to configure the WiFi settings.

      Send a 2 to set the WiFi Network Name. You'll be prompted to set the network name. In this case, the network name is sparkfun. Once you enter the name, hit the enter key.

      Send a 2 to set the WiFi password. You'll be prompted to set the password. As you send the password, each character will be masked by a asterisk (i.e. *) Once you enter the name, hit the enter key.

      Follow the prompts to exit out of the menu properly so that the DataLogger IoT saves the settings.

      Once you see the message [I] Saving System Settings and data on the output, hit the reset button on the board. You can also use the menu to perform a device restart, however you will need to ensure that you receive the message indicating that the settings were saved before restarting the device.

      Once the device has restarted, the DataLogger will provide an output as it is initializing. If the WiFi credentials are saved properly, you will receive a message indicating that your chosen network is connected to your WiFi network. If the time source is set to the default NTP client, you will also notice that the time will be synced to the latest date and time!

      "},{"location":"single_page/#example-adding-a-timestamp-to-data","title":"Example - Adding a Timestamp to Data","text":"

      Open a Serial Terminal, connect to the COM port that your DataLogger enumerated to, and set it to 115200 baud. In this case, we connected to COM13. Press any key to enter the Main Menu. Type 1 to enter the Settings menu. Then send a 6 to adjust how data is logged.

      Send a 1 to configure the timestamp for each log entry. The settings in this menu relate to the system clock and is dependent on the reference clock. You'll be prompted with different formats. In this example, we sent a a 4 to have a timestamp with the USA date format.

      Follow the prompts to exit out of the menu properly so that the DataLogger IoT saves the settings. Once you see the message [I] Saving System Settings, the DataLogger IoT will add a timestamp with your preferred format to each log entry. Assuming that you have the output set to the serial terminal, you should see the timestamp attached to the output after the system settings are saved like the image below.

      "},{"location":"single_page/#example-factory-reset","title":"Example - Factory Reset","text":"

      A factory reset will move the boot firmware of the device to the firmware imaged installed at the factory and erase any on-board stored settings on the device. This is helpful if an update fails, or an update has issues that prevent proper operations.

      This option is available on ESP32 devices that contained a factory firmware partition that contains a bootable firmware image. Consult the specific product's production and build system for further details.

      Open a Serial Terminal, connect to the COM port that your DataLogger enumerated to, and set it to 115200 baud. In this case, we connected to COM13. Press any key to enter the Main Menu. Type 1 to enter the Settings menu. Then type 16 to enter the System Update Menu. Finally, type 2 to enter the Factory Reset option.

      The user is presented a prompt to continue. To launch a factory reset, the value of Y should be entered. To abort the update, enter n or press the Esc key.

      When a Y is entered, the system performs the following:

      • Set the boot image to the Factory installed firmware
      • Erase any settings stored in the on-board flash memory
      • Reboot the device
      "},{"location":"single_page/#example-updating-firmware","title":"Example - Updating Firmware","text":"

      Danger

      Please think very carefully before uploading any Arduino sketches to your DataLogger IoT.

      You will overwrite the DataLogger IoT firmware, leaving it unable to update or restore itself.

      Each DataLogger IoT comes pre-programmed with amazing firmware which can do so much. It is designed to be able to update itself and restore itself if necessary. But it can not do that if you overwrite the firmware with any Arduino sketch. It is just like erasing the restore partition on your computer hard drive. Do not do it - unless you really know what you are doing.

      Really. We mean it.

      "},{"location":"single_page/#firmware-update-sd-card","title":"Firmware Update - SD Card","text":"

      This action enables the ability to update the onboard firmware to an image file contained an SD card. This user is presented a list of available firmware images files contained in root directory of the on-board SD card, and updates the board to the selected file.

      This option is available on ESP32 devices that contained two update firmware (OTA type) partitions within the on-board device flash memory. Consult the specific products production and build system for further details.

      To download the latest firmware and update through the microSD card, you will need to head to the GitHub repository containing the firmware. Select the firmware version and download.

      GitHub: SparkFun DataLogger | Firmware Releases

      Once downloaded, insert the microSD card into a card reader or USB adapter. Then move the files into the memory card's root directory. Below shows an image of v01.00.01 and v01.00.02 on a Windows OS.

      Once the files are copied to the memory card, eject the microSD card from your computer. Insert the card back into the DataLogger IoT's microSD card socket. Connect the DataLogger IoT to your computer using a USB cable.

      Note

      What's going on here?!? This tutorial was updated for firmware version 01.01.00!!! You will notice this menu option has changed to 17 !!!

      Open a Serial Terminal, connect to the COM port that your DataLogger enumerated to, and set it to 115200 baud. In this case, we connected to COM13. Press any key to enter the Main Menu. Type 1 to enter the Settings menu. Then type 17 to enter the System Update Menu. Finally, type 3 to update the firmware from the microSD card. You should see an image similar to the output below.

      The system will search the root directory of the on-board SD card for available firmware files. The firmware files are selected using the following criteria:

      • The file is contained in the root \"/\" folder of the SD card.
      • The filename has a \".bin\" extension.
      • The filename starts with a specified name prefix. The prefix is optional and is set by the developers at SparkFun using this action.
        • For example, the DataLogger IoT boards use a prefix value of \"SparkFun_DataLoggerIoT_\".

      Once a file is selected, the system new firmware is read off the SD card and written to the device.

      And once updated, the system is rebooted and starts using the new firmware image!

      "},{"location":"single_page/#firmware-update-over-the-air-ota","title":"Firmware Update - Over-the-Air (OTA)","text":"

      This action enables the ability to update the onboard firmware to an image file contained on an update server, which is accessed via the WiFi network the system is connected to. This Over-The-Air (OTA) capability contacts the systems update server and searches for newer firmware (later version) for the specific board.

      This option is available on ESP32 devices that contained two update firmware (OTA type) partitions within the on-board device flash memory. Consult the specific products production and build system for further details.

      If you have not already, connect the DataLogger IoT to your computer using a USB cable.

      Open a Serial Terminal, connect to the COM port that your DataLogger enumerated to, and set it to 115200 baud. In this case, we connected to COM13. Press any key to enter the Main Menu. Type 1 to enter the Settings menu. Then type 16 to enter the System Update Menu. Finally, type 4 to update the firmware over-the-air.

      When this option is selected, the system will contact the update server and search for available upgrade firmware, selecting the latest version available. If a newer version is found, a prompt is presented to confirm the upgrade.

      Note

      For the upgrade option to occur, a the system must be connected to a network and have access to the firmware OTA server.

      Typing Y (or hitting enter) starts the update operation. As the firmware is downloaded, the percent complete status is updated. If connectivity fails during the download, the operation is halted and the update aborted.

      Once the update file is downloaded, it is verified as being the correct file. Once verified, the system is rebooted and starts using the new firmware image! You will notice the firmware version change as the DataLogger IoT initializes.

      "},{"location":"single_page/#example-mqtt","title":"Example - MQTT","text":""},{"location":"single_page/#connecting-and-publishing-data-to-mqtt","title":"Connecting and Publishing Data to MQTT","text":"

      One of the key features of the DataLogger IoT is it's simplified access to IoT service providers and servers. This document outlines how output from a DataLogger device is sent to an MQTT Broker.

      Image Courtesy of MQTT

      The following is covered by this document:

      • Overview of the MQTT connection
      • How a user configures and uses the MQTT connection
      • MQTT examples
      "},{"location":"single_page/#general-operation","title":"General Operation","text":"

      MQTT connectivity allows data generated by the DataLogger IoT to be published to an MQTT Broker under a user configured topic. MQTT is an extremely flexible and low overhead data protocol that is widely used in the IoT field.

      The general use pattern for MQTT is that data is published to a topic on a MQTT broker. The data is then sent to any MQTT client that has subscribed to the specified topic.

      The DataLogger IoT supports MQTT connections, allowing an end user to enter the parameters for the particular MQTT Broker for the application to publish data to. When the application outputs data to the broker, the DataLogger IoT publishes the available information to the specified \"topic\" with the payload that is a JSON document.

      "},{"location":"single_page/#data-structure","title":"Data Structure","text":"

      Data is published to the MQTT broker as a JSON object, which contains a collection of sub-objects. Each sub-object represents a data source in the sensor, and contains the current readings from that source.

      The following is an example of the data posted - note, this representation was \"pretty printed\" for readability.

      {\n  \"MAX17048\": {\n    \"Voltage (V)\": 4.304999828,\n    \"State Of Charge (%)\": 115.0625,\n    \"Change Rate (%/hr)\": 0\n  },\n  \"CCS811\": {\n    \"CO2\": 620,\n    \"VOC\": 33\n  },\n  \"BME280\": {\n    \"Humidity\": 25.03613281,\n    \"TemperatureF\": 79.64599609,\n    \"TemperatureC\": 26.46999931,\n    \"Pressure\": 85280.23438,\n    \"AltitudeM\": 1430.44104,\n    \"AltitudeF\": 4693.04834\n  },\n  \"ISM330\": {\n    \"Accel X (milli-g)\": -53.31399918,\n    \"Accel Y (milli-g)\": -34.03800201,\n    \"Accel Z (milli-g)\": 1017.236023,\n    \"Gyro X (milli-dps)\": 542.5,\n    \"Gyro Y (milli-dps)\": -1120,\n    \"Gyro Z (milli-dps)\": 262.5,\n    \"Temperature (C)\": 26\n  },\n  \"MMC5983\": {\n    \"X Field (Gauss)\": -0.200622559,\n    \"Y Field (Gauss)\": 0.076416016,\n    \"Z Field (Gauss)\": 0.447570801,\n    \"Temperature (C)\": 29\n  }\n}\n
      "},{"location":"single_page/#mqtt-broker-connection-setup","title":"MQTT Broker Connection Setup","text":"

      To connect to a MQTT Broker, the following information is needed:

      • The server name/address
      • The server port
      • The topic to post to
      • [optional] The name of the device/Client name publishing the data
      • [optional] A username - if required
      • [optional] A password - if required

      These values are set using the standard DataLogger methods - the interactive menu system, or a JSON file.

      "},{"location":"single_page/#mqtt-menu-system","title":"MQTT Menu System","text":"

      We'll need to adjust the settings for the MQTT Client using the MQTT Menu System.

      For users that are interested in using the menu system, open a Serial Terminal, connect to the COM port that your DataLogger enumerated to, and set it to 115200 baud. In this case, we connected to COM13. Press any key to enter the Main Menu. Type 1 to enter the Settings menu. Then type 9 to enter the MQTT Client Menu. When the menu system for the MQTT connection is presented, the following options are displayed:

      The options are:

      • Enable/Disable the connection
      • Broker Port - The standard port for mqtt is 1883
      • Broker Server - This is just the name of the server
      • MQTT Topic - A string
      • Client Name
      • Username
      • Password
      • Buffer Size

      At a minimum, the Broker Port, Broker Server Name, and MQTT Topic need to be set. What parameters are required depends on the settings of the broker being used.

      Note

      If a secure connection is being used with the MQTT broker, use the MQTT Secure Client option of the DataLogger IoT. This option supports secure connectivity.

      Note

      The Buffer Size option is dynamic by default, adapting to the size of the payload being sent. If runtime memory is a concern, set this value to a static size that supports the device operation.

      Once all these values are set, the system will publish data to the specified MQTT Broker, following the JSON information structure noted earlier in this document.

      "},{"location":"single_page/#json-file-entries","title":"JSON File Entries","text":"

      If a JSON file is being used as an option to import settings into the DataLogger IoT, the following entries are used for the MQTT IoT connection:

      \"MQTT Client\": {\n    \"Enabled\": false,\n    \"Port\": 1883,\n    \"Server\": \"my-mqttserver.com\",\n    \"MQTT Topic\": \"/sparkfun/datalogger1\",\n    \"Client Name\": \"mysensor system\",\n    \"Buffer Size\": 0,\n    \"Username\": \"\",\n    \"Password\": \"\"\n  },\n

      Where:

      • Enabled - Set to true to enable the connection.
      • Port - Set to the broker port.
      • Server - The MQTT broker server.
      • MQTT Topic - The topic to publish to.
      • Client Name - Optional client name.
      • Buffer Size - Internal transfer buffer size.
      • Username - Broker user name if being used.
      • Password - Broker password if being used.

      Tip

      To load the values by the system at startup using a JSON file and microSD card, you will need to configure the Save Settings. This JSON file will be created with the \"Save to Fallback\" option. Make sure to enable the MQTT Client as well.

      "},{"location":"single_page/#testing-the-mqtt-connection","title":"Testing the MQTT Connection","text":"

      Use of a MQTT connection is fairly straightforward - just requiring the entry of broker details into the connection settings.

      To test the connection, you need a MQTT broker available. A quick method to setup a broker is by installing the mosquitto package on a Raspberry Pi computer. Our basic MQTT Tutorial provides some basic setup for a broker.

      Introduction to MQTT

      This MQTT Broker Tutorial has more details, covering the setup needed for modern mosquitto configurations.

      Random Nerd Tutorials: Install Mosquitto Broker on Raspberry Pi

      And once the broker is setup, the messages published by the IoT sensor are visible using the mosquitto_sub command as outlined. For example, to view messages posted to a the topic \"/sparkfun/datalogger1\", the following command is used:

      mosquitto_sub -t \"/sparkfun/datalogger1\"\n

      This assumes the MQTT broker is running on the same machine, and using the default port number.

      "},{"location":"single_page/#example-aws","title":"Example - AWS","text":""},{"location":"single_page/#creating-and-connecting-to-an-aws-iot-device-thing","title":"Creating and Connecting to an AWS IoT Device (Thing)","text":"

      One of the key features of the DataLogger IoT is it's simplified access to IoT service providers. This document outlines how an AWS IoT device is used by the DataLogger IoT.

      Image Courtesy of Amazon Web Services (AWS)

      The following is covered by this document:

      • Device (Thing) creation in AWS
      • Securely connecting the device
      • How data is posted from the DataLogger IoT to the AWS Device via it's Shadow

      Currently, the AWS IoT device connection is a single direction - used to post data from the hardware to the IoT AWS Device via the AWS IoT devices shadow. Configuration information from AWS IoT to the DataLogger IoT is currently not implemented.

      "},{"location":"single_page/#general-operation_1","title":"General Operation","text":"

      AWS IoT enables connectivity between an IoT / Edge device and the AWS Cloud Platform, implementing secure endpoints and device models within the AWs infrastructure. This infrastructure allows edge devices to post updates, status and state to the AWS infrastructure for analytics, monitoring and reporting.

      In AWS IoT, an virtual representation of an actual device is created and referred to as a Thing. The virtual device/Thing is allocated a connection endpoint, security certificates and a device shadow - a JSON document used to persist, communicate and manage device state within AWS.

      The actual IoT device communicates with it's AWS representation via a secure MQTT connection, posting JSON document payloads to a set of pre-defined topics. Updates are posted to the AWS IoT device shadow, which is then accessed within AWS for further process as defined by the users particular cloud implementation.

      "},{"location":"single_page/#creating-a-device-in-aws-iot","title":"Creating a Device in AWS IoT","text":"

      The following discussion outlines the basic steps taken to create a Thing in AWS IoT that the DataLogger IoT can connect to. First step is to log into your AWS account and create a thing.

      Click Here to Log into AWS

      Once logged into your AWS account, select IoT Core from the menu of services.

      From the IoT Core console page, under the Manage section, select All Devices > Things

      On the resultant Things Page, select the Create Things button.

      AWS IoT will then take you through the steps to create a device. Selections made for a demo Thing are:

      • Create single thing
      • Thing Properties
      • Enter a name for your thing - for this example TestThing23
      • Device Shadow - select Unnamed shadow (classic)
      • Auto-generate a new certificate
      • Attach policies to certificate - This is discussed later in this document
      • Select Create thing

      Upon creation, AWS IoT presents you with a list of downloadable certificates and keys. Some of these are only available at this step. The best option is to download everything presented - three of these are used by the DataLogger IoT. The following should be downloaded:

      • Device Certificate
      • Public Key File
      • Private Key File
      • Root CA certificates - (for example: Amazon Root CA 1 )

      At this point, the new AWS IoT thing is created and listed on the AWS IoT Things Console

      "},{"location":"single_page/#security-policy","title":"Security Policy","text":"

      To write to the IoT device, a security policy that enables this is needed, and the policy needs to be assigned to the devices certificate.

      To create a Policy, select the Manage > Security > Policies menu item from the left side menu of the AWS IoT panel. Once on this page, select the Create policy button to create a new policy.

      When entering the policy, provide a name that fits your need. For this example, the name NewThing23Policy is used. For the Policy document, you can manually enter the security entires, or enter them as a JSON document. The JSON document used for this example is:

      {\n  \"Version\": \"2012-10-17\",\n  \"Statement\": [\n    {\n      \"Effect\": \"Allow\",\n      \"Action\": \"iot:Connect\",\n      \"Resource\": \"*\"\n    },\n    {\n      \"Effect\": \"Allow\",\n      \"Action\": \"iot:Subscribe\",\n      \"Resource\": \"*\"\n    },\n    {\n      \"Effect\": \"Allow\",\n      \"Action\": \"iot:Receive\",\n      \"Resource\": \"*\"\n    },\n    {\n      \"Effect\": \"Allow\",\n      \"Action\": \"iot:Publish\",\n      \"Resource\": \"*\"\n    },\n    {\n      \"Effect\": \"Allow\",\n      \"Action\": \"iot:GetThingShadow\",\n      \"Resource\": \"*\"\n    },\n    {\n      \"Effect\": \"Allow\",\n      \"Action\": \"iot:UpdateThingShadow\",\n      \"Resource\": \"*\"\n    }\n  ]\n}\n

      Once the policy is created, go back to the IoT Device/Thing created above and associate this policy to the device Certificate.

      • Go to your device Manage > All devices > Things
      • Select the device - TestThing23 for this example
      • Select the Certificates tab
      • Select the listed Certificate (it's a very long hex number)
      • At the bottom right of the page, select the Attach policies button and select the Policy created above.

      At this point, AWS IoT is ready for a device to connect and receive data.

      "},{"location":"single_page/#aws-configuration","title":"AWS Configuration","text":"

      The specifics for the AWS IoT Thing must be configured. This includes the following:

      • Server name/host
      • MQTT topic to update
      • Client Name - The AWS IoT Thing Name
      • CA Certificate Chain
      • Client Certificate
      • Client Key
      "},{"location":"single_page/#server-namehostname","title":"Server Name/Hostname","text":"

      This value is obtained from the AWS IoT Device page for the created device. When on this page, select the Device Shadows tab, and then select the Classic Shadow shadow, which is listed. Note a secure connection is used, so the port for the connection is 8883.

      Selecting the Classic Shadow entry provides the Server Name/Hostname for the device, as well as the MQTT topic for this device.

      Note

      The server name is obtained from the Device Shadow URL entry

      "},{"location":"single_page/#mqtt-topic","title":"MQTT Topic","text":"

      The MQTT topic value is based uses the MQTT topic prefix from above, and has the value update added to it. So for this example, the MQTT topic is:

      $aws/things/TestThing23/shadow/update\n
      "},{"location":"single_page/#client-name","title":"Client Name","text":"

      This is the AWS IoT name of the thing. For the provided example, the value is TestThing23

      "},{"location":"single_page/#ca-certificate-chain","title":"CA Certificate Chain","text":"

      This value was downloaded as a file during the creation process. The contents of this file can be passed on to the DataLogger IoT by copying the file containing the data onto a devices SD Card and setting the filename property for the DataLogger IoT.

      "},{"location":"single_page/#client-certificate","title":"Client Certificate","text":"

      This value was downloaded as a file during the creation process. The contents of this file can be passed on to the DataLogger IoT by copying the file containing the data onto a devices SD Card and setting the filename property for the DataLogger IoT.

      "},{"location":"single_page/#client-key","title":"Client Key","text":"

      This value was downloaded as a file during the creation process. The contents of this file can be passed on to the DataLogger IoT by copying the file containing the data onto a devices SD Card and setting the filename property for the DataLogger IoT.

      "},{"location":"single_page/#setting-properties","title":"Setting Properties","text":"

      The above property values must be set on the DataLogger before use. They can be set manually by using the menu system like the previous MQTT example.

      For users that are interested in using the menu system, you will need to open a Serial Terminal, connect to the COM port that your DataLogger enumerated to, and set it to 115200 baud. In this case, we connected to COM13. Press any key to enter the Main Menu. Type 1 to enter the Settings menu. Then type 11 to enter the AWS IoT Menu. When the menu system for the AWS IoT connection is presented, you will need to configure the property values as listed in the JSON file. Saving the values through the menu system will save the credentials to the ESP32's persistent memory. The following options are displayed:

      The alternative to using the menu system is a JSON file. These values can be set using a JSON file that is loaded by the system at startup. For the DataLogger IoT example outlined in this document, the entries in the settings JSON file are as follows:

      \"AWS IoT\": {\n    \"Enabled\": true,\n    \"Port\": 8883,\n    \"Server\": \"avgpd2wdr5s6u-ats.iot.us-east-1.amazonaws.com\",\n    \"MQTT Topic\": \"$aws/things/TestThing23/shadow/update\",\n    \"Client Name\": \"TestThing23\",\n    \"Buffer Size\": 0,\n    \"Username\": \"\",\n    \"Password\": \"\",\n    \"CA Certificate\": \"\",\n    \"Client Certificate\": \"\",\n    \"Client Key\": \"\",\n    \"CA Cert Filename\": \"AmazonRootCA1.pem\",\n    \"Client Cert Filename\": \"TestThing23_DevCert.crt\",\n    \"Client Key Filename\": \"TestThing23_Private.key\"\n  },\n

      Besides updating the Server, MQTT Topic, Client Name, CA Cert Filename, Client Cert Filename, and Client Key Filename, you will need to also ensure that the port is set to 8883. The default in previous firmware versions was 1883. As of firmware v01.00.04, the default is 8883. You will need to adjust the port value to properly connect to the AWS IoT service. Don't forget to enable AWS IoT service by setting the value to true. If the JSON file is saved in the microSD card, you can load the credentials to the DataLogger IoT.

      Tip

      To load the values by the system at startup using a JSON file and microSD card, you will need to configure the Save Settings. This JSON file will be created with the \"Save to Fallback\" option. Make sure to enable the AWS IoT as well.

      "},{"location":"single_page/#operation","title":"Operation","text":"

      Once the device is configured and running, updates in AWS IoT are listed in the Activity tab of the devices page. For the test device in this document, this page looks like:

      Opening up an update, you can see the data being set to AWS IoT in a JSON format.

      "},{"location":"single_page/#example-thingspeak","title":"Example - ThingSpeak","text":""},{"location":"single_page/#creating-and-connecting-to-thingspeak","title":"Creating and Connecting to ThingSpeak","text":"

      One of the key features of the DataLogger IoT is it's simplified access to IoT service providers. This document outlines how a ThinkSpeak output is used by the DataLogger IoT.

      Image Courtesy of ThingSpeak

      The following is covered by this document:

      • Creating a ThingSpeak Channel and MQTT Connection
      • Securely connecting the ThingSpeak
      • How data is posted from the DataLogger IoT to ThingSpeak
      "},{"location":"single_page/#general-operation_2","title":"General Operation","text":""},{"location":"single_page/#thingspeak-structure","title":"ThingSpeak Structure","text":"

      The structure of ThingSpeak is based off of the concept of Channels, with each channel supporting up to eight fields for data specific to the data source. Each channel is named, and has a unique ID associated with it. One what to think of it is that a Channel is a grouping of associated data values or fields.

      The fields of a channel are enumerated as Field1, Field2, ..., Field8, but each field can be named to simplify data access and understanding.

      As data is reported to a ThingSpeak channel, the field values are accessible for further processing or visualization output.

      "},{"location":"single_page/#data-structure_1","title":"Data Structure","text":"

      The DataLogger IoT is constructed around the concept of Devices which are often a type of sensor that can output a set of data values per observation or sample.

      "},{"location":"single_page/#mapping-data-to-thingspeak","title":"Mapping Data to ThingSpeak","text":"

      The concept of Channels that contain Fields in ThingSpeak is similar to the Devices that contain Data within the DataLogger IoT, and this similarity is the mapping model used by the DataLogger IoT. Specifically:

      • Devices == Channels
      • Data == Fields

      During configuration of the DataLogger IoT, the mapping between the Device and ThingSpeak channel is specified. The data to field mapping is automatically created by the DataLogger IoT following the data reporting order from the specific device driver.

      "},{"location":"single_page/#creating-a-device-to-a-thingspeak-channel","title":"Creating a Device to a ThingSpeak Channel","text":"

      The following discussion outlines the basic steps taken to create a Channel in ThingSpeak and then connect it to the DataLogger's Device. First step is to log into your ThingSpeak and create a Channel.

      Click Here to Log into ThingSpeak

      Once logged into your ThingSpeak account, select Channels > My Channels menu item and on the My Channel page, select the New Channel button.

      On the presented channel page, name the channel and fill in the specific channel fields. The fields should map to the data fields reported from the Device being linked to this channel. Order is important, and is determined by looking at output of a device to the serial device (or reviewing the device driver code).

      Once the values are entered, select Save Channel. ThingSpeak will now show list of Channel Stats, made up of line plots for each field specified for the channel.

      Note

      Key note - at the top of this page is listed the Channel ID. Note this number - it is used to map a Device to a ThingSpeak Channel.

      "},{"location":"single_page/#setting-up-thingspeak-mqtt","title":"Setting Up ThingSpeak MQTT","text":"

      The DataLogger IoT uses MQTT to post data to a channel. From the ThingSpeak menu, select Devices > MQTT, which displays a list of your MQTT devices. From this page, select the Add a new device button.

      On the presented dialog, enter a name for the MQTT connection and in the Authorize channels to access, select the channel created earlier. Once you select a channel, click the Add Channel button.

      Note

      More channels can be added later.

      Note

      When the MQTT device is created, a set of credentials (Client ID, Username, and Password) is provided. Copy or download these values, since the password in not accessible after this step.

      The selected Channel is then listed in the Authorized Channel table. Ensure that the Allow Publish and Allow Subscribe attributes are enabled for the added channel.

      At this point, the ThingSpeak Channel is setup for access by the DataLogger IoT.

      "},{"location":"single_page/#thingspeak-configuration","title":"ThingSpeak Configuration","text":"

      Once the device is integrated into the application, the specifics for the ThingSpeak Channel(s) must be configured. This includes the following:

      • Server Name/Hostname
      • Client Name
      • User Name
      • Password
      • Device to Channel mapping
      • CA Certificate Chain
      "},{"location":"single_page/#server-namehostname_1","title":"Server Name/Hostname","text":"

      This value is hostname of the ThingSpeak mqtt connection, which is mqtt3.thingspeak.com as note at ThingSpeakMQTT Basics page. Note a secure connection is used, so the port for the connection is 8883.

      "},{"location":"single_page/#client-nameid","title":"Client Name/ID","text":"

      The Client Name/ID is found under MQTT connection details listed in the Devices > MQTT section of ThingSpeak.

      "},{"location":"single_page/#username","title":"Username","text":"

      The Username is found under MQTT connection details listed in the Devices > MQTT section of ThingSpeak.

      "},{"location":"single_page/#password","title":"Password","text":"

      The connection password was provided when the MQTT device was created. If you lost this value, you can regenerate a password on the MQTT Device information page.

      "},{"location":"single_page/#certificate-file","title":"Certificate File","text":"

      You can download the cert file for ThingSpeak.com page using a web-browser. Click on the security details of this page, and navigate the dialog (browser dependent) to download the certificate. The downloaded file is the made available for the DataLogger IoT to use as a file that is loaded at runtime)

      "},{"location":"single_page/#setting-properties_1","title":"Setting Properties","text":"

      The above property values must be set on the DataLogger IoT before use. They can be manually by using the menu system like the previous MQTT example.

      For users that are interested in using the menu system, you will need to open a Serial Terminal, connect to the COM port that your DataLogger enumerated to, and set it to 115200 baud. In this case, we connected to COM13. Press any key to enter the Main Menu. Type 1 to enter the Settings menu. Then type 12 to enter the ThingSpeak MQTT Menu. When the menu system for the ThingSpeak MQTT connection is presented, you will need to configure the property values as listed in the JSON file. Saving the values through the menu system will save the credentials to the ESP32's persistent memory. The following options are displayed:

      The alternative to using the menu system is a JSON file. These values can be set using a JSON file that is loaded by the system at startup. For the ThingSpeak example outlined in this document, the entries in the settings JSON file are as follows:

      \"ThingSpeak MQTT\": {\n    \"Enabled\": true,\n    \"Port\": 8883,\n    \"Server\": \"mqtt3.thingspeak.com\",\n    \"MQTT Topic\": \"\",\n    \"Client Name\": \"MQTT_Device_Client_ID\",\n    \"Buffer Size\": 0,\n    \"Username\": \"MQTT_Device_Username\",\n    \"Password\": \"MQTT_Device_Password\",\n    \"CA Cert Filename\": \"ThingspeakCA.cer\",\n    \"Channels\" : \"BME280=2054891\"\n  }\n

      Note

      The Channels value is a list of [DEVICE NAME]=[Channel ID] pairs. Each pair is separated by a comma. In this case, the device name BME280 and the channel ID was 2054891. Make sure to match the device name that was loaded on start up with the unique channel ID that was generated when creating a ThingSpeak Channel.

      Besides updating the Server, Client Name, Username, Password, CA Cert Filename, and Channels, you will need to also ensure that the port is set to 8883. The default in previous firmware versions was 1883. As of firmware v01.00.04, the default is 8883. You will need to adjust the port value to properly connect to the ThingSpeak service. Don't forget to enable ThingSpeak MQTT service by setting the value to true. If the JSON file is saved in the microSD card, you can load the credentials to the DataLogger IoT.

      Tip

      To load the values by the system at startup using a JSON file and microSD card, you will need to configure the Save Settings. This JSON file will be created with the \"Save to Fallback\" option. Make sure to enable the ThingSpeak MQTT as well.

      "},{"location":"single_page/#monitoring-output","title":"Monitoring Output","text":"

      Once the connector is configured and the DataLogger IoT is connected to ThingSpeak, as data is posted, the results are show on the Channel Stats page for your Channel. For the above example, the output of a SparkFun BME280 sensor produces the following output:

      "},{"location":"single_page/#setting-up-2x-or-more-devices","title":"Setting Up 2x or More Devices","text":"

      For users that are setting up 2x or more devices on the DataLogger IoT, you will need to ensure that each device has their own ThingSpeak Channel. Unfortunately, you are not able to plot sensor readings from two devices in the same channel.

      The following example demonstrates how to set up two devices for ThingSpeak on the DataLogger IoT. In this case, we will use the BME688 and BME680 and their respective default I2C address. This is also a good example of what to do when two devices use the same device driver. Head to ThingSpeak to create a channel for each device connected to the DataLogger IoT. Include a field for each device data that the DataLogger provides. The name of the channel does not need to match the device name or I2C address.

      Creating a Channel for the BME688 Creating a Channel for the BME680

      Once the channels are created, you will be provided with a unique channel ID for each channel. Make sure to take note of the number as explained earlier.

      Note

      The alternative I2C address for the BME688 and BME680 uses the same address as the default of the other sensor:

      • BME680: 0x77 (Default) or 0x76
      • BME688: 0x76 (Default) or 0x77

      Make sure to avoid using the same address when connecting the sensors to the same DataLogger IoT.

      When setting up the connection, you will need to authorize both channels. In this example, we included the channels for the BME688 [x076] and BME680 [x077].

      Authorizing 2x Channels through the Same Connection

      Now that the ThingSpeak MQTT connection is setup, adjust the ThingSpeak configuration for the DataLogger IoT by including the credentials (i.e. Client Name, Username, and Password) and channels. We will assume that you have included the ThingSpeak CA certificate file in the root directory of the microSD card already. When including the device name with their respective channel, ensure that the device name matches the name that was loaded on startup. For example, the BME688 and BME680 were loaded on startup as BME68x and BME68x [x77], respectively. Since we are only interested in plotting the BME688 and BME680, we will ignore the MAX17048 that was loaded on startup as well. Under /Settings/ThingSpeak MQTT/Channels, you will enter the string for the device names, each of their respective channel IDs, and a comma separating the two channels like so: BME68x=2613826, BME68x [x77]=2613825.

      DataLogger IoT Device Name Loaded during Startup Device Name and Channel for Both Sensors

      Note

      Whenever there are multiple devices using the same device driver (each with unique I2C addresses), the DataLogger IoT will display the device address for each additional device that is loading the same driver. As shown above, the first device name did not include the device's I2C address. The second device name using the same driver included its I2C address. Of course, there is an configuration that enables you to always include the address of all the device names.

      The alternative to using the menu system is the JSON file. In this case, we updated channels for the BME688 and BME680. Not shown are the ThingSpeak Client Name, Username, and Password.

      \"ThingSpeak MQTT\": {\n    \"Enabled\": true,\n    \"Port\": 8883,\n    \"Server\": \"mqtt3.thingspeak.com\",\n    \"MQTT Topic\": \"\",\n    \"Client Name\": \"MQTT_Device_Client_ID\",\n    \"Buffer Size\": 0,\n    \"Username\": \"MQTT_Device_Username\",\n    \"Password\": \"MQTT_Device_Password\",\n    \"CA Cert Filename\": \"ThingspeakCA.cer\",\n    \"Channels\" : \"BME68x=2613826, BME68x [x77]=2613825\"\n  }\n

      Note

      If users configure the DataLogger IoT to always include the device address with the device names (i.e. /Settings/Application Settings with Device Names=1), you will need to match the device names for BME688 and BME680 that were loaded on startup as BME68x [x76] and BME68x [x77], respectively. Note the BME688 device name included a space and [x76] in this case. Remember, we are only interested in plotting hte BME688 and BME680 in this case so we will ignore the MAX17048 that was loaded on startup.

      DataLogger IoT Device Names Loaded during Startup Device Name and Channel for Both Sensors with Respective Addresses

      Again, the alternative to using the menu system is the JSON file. In this case, we updated channels for the BME688 and BME680. We also included the address name for the BME688 like the configuration menu. Not shown are the ThingSpeak Client Name, Username, and Password.

      \"ThingSpeak MQTT\": {\n    \"Enabled\": true,\n    \"Port\": 8883,\n    \"Server\": \"mqtt3.thingspeak.com\",\n    \"MQTT Topic\": \"\",\n    \"Client Name\": \"MQTT_Device_Client_ID\",\n    \"Buffer Size\": 0,\n    \"Username\": \"MQTT_Device_Username\",\n    \"Password\": \"MQTT_Device_Password\",\n    \"CA Cert Filename\": \"ThingspeakCA.cer\",\n    \"Channels\" : \"BME68x [x76]=2613826, BME68x [x77]=2613825\"\n  }\n

      Save the configuration to persistent memory and exit out of the configuration menu. Wait a few seconds for the DataLogger IoT to read the sensors and output the readings to the Serial Terminal. Open ThingSpeak channels in separate browser windows. In this case, we had the BME688 and the BME680 in private view. You should see sensor readings update and plot on the charts.

      ThingSpeak Graphing the BME688 and BME680 in Seperate Channels on Two Browser Windows"},{"location":"single_page/#example-azure","title":"Example - Azure","text":""},{"location":"single_page/#creating-and-connecting-to-an-azure-iot-device","title":"Creating and Connecting to an Azure IoT Device","text":"

      One of the key features of the DataLogger IoT is it's simplified access to IoT service providers. This document outlines how an Azure IoT device is used by the DataLogger IoT.

      Image Courtesy of Microsoft Azure

      The following is covered by this document:

      • Device creation Azure
      • Securely connecting the device
      • How data is posted from the DataLogger IoT to the Azure Device

      Currently, the Azure IoT device connection is a single direction - it is used to post data from the hardware to the Azure IoT Device. Configuration information from Azure IoT to the DataLogger IoT is currently not implemented.

      "},{"location":"single_page/#general-operation_3","title":"General Operation","text":"

      Azure IoT enables connectivity between an IoT / Edge device and the Azure Cloud Platform, implementing secure endpoints and device models within the Azure infrastructure. This infrastructure allows edge devices to post updates, status and state to the Azure infrastructure for analytics, monitoring and reporting.

      In Azure IoT, an virtual representation of an actual device is created and referred to as a Device. The virtual device is allocated a connection endpoint, security certificates and a device digital twin - a JSON document used to persist, communicate and manage device state within Azure. Unlike AWS IoT, data from the device isn't posted to the devices digital twin (AWS Shadow), but to the device directly.

      The actual IoT device communicates with it's Azure representation via a secure MQTT connection, posting JSON document payloads to a set of pre-defined topics. Updates are posted directly to the Azure device, which is then accessed within Azure for further process as defined by the users particular cloud implementation.

      "},{"location":"single_page/#creating-a-device-in-azure-iot","title":"Creating a Device in Azure IoT","text":"

      The following discussion outlines the basic steps taken to create a Device in Azure IoT that the DataLogger IoT can connect to. First step is to log into your Azure account and create an IoT Hub for your device.

      Click Here to Log into Microsoft Azure

      Once logged into your Microsoft Azure account, select Internet of Things > IoT Hub from the menu of services.

      "},{"location":"single_page/#create-an-iot-hub","title":"Create an IoT Hub","text":"

      This IoT Hub page lists all the IoT hubs available for your account. To add a device, you need to create a new IoT Hub.

      Follow the Hub Creation workflow - key settings used for a DataLogger demo device:

      • Used the \"Free Tier\" for testing and development.
      • Networking
        • Connectivity - Public Access
        • Minimum TLS Version - 1.0

      The remaining settings were set at their default values.

      "},{"location":"single_page/#create-a-device","title":"Create a Device","text":"

      Once the IoT Hub is created, a Device needs to be created within the hub. The device represents the connection to the actual DataLogger IoT device.

      To create a device, select the Device management > Devices from the IoT Hub menu and the select the + Add Device menu item

      In the create device dialog:

      • Enter a name for the device
      • Select an Authentication type of Symmetric key
      • Auto-generate keys enabled

      Once created, the device is listed in the Devices list of the IoT Hub. Selecting the device gives you the device ID and keys used to communicate with the device. Note, when connecting to the device with the DataLogger IoT, the Primary Key value is used.

      "},{"location":"single_page/#azure-configuration","title":"Azure Configuration","text":"

      Once the DataLogger IoT is integrated into the application, the specifics for the Azure IoT Thing must be configured. This includes the following:

      • Server Name/Hostname
      • Device Key
      • Device ID
      • CA Certificate Chain
      "},{"location":"single_page/#server-namehostname_2","title":"Server Name/Hostname","text":"

      This value is hostname of the created IoT Hub and is obtained from the Overview page of the IoT Hub. Note a secure connection is used, so the port for the connection is 8883.

      "},{"location":"single_page/#device-id","title":"Device ID","text":"

      The Device ID is obtained from the device detail page. This page is accessible via the Device listing page, which is accessed via the Device management > Devices menu item. The selected device of interest (TestDevice2023 for this example) provides the device ID and Primary Key.

      "},{"location":"single_page/#device-primary-key","title":"Device Primary Key","text":"

      This is obtained via the Device details page, as outlined in the previous section.

      Note

      You view and copy the key via the icons on the right of the key entry line.

      "},{"location":"single_page/#root-certificate-authority-ca-file","title":"Root Certificate Authority - CA file","text":"

      The Certificate Authority file for Azure is downloaded from this page:

      Microsoft: Azure Certificate Authority details

      The file to download is the Baltimore CyberTrust Root entry in the Root Certificate Authorities section of the page.

      "},{"location":"single_page/#setting-properties_2","title":"Setting Properties","text":"

      The above property values must be set on the DataLogger IoT before use. They can be set manually by using the menu system like the previous MQTT example.

      For users that are interested in using the menu system, you will need to open a Serial Terminal, connect to the COM port that your DataLogger enumerated to, and set it to 115200 baud. In this case, we connected to COM13. Press any key to enter the Main Menu. Type 1 to enter the Settings menu. Then type 13 to enter the Azure IoT Menu. When the menu system for the Azure IoT connection is presented, you will need to configure the property values as listed in the JSON file. Saving the values through the menu system will save the credentials to the ESP32's persistent memory. The following options are displayed:

      The alternative to using the menu system is a JSON file. These values can be set using a JSON file that is loaded by the system at startup. For the example outlined in this document, the entries in the settings JSON file are as follows:

      \"Azure IoT\": {\n    \"Enabled\": true,\n    \"Port\": 8883,\n    \"Server\": \"sparkfun-datalogger-hub.azure-devices.net\",\n    \"MQTT Topic\": \"\",\n    \"Client Name\": \"\",\n    \"Buffer Size\": 0,\n    \"Username\": \"\",\n    \"Password\": \"\",\n    \"Device Key\" : \"My-Super-Secret-Device-Key\",\n    \"Device ID\"  : \"TestDevice2023\",\n    \"CA Cert Filename\": \"AzureRootCA.pem\"\n  },\n

      Besides updating the Server, Device Key, Device ID, and CA Cert Filename, you will need to also ensure that the port is set to 8883. The default in previous firmware versions was 1883. As of firmware v01.00.04, the default is 8883. You will need to adjust the port value to properly connect to the Azure IoT service. Don't forget to enable Azure IoT service by setting the value to true. If the JSON file is saved in the microSD card, you can load the credentials to the DataLogger IoT.

      Tip

      To load the values by the system at startup using a JSON file and microSD card, you will need to configure the Save Settings. This JSON file will be created with the \"Save to Fallback\" option. Make sure to enable the Azure IoT as well.

      "},{"location":"single_page/#operation-and-monitoring","title":"Operation and Monitoring","text":"

      Once the DataLogger IoT device is configured and running, the Azure IoT capability in the DataLogger IoT posts messages via MQTT to the connected Azure Device via it's IoT Hub. Messages to the device are posted as Telemetry Data for the device.

      The easiest method to view the Telemetry data being sent to an Azure Iot Device is via the Azure IoT Hub extension for the Visual Studio Code editor.

      Once installed, and connected to Azure via the Azure Account extension, you can connect to the target IoT Hub, and monitor telemetry data for a IoT device.

      "},{"location":"single_page/#connect-to-your-azure-iot-hub","title":"Connect to Your Azure IoT Hub","text":"

      On the Explorer panel of Visual Studio Code, click on the ... menu of the AZURE IOT HUB section. In the popup menu, select the Select IoT Hub menu entry.

      The available IoT Hubs are displayed in the editors command prompt. Select the desired hub and press Enter (or click).

      The hub is then displayed in the AZURE IOT HUB section of the editor Explorer. Expanding the Devices section of the Hub will list the example device created above.

      "},{"location":"single_page/#monitoring","title":"Monitoring","text":"

      To monitor the telemetry data send to a device, right click on the device, TestDevice2023 in this example, select the menu entry Start Monitoring Build-in Event Endpoint.

      Once selected, the editor output console will start displaying output for the selected device. For the above example, with a device that has environmental sensors attached, the output appears as follows:

      To stop monitoring, click the Stop Monitoring build-in event endpoint item that is displayed in the status bar of the editor.

      A menu option to stop monitoring is also available from the ... menu of the AZURE IOT HUB section in the editor Explorer panel.

      "},{"location":"single_page/#example-http","title":"Example - HTTP","text":""},{"location":"single_page/#connecting-and-sending-output-to-an-http-server","title":"Connecting and Sending Output to an HTTP Server","text":"

      One of the key features of the DataLogger IoT is it's simplified access to IoT service providers and servers. This document outlines how output from a DataLogger IoT device is sent to an HTTP server.

      The following is covered by this document:

      • Overview of the HTTP connection
      • How a user configures and uses the HTTP connection
      • Use examples
      "},{"location":"single_page/#general-operation_4","title":"General Operation","text":"

      HTTP connectivity allows data generated by the DataLogger IoT to be sent to an HTTP server. An HTTP endpoint is provided to the HTTP action within the DataLogger IoT, and when data is output, a JSON representation of the data is published to the endpoint via an HTTP POST operation. The body of the POST operation contains the a JSON document that encapsulates the sent DataLogger IoT data.

      "},{"location":"single_page/#data-structure_2","title":"Data Structure","text":"

      Data is sent to the HTTP server as a JSON object, which contains a collection of sub-object. Each sub-object represents a data source in the sensor, and contains the current readings from that source.

      The following is an example of the data posted - note, this representation was \"pretty printed\" for readability.

      {\n  \"MAX17048\": {\n    \"Voltage (V)\": 4.304999828,\n    \"State Of Charge (%)\": 115.0625,\n    \"Change Rate (%/hr)\": 0\n  },\n  \"CCS811\": {\n    \"CO2\": 620,\n    \"VOC\": 33\n  },\n  \"BME280\": {\n    \"Humidity\": 25.03613281,\n    \"TemperatureF\": 79.64599609,\n    \"TemperatureC\": 26.46999931,\n    \"Pressure\": 85280.23438,\n    \"AltitudeM\": 1430.44104,\n    \"AltitudeF\": 4693.04834\n  },\n  \"ISM330\": {\n    \"Accel X (milli-g)\": -53.31399918,\n    \"Accel Y (milli-g)\": -34.03800201,\n    \"Accel Z (milli-g)\": 1017.236023,\n    \"Gyro X (milli-dps)\": 542.5,\n    \"Gyro Y (milli-dps)\": -1120,\n    \"Gyro Z (milli-dps)\": 262.5,\n    \"Temperature (C)\": 26\n  },\n  \"MMC5983\": {\n    \"X Field (Gauss)\": -0.200622559,\n    \"Y Field (Gauss)\": 0.076416016,\n    \"Z Field (Gauss)\": 0.447570801,\n    \"Temperature (C)\": 29\n  }\n}\n
      "},{"location":"single_page/#http-connection-setup","title":"HTTP Connection Setup","text":"

      To connect to an HTTP server endpoint, the following information is needed:

      • The URL of the endpoint
      • The SSL certificate for the target server, if the connection is secure (HTTPS)

      These values are set using the standard DataLogger methods - the interactive menu system, or a JSON file.

      "},{"location":"single_page/#menu-system","title":"Menu System","text":"

      For users that are interested in using the menu system, you will need to open a Serial Terminal, connect to the COM port that your DataLogger enumerated to, and set it to 115200 baud. In this case, we connected to COM13. Press any key to enter the Main Menu. Type 1 to enter the Settings menu. Then type 14 to enter the HTTP IoT Menu. When the menu system for the HTTP IoT connection is presented, you will need to configure the property values as listed in the JSON file. Saving the values through the menu system will save the credentials to the ESP32's persistent memory. The following options are displayed:

      The options are:

      • Enable/Disable the connection
      • Set the URL for the endpoint
      • Set the name of the CA Cert file for a secure connection (HTTP)

      To set the HTTP URL/endpoint - select two (2) in the menu, and enter the URL. For this example, we'll enter: http://mysparkfunexample.com:8091 .

      In the above example, the URL/HTTP Endpoint is on a server called mysparkfunexample.com, on port 8091. Once set, the system will post data to this URL.

      If the endpoint is a secure ssl (HTTPS) connection, the certificate for the server is required. Because of the size of the certificates, the value is provided as a file that is loaded into the system by the attached SD card.

      The above example show providing a certificate filename of example.cer.

      Once all these values are set, the system will post data to the specified HTTP endpoint, following the JSON information structure noted earlier in this document.

      "},{"location":"single_page/#json-file-entries_1","title":"JSON File Entries","text":"

      If a JSON file is being used as an option to import settings into the DataLogger IoT, the following entries are used for the HTTP IoT connection:

      \"HTTP IoT\": {\n    \"Enabled\": false,\n    \"URL\": \"<the URL>\",\n    \"CA Cert Filename\": \"<certificate filename>\"\n  }\n

      Where:

      • Enabled - Set to true to enable the connection.
      • URL - Set to the URL for the connection.
      • CA Cert Filename - Set to the cert filename on the SD card if being used.

      If the JSON file is saved in the microSD card, you can load the credentials to the DataLogger IoT.

      Tip

      To load the values by the system at startup using a JSON file and microSD card, you will need to configure the Save Settings. This JSON file will be created with the \"Save to Fallback\" option. Make sure to enable the HTTP IoT as well.

      "},{"location":"single_page/#example-connecting-to-a-http-server","title":"Example - Connecting to a HTTP Server","text":"

      In this example, a simple HTTP Server is creating using Node JS, and the HTTP connection in the DataLogger IoT is used to post data to this server. The received data is output to the console from there server.

      "},{"location":"single_page/#the-server","title":"The Server","text":"

      The following javascript/node code creates a HTTP server on port 8090, and outputs received data to the console.

      var http = require('http');\n\n// Setup the endpoint server\nvar myServer = http.createServer(function (req, res) {\n\n  // Initialize our body string\n  var body=\"\";\n\n  // on data callback, append chunk to our body string\n  req.on('data', function(chunk){\n    body += chunk;\n  });\n\n  // On end callback, output the body to the console\n  req.on('end', function(){\n    // parse json string, then stringify it back for 'pretty printing'\n    console.log(\"payload: \" + JSON.stringify(JSON.parse(body),null,2));\n  });\n\n  // send a reply\n  res.writeHead(200, {'Content-Type': 'text/plain'});\n  res.end('n');\n  // Just listen on our port\n}).listen(8090);\n

      The setup and use of node js is system dependant is beyond the scope of this document. However, Node JS is easily installed with your systems package manager (brew on macOS, Linux distribution package manager (apt, yum, ...etc), on Windows, the WSL is recommended).

      Once Node is setup, the above server is run via the following command (assuming the implementation is in a file called simple_http.js):

      node ./simple_http.js\n

      As data is sent by the DataLogger IoT, the following is output to the console from the server:

      "},{"location":"single_page/#obtaining-a-sites-security-certificate","title":"Obtaining a Sites Security Certificate","text":"

      Accessing a sites SSL/Secure Certificate is done via a web browser. The method for each browser is different. The following example uses Edge, which is similar to the operation in Chrome.

      First, browse to the desired site/server. Click the Secure/Security area/button next to the URL to bring up the security detail page. On this page, select the Connection is secure menu option

      Next, on the page shown, select the certificate button on the upper right of the dialog.

      When you select this button, the certificate details dialog is displayed. On this page, select the Details tab, and select the Export... button on the lower right of the dialog. This will save the sites SSL/Security certificate to a location you specify.

      Once saved, place this file on the SD card your system/DataLogger is using, and set the filename in the HTTP connection menu or settings JSON file.

      "},{"location":"single_page/#example-arduino-cloud","title":"Example - Arduino Cloud","text":"

      Arduino

      At the time of writing, Arduino's IoT service was referred to as the \"Arduino IoT Cloud.\" Arduino updated the service with a different UI and is now referring to the service as the \"Arduino Cloud\".\" When referencing the Arduino IoT or Arduino IoT Cloud in this tutorial, we are referring to the Arduino Cloud.

      "},{"location":"single_page/#creating-and-connecting-to-an-arduino-cloud-device","title":"Creating and Connecting to an Arduino Cloud Device","text":"

      One of the key features of the SparkFun DataLogger IoT is it's simplified access to IoT service providers. This document outlines how an Arduino Cloud Device is used by the DataLogger IoT.

      The following is covered by this document:

      • Structure of the Arduino Cloud Devices
      • Device creation in the Arduino Cloud
      • Setup of the Arduino Driver
      • How data is posted from the DataLogger IoT to the Arduino Device

      Currently, the Arduino Cloud device connection is a single direction - used to post data from the DataLogger IoT to an Arduino Cloud device.

      Image Courtesy of Arduino

      Note

      To take advantage of the API's in Arduino Cloud, you will also need to have a service plan with your account.

      "},{"location":"single_page/#general-operation_5","title":"General Operation","text":"

      The Arduino Cloud enables connectivity between an IoT/Edge Arduino enabled device and the cloud. The edge device updates data in the Arduino Cloud by updating variables or parameters attached to a cloud device.

      In the Arduino Cloud, the edge device is represented by a Device which has a virtual Thing attached/associated with it. The Thing acts as a container for a list of parameters or variables which represent the data values received from the edge device. As values on the edge device update, they are transmitted to the Arduino Cloud.

      For a SparkFun DataLogger IoT connected to an Arduino Cloud device, the output parameters of a device connected to the DataLogger are mapped to variables within the Arduino Cloud Device's Thing using a simple pattern of DeviceName_ParameterName for the name of the variable in the Arduino Cloud.

      "},{"location":"single_page/#creating-a-device-in-arduino-cloud","title":"Creating a Device in Arduino Cloud","text":"

      The first step connecting to the Arduino Cloud is setting up a device within the cloud. A device is a logical element that represents a physical device.

      Log into your Arduino Cloud account.

      Click Here to Log into Arduino Cloud

      Click on the expand menu icon on the upper left (e.g. the three lines stacked like a \"hamburger\"; \u2630) and select Devices. If your window is big enough, then it will show up on the navigation bar.

      This page lists your currently defined devices. If there are no defined devices, select the Add Device button. This will probably be at the bottom of the page. The location of this button will change once the page has a device (or if there is an update to Arduino's user interface).

      A device type selection dialog is then shown. Since we are connecting a DataLogger IoT board to the system, and not connected a known device, select DIY - Any Device to manually include the DataLogger IoT.

      Once selected, another dialog is presented. Just select Continue. At this point you can provide a name for your device. In this case we named it as \"MyDataLoggerIoT.\"

      The next screen is the critical step of the device creation process. This step is the one time the Device Secret Key is available. The provided Device ID and Device Secret Key values are needed to connect to the Arduino Cloud. Once this step is completed, the Secret Key is no longer available.

      The easiest way to capture these values is by downloading as a PDF file, which is offered on the setup page. Click on the download the PDF and save it to a safe location. When ready, click on the check box indicating that you have saved the values and select the Continue button.

      "},{"location":"single_page/#arduino-cloud-api-keys","title":"Arduino Cloud API Keys","text":"

      In addition to creating a device, to access the Arduino Cloud, the driver requires an API Key. This allows the DataLogger IoT's Arduino Cloud driver to access the web API of the Arduino Cloud. This API is used to setup the connection to the Arduino Cloud.

      To create an API key, click on the menu bar to expand and select your Arduino account profile > Personal Settings.

      This menu takes you to a list of existing API Keys. If you have not created one yet, the list will have nothing in it like the image below. From this page, select the CREATE API KEY button.

      Note

      Users will need a service plan in order to take advantage of the API.

      In the presented dialog, enter a name for the API key. In this case, we named it \"MyDataLoggerKey\".

      Once the name is entered, click CONTINUE. A page with the new API key is presented. Like in Device Creation, this page contains a secret that is only available on this page during this process.

      Make note of the Client ID and Client Secret values on this page. The best method to capture these values is to download the PDF file offered on this page. Click on the download the PDF and save it to a safe location. When ready, click on the check box indicating that you have saved the values and select the DONE button.

      At this point, the Arduino Cloud is setup for connection by the driver.

      "},{"location":"single_page/#arduino-cloud-configuration","title":"Arduino Cloud Configuration","text":"

      To add an Arduino Cloud Device as a destination DataLogger IoT, the Arduino Cloud connection is enabled via the DataLogger menu system and the connection values, obtained from the Arduino Cloud (see above), are set in the connection properties.

      The specifics for the Arduino Cloud must be configured. This includes the following:

      • Thing Name
      • Thing ID
      • API Client ID
      • API Secret
      • Device Secret
      • Device ID

      Note

      The Thing Name does not necessarily need to be configured. However, there will be less confusion if you set this up before connecting the DataLogger IoT to the Cloud. The Thing ID will automatically be generated and saved once there is a connection available.

      "},{"location":"single_page/#thing-name","title":"Thing Name","text":"

      The name of the Arduino Cloud Thing to use. If the Thing doesn't exist on startup, the driver will create a Thing and be named \"Untitled\" if you do not provide a name.

      Note

      Note satisfied with the default \"Untitled\" as the Thing's name? You can rename the Thing Name after creating the Thing. Note that you will need to manually rename the Thing Name on the Arduino Cloud and DataLogger IoT.

      "},{"location":"single_page/#thing-id","title":"Thing ID","text":"

      This is the ID of the Thing being used. This value is obtained by the following methods:

      • If the driver creates a new Thing, the ID is obtained and used.
      • If an existing Thing is connected to the DataLogger IoT, the driver retrieves it's ID.

      Note

      In this case, the driver cannot create any new variables until the system is restarted.

      • The user creates a new Thing using the web interface of Arduino Cloud, and provides the Thing Name and Thing ID .
      "},{"location":"single_page/#api-client-id-and-secret","title":"API Client ID and Secret","text":"

      These values are used to provide API access by the driver. This access allows for the creation/use of a Thing and Variables within the Arduino Cloud. These are obtained via the steps outlined earlier in this document.

      "},{"location":"single_page/#device-secret-and-id","title":"Device Secret and ID","text":"

      These values are used to identify the Arduino device that is connected to. These are obtained via the steps outlined earlier in this document.

      "},{"location":"single_page/#setting-properties_3","title":"Setting Properties","text":"

      The above property values must be set in the DataLogger's Arduino Cloud driver before use. They can be manually by using the menu system like the previous MQTT example.

      For users that are interested in using the menu system, open a Serial Terminal, connect to the COM port that your DataLogger enumerated to, and set it to 115200 baud. In this case, we connected to COM13. Press any key to enter the Main Menu. Type 1 to enter the Settings menu. Then type 16 to enter the Arduino IoT Menu. When the menu system for the Arduino IoT is presented, you will need to configure the property values as listed in the JSON file. Saving the values through the menu system will save the credentials to the ESP32's persistent memory. The following options are displayed:

      The alternative to using the menu system is a JSON file. These values can be set using a JSON file that is loaded by the system at startup. For the DataLogger Arduino Cloud example outlined in this document, the entries in the setting's JSON file are as follows:

      \"Arduino IoT\": {\n    \"Enabled\": true,\n    \"Thing Name\": \"SparkFunThing1\",\n    \"API Client ID\": \"MY_API_ID\",\n    \"API Secret\": \"MY_API_SECRET\",\n    \"Device Secret\": \"MY_DEVICE_SECRET\",\n    \"Device ID\": \"MY_DEVICE_ID\"            \n  },\n

      You will need to update the API Client ID, API Secret, Device Secret, and Device ID with the values that were obtained earlier. Don't forget to enable Arduino Cloud service by setting the value to true. If the JSON file is saved in the microSD card, you will need to load the credentials to the DataLogger IoT.

      Tip

      To load the values by the system at startup using a JSON file and microSD card, you will need to configure the Save Settings. This JSON file will be created with the \"Save to Fallback\" option. Make sure to enable the Arduino IoT as well.

      "},{"location":"single_page/#operation_1","title":"Operation","text":"

      On startup, when the first values are written from the DataLogger IoT, the connection to the Arduino Cloud is made. During this connection, the system connects to the specified Thing and variables are mapped between the DataLogger Device values and Arduino Cloud Variables. If needed, variables can be created manually in the cloud.

      While this initial setup takes seconds to complete, updates to the values on the Arduino Cloud are rapid as soon as there is a connection available.

      "},{"location":"single_page/#viewing-values","title":"Viewing Values","text":"

      Once the DataLogger IoT device is configured and running, updates in Arduino Cloud are listed in the Things tab of the Arduino Cloud page. Clicking the target Thing provides access to the current variable values that are connected to the DataLogger IoT. Your mileage may vary depending on the compatible device that is connected to the DataLogger IoT. In this case, we were able to see the built-in sensors that were connected on the DataLogger IoT - 9DoF.

      Note

      Not seeing certain variables on your list? Check your connections to make sure that the compatible device is connected to the DataLogger IoT. You may also have certain outputs disabled (like the connected sensors or timestamp).

      Note

      Having problems connecting new variables with the DataLogger IoT? When swapping out compatible Qwiic enabled devices, you may need to delete previous cloud variables so that the DataLogger IoT is able re-initialize them on the next power cycle.

      "},{"location":"single_page/#create-a-dashboard","title":"Create a Dashboard","text":"

      With the data now available in the Arduino Cloud as variables, it is a simple step create a dashboard that plots the data values.

      The general steps to create a simple dashboard include:

      • Select the Dashboards section of the Arduino Cloud.
      • Select the Build Dashboard button. If you have a dashboard already built, the location of the button will change and the button will be renamed: Create.
      • Click the edit button (i.e. the icon that looks like a paper and pencil, this is next to the eye).
      • Add an element to the dashboard -- for this example select ADD ^ > Advanced Chart.
      • On the Chart's Widget Settings select Link Variables to add readings.
      • The DataLogger IoT Variables are listed - select the variable to link.
      • Continue this step until all the desired variables are linked to the chart. You can select up to 5x variables at a time. Click on the Link Variables button after selecting the variables.
      • This will bring you back to the Chart's Widget Settings window. Configure any preferences that to display (i.e. variable colors, labels, etc.). When all variables are linked and the Chart Widget Settings is configured, select Done.

      The created dashboard then displays the values posted from the SparkFun DataLogger IoT. You can continue adding additional readings on the dashboard that you were not able to fit on graph or even rename the Dashboard view. In this case, we displayed accelerometer values and temperature in degrees Celsius from the DataLogger IoT - 9DoF.

      Note

      Not seeing any values on the LIVE view? Try clicking on the other time periods to see the values posted.

      Using compatible Qwiic enabled devices, you can also display additional readings that are not available with the built-in sensors. In this case, we were able to display humidity, temperature in degrees Fahrenheit, equivalent CO2, TVOC, and AQI with the DataLogger IoT and Environmental Combo Breakout (ENS160/BME280).

      "},{"location":"single_page/#viewing-and-downloading-log-files-using-the-iot-web-server","title":"Viewing and Downloading Log Files using the IoT Web Server","text":"

      As of firmware v01.02.00, log files can be viewed and downloaded over a WiFi network! This saves you time by allowing you to download the files without the need to disconnect the DataLogger IoT and manually remove microSD card.

      The following is covered by this document:

      • How a user configures and uses the HTTP connection
      • Use examples
      "},{"location":"single_page/#iot-web-server-connection-setup","title":"IoT Web Server Connection Setup","text":"

      To connect to the ESP32's IoT Web Server, the following information is needed:

      • The server name/address
      • [optional] A username - if required
      • [optional] A password - if required
      "},{"location":"single_page/#iot-web-server-menu-system","title":"IoT Web Server Menu System","text":"

      We'll need to adjust the settings for the IoT Web Server.

      For users that are interested in using the menu system, open a Serial Terminal, connect to the COM port that your DataLogger enumerated to, and set it to 115200 baud. In this case, we connected to COM13. Press any key to enter the Main Menu. Type 1 to enter the Settings menu. Then type 17 to enter the IoT Web Server Menu. When the menu system for the IoT Web Server is presented, the following options are displayed:

      The options are:

      • Enable/Disable the connection
      • Username
      • Password
      • Enable/Disable mDNS support
      • mDNS name

      At a minimum, you will just need to enable the connection. However, we recommend enabling mDNS support if it is supported in your network.

      Once all these values are set, the system will serve the log files in your local 2.4GHz WiFi network following the JSON information structure noted below in this document.

      "},{"location":"single_page/#json-file-entries_2","title":"JSON File Entries","text":"

      If a JSON file is being used as an option to import settings into the DataLogger IoT, the following entries are used for the IoT web server:

      \"IoT Web Server\": {\n  \"Enabled\": false,\n  \"Username\": \"\",\n  \"Password\": \"\",\n  \"mDNS Support\": false,\n  \"mDNS Name\": \"dataloggerAD6B8\"\n},\n

      Where:

      • Enabled - Set to true to enable the connection.
      • Username - Web server user name if being used.
      • Password - Web server password if being used.
      • mDNS Support - Set to true if multicast DNS is supported. This allows you to enter the address as \"http://dataloggerXXXXX.local\" (where XXXXX is generated from the last 5x characters from your board ID) rather than typing the exact IP address of the ESP32.
      • mDNS Name - Multicast DNS name. In this case, the default name was set to dataloggerAD6B8. This name will be different depending on your DataLogger IoT's board ID so AD6B8 will be different for your board.

      Tip

      To load the values by the system at startup using a JSON file and microSD card, you will need to configure the Save Settings. This JSON file will be created with the \"Save to Fallback\" option. Make sure to enable the MQTT Client as well.

      "},{"location":"single_page/#connect-and-download-log-file","title":"Connect and Download Log File","text":"

      Note

      You will need to make sure that the ESP32 is on the same network as your computer in order to access the log files.

      Note

      When authentication is enabled, some browsers might require a second login depending on user settings.

      Once the web server is enabled and the settings are saved, you will need to reboot the DataLogger IoT. As the DatLogger initializes, it will connect to your WiFi Network. Take note of the mDNS address, in this case, it was \"http://dataloggerAD6B8.local\".

      Once the DataLogger IoT has finished initializing, open web browser. Connect the DataLogger IoT by entering the address \"http://dataloggerXXXXX.local\", where XXXX is the last 5x characters of your board ID. You will be presented with the log files available on the microSD card. Click on a log file to download and save it to your computer.

      Note

      If mDNS is not supported, you can also enter the IP address of the Datalogger IoT into a web browser to view and download the log files. You can view the IP address when the DataLogger IoT is initializing. If you have administrative privileges to the WiFi Network, you can also view the IP address through your WiFi router as well.

      Now that you have downloaded the log files, try graphing the data on a spreadsheet!

      "},{"location":"single_page/#example-how-to-convert-comma-separated-values-csv-to-a-spreadsheet","title":"Example - How to Convert Comma Separated Values (CSV) to a Spreadsheet","text":"

      The DataLogger IoT is great at displaying real time data with an IoT service whenever there is an Internet connection available. For those that want to use the DataLogger IoT without a WiFi connection and/or you just want to save data to a microSD card, you can import the comma separated values (CSV) from the text file into a spreadsheet to graph the values.

      There are a few spreadsheet programs available that can take text files with CSV but for the scope of this tutorial, we will be using Google Sheets\u2122 to convert the CSV output to a graph.

      Image Courtesy of Google Sheets.

      Note

      Google and Google Sheets are trademarks of Google LLC. This tutorial is not endorsed by or affiliated with Google in any way. We just thought it was a sweet tool to visualize all the data that was collected by your snazzy DataLogger IoT. \ud83d\ude09

      "},{"location":"single_page/#log-some-data","title":"Log Some Data!","text":"

      At this point, we will assume that you have configured connected your devices to the DataLogger IoT and configured its settings. Insert the microSD card into it's socket. Power the DataLogger IoT up and start logging some data! In this case, we were using the DataLogger IoT using the Qwiic Environmental Combo Breakout - ENS160/BME280. of course, you could have other compatible Qwiic-enabled devices connected depending on your setup. For simplicity, a WiFi connection was used to synchronize the clock to the NTP server and a computer's USB port was used to power everything.

      Tip

      For users without an Internet connection to sync the clock to the NTP server, you may want to consider using a compatible Qwiic-enabled device like the Qwiic Real Time Clock (RTC) Module - RV-8803 or a Qwiic-enabled u-blox GNSS module. Note that you will need to configure the time to your area before logging any data. U-blox GNSS modules would also need to be able to view a few satellites before being able to synchronize to the UTC.

      Note

      For users that require a timestamp with their datasets, make sure to enable timestamp.

      "},{"location":"single_page/#download-the-log-files","title":"Download the Log Files","text":"

      Users can download the log files to your computer with the IoT Web Server. You will need to update firmware to v01.02.00 and enable this feature. For more information, check out the previous example to view and download log files using the IoT web server.

      Examples: Viewing and Downloading Log Files using the IoT Web Server

      Of course, users can follow the old school method and manually grab the files using a microSD card reader. When ready, remove power from the DataLogger IoT and eject the microSD card from the socket. Insert the microSD card into an adapter and connect to your computer.

      "},{"location":"single_page/#importing-csv-to-a-spreadsheet","title":"Importing CSV to a Spreadsheet","text":"

      Log into your Google account and open Google Sheets to create a new spreadsheet.

      Click Here to Create a New Google Spreadsheet

      Head to the menu and select: File > Import.

      A window will pop up with some options to import a file. Click the Upload tab. Click on the Browse button to choose the file. Or drag and drop the file into the upload area. In this case, the DataLogger IoT saved the comma separated values to a text file called sfe0003.txt.

      Note

      Not seeing any data in the file or even a text file saved in the root directory? Make sure that the microSD card is formatted correctly and the DataLogger is configured properly. In the menu, make sure to have the SD Card Format enabled and set to the correct format. In this case, we are using the default CSV format.

      Another window will pop up asking how to import the file. From the drop down menu, select: Import location > Create new spreadsheet and Separator Type > Detect automatically. Since the file will include commas to separate each reading, Google Sheets should automatically separate text and values into each cell. Otherwise, you can select comma as the separator type.

      Note

      If you have the file open, you can also manually paste the CSV data into the spreadsheet. Select all the contents of the text file and copy the contents onto your clipboard. Right click the cell closest to the top and farthest to the left of the spreadsheet (i.e. A1). Then paste the data. One caveat is that Google Sheets may have problems where it only pastes the title of each column.

      If you see this happen, you will need to select all but the header row from the text file. Then copy the contents onto your clipboard again. Right click on the next row the titles (i.e. A2) and paste the data.

      Tip

      To separate the values to each column, highlight everything in the column. Then head to the menu and select: Data > Split text into columns

      "},{"location":"single_page/#graphing-your-datasets","title":"Graphing Your Datasets","text":"

      Hold down the Shift button on your keyboard and select the columns that you would like to graph using your mouse. Once the data is highlighted, head to the menu and select: Insert > Chart.

      The values that were selected will be graphed. You will want to be careful about including too many datasets on the graph as it can be hard to read when they are not in the same range.

      At this point, try formatting the data based on your preferences and export the graph. The graph below was formatted and exported to a PNG. Note that the values for the AQI were moved to the right of the graph for a better viewing since they were smaller than the datasets for TVOC and eCO2.

      Note

      There are additional features to help format your data based on your personal preferences! Select the column that you would like to format. Then head to the menu: Format > Number. Select the format that you would like to apply to the dataset. In this case, we adjusted the General Time with Custom Date and Time to show a 12-hour format before creating a new graph.

      "},{"location":"single_page/#appendix-supported-qwiic-devices","title":"Appendix - Supported Qwiic Devices","text":""},{"location":"single_page/#datalogger-iot-supported-devices","title":"DataLogger IoT Supported Devices","text":"

      The following lists the devices/boards supported by the DataLogger IoT boards. New drivers to support a device will be listed under the firmware version.

      "},{"location":"single_page/#version-010100","title":"Version 01.01.00","text":"
      • SparkFun Indoor Air Quality Sensor - ENS160 (Qwiic)
      • SparkFun Photoacoustic Spectroscopy CO2 Sensor - PASCO2V01 (Qwiic)
      • SparkFun Human Presence and Motion Sensor - STHS34PF80 (Qwiic)
      • SparkFun Tristimulus Color Sensor - OPT4048DTSR
      • SparkFun Triad Spectroscopy Sensor - AS7265x
      "},{"location":"single_page/#version-010000","title":"Version 01.00.00","text":"
      • ACS37800 Power Meter
      • SparkFun Qwiic 12 Bit ADC - 4 Channel (ADS1015)
      • Qwiic PT100 - ADS122C04
      • Qwiic Humidity AHT20
      • SparkFun Grid-EYE Infrared Array Breakout - AMG8833 (Qwiic)
      • SparkFun Pulse Oximeter and Heart Rate Sensor - MAX30101 & MAX32664 (Qwiic)
      • SparkFun Atmospheric Sensor Breakout - BME280 (Qwiic)
      • SparkFun Environmental Combo Breakout - CCS811/BME280 (Qwiic)
      • SparkFun Environmental Sensor Breakout - BME680 (Qwiic)
      • SparkFun Environmental Sensor - BME688 (Qwiic)
      • SparkFun Pressure Sensor - BMP384 (Qwiic)
      • SparkFun Pressure Sensor - BMP581 (Qwiic)
      • SparkFun Qwiic Button
      • SparkFun Air Velocity Sensor Breakout - FS3000-1015 (Qwiic)
      • SparkFun Air Velocity Sensor Breakout - FS3000-1005 (Qwiic)
      • SparkFun GPS-RTK2 Board - ZED-F9P (Qwiic)
      • SparkFun GPS-RTK-SMA Breakout - ZED-F9P (Qwiic)
      • SparkFun GPS-RTK Board - NEO-M8P-2 (Qwiic)
      • SparkFun GPS Breakout - Chip Antenna, SAM-M8Q (Qwiic)
      • SparkFun GPS Breakout - NEO-M9N, Chip Antenna (Qwiic)
      • SparkFun GPS Breakout - ZOE-M8Q (Qwiic)
      • SparkFun 6DoF IMU Breakout - ISM330DHCX (Qwiic) and on-board SPI 9DOF
      • SparkFun 9DoF IMU Breakout - ISM330DHCX, MMC5983MA (Qwiic)
      • Qwiic Pressure Sensor - LPS25HB
      • Qwiic Fuel Gauge - MAX17048
      • SparkFun Qwiic Thermocouple Amplifier - MCP9600 (PCC Connector)
      • SparkFun Qwiic Thermocouple Amplifier - MCP9600 (Screw Terminals)
      • SparkFun Qwiic MicroPressure Sensor
      • SparkFun Micro Magnetometer - MMC5983MA (Qwiic) and on-board SPI 9DOF
      • Pressure Sensor (Qwiic) - MS5637
      • Qwiic Pressure/Humidity/Temp (PHT) Sensor - MS8607
      • SparkFun Qwiic Scale - NAU7802
      • SparkFun Real Time Clock Module - RV-8803 (Qwiic)
      • CO\u2082 Humidity and Temperature Sensor - SCD30
      • CO\u2082 Humidity and Temperature Sensor - SCD40 (Qwiic)
      • SparkX Differential Pressure Sensor - SDP31 (Qwiic)
      • Particle, VOC, Humidity, and Temperature Sensor - SEN54
      • SparkFun Air Quality Sensor - SGP30 (Qwiic)
      • SparkFun Air Quality Sensor - SGP40 (Qwiic)
      • SparkFun Humidity Sensor Breakout - SHTC3 (Qwiic)
      • SparkFun Qwiic Dynamic NFC/RFID Tag
      • CO\u2082 Sensor - STC31 (Qwiic)
      • SparkFun Qwiic dToF Imager - TMF8821
      • SparkFun Qwiic dToF Imager - TMF8820
      • SparkFun High Precision Temperature Sensor - TMP117 (Qwiic)
      • SparkFun Qwiic Twist - RGB Rotary Encoder Breakout
      • SparkFun Proximity Sensor Breakout - 20cm, VCNL4040 (Qwiic)
      • SparkFun UV Light Sensor Breakout - VEML6075 (Qwiic)
      • Ambient Light Sensor - VEML7700 (Qwiic)
      • SparkFun Distance Sensor Breakout - 4 Meter, VL53L1X (Qwiic)
      • SparkFun Qwiic ToF Imager - VL53L5CX
      "},{"location":"single_page/#troubleshooting","title":"Troubleshooting","text":"

      Note

      Not working as expected and need help?

      If you need technical assistance and more information on a product that is not working as you expected, we recommend heading on over to the SparkFun Technical Assistance page for some initial troubleshooting.

      SparkFun Technical Assistance Page

      If you don't find what you need there, the SparkFun Forums are a great place to find and ask for help. If this is your first visit, you'll need to create a Forum Account to search product forums and post questions.

      Create New Forum Account Log Into SparkFun Forums

      "},{"location":"single_page/#issues-connecting-to-iot-service","title":"Issues Connecting to IoT Service","text":"

      Having trouble connecting your DataLogger IoT to an IoT service? Make sure to check your credentials and ensure that the configuration matches the IoT Service (such as your WiFi network, port, server, topic, certificates, keys, etc. to name a few). Make sure to also include the associated certificates and keys in the microSD card as well. You may see an output similar to the following errors below.

      "},{"location":"single_page/#aws-iot-error","title":"AWS IoT Error","text":"

      The following error occurred when the DataLogger IoT was initializing with AWS.

      [W] AWS IoT disconnected - reconnecting.......[E] AWS IoT: MQTT connection failed. Error Code: -2\n

      In this case, the DataLogger IoT failed to connect to AWS IoT service because the port was using the default value that was saved: 1883. Ensure that the port is set to 8883 for your IoT service (e.g. AWS IoT, Azure, and ThingSpeak) and saved in persistent memory in order for the DataLogger IoT to successfully connect. As of firmware v01.00.04, the default is 8883.

      "},{"location":"single_page/#thinkspeak-iot-error","title":"ThinkSpeak IoT Error","text":"

      The following error occurred when the DataLogger IoT was initializing with ThingSpeak.

      [I] ThingSpeak MQTT: connecting to MQTT endpoint mqtt3.thingspeak.com:8883 .......[E] ThingSpeak MQTT: Connection Error [4]\n

      In this case, the DataLogger IoT failed to connect to ThingSpeak service because the credentials were entered incorrectly. Ensure that the and saved in persistent memory in order for the DataLogger IoT to successfully connect.

      "},{"location":"single_page/#arduino-cloud-error-1","title":"Arduino Cloud Error 1","text":"

      The following error was occurred when the DataLogger IoT was initializing with Arduino Cloud.

      [W] ArduinoIoT - Thing Name not provided\n.\n.\n.\n[I] Arduino IoT: setup variables...[E] ArduinoIoT HTTP communication error [401] - token request\n[E] Arduino IoT Cloud not available or account credentials incorrect\n

      In this case, the DataLogger IoT failed to connect to the Arduino Cloud service because the credentials were incorrect. Ensure that the credentials (i.e. API client ID, API secret, device secret, device ID) are entered correctly and saved in persistent memory in order for the DataLogger IoT to successfully connect.

      "},{"location":"single_page/#arduino-cloud-error-2","title":"Arduino Cloud Error 2","text":"

      The following error was occurred when the DataLogger IoT was initializing with Arduino Cloud.

      [I] Arduino IoT: setup variables...[E] Arduino IoT: return code=400, failed to decode request body with content type \"application/json\": uuid: incorrect UUID length 37 in string \"a111aaa1-1111-1111-1a1a-1a11111a1111\\r\"\n

      In this case, the DataLogger IoT failed to connect to the Arduino Cloud service because the credentials were incorrect. The string was supposed to be the device ID. When copying and pasting the device ID from a PDF that was generated with the Arduino Cloud, a carriage return (\\r) was also copied and entered in the serial terminal. By pasting the device ID into a text editor and then re-copying/pasting it into the serial terminal helped to ensure that the credentials were entered correctly.

      Note

      The device ID in this example was a randomly generated string. You will need to check to make sure that your device matches the one that the Arduino Cloud generated specifically for your account.

      "},{"location":"single_page/#arduino-cloud-error-3","title":"Arduino Cloud Error 3","text":"

      The following error was occurred when the DataLogger IoT was initializing with Arduino Cloud.

      [I] Arduino IoT: setup variables...ArduinoIoTCloudTCP::handle_ConnectMqttBroker could not connect to mqtts-up.iot.arduino.cc:8884\nArduinoIoTCloudTCP::handle_ConnectMqttBroker 1 connection attempt at tick time 301939\nArduinoIoTCloudTCP::handle_ConnectMqttBroker could not connect to mqtts-up.iot.arduino.cc:8884\nArduinoIoTCloudTCP::handle_ConnectMqttBroker 2 connection attempt at tick time 307420\n[E] Arduino IoT: No Arduino Thing ID provided. Enter ID, delete Thing (SparkFunThing1) on Cloud, or enter new Thing Name.\n

      In this case, the DataLogger IoT failed to connect to the Arduino Cloud service because there was already a Thing that was created. By deleting the Thing in the Arduino Cloud, the DataLogger IoT was able to automatically create another Thing and setup the variables.

      "},{"location":"single_page/#thingspeak-data-points-not-updating","title":"ThingSpeak Data Points Not Updating","text":"

      If your DataLogger IoT is connected to ThingSpeak but you do not see any data, ensure that the device name matches the Qwiic device that is connect to the DataLogger IoT. For example, the DataLogger IoT and Qwiic-enabled ENS160 was able to connect to ThingSpeak as shown in the image on the bottom left. However, there were no data points in any of the graphs as shown on ThingSpeak as shown in the image on the bottom right.

      If you head back into the configuration menu for the DataLogger's ThingSpeak channel, make sure that the matches the connected Qwiic device's name that was shown during initialization. In this case, the device that was loaded and detected was ENS160. Then add the channel ID before saving the system settings.

      Note

      Only one device can be loaded per channel! ThingSpeak is not able graph two different devices in the same channel.

      Head back to your ThingSpeak Channel to verify that data is being plotted on the graphs.

      "},{"location":"single_page/#u-blox-i2c-device-disappears-when-iot-datalogger-initializes","title":"U-Blox I2C Device Disappears when IoT DataLogger Initializes","text":"

      If you have issues where a u-blox device that is connected to the I2C port fails to connect a second time when the IoT DataLogger initializes, this is due to a bug in the firmware from an initial release. You may see an output similar to the following message and image shown below.

      [W] GNSS::isConnected no traffic seen (first attempt)\n

      If you see the following output and the IoT DataLogger not loading your u-blox device, you will need to update the firmware to v01.00.03 and above. For more information, make sure to check out the tutorial on updating your IoT DataLogger's firmware.

      "},{"location":"single_page/#resources","title":"Resources","text":"

      Now that you've successfully got your DataLogger IoT up and running, it's time to incorporate it into your own project! For more information, check out the resources below:

      • DataLogger IoT - 9DoF
        • Schematic (PDF)
        • Eagle Files (ZIP)
        • Board Dimensions (PNG)
        • Fritzing Part (FZPZ)
      • DataLogger IoT
        • Schematic (PDF)
        • Eagle Files (ZIP)
        • Board Dimensions (PNG)
        • Fritzing Part (FZPZ)
      • CH340 Drivers
      • Firmware
      • GitHub Hardware Repo
        • SparkFun DataLogger IoT - 9DoF
        • SparkFun DataLogger IoT
      • SFE Showcase
        • DataLogger IoT - 9DoF
        • DataLogger IoT

      Or check out these related blog posts.

      • IoT Platforms and Protocols

      • Extending the Reach of Data Logging

      • Send Sensor Data to AWS All In Under 15 Minutes

      "},{"location":"supported_devices/","title":"DataLogger IoT Supported Devices","text":"

      The following lists the devices/boards supported by the DataLogger IoT boards. New drivers to support a device will be listed under the firmware version.

      "},{"location":"supported_devices/#version-010100","title":"Version 01.01.00","text":"
      • SparkFun Indoor Air Quality Sensor - ENS160 (Qwiic)
      • SparkFun Photoacoustic Spectroscopy CO2 Sensor - PASCO2V01 (Qwiic)
      • SparkFun Human Presence and Motion Sensor - STHS34PF80 (Qwiic)
      • SparkFun Tristimulus Color Sensor - OPT4048DTSR
      • SparkFun Triad Spectroscopy Sensor - AS7265x
      "},{"location":"supported_devices/#version-010000","title":"Version 01.00.00","text":"
      • ACS37800 Power Meter
      • SparkFun Qwiic 12 Bit ADC - 4 Channel (ADS1015)
      • Qwiic PT100 - ADS122C04
      • Qwiic Humidity AHT20
      • SparkFun Grid-EYE Infrared Array Breakout - AMG8833 (Qwiic)
      • SparkFun Pulse Oximeter and Heart Rate Sensor - MAX30101 & MAX32664 (Qwiic)
      • SparkFun Atmospheric Sensor Breakout - BME280 (Qwiic)
      • SparkFun Environmental Combo Breakout - CCS811/BME280 (Qwiic)
      • SparkFun Environmental Sensor Breakout - BME680 (Qwiic)
      • SparkFun Environmental Sensor - BME688 (Qwiic)
      • SparkFun Pressure Sensor - BMP384 (Qwiic)
      • SparkFun Pressure Sensor - BMP581 (Qwiic)
      • SparkFun Qwiic Button
      • SparkFun Air Velocity Sensor Breakout - FS3000-1015 (Qwiic)
      • SparkFun Air Velocity Sensor Breakout - FS3000-1005 (Qwiic)
      • SparkFun GPS-RTK2 Board - ZED-F9P (Qwiic)
      • SparkFun GPS-RTK-SMA Breakout - ZED-F9P (Qwiic)
      • SparkFun GPS-RTK Board - NEO-M8P-2 (Qwiic)
      • SparkFun GPS Breakout - Chip Antenna, SAM-M8Q (Qwiic)
      • SparkFun GPS Breakout - NEO-M9N, Chip Antenna (Qwiic)
      • SparkFun GPS Breakout - ZOE-M8Q (Qwiic)
      • SparkFun 6DoF IMU Breakout - ISM330DHCX (Qwiic) and on-board SPI 9DOF
      • SparkFun 9DoF IMU Breakout - ISM330DHCX, MMC5983MA (Qwiic)
      • Qwiic Pressure Sensor - LPS25HB
      • Qwiic Fuel Gauge - MAX17048
      • SparkFun Qwiic Thermocouple Amplifier - MCP9600 (PCC Connector)
      • SparkFun Qwiic Thermocouple Amplifier - MCP9600 (Screw Terminals)
      • SparkFun Qwiic MicroPressure Sensor
      • SparkFun Micro Magnetometer - MMC5983MA (Qwiic) and on-board SPI 9DOF
      • Pressure Sensor (Qwiic) - MS5637
      • Qwiic Pressure/Humidity/Temp (PHT) Sensor - MS8607
      • SparkFun Qwiic Scale - NAU7802
      • SparkFun Real Time Clock Module - RV-8803 (Qwiic)
      • CO\u2082 Humidity and Temperature Sensor - SCD30
      • CO\u2082 Humidity and Temperature Sensor - SCD40 (Qwiic)
      • SparkX Differential Pressure Sensor - SDP31 (Qwiic)
      • Particle, VOC, Humidity, and Temperature Sensor - SEN54
      • SparkFun Air Quality Sensor - SGP30 (Qwiic)
      • SparkFun Air Quality Sensor - SGP40 (Qwiic)
      • SparkFun Humidity Sensor Breakout - SHTC3 (Qwiic)
      • SparkFun Qwiic Dynamic NFC/RFID Tag
      • CO\u2082 Sensor - STC31 (Qwiic)
      • SparkFun Qwiic dToF Imager - TMF8821
      • SparkFun Qwiic dToF Imager - TMF8820
      • SparkFun High Precision Temperature Sensor - TMP117 (Qwiic)
      • SparkFun Qwiic Twist - RGB Rotary Encoder Breakout
      • SparkFun Proximity Sensor Breakout - 20cm, VCNL4040 (Qwiic)
      • SparkFun UV Light Sensor Breakout - VEML6075 (Qwiic)
      • Ambient Light Sensor - VEML7700 (Qwiic)
      • SparkFun Distance Sensor Breakout - 4 Meter, VL53L1X (Qwiic)
      • SparkFun Qwiic ToF Imager - VL53L5CX
      "},{"location":"troubleshooting/","title":"Troubleshooting","text":"

      Note

      Not working as expected and need help?

      If you need technical assistance and more information on a product that is not working as you expected, we recommend heading on over to the SparkFun Technical Assistance page for some initial troubleshooting.

      SparkFun Technical Assistance Page

      If you don't find what you need there, the SparkFun Forums are a great place to find and ask for help. If this is your first visit, you'll need to create a Forum Account to search product forums and post questions.

      Create New Forum Account Log Into SparkFun Forums

      "},{"location":"troubleshooting/#issues-connecting-to-iot-service","title":"Issues Connecting to IoT Service","text":"

      Having trouble connecting your DataLogger IoT to an IoT service? Make sure to check your credentials and ensure that the configuration matches the IoT Service (such as your WiFi network, port, server, topic, certificates, keys, etc. to name a few). Make sure to also include the associated certificates and keys in the microSD card as well. You may see an output similar to the following errors below.

      "},{"location":"troubleshooting/#aws-iot-error","title":"AWS IoT Error","text":"

      The following error occurred when the DataLogger IoT was initializing with AWS.

      [W] AWS IoT disconnected - reconnecting.......[E] AWS IoT: MQTT connection failed. Error Code: -2\n

      In this case, the DataLogger IoT failed to connect to AWS IoT service because the port was using the default value that was saved: 1883. Ensure that the port is set to 8883 for your IoT service (e.g. AWS IoT, Azure, and ThingSpeak) and saved in persistent memory in order for the DataLogger IoT to successfully connect. As of firmware v01.00.04, the default is 8883.

      "},{"location":"troubleshooting/#thinkspeak-iot-error","title":"ThinkSpeak IoT Error","text":"

      The following error occurred when the DataLogger IoT was initializing with ThingSpeak.

      [I] ThingSpeak MQTT: connecting to MQTT endpoint mqtt3.thingspeak.com:8883 .......[E] ThingSpeak MQTT: Connection Error [4]\n

      In this case, the DataLogger IoT failed to connect to ThingSpeak service because the credentials were entered incorrectly. Ensure that the and saved in persistent memory in order for the DataLogger IoT to successfully connect.

      "},{"location":"troubleshooting/#arduino-cloud-error-1","title":"Arduino Cloud Error 1","text":"

      The following error was occurred when the DataLogger IoT was initializing with Arduino Cloud.

      [W] ArduinoIoT - Thing Name not provided\n.\n.\n.\n[I] Arduino IoT: setup variables...[E] ArduinoIoT HTTP communication error [401] - token request\n[E] Arduino IoT Cloud not available or account credentials incorrect\n

      In this case, the DataLogger IoT failed to connect to the Arduino Cloud service because the credentials were incorrect. Ensure that the credentials (i.e. API client ID, API secret, device secret, device ID) are entered correctly and saved in persistent memory in order for the DataLogger IoT to successfully connect.

      "},{"location":"troubleshooting/#arduino-cloud-error-2","title":"Arduino Cloud Error 2","text":"

      The following error was occurred when the DataLogger IoT was initializing with Arduino Cloud.

      [I] Arduino IoT: setup variables...[E] Arduino IoT: return code=400, failed to decode request body with content type \"application/json\": uuid: incorrect UUID length 37 in string \"a111aaa1-1111-1111-1a1a-1a11111a1111\\r\"\n

      In this case, the DataLogger IoT failed to connect to the Arduino Cloud service because the credentials were incorrect. The string was supposed to be the device ID. When copying and pasting the device ID from a PDF that was generated with the Arduino Cloud, a carriage return (\\r) was also copied and entered in the serial terminal. By pasting the device ID into a text editor and then re-copying/pasting it into the serial terminal helped to ensure that the credentials were entered correctly.

      Note

      The device ID in this example was a randomly generated string. You will need to check to make sure that your device matches the one that the Arduino Cloud generated specifically for your account.

      "},{"location":"troubleshooting/#arduino-cloud-error-3","title":"Arduino Cloud Error 3","text":"

      The following error was occurred when the DataLogger IoT was initializing with Arduino Cloud.

      [I] Arduino IoT: setup variables...ArduinoIoTCloudTCP::handle_ConnectMqttBroker could not connect to mqtts-up.iot.arduino.cc:8884\nArduinoIoTCloudTCP::handle_ConnectMqttBroker 1 connection attempt at tick time 301939\nArduinoIoTCloudTCP::handle_ConnectMqttBroker could not connect to mqtts-up.iot.arduino.cc:8884\nArduinoIoTCloudTCP::handle_ConnectMqttBroker 2 connection attempt at tick time 307420\n[E] Arduino IoT: No Arduino Thing ID provided. Enter ID, delete Thing (SparkFunThing1) on Cloud, or enter new Thing Name.\n

      In this case, the DataLogger IoT failed to connect to the Arduino Cloud service because there was already a Thing that was created. By deleting the Thing in the Arduino Cloud, the DataLogger IoT was able to automatically create another Thing and setup the variables.

      "},{"location":"troubleshooting/#thingspeak-data-points-not-updating","title":"ThingSpeak Data Points Not Updating","text":"

      If your DataLogger IoT is connected to ThingSpeak but you do not see any data, ensure that the device name matches the Qwiic device that is connect to the DataLogger IoT. For example, the DataLogger IoT and Qwiic-enabled ENS160 was able to connect to ThingSpeak as shown in the image on the bottom left. However, there were no data points in any of the graphs as shown on ThingSpeak as shown in the image on the bottom right.

      If you head back into the configuration menu for the DataLogger's ThingSpeak channel, make sure that the matches the connected Qwiic device's name that was shown during initialization. In this case, the device that was loaded and detected was ENS160. Then add the channel ID before saving the system settings.

      Note

      Only one device can be loaded per channel! ThingSpeak is not able graph two different devices in the same channel.

      Head back to your ThingSpeak Channel to verify that data is being plotted on the graphs.

      "},{"location":"troubleshooting/#u-blox-i2c-device-disappears-when-iot-datalogger-initializes","title":"U-Blox I2C Device Disappears when IoT DataLogger Initializes","text":"

      If you have issues where a u-blox device that is connected to the I2C port fails to connect a second time when the IoT DataLogger initializes, this is due to a bug in the firmware from an initial release. You may see an output similar to the following message and image shown below.

      [W] GNSS::isConnected no traffic seen (first attempt)\n

      If you see the following output and the IoT DataLogger not loading your u-blox device, you will need to update the firmware to v01.00.03 and above. For more information, make sure to check out the tutorial on updating your IoT DataLogger's firmware.

      "},{"location":"updating_firmware/","title":"Updating Firmware","text":"

      Danger

      Please think very carefully before uploading any Arduino sketches to your DataLogger IoT.

      You will overwrite the DataLogger IoT firmware, leaving it unable to update or restore itself.

      Each DataLogger IoT comes pre-programmed with amazing firmware which can do so much. It is designed to be able to update itself and restore itself if necessary. But it can not do that if you overwrite the firmware with any Arduino sketch. It is just like erasing the restore partition on your computer hard drive. Do not do it - unless you really know what you are doing.

      Really. We mean it.

      "},{"location":"updating_firmware/#firmware-update-sd-card","title":"Firmware Update - SD Card","text":"

      This action enables the ability to update the onboard firmware to an image file contained an SD card. This user is presented a list of available firmware images files contained in root directory of the on-board SD card, and updates the board to the selected file.

      This option is available on ESP32 devices that contained two update firmware (OTA type) partitions within the on-board device flash memory. Consult the specific products production and build system for further details.

      To download the latest firmware and update through the microSD card, you will need to head to the GitHub repository containing the firmware. Select the firmware version and download.

      GitHub: SparkFun DataLogger | Firmware Releases

      Once downloaded, insert the microSD card into a card reader or USB adapter. Then move the files into the memory card's root directory. Below shows an image of v01.00.01 and v01.00.02 on a Windows OS.

      Once the files are copied to the memory card, eject the microSD card from your computer. Insert the card back into the DataLogger IoT's microSD card socket. Connect the DataLogger IoT to your computer using a USB cable.

      Note

      What's going on here?!? This tutorial was updated for firmware version 01.01.00!!! You will notice this menu option has changed to 17 !!!

      Open a Serial Terminal, connect to the COM port that your DataLogger enumerated to, and set it to 115200 baud. In this case, we connected to COM13. Press any key to enter the Main Menu. Type 1 to enter the Settings menu. Then type 17 to enter the System Update Menu. Finally, type 3 to update the firmware from the microSD card. You should see an image similar to the output below.

      The system will search the root directory of the on-board SD card for available firmware files. The firmware files are selected using the following criteria:

      • The file is contained in the root \"/\" folder of the SD card.
      • The filename has a \".bin\" extension.
      • The filename starts with a specified name prefix. The prefix is optional and is set by the developers at SparkFun using this action.
        • For example, the DataLogger IoT boards use a prefix value of \"SparkFun_DataLoggerIoT_\".

      Once a file is selected, the system new firmware is read off the SD card and written to the device.

      And once updated, the system is rebooted and starts using the new firmware image!

      "},{"location":"updating_firmware/#firmware-update-over-the-air-ota","title":"Firmware Update - Over-the-Air (OTA)","text":"

      This action enables the ability to update the onboard firmware to an image file contained on an update server, which is accessed via the WiFi network the system is connected to. This Over-The-Air (OTA) capability contacts the systems update server and searches for newer firmware (later version) for the specific board.

      This option is available on ESP32 devices that contained two update firmware (OTA type) partitions within the on-board device flash memory. Consult the specific products production and build system for further details.

      If you have not already, connect the DataLogger IoT to your computer using a USB cable.

      Open a Serial Terminal, connect to the COM port that your DataLogger enumerated to, and set it to 115200 baud. In this case, we connected to COM13. Press any key to enter the Main Menu. Type 1 to enter the Settings menu. Then type 16 to enter the System Update Menu. Finally, type 4 to update the firmware over-the-air.

      When this option is selected, the system will contact the update server and search for available upgrade firmware, selecting the latest version available. If a newer version is found, a prompt is presented to confirm the upgrade.

      Note

      For the upgrade option to occur, a the system must be connected to a network and have access to the firmware OTA server.

      Typing Y (or hitting enter) starts the update operation. As the firmware is downloaded, the percent complete status is updated. If connectivity fails during the download, the operation is halted and the update aborted.

      Once the update file is downloaded, it is verified as being the correct file. Once verified, the system is rebooted and starts using the new firmware image! You will notice the firmware version change as the DataLogger IoT initializes.

      "},{"location":"wifi_network/","title":"Connecting to a WiFi Network","text":"

      Note

      The ESP32-WROOM can only connect to a 2.4GHz WiFi network. Unfortunately, 5GHz is not supported on the ESP32-WROOM module.

      To take advantage of syncing the DataLogger to the Network Time Protocol (NTP), logging data to an IoT service, or updating firmware OTA, you will need to connect to a 2.4GHz WiFi network.

      Open a Serial Terminal, connect to the COM port that your DataLogger enumerated to, and set it to 115200 baud. In this case, we connected to COM13. Press any key to enter the Main Menu. Type 1 to enter the Settings menu. Then send a 4 to configure the WiFi settings.

      Send a 2 to set the WiFi Network Name. You'll be prompted to set the network name. In this case, the network name is sparkfun. Once you enter the name, hit the enter key.

      Send a 2 to set the WiFi password. You'll be prompted to set the password. As you send the password, each character will be masked by a asterisk (i.e. *) Once you enter the name, hit the enter key.

      Follow the prompts to exit out of the menu properly so that the DataLogger IoT saves the settings.

      Once you see the message [I] Saving System Settings and data on the output, hit the reset button on the board. You can also use the menu to perform a device restart, however you will need to ensure that you receive the message indicating that the settings were saved before restarting the device.

      Once the device has restarted, the DataLogger will provide an output as it is initializing. If the WiFi credentials are saved properly, you will receive a message indicating that your chosen network is connected to your WiFi network. If the time source is set to the default NTP client, you will also notice that the time will be synced to the latest date and time!

      "},{"location":"javascript/","title":"javascript directory","text":"

      This folder should contain the files for the custom javascript that is enabled in the product documentation

      "},{"location":"relnotes/","title":"Latest DataLogger IoT Firmware Release Notes","text":"
      • Version 01.1.01 - Overview of the Release
      "},{"location":"relnotes/rn_v010101/","title":"Rn v010101","text":""},{"location":"relnotes/rn_v010101/#sparkfun-datalogger-iot-firmware-version-11","title":"SparkFun DataLogger Iot Firmware - Version 1.1","text":"

      November 11th, 2023

      Today we\u2019re releasing the latest firmware update for our line of DataLogger IoT products. While we\u2019ve released a services of small defect correct releases over the base 6 months, this release, officially version v1.01.00, is the first to include major new functionality.

      And with the release, we feel we\u2019ve covered all aspects of a 1.1 release \u2013 new functionality, feature enhancements, and a wide variety of bug fixes.

      "},{"location":"relnotes/rn_v010101/#new-feature-arduino-iot-cloud-connectivity","title":"New Feature \u2013 Arduino IoT Cloud Connectivity","text":"

      Being an IoT focused product, one of our goals is to enable easy access to leading IoT data services. With this in mind, for this release we worked closely with the Arduino Team to enabled easy access to the Arduino IoT Cloud directly from a DataLogger IoT Device.

      Setup a device in the Arduino IoT Cloud, add the credentials to your DataLogger IoT and you have a IoT based dashboard up and running in minutes. Want to add an additional device to your dashboard \u2013 add the device to the DataLogger IoT and the combined system automatically adds the additional data to the Arduino Cloud \u2013 ready for use in your dashboard.

      The above image shows data from a DataLogger IoT with an Environmental Combo attached to it and mapping data to a plot graphic on an Arduino IoT Cloud dashboard.

      And like all DataLogger IoT functionality, no programming is required to produces these plots \u2013 all settings are provided via the intuitive serial console menu interface or set via a settings JSON file.

      With the combination of the DataLogger IoT, qwiic and Arduino IoT Cloud, creating a IoT dashboard is as easy as Plug-in, Connect, Plot!

      "},{"location":"relnotes/rn_v010101/#new-qwiic-sensor-board-support","title":"New qwiic Sensor Board Support","text":"

      The original release of the DataLogger IoT firmware supported around 50 SparkFun qwiic development boards and with this release we\u2019ve added six boards additional qwiic boards.

      Selecting from our recently added qwiic products as well as from the list of popular products, the release of version 1.1 adds support for the following qwiic development boards.

      • SparkFun Indoor Air Quality Sensor - ENS160
      • SparkFun Photoacoustic Spectroscopy CO2 Sensor - PASCO2V01
      • SparkFun Human Presence and Motion Sensor - STHS34PF80
      • SparkFun Tristimulus Color Sensor - OPT4048DTSR
      • SparkFun Triad Spectroscopy Sensor - AS7265x
      • SparkFun 6DoF IMU Breakout - BMI270

      Like previously supported qwiic boards, the DataLogger IoT firmware automatically detects when a device is connected, making the new device automatically available to the logging, menu and IoT systems of the DataLogger IoT.

      All these new sensors are great adds on their own, but the ENS160 has the additional feature of supporting temperature and humidity calibration inputs. Connect an BME280 or an SHTC3 qwiic board along with an ENS160, and the DataLogger automatically connects the two devices!

      "},{"location":"relnotes/rn_v010101/#feature-improvements","title":"Feature Improvements","text":"

      In addition to the new functionality, we also took input from our customers (and our own use) to expand and enhance existing features. While a wide variety of small additions were made, a few notable additions include:

      Improved Reference Clock Management \u2013 the internal logic was reviewed and improved. Additionally, time zone support was moved out of the NTP system, and into the overall clock management subsystem.

      Internal JSON Buffer Limits \u2013 The initial firmware had fixed sizes for the internal JSON data buffers used for saving settings and log data output. These values are now user settable in the settings menu. This allows for larger buffer support, as well as reducing the internal buffers to what you need.

      Add Device IDs to the Device Names \u2013 As you add more devices to a DataLogger, it\u2019s difficult to match the device to the actual devices address. Addresses are now included in device listings and the option to include the address in the device name is now available.

      Board Information in the Log Stream \u2013 To improve identification of a data source within the data log stream, the Board Name and Board ID can now be added if desired. As part of this functionality, a configurable name is now available for each DataLogger IoT.

      "},{"location":"relnotes/rn_v010101/#bug-fixes","title":"Bug Fixes","text":"

      We also squashed a variety of defects in the firmware. Some of the more notable issued fixed in this release:

      • Incorporated a fix in the RV8803 RTC Clock Arduino Library that resolved force ti* shifts based on time zone. This was a \u201creal nasty one\u201d to resolve!
      • Improved runtime memory (ram) consumption
      • Resolved issues with devices having the same name. Now the second device adds it* address to the name.
      • Improvements to the Machinechat IoT driver
      "},{"location":"relnotes/rn_v010101/#in-summary","title":"In Summary","text":"

      With the release of DataLogger IoT firmware version 1.1.0 we continue to enhance the capabilities of our DataLogger IoT line \u2013 adding to our IOT service, supported devices as well as improving the overall quality of the system.

      And this new functionality is available today at the DataLogger repo. The update is free, available as an over-the-air upgrade, or as a file uploaded via an SD Card. Just select the \u201cSystem Update\u201d option within the DataLogger IoT menu system and select your desired upgrade option.

      "},{"location":"relnotes/rn_v010200/","title":"Rn v010200","text":""},{"location":"relnotes/rn_v010200/#sparkfun-datalogger-iot-firmware-version-12","title":"SparkFun DataLogger IoT Firmware - Version 1.2","text":"

      April 15th, 2024_

      With the release of our version 1.2 software for our DataLogger IoT products, we continue to add additional functionally to the products capability, as well as fix a number of issues.

      And with the release, we feel we\u2019ve covered all aspects of a 1.1 release \u2013 new functionality, feature enhancements, and a wide variety of bug fixes.

      "},{"location":"relnotes/rn_v010200/#new-features-and-enhancements","title":"New Features and Enhancements","text":""},{"location":"relnotes/rn_v010200/#log-file-download-via-a-web-interface","title":"Log File Download via a Web Interface","text":"

      To allow access to log files located on the DataLogger IoT device, without requiring the removal of the SD card, a new Web Interface is provided. Once enabled, you can browse the on-board log files of the DataLogger. Clicking on a a filename will download the file.

      Currently file browse and download options are available, but we plan on expanding this feature in the future.

      Additionally, this feature has the following options:

      • mDNS functionality allowing you to set a network name for a device if mDNS is supported on your network
      • Username/Password authentication for the web interface.

      Note: For authentication use - currently some browsers might require a second login depending on settings.

      Note: The datalogger requires restarting if the web interface is enabled

      This feature is enabled in settings under the Preview heading.

      "},{"location":"relnotes/rn_v010200/#startup-command-menu-and-delay","title":"Startup Command Menu and Delay","text":"

      To allow start-up time configuration and delay, a Startup Menu was added to the system. Now, at startup a short menu is presented for a brief period, allowing modification of the startup options of the DataLogger.

      Startup Menu options:

      Pressing the highlighted letter while the menu is active, will change the behavior of the system. This change only affects the current system session.

      The options include:

      • 'n' - Normal startup
      • 'a' - Disable I2C device auto load on startup
      • 'l' - List the I2C devices supported. This device table is discarded after auto-load
      • 'w' - Disable WiFi
      • 's' - Disable preference restore during startup

      In addition, the amount of time the menu is displayed is adjustable. This settings is on the Settings/Application Settings page, under the Advanced section.

      "},{"location":"relnotes/rn_v010200/#quick-commands","title":"Quick (!) Commands","text":"

      The addition of a quick (!) command system that allows for the direct execution of commands directly from the serial console, bypassing the serial menu system.

      An example of this is the display of the \"about\" page for the system. Normally this would require navigating the serial menu system. With the quick command system, entering the value of \"!about\" at the serial console will display the about page.

      The following commands are supported:

      command Description !about Display the system about page !clear-settings Clear the on board system preferences with a yes/no prompt !clear-settings-forced Clear the on board system preferences with no prompt !devices List the currently connected devices !factory-reset Perform a factory reset - presents a Y/N prompt !heap Display the current system heap memory usage !help List the available quick commands !json-settings For setting the device settings via a serial connection. When this command is sent, the system expects to receive a JSON settings file !log-now Perform a log observation event !log-rate If log rate measurement is enabled, the current log rate is printed !reset-device Reset the device - erasing any saved settings and restarting the device !reset-device-forced Reset the device, but without a Y/N prompt !restart Restart the device !restart-forced Restart the device without a Y/N prompt !save-settings Save the current settings to on-board flash !sdcard Output the current SD card usage statistics !systime Output current system time !uptime The uptime of the device !device-id The ID for the device !version The version of the firmware !wifi Output current system WiFi state"},{"location":"relnotes/rn_v010200/#log-data-rate","title":"Log Data Rate","text":"

      The DataLogger system can now measure the data logging rate. Once this feature is enabled, the system will monitor the time between log events. This value is averaged over the latest 10 log events.

      "},{"location":"relnotes/rn_v010200/#system-info-in-the-log-stream","title":"System Info in the log stream","text":"

      The system operational parameters can now be added to log stream. This is useful to monitor system resource uses over time, or just perform general debugging.

      Currently the following information is provided:

      • WiFi SSID
      • WiFi RSSI
      • Memory Heap free space in bytes
      • SD Card free space in bytes
      • Uptime in MS
      "},{"location":"relnotes/rn_v010200/#feature-improvements","title":"Feature Improvements","text":"

      In addition to the new functionality, we also took input from our customers (and our own use) to expand and enhance existing features. While a wide variety of small additions were made, a few notable additions include:

      Serial Console - Value Display \u2013 The serial console now shows the current setting value in the menu system. Previously this value was only show once that item was selected.

      Serial Console Color \u2013 Text highlighting and color were added to the serial console output. If your serial console application/command supports it, the menu system highlights key values. This setting is controlled in the Settings/Application Settings section of the settings menu.

      Startup Messages \u2013 Normally a verbose log of startup options and settings are displayed at system startup. The about of information is now controllable - with values of Normal, Compact, Disabled.

      Improved Device Auto-Load \u2013 A major update to the I2C auto-load device detection logic that improves device detection and address collision prevention.

      General System Enhancements \u2013 Internal system job dispatch subsystem update to increase performance throughput. Overall decrease in static and dynamic memory usage.

      "},{"location":"relnotes/rn_v010200/#bug-fixes","title":"Bug Fixes","text":"

      We also squashed a variety of defects in the firmware. Some of the more notable issued fixed in this release:

      • Fixed issue with the LED display logic that caused a system crash if the log interval was less than 100ms
      • Incorporate driver updates for greater NAU7802 device output value stability
      • Incorporate driver update for the MMC5983MA device
      "},{"location":"relnotes/rn_v010200/#in-summary","title":"In Summary","text":"

      With the release of DataLogger IoT firmware version 1.2.0 we continue to enhance the capabilities of our DataLogger IoT line \u2013 adding to our IOT service, supported devices as well as improving the overall quality of the system.

      And this new functionality is available today at the DataLogger repo. The update is free, available as an over-the-air upgrade, or as a file uploaded via an SD Card. Just select the \u201cSystem Update\u201d option within the DataLogger IoT menu system and select your desired upgrade option.

      "}]} \ No newline at end of file diff --git a/single_page/index.html b/single_page/index.html new file mode 100644 index 0000000..5cc5f3f --- /dev/null +++ b/single_page/index.html @@ -0,0 +1,6068 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + Introduction - SparkFun DataLogger IoT Hookup Guide + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      + +
      + + + + + + +
      + + + + + + + + + + + +
      + +
      + + + + +
      +
      + + + + + + + + + + + + + + + +
      + + + + + + + + + +
      + + + + + + + + + + + + + + + + + + + + + + + + + + +

      Introduction

      +

      The SparkFun DataLogger IoT is a data logger that comes preprogrammed to automatically log IMU, GPS, and various pressure, humidity, and distance sensors. All without writing a single line of code! They come in two flavors: The SparkFun DataLogger IoT - 9DoF and the SparkFun DataLogger IoT. Both versions of the DataLogger IoT automatically detects, configures, and logs Qwiic sensors. It was specifically designed for users who just need to capture a lot of data to a CSV or JSON file, and get back to their larger project. Save the data to a microSD card or send it wirelessly to your preferred Internet of Things (IoT) service!

      + +
      + + + + + +
      + + + +
      +
      + +

      Required Materials

      +
      +

      Battery Polarity

      +

      Please make sure that you use one of our recommended Lithium Ion batteries. Some batteries use the same JST connector as ours but have the opposite polarity. Connecting one of these to your DataLogger IoT will destroy it. If you are going to use your own battery, it is up to you to ensure it has the correct polarity.

      +
      +

      To follow along with this tutorial, you will need the following materials. You may not need everything though depending on what you have. Add it to your cart, read through the guide, and adjust the cart as necessary.

      + + +

      The Sensors

      +

      Straight out of the box anti-static bag, the DataLogger IoT is ready to log data from its built-in ISM330DHCX Inertial Measurement Unit (IMU) and MMC5983MA magnetometer. Only want to log magnetometer, accelerometer, gyro or temperature data? You’re good to go! But the fun is only just beginning…

      +

      The DataLogger IoT is preprogrammed to automatically log data from all of the following sensors, so you may wish to add one or more of these to your shopping cart too. (More sensors are being added all the time and it is really easy to upgrade the DataLogger IoT to support them. But we'll get to that in a moment!). Currently, auto-detection is supported on the following Qwiic-enabled products (with the exception of the ISM330DHCX and MMC5983 which is built-in on the SPI port):

      +
      +

      Note

      +

      For a list of supported devices based on the firmware, you can check out the list of supported Qwiic Devices in the appendix. We simply categorized the supported devices below based on the type.

      +
      +
        +
      • Any u-Blox GNSS Modules (Lat/Long, Altitude, Velocity, SIV, Time, Date) such as: +
      • +
      • Inertial Measurement Unit (Accelerometer and Gyro):
          +
        • ISM330DHCX IMU (Built-in for the 9DoF version via SPI)
        • +
        +
      • +
      • Magnetometer:
          +
        • MMC5983 (Built-in for the 9DoF version via SPI)
        • +
        +
      • +
      • Distance: +
      • +
      • Pressure, Altitude, Humidity and Temperature Data: +
      • +
      • Air Quality and Environmental Sensors:
          +
        • CCS811 Air Quality (CO2 and VOC)
        • +
        • ENS160 Indoor Air Quality (AQI, eCO2, and TVOC)
        • +
        • PASCO2V01 Air Quality (CO2)
        • +
        • SGP30 Air Quality (TVOC, CO2, H2, Ethanol)
        • +
        • SGP40 Air Quality (VOC, Humidity, Temperature)
        • +
        • SCD30 CO2, Humidity, and Temperature
        • +
        • SCD40 CO2, Humidity, and Temperature
        • +
        • BME680 Air Quality (Pressure, Humidity, Temperature, Gas, VOCs)
        • +
        • BME688 Air Quality (Pressure, Humidity, Temperature, Gas, VOCs, VSC, CO, Gas)
        • +
        • FS3000 Air Velocity
        • +
        • SEN54 Environmental Sensor Node (Particle, VOC, Humidity, and Temperature)
        • +
        • STC31 CO2 and Temperature sensor
        • +
        • VEML6075 UV
        • +
        • VEML7700 Ambient Light and Lux
        • +
        • OPT4048DTSR Tristimulus Color
        • +
        • AS7265x Triad Spectroscopy
        • +
        +
      • +
      • Temperature: +
      • +
      • Power: +
      • +
      • Real Time Clock: +
      • +
      • NFC/RFID: +
      • +
      • Weight:
          +
        • NAU7802 Qwiic Scale Load Cell Amplifier
        • +
        +
      • +
      • Miscellaneous: +
      • +
      • Analog Voltage:
          +
        • ADS1015 12-bit 4-channel Differential ADC
        • +
        • ADS122C04 24-bit Differential ADC found on the PT100
        • +
        +
      • +
      +

      Suggested Reading

      +

      If you aren't familiar with the Qwiic system, we recommend reading here for an overview.

      +
      + + + + + + + +
      +
      Qwiic Connect System +
      +
      +
      + +

      If you aren’t familiar with the following concepts, we also recommend checking out a few of these tutorials before continuing.

      + +

      Hardware Overview

      +

      In this section, we will highlight the hardware and pins that are broken out on the SparkFun DataLogger IoT. At the time of writing, we highlighted the SparkFun DataLogger IoT - 9DoF. However, this also applies for the SparkFun DataLogger IoT.

      +
      + + + + + + + + + +
      DataLogger IoT - 9DoF (Top View)DataLogger IoT - 9DoF (Bottom View)
      DataLogger IoT - 9DoF (Top View)DataLogger IoT - 9DoF (Bottom View)
      +
      + +

      The SparkFun DataLogger is pretty much the same with the exception of the following features listed below. We'll include notes highlighting the differences in each section.

      +
        +
      • No built - in 6DoF IMU - ISM330DHCX
      • +
      • No built - in magnetometer - MMC5983
      • +
      • The addressable RGB LED - WS2812 is replaced with the side emitting addressable RGB LED - B3DQ3BRG
      • +
      • No IMU INT2 jumper
      • +
      • No Mag INT jumper
      • +
      • Included MEAS PTH Jumper
      • +
      • The "35 / A7" PTH on the edge of the board is replace with a "5" PTH.
      • +
      +
      + + + + + + + + + +
      DataLogger IoT (Top View) + DataLogger IoT (Bottom View) +
      DataLogger IoT (Top View)DataLogger IoT (Bottom View)
      +
      + +

      ESP32-WROOM Module

      +

      The DataLogger IoT is populated with Espressif's ESP32-WROOM-32E module. Espressif's ESP32 WROOM ubiquitous IoT microcontroller is a powerful WiFi, BT, and BLE MCU module that targets a wide variety of applications. For the DataLogger IoT, the firmware currently utilizes the WiFi feature.

      +
      + + + + +
      ESP32-WROOM Highlighted on the DataLogger IoT - 9DoF
      +
      + +
      +

      Note

      +

      Currently the DataLogger IoT does not have BT or BLE. However, BT or BLE is being considered on a future firmware build to include this as a feature.

      +
      +

      Power

      +

      There are a variety of power and power-related nets broken out to connectors and through hole pads. Below list a few methods of powering the board up. There are protection diodes for the USB-C, 5V pin, and single cell LiPo battery. Power is regulated down to 3.3V for the system voltage. Depending on the settings and what is connected to the DataLogger IoT, the board can pull a minimum of 200µA in low power mode by itself.

      +
        +
      • USB-C
      • +
      • 5V Pin
      • +
      • Single Cell LiPo Battery
      • +
      • 3V3 Pin
      • +
      +

      USB-C and 5V

      +

      The DataLogger IoT comes equipped with a USB type C socket which you can use to connect it to your computer to view the output and configuration through the serial terminal, or plug in a USB-C power supply. The DataLogger IoT includes the configuration channel resistors needed to tell the power supply to deliver 5V. You can use your USB-C laptop charger as the power source should you need to, even though it normally delivers a much higher voltage.

      +

      There is also a 5V power input pin. You can use this to feed in 5V power from an external source. The maximum voltage is 6.0V. The 5V pin is diode-protected and so is the USB-C power input, so it is OK to have both connected at the same time. This pin is ideal if you want to power your DataLogger from regulated solar power or a different type of power supply. You can not use the 5V pin as an output.

      +
      + + + + + +
      + USB C Connector, 3.3V Voltage regulator, 5V PTH + 5V PTH
      +
      + +

      Voltage from the USB is regulated down to the XC6222 3.3V/700mA voltage regulators for the system voltage and Qwiic-enabled devices. USB power is also connected to the MCP73831 to charge a single cell LiPo battery at a default rate of 500mA.

      +

      For customers in North America, our NEMA Raspberry Pi Wall Adapter is a perfect choice. You can power the DataLogger IoT from our USB Battery Pack / Power Bank - TOL-15204 but you will need a USB-C cable too:

      + +

      LiPo Battery Input, Charger, and Fuel Gauge

      +
      +

      Warning

      +

      Battery Polarity: Please make sure that you use one of our recommended Lithium Ion batteries. Some batteries use the same JST connector as ours but have the opposite polarity. Connecting one of these to your DataLogger IoT will destroy it. If you are going to use your own battery, it is up to you to ensure it has the correct polarity.

      +
      +

      But of course you’re going to want to use the DataLogger IoT to log sensor data while on the move too. You can connect one of our standard single cell LiPo batteries to the DataLogger IoT and power it for hours, days or weeks depending on what sensors you have attached and how often you log data. The DataLogger IoT uses the built-in MCP73831 charger too which will charge your battery at 500mA when USB-C is connected. Please make sure your battery capacity is at least 500mAh (0.5Ah); bad things will happen if you try to charge our smallest batteries at 500mA. The board also includes the MAX17048 LiPo Fuel Gauge which allows you to determine how much power your LiPo battery has available. The 2-pin JST connector pins are broken out to PTHs on the edge of the board if you decide to solder a single cell LiPo battery directly to the board or power another device.

      +
      + + + + + +
      LiPo Charger and Fuel Gauge Circuit Highlighted on teh DataLogger IoT -  9DoF (Top View)LiPo Charger and Fuel Gauge Circuit Highlighted on teh DataLogger IoT -  9DoF (Bottom View)
      +
      + +

      3V3 Pins

      +

      For those going the old school route, you can also bypass the voltage regulators by soldering directly to the 3V3 and GND pin to provide power if your application has a regulated 3.3V. Note that this is only connected to the system voltage. You will also need to provide power to the 3V3 SWCH or Qwiic-enabled devices should you decide to bypass the voltage regulator.

      +
      + + + + + +
      3.3V Voltage Regulators, PTHs, and Qwiic Connectors Highlighted on the DataLogger IoT - 9DoF (Top View)3.3V Voltage Regulators, PTHs, and Qwiic Connectors Highlighted on the DataLogger IoT - 9DoF (Bottom View)
      +
      + +

      CH340 USB-to-Serial Converter

      +

      The top side of the board includes a CH340 USB-to-Serial Converter. The chip can be used to send serial data between the device and computer. You can view the output or configure the device through a serial terminal.

      +
      + + + + +
      CH340 Highlighted on the DataLogger IoT - 9DoF (Top View)
      +
      + +

      The driver should automatically install on most operating systems. However, there is a wide range of operating systems out there. You may need to install drivers the first time you connect the chip to your computer's USB port or when there are operating system updates. For more information, check out our How to Install CH340 Drivers Tutorial.

      + + +

      UART

      +

      The hardware serial UART pins are broken out on the edge of the board. For more information about Serial UART, check out the tutorial about Serial Communication for more information.

      +
        +
      • TXD: UART transmit pin. This is connected to pin 16.
      • +
      • RXD: UART receive pin. This is connected to pin 17.
      • +
      +
      +

      Note

      +

      The UART pins are not currently supported in the firmware for data logging.

      +
      +
      + + + + + +
      UART Pins Highlighted on the DataLogger IoT - 9DoF (Top View)UART Pins Highlighted on the DataLogger IoT - 9DoF (Bottom View)
      +
      + +

      Qwiic and I2C

      +
      +

      Note

      +

      You may notice a thin film over the vertial Qwiic connector. This is used by a pick-and-place machine when populating the component on the board before it goes through the reflow oven. This can be removed if you decide to use the vertical Qwiic connector with Qwiic-enabled devices.

      +
      +

      SparkFun's Qwiic Connect System uses 4-pin JST style connectors to quickly interface development boards with I2C sensors and more. No soldering required and there's no need to worry about accidentally swapping the SDA and SCL wires. The Qwiic connector is polarized so you know you’ll have it wired correctly every time, right from the start. Qwiic boards are daisy chain-able too so you can connect multiple sensors to the DataLogger IoT and log readings from all of them.

      +

      The board is populated with vertical and horizontal Qwiic connectors. These are also broken out to PTHs on the edge of the board.

      +
      + + + + + +
      Qwiic Connector and I2C PTH Highlighted on the DataLogger IoT - 9DoF (Top View)Qwiic Connector and I2C PTH Highlighted on the DataLogger IoT - 9DoF (Bottom View)
      +
      + +
        +
      • SCL: I2C clock pin. This is connected to pin 22 and a 2.2kΩ pull-up resistor.
      • +
      • SDA: I2C data pin. This is connected to pin 21 and a 2.2kΩ pull-up resistor.
      • +
      • 3V3 SW: The 3.3v pin is connected to the XC6222 voltage regulator's output to power the Qwiic devices.
      • +
      • GND: Common, ground voltage (0V reference) for the system
      • +
      +

      Connected to the line I2C line is the MAX17048 LiPo fuel gauge (7-bit unshifted address = 0x36).

      +

      Sometimes you might want to connect more than one of the same type of sensor to the DataLogger IoT. On the I2C bus, each device needs to have a unique address. On many of our boards, there are jumpers links which you can use to change the address and some have addresses that can be configured in software. But there are some where you cannot change the address. Typically, one would use a multiplexor. However, we currently do not have the DataLogger IoT configured to work with any multiplexors (i.e. Qwiic Mux Breakout).

      +
      +

      Note

      +

      Currently the Qwiic Mux is not compatible with the DataLogger IoT.

      +
      +

      The DataLogger IoT includes a dedicated 3.3V regulator for the Qwiic connector. This has several advantages including:

      +
        +
      • The DataLogger IoT can completely power-down the I2C sensors during sleep to prolong your battery life
      • +
      • There’s no risk of the Qwiic bus gulping too much current and causing problems for the ESP32
      • +
      +

      SPI

      +
      +

      Note

      +

      Besides the built-in ISM330DHCX and MMC5983MA, the SPI pins are not currently supported in the firmware for data logging.

      +
      +

      The SPI pins are broken out on the edge of the board. For those that are unfamiliar to PICO and POCI, check out the SPI tutorial for more information.

      +
        +
      • SCK: SPI clock pin. This is connect to pin 18.
      • +
      • PICO: SPI Peripheral In Controller Out. This is connected to pin 23.
      • +
      • POCI: SPI Peripheral Out Controller In. This is connected to pin 19.
      • +
      +
      + + + + + +
      SPI Pins Highlighted on the DataLogger IoT -9DoF (Top View)SPI Pins Highlighted on the DataLogger IoT - 9DoF (Bottom View)
      +
      + +

      Not shown in the image are the chip select (CS) pins. The 6DoF IMU's CS pin is connected to pin 5. The magnetometer's CS pin is connected to pin 27 which is not broken out.

      +
      +

      Note

      +

      On the DataLogger IoT, the IMU and magnetometer are not connected to the SPI port since they are not included on the board. Instead of pin "35 / A7" being broken out, pin "5" is broken out on the edge of the board.

      +

      + + + + + +
      Pin 5 Highlighted on the DataLogger IoT (Top View)Pin 5 Highlighted on the DataLogger IoT (Bottom View)
      +

      +
      +

      MicroSD Card Socket

      +

      The DataLogger IoT supports full 4-bit SDIO for fast logging and uses common microSD cards to record clear text, comma separated files. Flip over the DataLogger IoT and you'll see the latching microSD card socket. You probably already have a microSD card laying around. However, if you need any additional units, we have plenty in the SparkFun catalog. The DataLogger can use any size microSD card and supports FAT32 cards in addition to FAT16. Please ensure that your SD card is formatted correctly; we recommend the Raspberry Pi Imager Tool.

      +
      + + + + +
      MicroSD Card
      +
      + +

      Slide in your formatted SD card and it will click neatly into place. The edge of the SD card will stick out on the edge of the circuit board when it is inserted correctly.

      +
      +

      Warning

      +

      You should only insert or remove the SD card while the power is turned off or disconnected. Removing the card while the DataLogger IoT is logging will almost certainly corrupt your data.

      +
      +

      9 Degrees of Freedom (9DOF)

      +

      As stated earlier, included on every DataLogger IoT - 9DoF is a 6DoF Inertial Measurement Unit (IMU) for built-in logging of triple-axis accelerometer and gyro. There is also a built-in triple-axis magnetometer for a complete 9 degrees of freedom. Beside each IC is a silkcreen showing the reference axis. Both are connected to the ESP32 via the SPI port. Combined, you have 9 degrees of inertial measurement! Whereas the original 9DOF Razor used the old MPU-9250, this uses the ISM330DHCX and MMC5983MA. Oh, and if that wasn’t enough, it comes with a built-in temperature sensor on each IC too. So if you want to use the DataLogger IoT as a transportation logger, it will do that straight out of the anti-static bag!

      +
      + + + + +
      Accelerometer, Gyro, and Magnetometer
      +
      + +
      +

      Note

      +

      For users using the SparkFun DataLogger, there 6DoF IMU and magnetometer is not populated on the board. The associated silkscreen and jumpers for the sensors are also not included on the board.

      +

      + + +
      + + +
      DataLogger IoT (Top View) + DataLogger IoT (Bottom View) +
      +

      +
      +

      Analog Pins

      +
      +

      Note

      +

      The analog pins are not currently supported in the firmware for data logging.

      +
      +

      There are three 12-bit analog pins available and broken out on edge of the board.

      +
        +
      • 36 / A0: Analog A0. This is connected to pin 36.
      • +
      • 39 / A3: Analog A3. This is connected to pin 39.
      • +
      • 35 / A7: Analog A7. This is connected to pin 35.
      • +
      +
      + + + + + +
      Analog Pins Highlighted on the DataLogger IoT (Top View)Analog Pins Highlighted on the DataLogger IoT (Bottom View)
      +
      + +
      +

      Note

      +

      Instead of pin "35 / A7" being broken out on the DataLogger IoT, pin "5" is broken out on the edge of the board.

      +

      + + + + + +
      Pin 5 Highlighted on the DataLogger IoT (Top View)Pin 5 Highlighted on the DataLogger IoT (Bottom View)
      +

      +
      +

      Reset and Boot Buttons

      +
      +

      Note

      +

      You may notice a thin film over buttons. This is used by a pick-and-place machine when populating the component on the board before it goes through the reflow oven. This can be removed.

      +
      +

      There are two buttons available on the board for reset and boot. These are also broken out on the edge of the board as PTHs. If you have your DataLogger IoT mounted in an enclosure, you can also attach an external boot or reset switch too. Any Single Pole Normally-Open Push-To-Close momentary switch will do. Solder pin headers or wires to the RST and GND breakout pins and connect your external switch to those.

      +
        +
      • RESET: Pressing this button will pull the pin LOW and reset the program running on the ESP32 without unplugging the board.
      • +
      • BOOT: The boot button usually allows users to force the ESP32 into bootloader mode to manually flash new firmware to the ESP32. The ESP32 will remain in this mode until there is a power cycle or the reset button is pressed. As of firmware v01.00.02, this button has an extra function: pressing down on the user button for 20 seconds will erase on-board preference storage and restart the board. This is connected to pin 0 on the ESP32.
      • +
      +
      + + + + + +
      Reset and Boot Buttons (and associated PTHs) Highlighted on the DataLogger IoT - 9DoF (Top View)Reset and Boot Buttons (and associated PTHs) Highlighted on the DataLogger IoT - 9DoF (Bottom View)
      +
      + +

      Like other ESP32 development boards, these buttons are populated so that users can place the ESP32 module in bootloader mode. For users that need to place the board in bootloader mode when powered, you will need to:

      +
        +
      • Press the BOOT button.
      • +
      • While holding on the BOOT button, press the RESET button momentarily.
      • +
      • Finally, release the BOOT button.
      • +
      +

      Most of the time, users will simply have the board executing the firmware that is loaded on the ESP32 module and updating through the configuration menu either through the microSD card or OTA.

      +
      +

      Danger

      +

      Please think very carefully before uploading any Arduino sketches to your DataLogger IoT.

      +

      You will overwrite the DataLogger IoT firmware, leaving it unable to update or restore itself.

      +

      The DataLogger IoT comes pre-programmed with amazing firmware which can do so much. It is designed to be able to update itself and restore itself if necessary. But it can not do that if you overwrite the firmware with any Arduino sketch. It is just like erasing the restore partition on your computer hard drive. Do not do it - unless you really know what you are doing.

      +

      Really. We mean it.

      +
      +

      LEDs

      +

      There are three LEDs populated on the board. These can be disabled with their respective jumpers on the back of the board.

      +
        +
      • STAT: The status LED is connected to pin 25.
      • +
      • RGB: The WS2812-2020 RGB addressable LED is connected to pin 26. In addition to being disabled through the jumper on the back, you can also disable the LED through software. The following colors represent different states that the board is in.
          +
        • White: A solid white LED indicates that the board is currently being configured through the configuration menu.
        • +
        • Green: A solid green LED indicates that the board is initializing. As of firmware v01.00.02, the LED blinks green when on battery power indicating that the battery level is VBATT > 50%.
        • +
        • Blue: A blinking blue LED indicates that the board is reading sensor data and logging the values.
        • +
        • Yellow: A solid yellow LED indicates that a firmware update is in progress. As of firmware v01.00.02, the LED blinks yellow when on battery power indicating that the battery level is between 50% > VBATT > 10%.
        • +
        • Red: As of firmware v01.00.02, the LED blinks red when on battery power indicating that the battery level is VBATT < 10%.
        • +
        +
      • +
      • CHG: The on-board yellow CHG LED can be used to get an indication of the charge status of your battery. Below is a table of other status indicators depending on the state of the charge IC.
      • +
      +
      + + + + + + + + + + + + + + + + + + + + + +
      Charge StateLED status
      No BatteryFloating (should be OFF, but may flicker)
      ShutdownFloating (should be OFF, but may flicker)
      ChargingON
      Charge CompleteOFF
      +
      + +
      + + + + + +
      LEDsPTHS connected to LEDs
      +
      + +
      +

      Note

      +

      On the DataLogger IoT, we included the B3DQ3BRG addressable RGB LED instead of the WS2812 with the light emitting from the top of the IC. This side emitting LED uses the same protocol as the WS2812 and was a design choice for users placing the board in an enclosure.

      +

      + + + + +
      side emitting B3DQ3BRG Addressable RGB LED
      +

      +
      +

      Jumpers

      +

      There are seven jumpers on the back of the DataLogger IoT - 9DoF. For more information, check out our tutorial on working with jumper pads and PCB traces should you decide to cut the traces with a hobby knife.

      +
        +
      • SHLD: This jumper connects the USB Type C connector's shield pin to GND. Cut this to isolate the USB Type C connector's shield pin.
      • +
      • I2C: This three way jumper labeled as I2C are closed by default. By cutting the jumpers, it will disconnect the 2.2kΩ pull-up resistors for the I2C bus. Most of the time you can leave these alone unless your project requires you to disconnect the pull-up resistors.
      • +
      • STAT: This jumper connects the status LED to pin 25 and it is closed by default. Open the jumper to disable the LED.
      • +
      • RGB: This jumper connects the WS2812-2020 RGB addressable LED to pin 26 and it is closed by default. Open the jumper to disable the LED.
      • +
      • CHG: This jumper connects the charge LED on the MCP73831 charge IC and it is closed by default. Open the jumper to disable the LED.
      • +
      • IMU INT2: This jumper connects the ISM330DHCX IMU's interrupt pin to pin 35 and it is open by default. Add a solder jumper to connect.
      • +
      • MAG INT: This jumper connects the MMC5983MA magnetometer's interrupt pin to pin 35 and it is open by default. Add a solder jumper to connect.
      • +
      +
      + + + + +
      Jumpers
      +
      + +
      +

      Note

      +

      On the DataLogger IoT, the IMU INT2 or MAG INT jumpers are not included since it does not have the built in 6DoF IMU or magnetometer. With the extra real estate on the board, we were able to include a MEAS PTH and jumper on the board. By default, the jumper is closed. You can cut this jumper on the bottom side of the board to measure the DataLogger IoT’s current draw from external power.

      +

      + + + + + +
      MEAS Jumper on the DataLogger IoT (Top View)MEAS Jumper on the DataLogger IoT (Bottom View))
      +

      +
      +

      Board Dimensions

      +

      The overall length and width with the antenna connector is about 1.66" x 2.00". There are four mounting holes in the center of the board. Due to the size of the board and the ESP32 module, the mounting holes are positioned in a way for users to add two Qwiic enabled boards with a width of 1.0" instead of one Qwiic board.

      +
      + + + + + + + + + +
      + DataLogger IoT - 9DoF Board Dimensions + + DataLogger IoT Board Dimensions +
      DataLogger IoT - 9DoFDataLogger IoT
      +
      + +

      Hardware Hookup

      +

      In this section, we will go over how to connect to the SparkFun DataLogger IoT. At the time of writing, we used the DataLogger IoT - 9DoF. This hardware hookup explained in this section also applies for the DataLogger IoT.

      +

      Soldering to the PTHs

      +
      +

      Note

      +

      The UART, SPI, analog, and digital I/O pins are not currently supported in the firmware for data logging.

      +
      +

      For users that are interested in soldering to the edge of the board, we recommend soldering headers to the PTHs on the breakout for a permanent connection and using jumper wires. Of course, you could also solder wires to the breakout board as well. For a temporary connection during prototyping, you can use IC hooks like these.

      + +

      MicroSD Card

      +

      If all you want to do is display your sensor readings in a serial terminal or monitor (connected via USB-C) then, strictly, you don’t need to add a microSD card. But of course the whole point of the DataLogger IoT is that it can log readings from whatever sensors you have attached to microSD card. The data is logged in easy-to-read Comma Separated Value (CSV) text format by default. You can also set the format as JSON.

      +

      You probably already have a microSD card laying around but if you need any additional units, we have plenty in the store. The DataLogger IoT can use any size microSD card as long as it is formatted correctly. Please ensure your SD card is formatted correctly. There are different software tools available. Some are built into your operating system. We recommend using the Raspberry Pi Imager Tool to easily format the memory card as FAT32 using the GUI. Flip over the DataLogger IoT and you’ll see the latching microSD card socket. Slide in your formatted SD card and it will click neatly into place. Part of the edge of the SD card will stick out when fully inserted in the microSD card socket.

      +
      + + + + +
      Inserting MicroSD card
      +
      + +

      You should only insert or remove the SD card while the power is turned off or disconnected. Removing the card while the DataLogger IoT is logging will almost certainly corrupt your data. You can tell when the board has just logged by observing the addressable RGB LED. When enabled, the LED will blink blue after it has logged one data point.

      +

      After you’ve logged some data, you will find a new file on your SD card. There may also be additional files if you manually saved the firmware or preferences to the memory card.

      +
        +
      • sfe0001.txt: This is the file that contains the CSV or JSON sensor data. The format will depend on how you configured the DataLogger's output. We use .TXT as the file type so that your computer can open it in a simple text editor. The contents are all human-readable. But, if you want to, you can rename it as .CSV or .JSON instead. The file number is incremented for the next logging session.
      • +
      • datalogger.json: This file only appears when you save the settings as your fallback storage. The file will include all preferences saved for any connected device, WiFi credentials, certificates, and keys.
      • +
      • SparkFun_DataLoggerIoT*.bin: This file only appears when you save the firmware to the microSD card. Note that the asterisk (*) is the firmware version number (i.e. SparkFunDataLoggerIoT_01.00.01.bin).
      • +
      +

      To remove the microSD card, make sure power is disconnected from the DataLogger IoT. Then press the microSD card into the microSD socket. The memory card will be ejected and you will hear a click again. Once the card is ejected, you can insert it into a microSD card adapter or USB reader to be read on a computer.

      +
      + + + + +
      Memory Card Adapter and USB Reader for microSD cards
      +
      + +

      Qwiic Sensors

      +

      If you are going to attach extra sensors or any Qwiic-enabled device to the DataLogger IoT, then those need to be connected first before attaching a USB cable. It is a good idea to only attach or disconnect Qwiic sensors when the power is turned off or disconnected. The Qwiic bus is pretty tolerant to “hot swapping”, but: disconnecting a sensor while it is in use will confuse the DataLogger IoT software (most likely each value associated with the device will remain constant); and a new sensor won’t be detected until the firmware restarts.

      +

      Plug one end of your Qwiic cable into the DataLogger IoT and plug the other end into your sensor. If you want to add extra sensors, you can simply connect them to each other in a daisy chain. You will need a Qwiic cable for each sensor. Our Qwiic Cable Kit covers all the options.

      +
      + + + + + + + + + +
      DataLogger IoT and a Qwiic DeviceDataLogger and several Qwiic-Enabled Devices Daisy Chained
      DataLogger IoT and a Qwiic-Enabled DeviceDataLogger IoT and several Qwiic-Enabled Devices Daisy Chained
      +
      + +

      Our Qwiic sensors usually all have power indicator LEDs and I2C pull-up resistors. Depending on your application, you may want or need to disable these by cutting the jumper links on the sensor circuit boards. We have a tutorial that will show you how to do that safely.

      +

      Sometimes you might want to connect more than one of the same type of sensor to the DataLogger IoT. On the I2C bus, each device needs to have a unique address. On many of our boards, there are jumpers links which you can use to change the address and some have addresses that can be configured in software. But there are some where you cannot change the address - the NAU7802 Qwiic Scale being one example.

      +

      Typically one would use a multiplexor. However, we currently do not have the DataLogger IoT configured to work with any multiplexors (i.e. Qwiic Mux Breakout).

      +
      +

      Note

      +

      Currently the Qwiic Mux is not compatible with the DataLogger IoT.

      +
      +

      LiPo Battery

      +
      +

      Battery Polarity

      +

      Please make sure that you use one of our recommended Lithium Ion batteries. Some batteries use the same JST connector as ours but have the opposite polarity. Connecting one of these to your DataLogger IoT will destroy it. If you are going to use your own battery, it is up to you to ensure it has the correct polarity.

      +
      +

      Now is a good time to attach a LiPo battery, if you want the DataLogger IoT to keep logging when you disconnect USB-C.

      +
      + + + + +
      LiPo Battery Inserted
      +
      + +

      You can connect one of our standard single cell LiPo batteries to the DataLogger IoT and power it for hours, days or weeks depending on what sensors you have attached and how often you log data. The DataLogger IoT has a built-in charger too which will charge your battery at 500mA when USB-C is connected. Please make sure your battery capacity is at least 500mAh (0.5Ah); bad things will happen if you try to charge our smallest batteries at 500mA. The yellow CHG charging LED will light up while the battery is charging and will go out once charging is complete.

      +
      +

      Warning

      +

      The MCP73831 charge IC on the board is used on a few SparkFun products. For more information about the CHG status LED, we recommend taking look at the Hardware Overview. We also recommend taking a look at this tutorial for Single Cell LiPo Battery Care.

      +
      +

      USB Cable

      +

      The USB-C connector provides power to the DataLogger IoT and acts as a serial interface for configuration and data display.

      +
      + + + + +
      Insert USB
      +
      + +

      If you are going to use a microSD card to store your data, and why wouldn’t you, then insert that first before attaching your USB cable. You should only insert or remove the SD card while the power is turned off or disconnected. Removing the card while the DataLogger IoT is logging will almost certainly corrupt your data.

      +

      Likewise, it is a good idea to only attach or disconnect Qwiic sensors when the power is turned off or disconnected. The Qwiic bus is pretty tolerant to “hot swapping”, but: disconnecting a sensor while it is in use will confuse the DataLogger IoT software; and a new sensor won’t be detected until the firmware restarts.

      +

      Depending on what ports your computer has available, you will need one of the following cables:

      + +

      Use the cable to connect your DataLogger IoT to your computer and you will see the LEDs light as the DataLogger IoT starts up. The addressable RGB RGB LED will light up green for a second or two while the DataLogger IoT configures itself. It will flash blue while data is being logged to the microSD card. If you have jumped the gun and have a LiPo battery already connected, the yellow CHG charging LED may light up too.

      +

      If the addressable RGB LED does not light up, your DataLogger IoT is probably in deep sleep following a previous logging session. Pressing the RST reset button will wake it.

      +

      You’ll find full instructions on how to configure the DataLogger IoT later in this tutorial.

      +

      Standoffs

      +

      For users interested in stacking the Qwiic-enabled device on the DataLogger IoT or mounting in an enclosure, you will need some standoffs to mount the boards. When mounting, note that all four mounting holes are not positioned exactly for a 1.0"x1.0" sized Qwiic board. Only two of the four mounting holes are compatible for a 1.0"x1.0" sized Qwiic board. For example the image below shows the boards stacked on each side of the DataLogger IoT. On top, the Qwiic GPS (SAM-M10Q) breakout was also able to stack by rotating the board slightly and aligning the mounting holes on the 1.6"x1.6" sized board to the other mounting holes

      +
      + + + + +
      Qwiic-enabled boards connected and stacked on the DataLogger Using Standoffs
      +
      + +

      Preparing Your MicroSD card

      +

      Not all microSD cards are created equal. The capacity, read/write speed, and format vary depending on the manufacturer. In order to log data to the microSD card, you will need to ensure that your memory card is formatted as FAT32. You can also use FAT16. If the memory card is formatted as a different memory card, the DataLogger IoT will not be able to recognize the microSD card.

      +

      Checking MicroSD Card Format

      +

      While you can simply insert the microSD card into your DataLogger IoT and start logging, there may be a chance that the it will not recognize the memory card due to the format.

      +

      Checking MicroSD Card Format - Windows

      +

      To check to see if it is the correct format on a Windows you could head to the drive, right click, and select Properties.

      +
      + + microSD Card Properties +
      + +

      Once the properties are open, you should be able to tell what file system that the memory card uses. In this case, it was exFAT which is not compatible with the DataLogger IoT. You will need to reformat the memory card since it is not formatted as FAT32.

      +
      + + Check File System Windows +
      + +

      Checking MicroSD Card Format - macOS

      +

      To check to see if it is the correct format on a macOS, you could head to the drive on your desktop. Then right click, and select Get Info.

      +
      + + Get Info on MicroSD Card +
      + +

      A window will pop up indicating the microSD card properties. Under General: > Format:, you should be able to tell what file system that the memory card uses. In this case, it was exFAT which is not compatible with the DataLogger IoT. You will need to reformat the memory card since it is not formatted as FAT32.

      +
      + + exFAT +
      + +

      Download Raspberry Pi Imager

      +

      There are a few methods and programs available to reformat your microSD card as a FAT32. We found it easier to use the Raspberry Pi Imager Tool. Of course, you will only be using the tool to erase the contents of the microSD card and formatting it as a FAT32 system. You will not actually flash any image to the memory card. Click on the button below to download the tool from the Raspberry Pi Foundation. It is supported on Windows, macOS, and Ubuntu for x86.

      + + +

      Formatting as FAT32 using the Raspberry Pi Imager

      +

      After downloading and installing the software, open the Raspberry Pi Imager.

      +
      + + Raspberry Pi Imager +
      + +

      Under "Operating System", select "Erase" to "format card as FAT32."

      +
      + + Raspberry Pi Imager - Erase : Format as FAT32 +
      + +

      Under "Storage", select the drive that the microSD card appeared as on your computer.

      +
      + + Raspberry Pi Imager - Select Storage Drive +
      + +

      When ready, select "Write". After a few minutes, the microSD card should be formatted with FAT32.

      +
      + + Raspberry Pi Imager - Write +
      + +

      Once the memory card has finished formatting, eject the microSD from your computer. To check to see if the microSD card is formatted as FAT32, you can check its properties as explained earlier with your operating system. Below shows a screenshot from a Windows and macOS showing that the microSD card reformatted as a FAT32 file system.

      +
      + + + + + +
      + Check File System Windows + Check File System macOS
      +
      + +

      Configuration

      + + +

      Configuring the settings is as easy as opening a serial menu. You can use any serial monitor or terminal emulator to quickly and easily change and store the DataLogger IoT settings via its USB-C interface.

      +

      There are plenty of free alternatives out there to configure the DataLogger IoT. For the scope of this tutorial we will be using Tera Term.

      + +
      +

      Note

      +

      You will need a serial terminal client that supports edit characters. Most if not all modern serial terminal programs will have the ability to support interactive edits. Unfortunately, we have not had any success with CoolTerm. We have tested the DataLogger IoT with Tera Term, Minicom, and Screen.

      +
      +

      If this is the your first time using a terminal window, We recommend checking out the tutorial below for more information on serial terminal basics.

      + + + + +

      The above guides will show you how to open the correct port for the DataLogger IoT and how to set the baud rate to 115200 baud. You can change the DataLogger IoT's baud rate through the configuration menus too should you need to.

      +
      +

      Note

      +

      For users with an Arduino IDE, you could also use the Arduino Serial Monitor by setting the line ending to Newline. Users will also need to CTRL + Enter when sending any character to the DataLogger IoT. However, we recommend using one of the terminals mentioned earlier.

      +
      +

      Initialization and Serial Output

      +

      Connect the DataLogger IoT to a USB cable and connect to your computer. The addressable RGB LED will light up green as it initializes. As of firmware v1.0.2.00 - build 00013e, a Startup Menu was added to the system. This allows you to change the behavior of the DataLogger at start-up. This change only affects the current system session.

      +
      + Output when DataLogger IoT - 9DoF Start-up menu +
      + +
        +
      • 'n' — Normal startup
      • +
      • 'a' — Disable I2C device auto load on startup
      • +
      • 'l' — List the I2C devices supported. This device table is discarded after auto-load
      • +
      • 'w' — Disable WiFi
      • +
      • 's' — Disable preference restore during startup
      • +
      +
      +

      Note

      +

      The amount of time the start-up menu is displayed is adjustable. This settings can be configured in the Settings/Application Settings page, under the Advanced section.

      +
      +

      You should see the following output when the board initializes:

      +
      + Output when DataLogger IoT - 9DoF is initializing +
      + +

      The messages in the serial terminal provide us with the DataLogger's configuration and will vary depending on the firmware version that is loaded on the board.

      +
        +
      • The DataLogger IoT software version (in this case is v01.02.00 - build 00013e).
      • +
      • As the DataLogger IoT is initializing, the system settings are being restored from the last saved preference.
      • +
      • There no WiFi credentials and the board has failed to connect. This output will change once you provide the WiFi credentials and are able to connect to the network.
      • +
      • There are 3x devices currently detected and they are connected through I2C through the Qwiic port and SPI. These are the on-board sensors for the DataLogger IoT. There may be more devices that are detected depending on the firmware and what is connected to the ports. Since these were recognized, they were loaded onto the DataLogger IoT.
      • +
      • The current date and time is shown (by default), the date and time is set to 1-1-1970 and 00:00:00). This value will change depending on the clock source through NTP, RTC, or a u-blox GNSS module.
      • +
      • The time the board has been running will be shown in the uptime.
      • +
      • The primary external time source that the board syncs is currently through the NTP client. This can be configured depending on your clock source.
      • +
      • The board name (in this case, it was SparkFun DataLogger IoT - 9DoF)
      • +
      • The board ID (in this case, it was SFD16C8F0D1AD6B8)
      • +
      • The microSD card has been found, the type of memory card it is, the size of the memory card, how much memory is used, and how much is available.
      • +
      • If there is a WiFi network name saved, the SSID will be shown along with information indicating whether the board was able to connect to the WiFi network. By default there is no SSID saved in memory.
      • +
      • If there is a battery connected, the LiPo Battery Fuel Guage will indicate if there is one attached to the board.
      • +
      • Parameters for low power mode will be provided indicating if deep sleep is enabled, sleep interval, and wake interval.
      • +
      • Parameters for logging are also provided for the logging interval, the format for the serial output, format for the microSD card, current saved filename, and file rotation period.
      • +
      • The board will also show the available IoT services that are enabled for the DataLogger IoT.
      • +
      • Current settings to download log files via a web interface (included in firmware v01.02.00)
      • +
      • Supported devices through Qwiic or SPI will be listed if they are connected.
      • +
      • The output will finish by telling you what devices are connected to the DataLogger IoT again.
      • +
      +
      +

      Note

      +

      As of firmware v01.02.00, there is also a compact mode! By adjusting the setting, the ESP32 will output less at startup. This settings can be configured in the Settings/Application Settings page, under the Advanced section.

      +

      + Output when DataLogger IoT - 9DoF is initializing, compact +

      +
      +

      Once the DataLogger IoT has initialized, the DataLogger IoT will begin outputting comma separated values (CSV). This is the default output that is set for the DataLogger IoT - 9DoF. Of course, you will not have as many readings on the DataLogger IoT since the 6DoF IMU and magnetometer are not populated on that version of the board.

      +
      + CSV Output on the DataLogger IoT - 9DoF v01.02.00 +
      + +
      +

      Note

      +

      Depending on your DataLogger IoT preferences, your device may output as a JSON format like the image shown below. +

      + JSON Output on the DataLogger -IoT - 9DoF +

      +
      +

      The data scrolling up the screen show what each device's output is along with their associated unit if it is available. Your mileage will vary depending on the board version that you have and what device is connected:

      +
        +
      • MAX17048.Voltage (V)
      • +
      • MAX17048.State of Charge (%)
      • +
      • MAX17048.Charge Rate (%/hr)
      • +
      • ISM330.Accel X (milli-g)
      • +
      • ISM330.Accel Y (milli-g)
      • +
      • ISM330.Accel Z (milli-g)
      • +
      • ISM330.Gyro X (milli-dps)
      • +
      • ISM330.Gyro Y (milli-dps)
      • +
      • ISM330.Gyro Z (milli-dps)
      • +
      • ISM330.Temperature (C)
      • +
      • MMC5983.X Field (Gauss)
      • +
      • MMC5983.Y Field (Gauss)
      • +
      • MMC5983.Z Field (Gauss)
      • +
      • MMC5983.Temperature (C)
      • +
      +

      The output will vary depending on what is connected so you may get additional readings in the output and it may not be in the following order listed above. The logging rate defaults to about 0.067Hz (or 15000ms), so as the data scrolls past, you will see the last value settle at about 0.067Hz.

      + +

      Right! Let's open the main menu by pressing on any key in the serial terminal program.

      +
      + DataLogger IoT Main Menu +
      + +

      You will be prompted with a few options. Once in the configuration menu, all three colors of the addressable RGB LED will turn on to produce the color white indicating that you are navigating through the menu. Before we dive into the settings, lets check out a few commands and saving settings.

      +

      Quick (!) Command Reference

      +

      As of firmware v01.02.00, commands can be executed directly from the serial console thus bypassing the serial menu system! The following commands are supported.

      +
      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      Quick Command + Command Description +
      + !about + + Display the system about page +
      + !clear-settings + + Clear the on board system preferences with a yes/no prompt +
      + !clear-settings-forced + + Clear the on board system preferences with no prompt +
      + !devices + + List the currently connected devices +
      + !factory-reset + + Perform a factory reset - presents a Y/N prompt +
      + !heap + + Display the current system heap memory usage +
      + !help + + List the available quick commands +
      + !json-settings + + For setting the device settings via a serial connection. When this command is sent, the system expects to receive a JSON settings file +
      + !log-now + + Perform a log observation event +
      + !log-rate + + If log rate measurement is enabled, the current log rate is printed +
      + !reset-device + + Reset the device - erasing any saved settings and restarting the device +
      + !reset-device-forced + + Reset the device, but without a Y/N prompt +
      + !restart + + Restart the device +
      + !restart-forced + + Restart the device without a Y/N prompt +
      + !save-settings + + Save the current settings to on-board flash +
      + !sdcard + + Output the current SD card usage statistics +
      + !systime + + Output current system time +
      + !uptime + + The uptime of the device +
      + !device-id + + The ID for the device +
      + !version + + The version of the firmware +
      + !wifi + + Output current system WiFi state +
      +
      + +

      Typing a quick command and hitting the Enter button will result in the DataLogger IoT executing the command without the need to go through the menu system. Below is an example showing the !about quick command being sent and then executing the command as the DataLogger IoT is outputting CSV values to the serial terminal.

      +
      + Quick Command Entered +
      + +

      Exiting and Saving

      +

      When exiting the menus, you will be prompted with either an x or b. You can use either character when exiting the menus as well as X or B. Note that you will need to use either of these keys when making a change in order for the DataLogger IoT to save any changes in memory. Make sure that you receive the following message indicating that the settings were saved: [I] Saving System Settings. The DataLogger IoT will the continue reading the devices and outputting the readings through the serial terminal.

      +
      + Output Save Settings +
      + +

      Cancelling Changes

      +

      You can also use any of your Esc or arrow keys (i.e. , , , ) to exit. However, using the escape or arrow keys will not save any changes in memory once the reset button is hit or whenever power is cycled.

      +
      + Output when Cancelling Changes +
      + +

      Timeout from Inactivity

      +

      The menus will slowly exit out after 2 minutes of inactivity, so if you do not press a key the DataLogger IoT will return to its previous menu. It will continue to move back until it reaches the main menu. After another additional 2 minutes of inactivity, the board will exit begin logging data again. When the menu exits from inactivity, any changes will not be saved in memory as well.

      +
      + Output when Timing Out +
      + +

      Settings

      +

      Let's start by configuring the DataLogger's system settings. Send a 1 through the serial terminal. You will have the option to adjust various settings ranging from the your preferences, time source to synchronize the date and time, WiFi network, how the device logs data, which IoT service to use, and firmware updates.

      +
      + Settings Menu Options +
      + +
      +

      Note

      +

      You may notice after entering a 1 that there is a slight delay before the DataLogger IoT responds. The delay was added to allow some time for the DataLogger IoT to receive an additional digit for any option greater than 9. If you want to head to option 1 immediately without the slight delay, you can hit the Enter key to enter the Application Settings.

      +
      +

      We'll go over each of these options below.

      +

      General: Application Settings

      +

      In the Settings Menu, send a 1 to adjust the Application Settings. As of firmware v01.00.02, users can now adjust the baud rate of the serial console output and the menu system's timeout value.

      +
      + Application Settings Options +
      + +

      In the Application Settings Menu, users will be able to configure the addressable RGB's LED through software, menu timeout, microSD card's output format, serial console's output format, terminal's baud rate, deep sleep parameters, and view the current settings of the DataLogger IoT similar to when the board was initialized. Depending on your preference and how you are logging data, you can adjust the data as CSV or JSON.

      +
        +
      • 1 LED Enabled — Enable/Disable the on-board RGB LED activity
          +
        • Accepts a boolean value:
            +
          • 1 to enable (default)
          • +
          • 0 to disable
          • +
          +
        • +
        +
      • +
      • 2 Menu Timeout — Inactivity timeout period for the menu system
          +
        • Accepts the following values:
            +
          • 1 30 Seconds = 30
          • +
          • 2 60 Seconds = 60 (default)
          • +
          • 3 2 Minutes = 120
          • +
          • 4 5 Minutes = 300
          • +
          • 5 10 Minutes = 600
          • +
          • b Back
          • +
          +
        • +
        +
      • +
      • 3 Color Output — Use color output with the Serial console. (added as of firmware v01.02.00)
          +
        • Accepts a boolean value:
            +
          • 1 to enable (default)
          • +
          • 0 to disable
          • +
          +
        • +
        +
      • +
      • 4 Board Name — A specific name for this DataLogger
          +
        • Accepts a string
        • +
        +
      • +
      • 5 SD Card Format — Enable and set the output format
          +
        • Accepts the following values:
            +
          • 1 to disable = 0
          • +
          • 2 CSV format (default) = 1
          • +
          • 3 JSON format = 2
          • +
          +
        • +
        +
      • +
      • 6 Serial Console Format — Enable and set the output format
          +
        • Accepts the following values:
            +
          • 1 to disable = 0
          • +
          • 2 CSV format (default) = 1
          • +
          • 3 JSON format = 2
          • +
          +
        • +
        +
      • +
      • 7 JSON Buffer Size — Output buffer size in bytes
          +
        • Accepts an integer between 100 to 5000 :
            +
          • 1600 bytes (default)
          • +
          +
        • +
        +
      • +
      • 8 Terminal Baud Rate — Update terminal baud rate. Changes take effect on restart.
          +
        • Accepts an unsigned integer between 1200 to 50000:
            +
          • 115200 (default)
          • +
          +
        • +
        +
      • +
      • 9 Enable System Sleep — If enabled, sleep the system
          +
        • Accepts a boolean value:
            +
          • 1 to enable
          • +
          • 0 to disable (default)
          • +
          +
        • +
        +
      • +
      • 10 Sleep Interval (sec) — The interval the system will sleep for
          +
        • Accepts an integer between 5 to 86400 :
            +
          • 30 seconds (default)
          • +
          +
        • +
        +
      • +
      • 11 Wake Interval (sec) — The interval the system will operate between sleep period
          +
        • Accepts an unsigned integer between 60 to 86400 :
            +
          • 120 seconds (default)
          • +
          +
        • +
        +
      • +
      • 12 Startup Messages Level of message output at startup
          +
        • Accepts a value between 1 to 3 :
        • +
        • 1 Normal = 0 (default)
        • +
        • 2 Compact = 1
        • +
        • 3 Disabled = 2
        • +
        +
      • +
      • 13 Startup Delay Startup Menu Delay in Seconds
          +
        • Accepts a value between 0 to 60 :
            +
          • 2 seconds (default)
          • +
          +
        • +
        +
      • +
      • 14 Device Names Name always includes the device address
          +
        • Accepts a boolean value:
            +
          • 1 to enable (default)
          • +
          • 0 to disable
          • +
          +
        • +
        +
      • +
      • 15 About... — Details about the system
      • +
      • b Back
      • +
      +
      +

      Note

      +

      Once the baud rate is changed and saved, make sure to adjust the baud rate of your serial terminal when the board is reset. If you forgot the baud rate, you can hold the BOOT button down for 20 seconds to erase the on-board preferences (besides the baud rate, this also includes any other settings that were saved) and restart the board.

      +
      +

      When finished, you will need to exit the menus so that the DataLogger IoT saves the changes. Send a b to exit out this menu, b to exit out of the DataLogger IoT settings, and x to exit out of the main menu.

      +
      + Save Settings Menu +
      + +

      General: Save Settings

      +

      In the Settings menu, send a 2 to adjust the Save Settings. As of firmware v01.01.01, the JSON output buffer size is now user configurable. This will be under option "JSON File Buffer Size" when in the Save Settings Menu.

      +
      + +
      + +

      In the Save Settings Menu, users will be able to save, restore, or clear any preferences in memory (i.e. persistent storage) or a saved file to a fallback device (i.e. microSD card). Note that any passwords and secret keys are not saved in the save settings file. You will need to manually enter those values in the file saved on the microSD card.

      +
        +
      • 1 Fallback Restore — If unable to restore settings, use the fallback source (JSON File)
          +
        • Accepts a boolean value:
            +
          • 1 to enable (default)
          • +
          • 0 to disable
          • +
          +
        • +
        +
      • +
      • 2 Fallback Save — Save settings also saves on the fallback storage (JSON File)
          +
        • Accepts a boolean value:
            +
          • 1 to enable
          • +
          • 0 to disable (default)
          • +
          +
        • +
        +
      • +
      • 3 JSON File Buffer Size — The size in bytes used for the internal I/O buffer
          +
        • Accepts an unsigned integer:
        • +
        • 6400 (default, as of firmware v01.01.01)
        • +
        +
      • +
      • 4 Save Settings — Save current settings to persistent storage
          +
        • Accepts a yes/no:
            +
          • Y or y for yes
          • +
          • N or n for no
          • +
          +
        • +
        +
      • +
      • 5 Restore Settings — Restore saved settings
          +
        • Accepts a yes/no:
            +
          • Y or y for yes
          • +
          • N or n for no
          • +
          +
        • +
        +
      • +
      • 6 Clear Settings — Erase the saved settings on the device
          +
        • Accepts a yes/no:
            +
          • Y or y for yes
          • +
          • N or n for no
          • +
          +
        • +
        +
      • +
      • 7 Save to Fallback — Save System Settings to the fallback storage (JSON File)
          +
        • Accepts a yes/no:
            +
          • Y or y for yes
          • +
          • N or n for no
          • +
          +
        • +
        +
      • +
      • 8 Restore from Fallback — Restore system settings from the fallback storage (JSON File)
          +
        • Accepts a yes/no:
            +
          • Y or y for yes
          • +
          • N or n for no
          • +
          +
        • +
        +
      • +
      • b Back
      • +
      +

      If you have the Fallback Save enabled or selected the option Save to Fallback, you will notice an additional file called datalogger.json saved in the microSD card. This is the fallback file that is saved. Using a text editor, you can edit this file to adjust the settings or provide WiFi credentials, certificates, and keys. You can use option 7 to restore the settings on your DataLogger IoT.

      +
      + Fall Back Saved Settings Saved in the MicroSD Card as a JSON File +
      + +

      When finished, you will need to exit the menus so that the DataLogger IoT saves the changes. Send a b to exit out this menu, b to exit out of the DataLogger IoT settings, and x to exit out of the main menu.

      +
      + Output Save Settings Confirmation +
      + +

      General: Time Sources

      +
      +

      Note

      +

      Make sure to connect the ESP32-WROOM to a 2.4GHz WiFi network and ensure that is not a guest network that requires you to sign in. Unfortunately, 5GHz WiFi is not supported on the ESP32-WROOM module.

      +
      +

      In the Settings Menu, send 3 to manage the time reference sources. As of firmware v01.01.01, time zone support is at the clock level, not tied to NTP. The option to adjust the Time Zone is moved to the Time Sources menu.

      +
      + Time Source Menu Options +
      + +

      In this menu, you will have options to update the primary reference clock, update interval, add a secondary reference clock, and update it's interval. By default, the primary reference clock is set to use the Network Time Protocol (NTP). To synchronization the time, you will need to connect to a 2.4GHz WiFi network in order to update the time. To add a secondary clock, make sure to connect a compatible Qwiic-enabled devices that can keep track of time (i.e. Qwiic Real Time Clock Module - RV-8803 or a Qwiic-enabled u-blox GNSS module).

      +
      +

      Note

      +

      To adjust the time zone, you will need to enter a POSIX timezone string variable. Try checking out this CSV in this GitHub repo and searching for the timezone string variable in your area. For more information about POSIX format specification check out this article from IBM.

      +
      +
        +
      • 1 The Time Zone — Time zone setting string for the device
          +
        • Accepts a string:
            +
          • MST7MDT,M3.2.0,M11.1.0 (default, as of firmware v01.01.01)
          • +
          +
        • +
        +
      • +
      • 2 Reference Clock — The current reference clock source
          +
        • Accepts the following values:
            +
          • 1 for no clock
          • +
          • 2 for NTP Client (default)
          • +
          +
        • +
        +
      • +
      • 3 Update Interval — Main clock update interval in minutes. 0 = No update
          +
        • Accepts an unsigned integer:
            +
          • 0 = No update
          • +
          • 60 seconds (default)
          • +
          +
        • +
        +
      • +
      • 4 Enable Clock Fallback — Use a valid reference clock if the primary is not available
          +
        • Accepts a boolean value:
            +
          • 1 to enable (default)
          • +
          • 0 to disable
          • +
          +
        • +
        +
      • +
      • 5 Dependant Interval — Connected depedant clock update interval in minutes. 0 = No update
          +
        • Accepts an unsigned integer:
            +
          • 0 = No update
          • +
          • 60 seconds (default)
          • +
          +
        • +
        +
      • +
      • 6 Update Connected — Update connected clocks on main clock update
          +
        • Accepts a boolean value:
            +
          • 1 to enable (default)
          • +
          • 0 to disable
          • +
          +
        • +
        +
      • +
      • b Back
      • +
      +
      +

      Note

      +

      As an alternative to using the NTP, users can also add a compatible Qwiic-enabled device that can keep track of time (i.e. Qwiic Real Time Clock Module - RV-8803 or a Qwiic-enabled u-blox GNSS module). These can be set as the primary or secondary clock.

      +

      + + + + + +
      u-blox GNSS Module Attached via QwiicQwiic Real Time Clock Module - RV-8803 Attached via Qwiic
      +

      +

      Once attached, you will be prompted with additional options to select a primary reference clock.

      +

      + Time Source Reference Clock Options +

      +

      If you are using a u-blox GNSS module, make sure that you have enough satellites in view. The option to add or configure the GNSS will not be available if there are not enough satellites in view. If you are using the Qwiic Real Time Clock Module - RV-8803, you may need to go into the device settings to manually adjust the date and time.

      +
      +

      When finished, you will need to exit the menus so that the DataLogger IoT saves the changes. Send a b to exit out this menu, b to exit out of the DataLogger IoT settings, and x to exit out of the main menu.

      +
      + Output Save Settings Confirmation +
      + +

      Network: WiFi Network

      +
      +

      Note

      +

      The ESP32-WROOM can only connect to a 2.4GHz WiFi network. Unfortunately, 5GHz is not supported on the ESP32-WROOM module.

      +
      +

      In the Settings Menu, send a 4 to configure the WiFi settings. As of firmware v01.00.02, up to 4 sets of WiFi credentials can be saved.

      +
      + WiFi Network Menu Options +
      + +

      Once you are in the WiFi Network menu, you can enable/disable WiFi and save the WiFi network credentials. Once connected to a 2.4GHz WiFi network, you can synchronize the date and time, connect to an IoT service to log data, and update the latest firmware over-the-air. Since the WiFi is turned on by default, you will simply need to save the WiFi network's name and password.

      +
        +
      • 1 Enabled — Enable or Disable the WiFi Network connection
          +
        • Accepts a boolean value:
            +
          • 1 to enable (default)
          • +
          • 0 to disable
          • +
          +
        • +
        +
      • +
      • 2 Network Name — The SSID of the WiFi network
          +
        • Accepts a string:
            +
          • For example, if my network name is "MY_NETWORK_NAME", you would manually type MY_NETWORK_NAME. When finished hit the ENTER key
          • +
          +
        • +
        +
      • +
      • 3 Password — The Password to connect to the WiFi network
          +
        • Accepts a string:
            +
          • For example, if my network name is "MY_SUPER_SECRET_PASSWORD", you would manually type MY_SUPER_SECRET_PASSWORD. Note that as you type the password, each character will be replaced with an asterisk (*). When finished hit the ENTER key.
          • +
          +
        • +
        +
      • +
      • 4 Network 2 Name — Alternative network 2 SSID
          +
        • Accepts a string:
            +
          • For example, if my network name is "MY_NETWORK_NAME_2", you would manually type MY_NETWORK_NAME_2. When finished hit the ENTER key
          • +
          +
        • +
        +
      • +
      • 5 Network 2 Password — Alternative network 2 Password
          +
        • Accepts a string:
            +
          • For example, if my network name is "MY_SUPER_SECRET_PASSWORD_2", you would manually type MY_SUPER_SECRET_PASSWORD_2. Note that as you type the password, each character will be replaced with an asterisk (*). When finished hit the ENTER key.
          • +
          +
        • +
        +
      • +
      • 6 Network 3 Name — Alternative network 2 SSID
          +
        • Accepts a string:
            +
          • For example, if my network name is "MY_NETWORK_NAME_3", you would manually type MY_NETWORK_NAME_3. When finished hit the ENTER key
          • +
          +
        • +
        +
      • +
      • 7 Network 3 Password — Alternative network 3 Password
          +
        • Accepts a string:
            +
          • For example, if my network name is "MY_SUPER_SECRET_PASSWORD_3", you would manually type MY_SUPER_SECRET_PASSWORD_3. Note that as you type the password, each character will be replaced with an asterisk (*). When finished hit the ENTER key.
          • +
          +
        • +
        +
      • +
      • 8 Network 4 Name — Alternative network 2 SSID
          +
        • Accepts a string:
            +
          • For example, if my network name is "MY_NETWORK_NAME_4", you would manually type MY_NETWORK_NAME_4. When finished hit the ENTER key
          • +
          +
        • +
        +
      • +
      • 9 Network 4 Password — Alternative network 4 Password
          +
        • Accepts a string:
            +
          • For example, if my network name is "MY_SUPER_SECRET_PASSWORD_4", you would manually type MY_SUPER_SECRET_PASSWORD_4. Note that as you type the password, each character will be replaced with an asterisk (*). When finished hit the ENTER key.
          • +
          +
        • +
        +
      • +
      • b Back
      • +
      +

      When finished, you will need to exit the menus so that the DataLogger IoT saves the changes. Send a b to exit out this menu, b to exit out of the DataLogger IoT settings, and x to exit out of the main menu.

      +
      + Output Save Settings Confirmation +
      + +

      Press the reset button or cycle power to restart the DataLogger IoT. You can also go through the menu and reset the device through software as well. Once the board is reset, the DataLogger will attempt to connect to a WiFi network. If you are successful, the output will indicate that the board connected to a WiFi network and will update the current time through a NTP Client.

      +
      + DataLogger IoT Re-initializing and Outputting WiFi Connected Message +
      + +
      +

      Note

      +

      If you have a Qwiic Dynamic NFC/RFID Tag connected to the board's Qwiic connector, you can easily update your WiFi credentials! Just make sure to save the WiFi credentials to the tag.

      +
      +
      +

      Note

      +

      If you saved your preferences to a JSON file on your microSD card's root directory, you can also save your WiFi credentials and load the system settings from the menu as well!

      +
      +

      Network: NTP Client

      +

      In the Settings menu, send a 5 to adjust the NTP Client settings. As of firmware v01.01.01, time zone support is at the clock level, not tied to the NTP. The option to adjust the Time Zone is moved to the Time Sources menu.

      +
      + NTP Client Menu Options +
      + +

      In this menu, users will have the option to enable/disable the NTP client, select the primary/secondary server, or adjust the time zone for your area.

      +
        +
      • 1 Enabled — Enable or Disable the NTP Client
          +
        • Accepts a boolean value:
            +
          • 1 to enable (default)
          • +
          • 0 to disable
          • +
          +
        • +
        +
      • +
      • 2 NTP Server One — The primary NTP Server to use
          +
        • Accepts a string:
            +
          • time.nist.gov (default)
          • +
          +
        • +
        +
      • +
      • 3 NTP Server Two — The secondary NTP Server to use
          +
        • Accepts a string:
            +
          • pool.ntp.org (default)
          • +
          +
        • +
        +
      • +
      • b Back
      • +
      +

      When finished, you will need to exit the menus so that the DataLogger IoT saves the changes. Send a b to exit out this menu, b to exit out of the DataLogger IoT settings, and x to exit out of the main menu.

      +
      + Output Save Settings Confirmation +
      + +

      Logging: Logger

      +

      In the Settings menu, send a 6 to adjust how data is logged.

      +
      + Logger Menu Options +
      + +

      In the Logger menu, users will have the option to add a timestamp, increment sample numbering, data format, or reset the sample counter. Note that the timestamp is the system clock and syncs with the reference clock that was chosen. Data from the Qwiic-enabled devices that keep track of time can also be included for each data entry by default.

      +
        +
      • 1 Timestamp Mode — Enable timestamp output and set the format of a log entry timestamp
          +
        • 1 for no timestamp (default) = 0
        • +
        • 2 for milliseconds since program start = 1
        • +
        • 3 for seconds since Epoch = 2
        • +
        • 4 for Date Time - USA Date format = 3
        • +
        • 5 for Date Time = 4
        • +
        • 6 for ISO08601 Timestamp = 5
        • +
        • 7 for ISO08601 Timestamp with Time Zone = 6
        • +
        +
      • +
      • 2 Sample Numbering — An incremental count of the current log entry
          +
        • Accepts a boolean value:
            +
          • 1 to enable
          • +
          • 0 to disable (default)
          • +
          +
        • +
        +
      • +
      • 3 Numbering Increment — Increment amount for Sample Numbering
          +
        • Accepts an unsigned integer between 1 to 10000:
            +
          • 1 (default)
          • +
          +
        • +
        +
      • +
      • 4 Output ID — Include the Board ID in the log output (added as of firmware v01.02.00)
          +
        • Accepts a boolean value:
            +
          • 1 to enable
          • +
          • 0 to disable (default)
          • +
          +
        • +
        +
      • +
      • 5 Output Name — Include the Board Name in the log output (added as of firmware v01.02.00)
          +
        • Accepts a boolean value:
            +
          • 1 to enable
          • +
          • 0 to disable (default)
          • +
          +
        • +
        +
      • +
      • 6 Rate Metric — Enable to record the logging rate data (added as of firmware v01.02.00)
          +
        • Accepts a boolean value:
            +
          • 1 to enable
          • +
          • 0 to disable (default)
          • +
          +
        • +
        +
      • +
      • 7 SD Card Format — Enable and set the output format
          +
        • Accepts an integer:
            +
          • 1 to disable = 0
          • +
          • 2 CSV format = 1 (default)
          • +
          • 3 JSON format = 2
          • +
          +
        • +
        +
      • +
      • 8 Serial Console Format — Enable and set the output format
          +
        • Accepts an integer:
            +
          • 1 to disable = 0
          • +
          • 2 CSV format = 1 (default)
          • +
          • 3 JSON format = 2
          • +
          +
        • +
        +
      • +
      • 9 System Info — Log system information (added as of firmware v01.02.00)
          +
        • Accepts a boolean value:
            +
          • 1 to enable
          • +
          • 0 to disable (default)
          • +
          +
        • +
        +
      • +
      • 10 Reset Sample Counter — Reset the sample number counter to the provided value
          +
        • Accepts an unsigned integer between 0 to 10000:
            +
          • 0 (default)
          • +
          +
        • +
        +
      • +
      • b Back
      • +
      +

      When finished, you will need to exit the menus so that the DataLogger IoT saves the changes. Send a b to exit out this menu, b to exit out of the DataLogger IoT settings, and x to exit out of the main menu.

      +
      + Output Save Settings Confirmation +
      + +

      Press the reset button or cycle power to restart the DataLogger IoT. You can also go through the menu and reset the device through software as well. Below is an example with the ISO08601 time that was added to the output.

      +
      + DataLogger IoT Re-initializing and Outputting Time in ISO08601 Time Format +
      + +

      Logging: Logging Timer

      +

      In the Settings menu, send an 7 to adjust the Logging Timer.

      +
      + Logging Timer Menu Options +
      + +

      Adjusting the interval for the Logging Timer will change the amount of time between log entries.

      +
        +
      • 1 Interval — The timer interval in milliseconds
          +
        • Accepts an integer:
            +
          • 15000 milliseconds (default)
          • +
          +
        • +
        +
      • +
      • b Back
      • +
      +

      When finished, you will need to exit the menus so that the DataLogger IoT saves the changes. Send a b to exit out this menu, b to exit out of the DataLogger IoT settings, and x to exit out of the main menu.

      +
      + Output Save Settings Confirmation +
      + +

      Logging: Data File

      +

      In the Settings menu, send an 8 to adjust the Logging Data File.

      +
      + Data File Menu Options +
      + +

      Adjusting these parameters allows you to change the filename prefix, the number the files starts at, and how often the DataLogger will create a new file on the microSD card. For example, the default file will be saved as sfe0001.txt. After 1 day, the DataLogger will rotate files by creating a new file named sfe0002.txt. The DataLogger will begin logging data in this new file. The purpose of this log rotation is to limit the size of each file prevent issues when opening large files.

      +
        +
      • 1 Rotate Period — Time between file rotation
          +
        • Accepts the following values:
            +
          • 1 for 6 hours = 6
          • +
          • 2 for 12 hours = 12
          • +
          • 3 for 1 day (24 hours) = 24 (default)
          • +
          • 4 for 2 days (48 hours) = 48
          • +
          • 5 for 1 week (168 hours) = 168
          • +
          +
        • +
        +
      • +
      • 2 File Start Number — The number the filename rotation starts with
          +
        • Accepts an unsigned integer:
            +
          • 1 (default)
          • +
          +
        • +
        +
      • +
      • 3 Filename Prefix — The prefix string for the generated filenames
          +
        • Accepts a string:
            +
          • sfe (default)
          • +
          +
        • +
        +
      • +
      • b Back
      • +
      +

      When finished, you will need to exit the menus so that the DataLogger IoT saves the changes. Send a b to exit out this menu, b to exit out of the DataLogger IoT settings, and x to exit out of the main menu.

      +
      + Output Save Settings Confirmation +
      + +

      The contents of the file will depend on how the data was saved (either CSV or JSON). Make sure that the SD Card format is enabled to either CSV or JSON with your desired device outputs turned on so that the DataLogger can save the readings.

      +

      When removing the microSD card, make sure to remove your power source. Then insert into it into microSD card adapter or USB reader. When connecting the memory card to your computer, you can use a text editor to view the saved readings. In this case, a Windows operating system was viewing the file sfe0000.txt and it was only file available in the microSD card.

      +
      + Readings Saved in Text File Shown in a Windows File Explorer +
      + +

      IoT Services: MQTT Client

      +

      In the Settings menu, send an 9 to adjust settings for the MQTT Client.

      +
      + MQTT Client Menu Options +
      + +
        +
      • 1 Enabled — Enable or Disable MQTT Client
          +
        • Accepts a boolean value:
            +
          • 1 to enable
          • +
          • 0 to disable (default)
          • +
          +
        • +
        +
      • +
      • 2 Port — The MQTT broker port to connect to
          +
        • Accepts an unsigned integer:
            +
          • 1883 (default)
          • +
          +
        • +
        +
      • +
      • 3 Server — The MQTT server to connect to
          +
        • Accepts a string
        • +
        +
      • +
      • 4 MQTT Topic — The MQTT topic to publish to
          +
        • Accepts a string
        • +
        +
      • +
      • 5 Client Name — Name of this device used for MQTT Communications
          +
        • Accepts a string
        • +
        +
      • +
      • 6 Username — Username to connect to an MQTT broker, if required.
          +
        • Accepts a string
        • +
        +
      • +
      • 7 Password — Password to connect to an MQTT broker, if required.
          +
        • Accepts a string
        • +
        +
      • +
      • 8 Buffer Size — MQTT payload buffer size. If 0, the buffer size is dynamic
          +
        • Accepts an unsigned int16:
            +
          • 0 for dynamic buffer size (default)
          • +
          +
        • +
        +
      • +
      • b Back
      • +
      +

      IoT Services: MQTT Secure Client

      +

      In the Settings menu, send an 10 to adjust settings for the MQTT Secure Client.

      +
      + MQTT Secure Client Menu Options +
      + +
        +
      • 1 Enabled — Enable or Disable MQTT Secure Client
          +
        • Accepts a boolean value:
            +
          • 1 to enable
          • +
          • 0 to disable (default)
          • +
          +
        • +
        +
      • +
      • 2 Port — The MQTT broker port to connect to
          +
        • Accepts an unsigned integer:
            +
          • 8883 (default, as of firmware v01.00.04)
          • +
          +
        • +
        +
      • +
      • 3 Server — The MQTT server to connect to
          +
        • Accepts a string
        • +
        +
      • +
      • 4 MQTT Topic — The MQTT topic to publish to
          +
        • Accepts a string
        • +
        +
      • +
      • 5 Client Name — Name of this device used for MQTT Communications
          +
        • Accepts a string
        • +
        +
      • +
      • 6 Username — Username to connect to an MQTT broker, if required.
          +
        • Accepts a string
        • +
        +
      • +
      • 7 Password — Password to connect to an MQTT broker, if required.
          +
        • Accepts a string
        • +
        +
      • +
      • 8 Buffer Size — MQTT payload buffer size. If 0, the buffer size is dynamic
          +
        • Accepts an unsigned int16:
            +
          • 0 for dynamic buffer size (default)
          • +
          +
        • +
        +
      • +
      • 9 CA Cert Filename — The File to load the certificate from
          +
        • Accepts a string
        • +
        +
      • +
      • 10 Client Cert Filename — The File to load the client certificate from
          +
        • Accepts a string
        • +
        +
      • +
      • 11 Client Key Filename — The File to load the client key from
          +
        • Accepts a string
        • +
        +
      • +
      • b Back
      • +
      +

      IoT Services: AWS IoT

      +

      In the Settings menu, send an 11 to adjust settings for the AWS IoT.

      +
      + AWS IoT Menu Options +
      + +
        +
      • 1 Enabled — Enable or Disable AWS IoT
          +
        • Accepts a boolean value:
            +
          • 1 to enable
          • +
          • 0 to disable (default)
          • +
          +
        • +
        +
      • +
      • 2 Port — The MQTT broker port to connect to
          +
        • Accepts an unsigned integer:
            +
          • 8883 (default, as of firmware v01.00.04)
          • +
          +
        • +
        +
      • +
      • 3 Server — The MQTT server to connect to
          +
        • Accepts a string
        • +
        +
      • +
      • 4 MQTT Topic — The MQTT topic to publish to
          +
        • Accepts a string
            +
          • $aws/things//shadow/update (default)
          • +
          +
        • +
        +
      • +
      • 5 Client Name — Name of this device used for MQTT Communications
          +
        • Accepts a string
        • +
        +
      • +
      • 6 Username — Username to connect to an MQTT broker, if required.
          +
        • Accepts a string
        • +
        +
      • +
      • 7 Password — Password to connect to an MQTT broker, if required.
          +
        • Accepts a string
        • +
        +
      • +
      • 8 Buffer Size — MQTT payload buffer size. If 0, the buffer size is dynamic
          +
        • Accepts an unsigned int16:
            +
          • 0 for dynamic buffer size (default)
          • +
          +
        • +
        +
      • +
      • 9 CA Cert Filename — The File to load the certificate from
          +
        • Accepts a string
        • +
        +
      • +
      • 10 Client Cert Filename — The File to load the client certificate from
          +
        • Accepts a string
        • +
        +
      • +
      • 11 Client Key Filename — The File to load the client key from
          +
        • Accepts a string
        • +
        +
      • +
      • b Back
      • +
      +

      IoT Services: ThingSpeak MQTT

      +

      In the Settings menu, send an 12 to adjust settings for ThingSpeak MQTT

      +
      + ThingSpeak MQTT Menu Options +
      + +
        +
      • 1 Enabled — Enable or Disable ThingSpeak MQTT
          +
        • Accepts a boolean value:
            +
          • 1 to enable
          • +
          • 0 to disable (default)
          • +
          +
        • +
        +
      • +
      • 2 Port — The MQTT broker port to connect to
          +
        • Accepts an unsigned integer:
            +
          • 8883 (default, as of firmware v01.00.04)
          • +
          +
        • +
        +
      • +
      • 3 Server — The MQTT server to connect to
          +
        • Accepts a string
        • +
        +
      • +
      • 4 MQTT Topic — The MQTT topic to publish to
          +
        • Accepts a string
        • +
        +
      • +
      • 5 Client Name — Name of this device used for MQTT Communications
          +
        • Accepts a string
        • +
        +
      • +
      • 6 Username — Username to connect to an MQTT broker, if required.
          +
        • Accepts a string
        • +
        +
      • +
      • 7 Password — Password to connect to an MQTT broker, if required.
          +
        • Accepts a string
        • +
        +
      • +
      • 8 Buffer Size — MQTT payload buffer size. If 0, the buffer size is dynamic
          +
        • Accepts an unsigned int16:
            +
          • 0 for dynamic buffer size (default)
          • +
          +
        • +
        +
      • +
      • 9 CA Cert Filename — The File to load the certificate from
          +
        • Accepts a string
        • +
        +
      • +
      • 10 Client Cert Filename — The File to load the client certificate from
          +
        • Accepts a string
        • +
        +
      • +
      • 11 Client Key Filename — The File to load the client key from
          +
        • Accepts a string
        • +
        +
      • +
      • 12 Channels — Comma separated list of =
          +
        • Accepts a string
        • +
        +
      • +
      • b Back
      • +
      +

      IoT Services: Azure IoT

      +

      In the Settings menu, send an 13 to adjust settings for the Azure IoT.

      +
      + Azure IoT Menu Options +
      + +
        +
      • 1 Enabled — Enable or Disable Azure IoT
          +
        • Accepts a boolean value:
            +
          • 1 to enable
          • +
          • 0 to disable (default)
          • +
          +
        • +
        +
      • +
      • 2 Port — The MQTT broker port to connect to
          +
        • Accepts an unsigned integer:
            +
          • 8883 (default, as of firmware v01.00.04)
          • +
          +
        • +
        +
      • +
      • 3 Server — The MQTT server to connect to
          +
        • Accepts a string
        • +
        +
      • +
      • 4 MQTT Topic — The MQTT topic to publish to
          +
        • Accepts a string
        • +
        +
      • +
      • 5 Client Name — Name of this device used for MQTT Communications
          +
        • Accepts a string
        • +
        +
      • +
      • 6 Username — Username to connect to an MQTT broker, if required.
          +
        • Accepts a string
        • +
        +
      • +
      • 7 Password — Password to connect to an MQTT broker, if required.
          +
        • Accepts a string
        • +
        +
      • +
      • 8 Buffer Size — MQTT payload buffer size. If 0, the buffer size is dynamic
          +
        • Accepts an unsigned int16:
            +
          • 0 for dynamic buffer size (default)
          • +
          +
        • +
        +
      • +
      • 9 CA Cert Filename — The File to load the certificate from
          +
        • Accepts a string
        • +
        +
      • +
      • 10 Client Cert Filename — The File to load the client certificate from
          +
        • Accepts a string
        • +
        +
      • +
      • 11 Client Key Filename — The File to load the client key from
          +
        • Accepts a string
        • +
        +
      • +
      • 11 Device ID — The device id for the Azure IoT device
          +
        • Accepts a string
        • +
        +
      • +
      • 12 Device Key — The device key for the Azure IoT device
          +
        • Accepts a string
        • +
        +
      • +
      • b Back
      • +
      +

      IoT Services: HTTP IoT

      +

      In the Settings menu, send an 14 to adjust settings for the Azure IoT.

      +
      + HTTP IoT Menu Options +
      + +
        +
      • 1 Enabled — Enable or Disable the HTTP Client
          +
        • Accepts a boolean value:
            +
          • 1 to enable
          • +
          • 0 to disable (default)
          • +
          +
        • +
        +
      • +
      • 2 URL — The URL to call with log information
          +
        • Accepts a string
        • +
        +
      • +
      • 3 CA Cert Filename — The File to load the certificate from
          +
        • Accepts a string
        • +
        +
      • +
      • b Back
      • +
      +

      IoT Services: MachineChat

      +

      In the Settings menu, send an 15 to adjust settings for MachineChat.

      +
      + Machine Chat Menu Options +
      + +
        +
      • 1 Enabled — Enable or Disable the HTTP Client
          +
        • Accepts a boolean value:
            +
          • 1 to enable
          • +
          • 0 to disable (default)
          • +
          +
        • +
        +
      • +
      • 2 URL — The URL to call with log information
          +
        • Accepts a string
        • +
        +
      • +
      • 3 CA Cert Filename — The File to load the certificate from
          +
        • Accepts a string
        • +
        +
      • +
      • b Back
      • +
      +

      IoT Services: Arduino Cloud

      +
      +

      Arduino

      +

      At the time of writing, Arduino's IoT service was referred to as the "Arduino IoT Cloud." Arduino updated the service with a different UI and is now referring to the service as the "Arduino Cloud"." When referencing the Arduino IoT or Arduino IoT Cloud in this tutorial, we are referring to the Arduino Cloud.

      +
      +

      In the Settings menu, send an 16 to adjust settings for Arduino Cloud. This feature was added as of firmware v01.01.01.

      +
      + Arduino Cloud Menu Options +
      + +
        +
      • 1 Enabled — Enable or Disable the Arduino IoT Client
          +
        • Accepts a boolean value:
            +
          • 1 to enable
          • +
          • 0 to disable (default)
          • +
          +
        • +
        +
      • +
      • 2 Thing Name — The Thing Name to use for the IoT Device connection
          +
        • Accepts a string
        • +
        +
      • +
      • 3 Thing ID — The Thing ID to use for the IoT Device connection
          +
        • Accepts a string
        • +
        +
      • +
      • 4 API Client ID — The Arduino Cloud API Client ID
          +
        • Accepts a string
        • +
        +
      • +
      • 5 API Secret — The Arduino Cloud API Secret
          +
        • Accepts a string
        • +
        +
      • +
      • 6 Device Secret — The Arduino IoT Device Secret
          +
        • Accepts a string
        • +
        +
      • +
      • 7 Device ID — The Arduino IoT Cloud Device ID
          +
        • Accepts a string
        • +
        +
      • +
      • b Back
      • +
      +

      IoT Web Server

      +

      As of firmware v01.02.00, log files can be viewed and downloaded using the IoT Web Server feature if mDNS (multicast DNS) is supported on your network. This functionality is accessed via the Settings Menu, Type 17 to enter the System Update menu. Once this menu entry is selected, the following menu options are presented:

      +
      + IoT Web Server Options +
      + +
        +
      • 1 Enabled — Enabled or Disable the Web Server
          +
        • Accepts a boolean value
            +
          • 1 to enable
          • +
          • 0 to disable (default)
          • +
          +
        • +
        +
      • +
      • 2 Username — Web access control. Leave empty to disable authentication
          +
        • Accepts a string
        • +
        +
      • +
      • 3 Password — Web access control.
          +
        • Accepts a string
        • +
        +
      • +
      • 4 mDNS Support — Enable a name for the web address this device
          +
        • Accepts a boolean value
            +
          • 1 to enable
          • +
          • 0 to disable (default)
          • +
          +
        • +
        +
      • +
      • 5 mDNS Name — mDNS Name used for this device address
          +
        • Accepts a string
            +
          • dataloggerXXXXX, where XXXXX is the taken from the last 5x characters from your DataLogger IoT's board ID (default)
          • +
          +
        • +
        +
      • +
      • b Back
      • +
      +
      +

      Note

      +

      You will need to make sure that the ESP32 is on the same network as your computer in order to access the log files.

      +
      +
      +

      Note

      +

      When authentication is enabled, some browsers might require a second login depending on user settings.

      +
      +
      +

      Note

      +

      The SparkFun Datalogger IoT requires restarting if the web interface is enabled.

      +
      +

      For more information on how to use this feature, check out the section on viewing and downloading log files using the IoT web server.

      + + +

      Advanced: System Update

      +

      New sensors and features are being added all the time and we've made it really easy for you to keep your DataLogger IoT up to date. The System Update option provides the following functionality to the end user:

      +
        +
      • Restart the device
      • +
      • Performing a Factory Reset on the device
      • +
      • Updated the device firmware from a file on an SD Card.
      • +
      +
      +

      Note

      +

      What's going on here?!? This tutorial was updated for firmware version 01.02.00!!! You will notice this menu option has changed to 18 !!!

      +
      +

      This functionality is accessed via the Settings Menu, which is required to use this capability. Type 18 to enter the System Update menu. Once this menu entry is selected, the following menu options are presented:

      +
      + System Update Menu Options +
      + +
        +
      • 1 Device Restart — Restart/reboot the device
          +
        • Accepts the following values:
            +
          • Y or Y to restart or reboot the device using the current firmware and system preferences
          • +
          • N or n to cancel
          • +
          +
        • +
        +
      • +
      • 2 Factory Reset — Erase all settings and revert to original firmware
          +
        • Accepts the following values:
            +
          • Y or Y to factory reset the device
          • +
          • N or n to cancel
          • +
          +
        • +
        +
      • +
      • 3 Update Firmware - SD Card — Update the firmware from the SD card
          +
        • Accepts firmware in the /root directory of the microSD card with the file naming pattern SparkFunDataLoggerIoT*.bin, where the asterisk * is the firmware version number (i.e. SparkFunDataLoggerIoT_01.00.01.bin).
        • +
        +
      • +
      • 4 Update Firmware - OTA — Update the firmware over-the-air
          +
        • Connects to a server and searches for the latest firmware that is available. Note that you must be connected to a WiFi network to be able to update the board over-the-air.
        • +
        • Accepts the following values if there is new firmware available.
            +
          • Y or Y to update over-the-air
          • +
          • N or n to cancel
          • +
          +
        • +
        +
      • +
      • b Back
      • +
      +

      When finished, you will need to exit the menus so that the DataLogger IoT saves the changes. Send a b to exit out this menu, b to exit out of the DataLogger IoT settings, and x to exit out of the main menu.

      +
      + Output Save Settings Confirmation +
      + +

      For more information on how to update firmware manually or over-the-air, check out the section under Examples: Updating Firmware.

      + + +

      Device Settings

      +

      In the Main Menu, send a 2 through the serial terminal to adjust the devices settings.

      +
      + Device Settings Menu Options +
      + +

      This will bring up the connected devices that are currently available. You can configure each device and enable/disable each output. Below is a sample of the on-board devices available for the DataLogger IoT - 9DoF when only the MAX17048, ISM330, and MMC5983 are connected. As the DataLogger IoT - 9DoF initializes, the board will populate additional devices in this window if they are detected. Your mileage will vary depending on what is connected. On the DataLogger IoT you will not see the ISM330 or MMC5983 as an option since the 6DoF IMU and magnetometer are not populated on that version of the board.

      +
        +
      • 1 MAX17048 — MAX17048 LiPo Battery Fuel Gauge
          +
        • 1 Voltage (V) — Battery voltage (Volts)
            +
          • 1 to enable Voltage (V) (default)
          • +
          • 2 to disable Voltage (V)
          • +
          +
        • +
        • 2 State of Charge (%) — Battery state of charge (%)
            +
          • 1 to enable state of charge (%) (default)
          • +
          • 2 to disable state of charge (%)
          • +
          +
        • +
        • 3 Charge Rate (%/hr) — Battery charge change rate (%/hr)
            +
          • 1 to enable change rate (%/hr) (default)
          • +
          • 2 to disable change rate (%/hr)
          • +
          +
        • +
        +
      • +
      • 2 ISM330 — ISM330 Inertial Measurement Unit
          +
        • 1 Accel Data Rate (HZ) — Accelerometer Data Rate (Hz)
            +
          • 1 for Off
          • +
          • 2 for 12.5 Hz
          • +
          • 3 for 26 Hz
          • +
          • 4 for 52 Hz
          • +
          • 5 for 104 Hz (default)
          • +
          • 6 for 208 Hz
          • +
          • 7 for 416 Hz
          • +
          • 8 for 833 Hz
          • +
          • 9 for 1666 Hz
          • +
          • 10 for 3332 Hz
          • +
          • 11 for 6667 Hz
          • +
          • 12 for 1.6 Hz
          • +
          +
        • +
        • 2 Accel Full Scale (g) — Accelerometer Full Scall (g)
            +
          • 1 for 2 g
          • +
          • 2 for 16 g
          • +
          • 3 for 4 g (default)
          • +
          • 4 for 8 g
          • +
          +
        • +
        • 3 Gyro Data Rate (Hz) — Gyro Data Rate (Hz)
            +
          • 1 for Off
          • +
          • 2 for 12.5 Hz
          • +
          • 3 for 26 Hz
          • +
          • 4 for 52 Hz
          • +
          • 5 for 104 Hz (default)
          • +
          • 6 for 208 Hz
          • +
          • 7 for 416 Hz
          • +
          • 8 for 833 Hz
          • +
          • 9 for 1666 Hz
          • +
          • 10 for 3332 Hz
          • +
          • 11 for 6667 Hz
          • +
          +
        • +
        • 4 Gyro Full Scale (dps) — Gyro Full Scale (dps)
            +
          • 1 for 125 dps
          • +
          • 2 for 250 dps
          • +
          • 3 for 500 dps (default)
          • +
          • 4 for 1000 dps
          • +
          • 5 for 2000 dps
          • +
          • 6 for 4000 dps
          • +
          +
        • +
        • 5 Accel Filter LP2 — Accelerometer Filter LP2
            +
          • 1 to enable (default)
          • +
          • 2 to disable
          • +
          +
        • +
        • 6 Gyro Filter LP1 — Gyro Filter LP1
            +
          • 1 to enable (default)
          • +
          • 2 to disable
          • +
          +
        • +
        • 7 Accel Slope Filter — Accelerometer Slope Filter
            +
          • 1 for ODR/4
          • +
          • 2 for ODR/10
          • +
          • 3 for for ODR/20
          • +
          • 4 for ODR/45
          • +
          • 5 for ODR/100 (default)
          • +
          • 6 for ODR/200
          • +
          • 7 for ODR/400
          • +
          • 8 for ODR/800
          • +
          +
        • +
        • 8 Gyro LP1 Filter Bandwidth — Gyro LP1 Filter Bandwidth
            +
          • 1 Ultra Light
          • +
          • 2 Very Light
          • +
          • 3 Light
          • +
          • 4 Medium (default)
          • +
          • 5 Strong
          • +
          • 6 Very Strong
          • +
          • 7 Aggressive
          • +
          • 8 Extreme
          • +
          +
        • +
        • 9 Accel X (milli-g) — Accelerometer X (milli-g)
            +
          • 1 to enable
          • +
          • 2 to disable
          • +
          +
        • +
        • 10 Accel Y (milli-g) — Accelerometer Y (milli-g)
            +
          • 1 to enable
          • +
          • 2 to disable
          • +
          +
        • +
        • 11 Accel Z (milli-g) — Accelerometer Z (milli-g)
            +
          • 1 to enable
          • +
          • 2 to disable
          • +
          +
        • +
        • 12 Gyro X (milli-dps) — Gyro X (milli-g)
            +
          • 1 to enable
          • +
          • 2 to disable
          • +
          +
        • +
        • 13 Gyro Y (milli-dps) — Gyro Y (milli-g)
            +
          • 1 to enable
          • +
          • 2 to disable
          • +
          +
        • +
        • 14 Gyro Z (milli-dps) — Gyro Z (milli-g)
            +
          • 1 to enable
          • +
          • 2 to disable
          • +
          +
        • +
        • 15 Temperature (C) — The temperature in degrees C
            +
          • 1 to enable
          • +
          • 2 to disable
          • +
          +
        • +
        +
      • +
      • 3 MMC5983 — MMC5983 Magnetometer
          +
        • 1 Filter Bandwidth (Hz) — The filter bandwidth in Hz
            +
          • 1 100 Hz (default)
          • +
          • 2 200 Hz
          • +
          • 3 400 Hz
          • +
          • 4 800 Hz
          • +
          +
        • +
        • 2 Auto-Reset — Auto-Reset
            +
          • 1 to enable
          • +
          • 2 to disable
          • +
          +
        • +
        • 3 X Field (Gauss) — The X Field strength in Gauss
            +
          • 1 to enable
          • +
          • 2 to disable
          • +
          +
        • +
        • 4 Y Field (Gauss) — The Y Field strength in Gauss
            +
          • 1 to enable
          • +
          • 2 to disable
          • +
          +
        • +
        • 5 Z Field (Gauss) — The Z Field strength in Gauss
            +
          • 1 to enable
          • +
          • 2 to disable
          • +
          +
        • +
        • 6 Temperature (C) — The ambient temperature in degrees C
            +
          • 1 to enable
          • +
          • 2 to disable
          • +
          +
        • +
        +
      • +
      • b Back
      • +
      +

      When finished, you will need to exit the menus so that the DataLogger IoT saves the changes. Send a b to exit out this menu, b to exit out of the DataLogger IoT settings, and x to exit out of the main menu.

      +
      +  Output Save Settings Confirmation +
      + +
      +

      Warning

      +

      As you connect additional devices to the DataLogger IoT, the values associated with each device in this menu will change! Make sure to check your device settings menu after additional devices are attached should you decide to configure the additional devices and enable/disable their outputs. +

      + Additional Devices Connected and Showing up as Menu Options +

      +
      +

      Example - Connecting to a WiFi Network

      +
      +

      Note

      +

      The ESP32-WROOM can only connect to a 2.4GHz WiFi network. Unfortunately, 5GHz is not supported on the ESP32-WROOM module.

      +
      +

      To take advantage of syncing the DataLogger to the Network Time Protocol (NTP), logging data to an IoT service, or updating firmware OTA, you will need to connect to a 2.4GHz WiFi network.

      +

      Open a Serial Terminal, connect to the COM port that your DataLogger enumerated to, and set it to 115200 baud. In this case, we connected to COM13. Press any key to enter the Main Menu. Type 1 to enter the Settings menu. Then send a 4 to configure the WiFi settings.

      +
      + WiFi Network Menu Options +
      + +

      Send a 2 to set the WiFi Network Name. You'll be prompted to set the network name. In this case, the network name is sparkfun. Once you enter the name, hit the enter key.

      +
      + + Configure WiFi Network +
      + +

      Send a 2 to set the WiFi password. You'll be prompted to set the password. As you send the password, each character will be masked by a asterisk (i.e. *) Once you enter the name, hit the enter key.

      +
      + + Configure WiFi Password +
      + +

      Follow the prompts to exit out of the menu properly so that the DataLogger IoT saves the settings.

      +
      + Output Save Settings Confirmation +
      + +

      Once you see the message [I] Saving System Settings and data on the output, hit the reset button on the board. You can also use the menu to perform a device restart, however you will need to ensure that you receive the message indicating that the settings were saved before restarting the device.

      +

      Once the device has restarted, the DataLogger will provide an output as it is initializing. If the WiFi credentials are saved properly, you will receive a message indicating that your chosen network is connected to your WiFi network. If the time source is set to the default NTP client, you will also notice that the time will be synced to the latest date and time!

      +
      + + Configure WiFi Password +
      + +

      Example - Adding a Timestamp to Data

      +

      Open a Serial Terminal, connect to the COM port that your DataLogger enumerated to, and set it to 115200 baud. In this case, we connected to COM13. Press any key to enter the Main Menu. Type 1 to enter the Settings menu. Then send a 6 to adjust how data is logged.

      +
      + Logger Menu Options +
      + +

      Send a 1 to configure the timestamp for each log entry. The settings in this menu relate to the system clock and is dependent on the reference clock. You'll be prompted with different formats. In this example, we sent a a 4 to have a timestamp with the USA date format.

      +
      + + Configure Timestamp +
      + +

      Follow the prompts to exit out of the menu properly so that the DataLogger IoT saves the settings. Once you see the message [I] Saving System Settings, the DataLogger IoT will add a timestamp with your preferred format to each log entry. Assuming that you have the output set to the serial terminal, you should see the timestamp attached to the output after the system settings are saved like the image below.

      +
      + + Timestamped Data +
      + +

      Example - Factory Reset

      +

      A factory reset will move the boot firmware of the device to the firmware imaged installed at the factory and erase any on-board stored settings on the device. This is helpful if an update fails, or an update has issues that prevent proper operations.

      +

      This option is available on ESP32 devices that contained a factory firmware partition that contains a bootable firmware image. Consult the specific product's production and build system for further details.

      +

      Open a Serial Terminal, connect to the COM port that your DataLogger enumerated to, and set it to 115200 baud. In this case, we connected to COM13. Press any key to enter the Main Menu. Type 1 to enter the Settings menu. Then type 16 to enter the System Update Menu. Finally, type 2 to enter the Factory Reset option.

      +

      The user is presented a prompt to continue. To launch a factory reset, the value of Y should be entered. To abort the update, enter n or press the Esc key.

      +
      + + Reset Prompt +
      + +

      When a Y is entered, the system performs the following:

      +
        +
      • Set the boot image to the Factory installed firmware
      • +
      • Erase any settings stored in the on-board flash memory
      • +
      • Reboot the device
      • +
      +
      + + Reset Reboot +
      + +

      Example - Updating Firmware

      +
      +

      Danger

      +

      Please think very carefully before uploading any Arduino sketches to your DataLogger IoT.

      +

      You will overwrite the DataLogger IoT firmware, leaving it unable to update or restore itself.

      +

      Each DataLogger IoT comes pre-programmed with amazing firmware which can do so much. It is designed to be able to update itself and restore itself if necessary. But it can not do that if you overwrite the firmware with any Arduino sketch. It is just like erasing the restore partition on your computer hard drive. Do not do it - unless you really know what you are doing.

      +

      Really. We mean it.

      +
      +

      Firmware Update - SD Card

      +

      This action enables the ability to update the onboard firmware to an image file contained an SD card. This user is presented a list of available firmware images files contained in root directory of the on-board SD card, and updates the board to the selected file.

      +

      This option is available on ESP32 devices that contained two update firmware (OTA type) partitions within the on-board device flash memory. Consult the specific products production and build system for further details.

      +

      To download the latest firmware and update through the microSD card, you will need to head to the GitHub repository containing the firmware. Select the firmware version and download.

      + + +

      Once downloaded, insert the microSD card into a card reader or USB adapter. Then move the files into the memory card's root directory. Below shows an image of v01.00.01 and v01.00.02 on a Windows OS.

      +
      + + microSD Card Firmware Files +
      + +

      Once the files are copied to the memory card, eject the microSD card from your computer. Insert the card back into the DataLogger IoT's microSD card socket. Connect the DataLogger IoT to your computer using a USB cable.

      +
      + + + + + +
      Insert MicroSD CardDataLogger IoT Connecting USB
      +
      + +
      +

      Note

      +

      What's going on here?!? This tutorial was updated for firmware version 01.01.00!!! You will notice this menu option has changed to 17 !!!

      +
      +

      Open a Serial Terminal, connect to the COM port that your DataLogger enumerated to, and set it to 115200 baud. In this case, we connected to COM13. Press any key to enter the Main Menu. Type 1 to enter the Settings menu. Then type 17 to enter the System Update Menu. Finally, type 3 to update the firmware from the microSD card. You should see an image similar to the output below.

      +
      + + Firmware Files +
      + +

      The system will search the root directory of the on-board SD card for available firmware files. The firmware files are selected using the following criteria:

      +
        +
      • The file is contained in the root "/" folder of the SD card.
      • +
      • The filename has a ".bin" extension.
      • +
      • The filename starts with a specified name prefix. The prefix is optional and is set by the developers at SparkFun using this action.
          +
        • For example, the DataLogger IoT boards use a prefix value of "SparkFun_DataLoggerIoT_".
        • +
        +
      • +
      +

      Once a file is selected, the system new firmware is read off the SD card and written to the device.

      +
      + + Updating +
      + +

      And once updated, the system is rebooted and starts using the new firmware image!

      +
      + + Reboot +
      + +

      Firmware Update - Over-the-Air (OTA)

      +

      This action enables the ability to update the onboard firmware to an image file contained on an update server, which is accessed via the WiFi network the system is connected to. This Over-The-Air (OTA) capability contacts the systems update server and searches for newer firmware (later version) for the specific board.

      +

      This option is available on ESP32 devices that contained two update firmware (OTA type) partitions within the on-board device flash memory. Consult the specific products production and build system for further details.

      +

      If you have not already, connect the DataLogger IoT to your computer using a USB cable.

      +
      + + + + +
      DataLogger IoT Connecting USB
      +
      + +

      Open a Serial Terminal, connect to the COM port that your DataLogger enumerated to, and set it to 115200 baud. In this case, we connected to COM13. Press any key to enter the Main Menu. Type 1 to enter the Settings menu. Then type 16 to enter the System Update Menu. Finally, type 4 to update the firmware over-the-air.

      +
      System Update Menu Options +
      + +

      When this option is selected, the system will contact the update server and search for available upgrade firmware, selecting the latest version available. If a newer version is found, a prompt is presented to confirm the upgrade.

      +
      + + Select OTA Update +
      + +
      +

      Note

      +

      For the upgrade option to occur, a the system must be connected to a network and have access to the firmware OTA server.

      +
      +

      Typing Y (or hitting enter) starts the update operation. As the firmware is downloaded, the percent complete status is updated. If connectivity fails during the download, the operation is halted and the update aborted.

      +
      + + OTA Update Downloading +
      + +

      Once the update file is downloaded, it is verified as being the correct file. Once verified, the system is rebooted and starts using the new firmware image! You will notice the firmware version change as the DataLogger IoT initializes.

      +
      + + Updated OTA and Rebooted +
      + +

      Example - MQTT

      +

      Connecting and Publishing Data to MQTT

      +

      One of the key features of the DataLogger IoT is it's simplified access to IoT service providers and servers. This document outlines how output from a DataLogger device is sent to an MQTT Broker.

      +
      + MQTT Logo +
      + Image Courtesy of MQTT +
      + +

      The following is covered by this document:

      +
        +
      • Overview of the MQTT connection
      • +
      • How a user configures and uses the MQTT connection
      • +
      • MQTT examples
      • +
      +

      General Operation

      +

      MQTT connectivity allows data generated by the DataLogger IoT to be published to an MQTT Broker under a user configured topic. MQTT is an extremely flexible and low overhead data protocol that is widely used in the IoT field.

      +

      The general use pattern for MQTT is that data is published to a topic on a MQTT broker. The data is then sent to any MQTT client that has subscribed to the specified topic.

      +
      + MQTT Overview +
      + +

      The DataLogger IoT supports MQTT connections, allowing an end user to enter the parameters for the particular MQTT Broker for the application to publish data to. When the application outputs data to the broker, the DataLogger IoT publishes the available information to the specified "topic" with the payload that is a JSON document.

      +

      Data Structure

      +

      Data is published to the MQTT broker as a JSON object, which contains a collection of sub-objects. Each sub-object represents a data source in the sensor, and contains the current readings from that source.

      +

      The following is an example of the data posted - note, this representation was "pretty printed" for readability.

      +
      {
      +  "MAX17048": {
      +    "Voltage (V)": 4.304999828,
      +    "State Of Charge (%)": 115.0625,
      +    "Change Rate (%/hr)": 0
      +  },
      +  "CCS811": {
      +    "CO2": 620,
      +    "VOC": 33
      +  },
      +  "BME280": {
      +    "Humidity": 25.03613281,
      +    "TemperatureF": 79.64599609,
      +    "TemperatureC": 26.46999931,
      +    "Pressure": 85280.23438,
      +    "AltitudeM": 1430.44104,
      +    "AltitudeF": 4693.04834
      +  },
      +  "ISM330": {
      +    "Accel X (milli-g)": -53.31399918,
      +    "Accel Y (milli-g)": -34.03800201,
      +    "Accel Z (milli-g)": 1017.236023,
      +    "Gyro X (milli-dps)": 542.5,
      +    "Gyro Y (milli-dps)": -1120,
      +    "Gyro Z (milli-dps)": 262.5,
      +    "Temperature (C)": 26
      +  },
      +  "MMC5983": {
      +    "X Field (Gauss)": -0.200622559,
      +    "Y Field (Gauss)": 0.076416016,
      +    "Z Field (Gauss)": 0.447570801,
      +    "Temperature (C)": 29
      +  }
      +}
      +
      +

      MQTT Broker Connection Setup

      +

      To connect to a MQTT Broker, the following information is needed:

      +
        +
      • The server name/address
      • +
      • The server port
      • +
      • The topic to post to
      • +
      • [optional] The name of the device/Client name publishing the data
      • +
      • [optional] A username - if required
      • +
      • [optional] A password - if required
      • +
      +

      These values are set using the standard DataLogger methods - the interactive menu system, or a JSON file.

      +

      MQTT Menu System

      +

      We'll need to adjust the settings for the MQTT Client using the MQTT Menu System.

      +

      For users that are interested in using the menu system, open a Serial Terminal, connect to the COM port that your DataLogger enumerated to, and set it to 115200 baud. In this case, we connected to COM13. Press any key to enter the Main Menu. Type 1 to enter the Settings menu. Then type 9 to enter the MQTT Client Menu. When the menu system for the MQTT connection is presented, the following options are displayed:

      +
      + MQTT Menu +
      + +

      The options are:

      +
        +
      • Enable/Disable the connection
      • +
      • Broker Port - The standard port for mqtt is 1883
      • +
      • Broker Server - This is just the name of the server
      • +
      • MQTT Topic - A string
      • +
      • Client Name
      • +
      • Username
      • +
      • Password
      • +
      • Buffer Size
      • +
      +

      At a minimum, the Broker Port, Broker Server Name, and MQTT Topic need to be set. What parameters are required depends on the settings of the broker being used.

      +
      +

      Note

      +

      If a secure connection is being used with the MQTT broker, use the MQTT Secure Client option of the DataLogger IoT. This option supports secure connectivity.

      +
      +
      +

      Note

      +

      The Buffer Size option is dynamic by default, adapting to the size of the payload being sent. If runtime memory is a concern, set this value to a static size that supports the device operation.

      +
      +

      Once all these values are set, the system will publish data to the specified MQTT Broker, following the JSON information structure noted earlier in this document.

      +

      JSON File Entries

      +

      If a JSON file is being used as an option to import settings into the DataLogger IoT, the following entries are used for the MQTT IoT connection:

      +
      "MQTT Client": {
      +    "Enabled": false,
      +    "Port": 1883,
      +    "Server": "my-mqttserver.com",
      +    "MQTT Topic": "/sparkfun/datalogger1",
      +    "Client Name": "mysensor system",
      +    "Buffer Size": 0,
      +    "Username": "",
      +    "Password": ""
      +  },
      +
      +

      Where:

      +
        +
      • Enabled - Set to true to enable the connection.
      • +
      • Port - Set to the broker port.
      • +
      • Server - The MQTT broker server.
      • +
      • MQTT Topic - The topic to publish to.
      • +
      • Client Name - Optional client name.
      • +
      • Buffer Size - Internal transfer buffer size.
      • +
      • Username - Broker user name if being used.
      • +
      • Password - Broker password if being used.
      • +
      +
      +

      Tip

      +

      To load the values by the system at startup using a JSON file and microSD card, you will need to configure the Save Settings. This JSON file will be created with the "Save to Fallback" option. Make sure to enable the MQTT Client as well.

      +
      +

      Testing the MQTT Connection

      +

      Use of a MQTT connection is fairly straightforward - just requiring the entry of broker details into the connection settings.

      +

      To test the connection, you need a MQTT broker available. A quick method to setup a broker is by installing the mosquitto package on a Raspberry Pi computer. Our basic MQTT Tutorial provides some basic setup for a broker.

      +
      + + + + + + + +
      +
      Introduction to MQTT +
      +
      +
      + +

      This MQTT Broker Tutorial has more details, covering the setup needed for modern mosquitto configurations.

      + + +

      And once the broker is setup, the messages published by the IoT sensor are visible using the mosquitto_sub command as outlined. For example, to view messages posted to a the topic "/sparkfun/datalogger1", the following command is used:

      +
      mosquitto_sub -t "/sparkfun/datalogger1"
      +
      +

      This assumes the MQTT broker is running on the same machine, and using the default port number.

      +

      Example - AWS

      +

      Creating and Connecting to an AWS IoT Device (Thing)

      +

      One of the key features of the DataLogger IoT is it's simplified access to IoT service providers. This document outlines how an AWS IoT device is used by the DataLogger IoT.

      +
      + Powered by AWS IoT +
      + Image Courtesy of Amazon Web Services (AWS) +
      + +

      The following is covered by this document:

      +
        +
      • Device (Thing) creation in AWS
      • +
      • Securely connecting the device
      • +
      • How data is posted from the DataLogger IoT to the AWS Device via it's Shadow
      • +
      +

      Currently, the AWS IoT device connection is a single direction - used to post data from the hardware to the IoT AWS Device via the AWS IoT devices shadow. Configuration information from AWS IoT to the DataLogger IoT is currently not implemented.

      +

      General Operation

      +

      AWS IoT enables connectivity between an IoT / Edge device and the AWS Cloud Platform, implementing secure endpoints and device models within the AWs infrastructure. This infrastructure allows edge devices to post updates, status and state to the AWS infrastructure for analytics, monitoring and reporting.

      +

      In AWS IoT, an virtual representation of an actual device is created and referred to as a Thing. The virtual device/Thing is allocated a connection endpoint, security certificates and a device shadow - a JSON document used to persist, communicate and manage device state within AWS.

      +

      The actual IoT device communicates with it's AWS representation via a secure MQTT connection, posting JSON document payloads to a set of pre-defined topics. Updates are posted to the AWS IoT device shadow, which is then accessed within AWS for further process as defined by the users particular cloud implementation.

      +
      + MQTT Menu +
      + +

      Creating a Device in AWS IoT

      +

      The following discussion outlines the basic steps taken to create a Thing in AWS IoT that the DataLogger IoT can connect to. First step is to log into your AWS account and create a thing.

      + + +

      Once logged into your AWS account, select IoT Core from the menu of services.

      +
      + AWS IoT Core +
      + +

      From the IoT Core console page, under the Manage section, select All Devices > Things

      +

      On the resultant Things Page, select the Create Things button.

      +
      + AWS IoT Thing Create +
      + +

      AWS IoT will then take you through the steps to create a device. Selections made for a demo Thing are:

      +
        +
      • Create single thing
      • +
      • Thing Properties
      • +
      • Enter a name for your thing - for this example TestThing23
      • +
      • Device Shadow - select Unnamed shadow (classic)
      • +
      • Auto-generate a new certificate
      • +
      • Attach policies to certificate - This is discussed later in this document
      • +
      • Select Create thing
      • +
      +

      Upon creation, AWS IoT presents you with a list of downloadable certificates and keys. Some of these are only available at this step. The best option is to download everything presented - three of these are used by the DataLogger IoT. The following should be downloaded:

      +
        +
      • Device Certificate
      • +
      • Public Key File
      • +
      • Private Key File
      • +
      • Root CA certificates - (for example: Amazon Root CA 1 )
      • +
      +

      At this point, the new AWS IoT thing is created and listed on the AWS IoT Things Console

      +
      + New Thing Listed +
      + +

      Security Policy

      +

      To write to the IoT device, a security policy that enables this is needed, and the policy needs to be assigned to the devices certificate.

      +

      To create a Policy, select the Manage > Security > Policies menu item from the left side menu of the AWS IoT panel. Once on this page, select the Create policy button to create a new policy.

      +
      + New Policy +
      + +

      When entering the policy, provide a name that fits your need. For this example, the name NewThing23Policy is used. For the Policy document, you can manually enter the security entires, or enter them as a JSON document. The JSON document used for this example is:

      +
      {
      +  "Version": "2012-10-17",
      +  "Statement": [
      +    {
      +      "Effect": "Allow",
      +      "Action": "iot:Connect",
      +      "Resource": "*"
      +    },
      +    {
      +      "Effect": "Allow",
      +      "Action": "iot:Subscribe",
      +      "Resource": "*"
      +    },
      +    {
      +      "Effect": "Allow",
      +      "Action": "iot:Receive",
      +      "Resource": "*"
      +    },
      +    {
      +      "Effect": "Allow",
      +      "Action": "iot:Publish",
      +      "Resource": "*"
      +    },
      +    {
      +      "Effect": "Allow",
      +      "Action": "iot:GetThingShadow",
      +      "Resource": "*"
      +    },
      +    {
      +      "Effect": "Allow",
      +      "Action": "iot:UpdateThingShadow",
      +      "Resource": "*"
      +    }
      +  ]
      +}
      +
      +
      + Create Policy +
      + +

      Once the policy is created, go back to the IoT Device/Thing created above and associate this policy to the device Certificate.

      +
        +
      • Go to your device Manage > All devices > Things
      • +
      • Select the device - TestThing23 for this example
      • +
      • Select the Certificates tab
      • +
      • Select the listed Certificate (it's a very long hex number)
      • +
      • At the bottom right of the page, select the Attach policies button and select the Policy created above.
      • +
      +
      + Attach Policy +
      + +

      At this point, AWS IoT is ready for a device to connect and receive data.

      +

      AWS Configuration

      +

      The specifics for the AWS IoT Thing must be configured. This includes the following:

      +
        +
      • Server name/host
      • +
      • MQTT topic to update
      • +
      • Client Name - The AWS IoT Thing Name
      • +
      • CA Certificate Chain
      • +
      • Client Certificate
      • +
      • Client Key
      • +
      +

      Server Name/Hostname

      +

      This value is obtained from the AWS IoT Device page for the created device. When on this page, select the Device Shadows tab, and then select the Classic Shadow shadow, which is listed. Note a secure connection is used, so the port for the connection is 8883.

      +
      + Shadow Details +
      + +

      Selecting the Classic Shadow entry provides the Server Name/Hostname for the device, as well as the MQTT topic for this device.

      +
      + Shadow Details +
      + +
      +

      Note

      +

      The server name is obtained from the Device Shadow URL entry

      +
      +

      MQTT Topic

      +

      The MQTT topic value is based uses the MQTT topic prefix from above, and has the value update added to it. So for this example, the MQTT topic is:

      +
      $aws/things/TestThing23/shadow/update
      +
      +

      Client Name

      +

      This is the AWS IoT name of the thing. For the provided example, the value is TestThing23

      +

      CA Certificate Chain

      +

      This value was downloaded as a file during the creation process. The contents of this file can be passed on to the DataLogger IoT by copying the file containing the data onto a devices SD Card and setting the filename property for the DataLogger IoT.

      +

      Client Certificate

      +

      This value was downloaded as a file during the creation process. The contents of this file can be passed on to the DataLogger IoT by copying the file containing the data onto a devices SD Card and setting the filename property for the DataLogger IoT.

      +

      Client Key

      +

      This value was downloaded as a file during the creation process. The contents of this file can be passed on to the DataLogger IoT by copying the file containing the data onto a devices SD Card and setting the filename property for the DataLogger IoT.

      +

      Setting Properties

      +

      The above property values must be set on the DataLogger before use. They can be set manually by using the menu system like the previous MQTT example.

      +

      For users that are interested in using the menu system, you will need to open a Serial Terminal, connect to the COM port that your DataLogger enumerated to, and set it to 115200 baud. In this case, we connected to COM13. Press any key to enter the Main Menu. Type 1 to enter the Settings menu. Then type 11 to enter the AWS IoT Menu. When the menu system for the AWS IoT connection is presented, you will need to configure the property values as listed in the JSON file. Saving the values through the menu system will save the credentials to the ESP32's persistent memory. The following options are displayed:

      +
      + AWS IoT Menu +
      + +

      The alternative to using the menu system is a JSON file. These values can be set using a JSON file that is loaded by the system at startup. For the DataLogger IoT example outlined in this document, the entries in the settings JSON file are as follows:

      +
      "AWS IoT": {
      +    "Enabled": true,
      +    "Port": 8883,
      +    "Server": "avgpd2wdr5s6u-ats.iot.us-east-1.amazonaws.com",
      +    "MQTT Topic": "$aws/things/TestThing23/shadow/update",
      +    "Client Name": "TestThing23",
      +    "Buffer Size": 0,
      +    "Username": "",
      +    "Password": "",
      +    "CA Certificate": "",
      +    "Client Certificate": "",
      +    "Client Key": "",
      +    "CA Cert Filename": "AmazonRootCA1.pem",
      +    "Client Cert Filename": "TestThing23_DevCert.crt",
      +    "Client Key Filename": "TestThing23_Private.key"
      +  },
      +
      +

      Besides updating the Server, MQTT Topic, Client Name, CA Cert Filename, Client Cert Filename, and Client Key Filename, you will need to also ensure that the port is set to 8883. The default in previous firmware versions was 1883. As of firmware v01.00.04, the default is 8883. You will need to adjust the port value to properly connect to the AWS IoT service. Don't forget to enable AWS IoT service by setting the value to true. If the JSON file is saved in the microSD card, you can load the credentials to the DataLogger IoT.

      +
      +

      Tip

      +

      To load the values by the system at startup using a JSON file and microSD card, you will need to configure the Save Settings. This JSON file will be created with the "Save to Fallback" option. Make sure to enable the AWS IoT as well.

      +
      +

      Operation

      +

      Once the device is configured and running, updates in AWS IoT are listed in the Activity tab of the devices page. For the test device in this document, this page looks like:

      +
      + Shadow Activity +
      + +

      Opening up an update, you can see the data being set to AWS IoT in a JSON format.

      +
      + Shadow Data +
      + +

      Example - ThingSpeak

      +

      Creating and Connecting to ThingSpeak

      +

      One of the key features of the DataLogger IoT is it's simplified access to IoT service providers. This document outlines how a ThinkSpeak output is used by the DataLogger IoT.

      +
      + ThingSpeak Logo +
      + Image Courtesy of ThingSpeak +
      + +

      The following is covered by this document:

      +
        +
      • Creating a ThingSpeak Channel and MQTT Connection
      • +
      • Securely connecting the ThingSpeak
      • +
      • How data is posted from the DataLogger IoT to ThingSpeak
      • +
      +

      General Operation

      +

      ThingSpeak Structure

      +

      The structure of ThingSpeak is based off of the concept of Channels, with each channel supporting up to eight fields for data specific to the data source. Each channel is named, and has a unique ID associated with it. One what to think of it is that a Channel is a grouping of associated data values or fields.

      +

      The fields of a channel are enumerated as Field1, Field2, ..., Field8, but each field can be named to simplify data access and understanding.

      +

      As data is reported to a ThingSpeak channel, the field values are accessible for further processing or visualization output.

      +

      Data Structure

      +

      The DataLogger IoT is constructed around the concept of Devices which are often a type of sensor that can output a set of data values per observation or sample.

      +

      Mapping Data to ThingSpeak

      +

      The concept of Channels that contain Fields in ThingSpeak is similar to the Devices that contain Data within the DataLogger IoT, and this similarity is the mapping model used by the DataLogger IoT. Specifically:

      +
        +
      • Devices == Channels
      • +
      • Data == Fields
      • +
      +
      + DataLogger to ThingSpeak Mapping +
      + +

      During configuration of the DataLogger IoT, the mapping between the Device and ThingSpeak channel is specified. The data to field mapping is automatically created by the DataLogger IoT following the data reporting order from the specific device driver.

      +

      Creating a Device to a ThingSpeak Channel

      +

      The following discussion outlines the basic steps taken to create a Channel in ThingSpeak and then connect it to the DataLogger's Device. First step is to log into your ThingSpeak and create a Channel.

      + + +

      Once logged into your ThingSpeak account, select Channels > My Channels menu item and on the My Channel page, select the New Channel button.

      +
      + New Channel +
      + +

      On the presented channel page, name the channel and fill in the specific channel fields. The fields should map to the data fields reported from the Device being linked to this channel. Order is important, and is determined by looking at output of a device to the serial device (or reviewing the device driver code).

      +
      + New Channel +
      + +

      Once the values are entered, select Save Channel. ThingSpeak will now show list of Channel Stats, made up of line plots for each field specified for the channel.

      +
      +

      Note

      +

      Key note - at the top of this page is listed the Channel ID. Note this number - it is used to map a Device to a ThingSpeak Channel.

      +
      +

      Setting Up ThingSpeak MQTT

      +

      The DataLogger IoT uses MQTT to post data to a channel. From the ThingSpeak menu, select Devices > MQTT, which displays a list of your MQTT devices. From this page, select the Add a new device button.

      +

      On the presented dialog, enter a name for the MQTT connection and in the Authorize channels to access, select the channel created earlier. Once you select a channel, click the Add Channel button.

      +
      +

      Note

      +

      More channels can be added later.

      +
      +
      + MQTT on ThingSpeak +
      + +
      +

      Note

      +

      When the MQTT device is created, a set of credentials (Client ID, Username, and Password) is provided. Copy or download these values, since the password in not accessible after this step.

      +
      +

      The selected Channel is then listed in the Authorized Channel table. Ensure that the Allow Publish and Allow Subscribe attributes are enabled for the added channel.

      +
      + MQTT Channel Authorization on ThingSpeak +
      + +

      At this point, the ThingSpeak Channel is setup for access by the DataLogger IoT.

      +

      ThingSpeak Configuration

      +

      Once the device is integrated into the application, the specifics for the ThingSpeak Channel(s) must be configured. This includes the following:

      +
        +
      • Server Name/Hostname
      • +
      • Client Name
      • +
      • User Name
      • +
      • Password
      • +
      • Device to Channel mapping
      • +
      • CA Certificate Chain
      • +
      +

      Server Name/Hostname

      +

      This value is hostname of the ThingSpeak mqtt connection, which is mqtt3.thingspeak.com as note at ThingSpeakMQTT Basics page. Note a secure connection is used, so the port for the connection is 8883.

      +

      Client Name/ID

      +

      The Client Name/ID is found under MQTT connection details listed in the Devices > MQTT section of ThingSpeak.

      +

      Username

      +

      The Username is found under MQTT connection details listed in the Devices > MQTT section of ThingSpeak.

      +

      Password

      +

      The connection password was provided when the MQTT device was created. If you lost this value, you can regenerate a password on the MQTT Device information page.

      +

      Certificate File

      +

      You can download the cert file for ThingSpeak.com page using a web-browser. Click on the security details of this page, and navigate the dialog (browser dependent) to download the certificate. The downloaded file is the made available for the DataLogger IoT to use as a file that is loaded at runtime)

      +

      Setting Properties

      +

      The above property values must be set on the DataLogger IoT before use. They can be manually by using the menu system like the previous MQTT example.

      +

      For users that are interested in using the menu system, you will need to open a Serial Terminal, connect to the COM port that your DataLogger enumerated to, and set it to 115200 baud. In this case, we connected to COM13. Press any key to enter the Main Menu. Type 1 to enter the Settings menu. Then type 12 to enter the ThingSpeak MQTT Menu. When the menu system for the ThingSpeak MQTT connection is presented, you will need to configure the property values as listed in the JSON file. Saving the values through the menu system will save the credentials to the ESP32's persistent memory. The following options are displayed:

      +
      + ThingSpeak MQTT Menu +
      + +

      The alternative to using the menu system is a JSON file. These values can be set using a JSON file that is loaded by the system at startup. For the ThingSpeak example outlined in this document, the entries in the settings JSON file are as follows:

      +
      "ThingSpeak MQTT": {
      +    "Enabled": true,
      +    "Port": 8883,
      +    "Server": "mqtt3.thingspeak.com",
      +    "MQTT Topic": "",
      +    "Client Name": "MQTT_Device_Client_ID",
      +    "Buffer Size": 0,
      +    "Username": "MQTT_Device_Username",
      +    "Password": "MQTT_Device_Password",
      +    "CA Cert Filename": "ThingspeakCA.cer",
      +    "Channels" : "BME280=2054891"
      +  }
      +
      +
      +

      Note

      +

      The Channels value is a list of [DEVICE NAME]=[Channel ID] pairs. Each pair is separated by a comma. In this case, the device name BME280 and the channel ID was 2054891. Make sure to match the device name that was loaded on start up with the unique channel ID that was generated when creating a ThingSpeak Channel.

      +
      +

      Besides updating the Server, Client Name, Username, Password, CA Cert Filename, and Channels, you will need to also ensure that the port is set to 8883. The default in previous firmware versions was 1883. As of firmware v01.00.04, the default is 8883. You will need to adjust the port value to properly connect to the ThingSpeak service. Don't forget to enable ThingSpeak MQTT service by setting the value to true. If the JSON file is saved in the microSD card, you can load the credentials to the DataLogger IoT.

      +
      +

      Tip

      +

      To load the values by the system at startup using a JSON file and microSD card, you will need to configure the Save Settings. This JSON file will be created with the "Save to Fallback" option. Make sure to enable the ThingSpeak MQTT as well.

      +
      +

      Monitoring Output

      +

      Once the connector is configured and the DataLogger IoT is connected to ThingSpeak, as data is posted, the results are show on the Channel Stats page for your Channel. For the above example, the output of a SparkFun BME280 sensor produces the following output:

      +
      + ThingSpeak Stats +
      + +

      Setting Up 2x or More Devices

      +

      For users that are setting up 2x or more devices on the DataLogger IoT, you will need to ensure that each device has their own ThingSpeak Channel. Unfortunately, you are not able to plot sensor readings from two devices in the same channel.

      +

      The following example demonstrates how to set up two devices for ThingSpeak on the DataLogger IoT. In this case, we will use the BME688 and BME680 and their respective default I2C address. This is also a good example of what to do when two devices use the same device driver. Head to ThingSpeak to create a channel for each device connected to the DataLogger IoT. Include a field for each device data that the DataLogger provides. The name of the channel does not need to match the device name or I2C address.

      +
      + + + + + + + + + +
      Creating a Channel for the BME688Creating a Channel for the BME680
      Creating a Channel for the BME688Creating a Channel for the BME680
      +
      + +

      Once the channels are created, you will be provided with a unique channel ID for each channel. Make sure to take note of the number as explained earlier.

      +
      +

      Note

      +

      The alternative I2C address for the BME688 and BME680 uses the same address as the default of the other sensor:

      +
        +
      • BME680: 0x77 (Default) or 0x76
      • +
      • BME688: 0x76 (Default) or 0x77
      • +
      +

      Make sure to avoid using the same address when connecting the sensors to the same DataLogger IoT.

      +
      +

      When setting up the connection, you will need to authorize both channels. In this example, we included the channels for the BME688 [x076] and BME680 [x077].

      +
      + + + + + + + +
      Authorizing 2x Channels through the Same Connection
      Authorizing 2x Channels through the Same Connection
      +
      + +

      Now that the ThingSpeak MQTT connection is setup, adjust the ThingSpeak configuration for the DataLogger IoT by including the credentials (i.e. Client Name, Username, and Password) and channels. We will assume that you have included the ThingSpeak CA certificate file in the root directory of the microSD card already. When including the device name with their respective channel, ensure that the device name matches the name that was loaded on startup. For example, the BME688 and BME680 were loaded on startup as BME68x and BME68x [x77], respectively. Since we are only interested in plotting the BME688 and BME680, we will ignore the MAX17048 that was loaded on startup as well. Under /Settings/ThingSpeak MQTT/Channels, you will enter the string for the device names, each of their respective channel IDs, and a comma separating the two channels like so: BME68x=2613826, BME68x [x77]=2613825.

      +
      + + + + + + + + + +
      DataLogger IoT Device Name Loaded during StartupDevice Name and Channel for Both Sensors
      DataLogger IoT Device Name Loaded during StartupDevice Name and Channel for Both Sensors
      +
      + +
      +

      Note

      +

      Whenever there are multiple devices using the same device driver (each with unique I2C addresses), the DataLogger IoT will display the device address for each additional device that is loading the same driver. As shown above, the first device name did not include the device's I2C address. The second device name using the same driver included its I2C address. Of course, there is an configuration that enables you to always include the address of all the device names.

      +
      +

      The alternative to using the menu system is the JSON file. In this case, we updated channels for the BME688 and BME680. Not shown are the ThingSpeak Client Name, Username, and Password.

      +
      "ThingSpeak MQTT": {
      +    "Enabled": true,
      +    "Port": 8883,
      +    "Server": "mqtt3.thingspeak.com",
      +    "MQTT Topic": "",
      +    "Client Name": "MQTT_Device_Client_ID",
      +    "Buffer Size": 0,
      +    "Username": "MQTT_Device_Username",
      +    "Password": "MQTT_Device_Password",
      +    "CA Cert Filename": "ThingspeakCA.cer",
      +    "Channels" : "BME68x=2613826, BME68x [x77]=2613825"
      +  }
      +
      +
      +

      Note

      +

      If users configure the DataLogger IoT to always include the device address with the device names (i.e. /Settings/Application Settings with Device Names=1), you will need to match the device names for BME688 and BME680 that were loaded on startup as BME68x [x76] and BME68x [x77], respectively. Note the BME688 device name included a space and [x76] in this case. Remember, we are only interested in plotting hte BME688 and BME680 in this case so we will ignore the MAX17048 that was loaded on startup.

      +

      + + + + + + + + + +
      DataLogger IoT Device Names Loaded during StartupDevice Name and Channel for Both Sensors with Respective Addresses
      DataLogger IoT Device Names Loaded during StartupDevice Name and Channel for Both Sensors with Respective Addresses
      +

      +

      Again, the alternative to using the menu system is the JSON file. In this case, we updated channels for the BME688 and BME680. We also included the address name for the BME688 like the configuration menu. Not shown are the ThingSpeak Client Name, Username, and Password.

      +
      "ThingSpeak MQTT": {
      +    "Enabled": true,
      +    "Port": 8883,
      +    "Server": "mqtt3.thingspeak.com",
      +    "MQTT Topic": "",
      +    "Client Name": "MQTT_Device_Client_ID",
      +    "Buffer Size": 0,
      +    "Username": "MQTT_Device_Username",
      +    "Password": "MQTT_Device_Password",
      +    "CA Cert Filename": "ThingspeakCA.cer",
      +    "Channels" : "BME68x [x76]=2613826, BME68x [x77]=2613825"
      +  }
      +
      +
      +

      Save the configuration to persistent memory and exit out of the configuration menu. Wait a few seconds for the DataLogger IoT to read the sensors and output the readings to the Serial Terminal. Open ThingSpeak channels in separate browser windows. In this case, we had the BME688 and the BME680 in private view. You should see sensor readings update and plot on the charts.

      +
      + + + + + + + +
      ThingSpeak Graphing the BME688 and BME680 in Seperate Channels on Two Browser Windows
      ThingSpeak Graphing the BME688 and BME680 in Seperate Channels on Two Browser Windows
      +
      + +

      Example - Azure

      +

      Creating and Connecting to an Azure IoT Device

      +

      One of the key features of the DataLogger IoT is it's simplified access to IoT service providers. This document outlines how an Azure IoT device is used by the DataLogger IoT.

      +
      + Microsoft Azure Logo +
      + Image Courtesy of Microsoft Azure +
      + +

      The following is covered by this document:

      +
        +
      • Device creation Azure
      • +
      • Securely connecting the device
      • +
      • How data is posted from the DataLogger IoT to the Azure Device
      • +
      +

      Currently, the Azure IoT device connection is a single direction - it is used to post data from the hardware to the Azure IoT Device. Configuration information from Azure IoT to the DataLogger IoT is currently not implemented.

      +

      General Operation

      +

      Azure IoT enables connectivity between an IoT / Edge device and the Azure Cloud Platform, implementing secure endpoints and device models within the Azure infrastructure. This infrastructure allows edge devices to post updates, status and state to the Azure infrastructure for analytics, monitoring and reporting.

      +

      In Azure IoT, an virtual representation of an actual device is created and referred to as a Device. The virtual device is allocated a connection endpoint, security certificates and a device digital twin - a JSON document used to persist, communicate and manage device state within Azure. Unlike AWS IoT, data from the device isn't posted to the devices digital twin (AWS Shadow), but to the device directly.

      +

      The actual IoT device communicates with it's Azure representation via a secure MQTT connection, posting JSON document payloads to a set of pre-defined topics. Updates are posted directly to the Azure device, which is then accessed within Azure for further process as defined by the users particular cloud implementation.

      +
      + Azure IoT Overview +
      + +

      Creating a Device in Azure IoT

      +

      The following discussion outlines the basic steps taken to create a Device in Azure IoT that the DataLogger IoT can connect to. First step is to log into your Azure account and create an IoT Hub for your device.

      + + +

      Once logged into your Microsoft Azure account, select Internet of Things > IoT Hub from the menu of services.

      +
      + Azure IoT Hub +
      + +

      Create an IoT Hub

      +

      This IoT Hub page lists all the IoT hubs available for your account. To add a device, you need to create a new IoT Hub.

      +

      Follow the Hub Creation workflow - key settings used for a DataLogger demo device:

      +
        +
      • Used the "Free Tier" for testing and development.
      • +
      • Networking
          +
        • Connectivity - Public Access
        • +
        • Minimum TLS Version - 1.0
        • +
        +
      • +
      +

      The remaining settings were set at their default values.

      +

      Create a Device

      +

      Once the IoT Hub is created, a Device needs to be created within the hub. The device represents the connection to the actual DataLogger IoT device.

      +

      To create a device, select the Device management > Devices from the IoT Hub menu and the select the + Add Device menu item

      +
      + Azure IoT Device Create +
      + +

      In the create device dialog:

      +
        +
      • Enter a name for the device
      • +
      • Select an Authentication type of Symmetric key
      • +
      • Auto-generate keys enabled
      • +
      +
      + Azure IoT Device Create Form +
      + +

      Once created, the device is listed in the Devices list of the IoT Hub. Selecting the device gives you the device ID and keys used to communicate with the device. Note, when connecting to the device with the DataLogger IoT, the Primary Key value is used.

      +
      + Azure IoT Device Details +
      + +

      Azure Configuration

      +

      Once the DataLogger IoT is integrated into the application, the specifics for the Azure IoT Thing must be configured. This includes the following:

      +
        +
      • Server Name/Hostname
      • +
      • Device Key
      • +
      • Device ID
      • +
      • CA Certificate Chain
      • +
      +

      Server Name/Hostname

      +

      This value is hostname of the created IoT Hub and is obtained from the Overview page of the IoT Hub. Note a secure connection is used, so the port for the connection is 8883.

      +
      + Hub Details +
      + +

      Device ID

      +

      The Device ID is obtained from the device detail page. This page is accessible via the Device listing page, which is accessed via the Device management > Devices menu item. The selected device of interest (TestDevice2023 for this example) provides the device ID and Primary Key.

      +
      + Azure IoT Device Details +
      + +

      Device Primary Key

      +

      This is obtained via the Device details page, as outlined in the previous section.

      +
      +

      Note

      +

      You view and copy the key via the icons on the right of the key entry line.

      +
      +

      Root Certificate Authority - CA file

      +

      The Certificate Authority file for Azure is downloaded from this page:

      + + +

      The file to download is the Baltimore CyberTrust Root entry in the Root Certificate Authorities section of the page.

      +
      + Azure Root CA +
      + +

      Setting Properties

      +

      The above property values must be set on the DataLogger IoT before use. They can be set manually by using the menu system like the previous MQTT example.

      +

      For users that are interested in using the menu system, you will need to open a Serial Terminal, connect to the COM port that your DataLogger enumerated to, and set it to 115200 baud. In this case, we connected to COM13. Press any key to enter the Main Menu. Type 1 to enter the Settings menu. Then type 13 to enter the Azure IoT Menu. When the menu system for the Azure IoT connection is presented, you will need to configure the property values as listed in the JSON file. Saving the values through the menu system will save the credentials to the ESP32's persistent memory. The following options are displayed:

      +
      + Azure IoT Menu +
      + +

      The alternative to using the menu system is a JSON file. These values can be set using a JSON file that is loaded by the system at startup. For the example outlined in this document, the entries in the settings JSON file are as follows:

      +
      "Azure IoT": {
      +    "Enabled": true,
      +    "Port": 8883,
      +    "Server": "sparkfun-datalogger-hub.azure-devices.net",
      +    "MQTT Topic": "",
      +    "Client Name": "",
      +    "Buffer Size": 0,
      +    "Username": "",
      +    "Password": "",
      +    "Device Key" : "My-Super-Secret-Device-Key",
      +    "Device ID"  : "TestDevice2023",
      +    "CA Cert Filename": "AzureRootCA.pem"
      +  },
      +
      +

      Besides updating the Server, Device Key, Device ID, and CA Cert Filename, you will need to also ensure that the port is set to 8883. The default in previous firmware versions was 1883. As of firmware v01.00.04, the default is 8883. You will need to adjust the port value to properly connect to the Azure IoT service. Don't forget to enable Azure IoT service by setting the value to true. If the JSON file is saved in the microSD card, you can load the credentials to the DataLogger IoT.

      +
      +

      Tip

      +

      To load the values by the system at startup using a JSON file and microSD card, you will need to configure the Save Settings. This JSON file will be created with the "Save to Fallback" option. Make sure to enable the Azure IoT as well.

      +
      +

      Operation and Monitoring

      +

      Once the DataLogger IoT device is configured and running, the Azure IoT capability in the DataLogger IoT posts messages via MQTT to the connected Azure Device via it's IoT Hub. Messages to the device are posted as Telemetry Data for the device.

      +

      The easiest method to view the Telemetry data being sent to an Azure Iot Device is via the Azure IoT Hub extension for the Visual Studio Code editor.

      +
      + Azure IoT Hub Extension +
      + +

      Once installed, and connected to Azure via the Azure Account extension, you can connect to the target IoT Hub, and monitor telemetry data for a IoT device.

      +

      Connect to Your Azure IoT Hub

      +

      On the Explorer panel of Visual Studio Code, click on the ... menu of the AZURE IOT HUB section. In the popup menu, select the Select IoT Hub menu entry.

      +
      + Select IoT Hub +
      + +

      The available IoT Hubs are displayed in the editors command prompt. Select the desired hub and press Enter (or click).

      +
      + Select IoT Hub +
      + +

      The hub is then displayed in the AZURE IOT HUB section of the editor Explorer. Expanding the Devices section of the Hub will list the example device created above.

      +
      + Select IoT Hub +
      + +

      Monitoring

      +

      To monitor the telemetry data send to a device, right click on the device, TestDevice2023 in this example, select the menu entry Start Monitoring Build-in Event Endpoint.

      +
      + Start Monitoring +
      + +

      Once selected, the editor output console will start displaying output for the selected device. For the above example, with a device that has environmental sensors attached, the output appears as follows:

      +
      + Monitor Output +
      + +

      To stop monitoring, click the Stop Monitoring build-in event endpoint item that is displayed in the status bar of the editor.

      +
      + Stop Monitoring +
      + +

      A menu option to stop monitoring is also available from the ... menu of the AZURE IOT HUB section in the editor Explorer panel.

      +

      Example - HTTP

      +

      Connecting and Sending Output to an HTTP Server

      +

      One of the key features of the DataLogger IoT is it's simplified access to IoT service providers and servers. This document outlines how output from a DataLogger IoT device is sent to an HTTP server.

      +

      The following is covered by this document:

      +
        +
      • Overview of the HTTP connection
      • +
      • How a user configures and uses the HTTP connection
      • +
      • Use examples
      • +
      +

      General Operation

      +

      HTTP connectivity allows data generated by the DataLogger IoT to be sent to an HTTP server. An HTTP endpoint is provided to the HTTP action within the DataLogger IoT, and when data is output, a JSON representation of the data is published to the endpoint via an HTTP POST operation. The body of the POST operation contains the a JSON document that encapsulates the sent DataLogger IoT data.

      +
      + HTTP Overview +
      + +

      Data Structure

      +

      Data is sent to the HTTP server as a JSON object, which contains a collection of sub-object. Each sub-object represents a data source in the sensor, and contains the current readings from that source.

      +

      The following is an example of the data posted - note, this representation was "pretty printed" for readability.

      +
      {
      +  "MAX17048": {
      +    "Voltage (V)": 4.304999828,
      +    "State Of Charge (%)": 115.0625,
      +    "Change Rate (%/hr)": 0
      +  },
      +  "CCS811": {
      +    "CO2": 620,
      +    "VOC": 33
      +  },
      +  "BME280": {
      +    "Humidity": 25.03613281,
      +    "TemperatureF": 79.64599609,
      +    "TemperatureC": 26.46999931,
      +    "Pressure": 85280.23438,
      +    "AltitudeM": 1430.44104,
      +    "AltitudeF": 4693.04834
      +  },
      +  "ISM330": {
      +    "Accel X (milli-g)": -53.31399918,
      +    "Accel Y (milli-g)": -34.03800201,
      +    "Accel Z (milli-g)": 1017.236023,
      +    "Gyro X (milli-dps)": 542.5,
      +    "Gyro Y (milli-dps)": -1120,
      +    "Gyro Z (milli-dps)": 262.5,
      +    "Temperature (C)": 26
      +  },
      +  "MMC5983": {
      +    "X Field (Gauss)": -0.200622559,
      +    "Y Field (Gauss)": 0.076416016,
      +    "Z Field (Gauss)": 0.447570801,
      +    "Temperature (C)": 29
      +  }
      +}
      +
      +

      HTTP Connection Setup

      +

      To connect to an HTTP server endpoint, the following information is needed:

      +
        +
      • The URL of the endpoint
      • +
      • The SSL certificate for the target server, if the connection is secure (HTTPS)
      • +
      +

      These values are set using the standard DataLogger methods - the interactive menu system, or a JSON file.

      + +

      For users that are interested in using the menu system, you will need to open a Serial Terminal, connect to the COM port that your DataLogger enumerated to, and set it to 115200 baud. In this case, we connected to COM13. Press any key to enter the Main Menu. Type 1 to enter the Settings menu. Then type 14 to enter the HTTP IoT Menu. When the menu system for the HTTP IoT connection is presented, you will need to configure the property values as listed in the JSON file. Saving the values through the menu system will save the credentials to the ESP32's persistent memory. The following options are displayed:

      +
      + HTTP Menu +
      + +

      The options are:

      +
        +
      • Enable/Disable the connection
      • +
      • Set the URL for the endpoint
      • +
      • Set the name of the CA Cert file for a secure connection (HTTP)
      • +
      +

      To set the HTTP URL/endpoint - select two (2) in the menu, and enter the URL. For this example, we'll enter: http://mysparkfunexample.com:8091 .

      +
      + Enter a URL +
      + +

      In the above example, the URL/HTTP Endpoint is on a server called mysparkfunexample.com, on port 8091. Once set, the system will post data to this URL.

      +

      If the endpoint is a secure ssl (HTTPS) connection, the certificate for the server is required. Because of the size of the certificates, the value is provided as a file that is loaded into the system by the attached SD card.

      +
      + Cert Filename +
      + +

      The above example show providing a certificate filename of example.cer.

      +

      Once all these values are set, the system will post data to the specified HTTP endpoint, following the JSON information structure noted earlier in this document.

      +

      JSON File Entries

      +

      If a JSON file is being used as an option to import settings into the DataLogger IoT, the following entries are used for the HTTP IoT connection:

      +
      "HTTP IoT": {
      +    "Enabled": false,
      +    "URL": "<the URL>",
      +    "CA Cert Filename": "<certificate filename>"
      +  }
      +
      +

      Where:

      +
        +
      • Enabled - Set to true to enable the connection.
      • +
      • URL - Set to the URL for the connection.
      • +
      • CA Cert Filename - Set to the cert filename on the SD card if being used.
      • +
      +

      If the JSON file is saved in the microSD card, you can load the credentials to the DataLogger IoT.

      +
      +

      Tip

      +

      To load the values by the system at startup using a JSON file and microSD card, you will need to configure the Save Settings. This JSON file will be created with the "Save to Fallback" option. Make sure to enable the HTTP IoT as well.

      +
      +

      Example - Connecting to a HTTP Server

      +

      In this example, a simple HTTP Server is creating using Node JS, and the HTTP connection in the DataLogger IoT is used to post data to this server. The received data is output to the console from there server.

      +

      The Server

      +

      The following javascript/node code creates a HTTP server on port 8090, and outputs received data to the console.

      +
      var http = require('http');
      +
      +// Setup the endpoint server
      +var myServer = http.createServer(function (req, res) {
      +
      +  // Initialize our body string
      +  var body="";
      +
      +  // on data callback, append chunk to our body string
      +  req.on('data', function(chunk){
      +    body += chunk;
      +  });
      +
      +  // On end callback, output the body to the console
      +  req.on('end', function(){
      +    // parse json string, then stringify it back for 'pretty printing'
      +    console.log("payload: " + JSON.stringify(JSON.parse(body),null,2));
      +  });
      +
      +  // send a reply
      +  res.writeHead(200, {'Content-Type': 'text/plain'});
      +  res.end('n');
      +  // Just listen on our port
      +}).listen(8090);
      +
      +

      The setup and use of node js is system dependant is beyond the scope of this document. However, Node JS is easily installed with your systems package manager (brew on macOS, Linux distribution package manager (apt, yum, ...etc), on Windows, the WSL is recommended).

      +

      Once Node is setup, the above server is run via the following command (assuming the implementation is in a file called simple_http.js):

      +
      node ./simple_http.js
      +
      +

      As data is sent by the DataLogger IoT, the following is output to the console from the server:

      +
      + HTTP Output +
      + +

      Obtaining a Sites Security Certificate

      +

      Accessing a sites SSL/Secure Certificate is done via a web browser. The method for each browser is different. The following example uses Edge, which is similar to the operation in Chrome.

      +

      First, browse to the desired site/server. Click the Secure/Security area/button next to the URL to bring up the security detail page. On this page, select the Connection is secure menu option

      +
      + cert step 1 +
      + +

      Next, on the page shown, select the certificate button on the upper right of the dialog.

      +
      + cert step 2 +
      + +

      When you select this button, the certificate details dialog is displayed. On this page, select the Details tab, and select the Export... button on the lower right of the dialog. This will save the sites SSL/Security certificate to a location you specify.

      +
      + cert step 3 +
      + +

      Once saved, place this file on the SD card your system/DataLogger is using, and set the filename in the HTTP connection menu or settings JSON file.

      +

      Example - Arduino Cloud

      +
      +

      Arduino

      +

      At the time of writing, Arduino's IoT service was referred to as the "Arduino IoT Cloud." Arduino updated the service with a different UI and is now referring to the service as the "Arduino Cloud"." When referencing the Arduino IoT or Arduino IoT Cloud in this tutorial, we are referring to the Arduino Cloud.

      +
      +

      Creating and Connecting to an Arduino Cloud Device

      +

      One of the key features of the SparkFun DataLogger IoT is it's simplified access to IoT service providers. This document outlines how an Arduino Cloud Device is used by the DataLogger IoT.

      +

      The following is covered by this document:

      +
        +
      • Structure of the Arduino Cloud Devices
      • +
      • Device creation in the Arduino Cloud
      • +
      • Setup of the Arduino Driver
      • +
      • How data is posted from the DataLogger IoT to the Arduino Device
      • +
      +

      Currently, the Arduino Cloud device connection is a single direction - used to post data from the DataLogger IoT to an Arduino Cloud device.

      +
      + Arduino Logo +
      + Image Courtesy of Arduino +
      + +
      +

      Note

      +

      To take advantage of the API's in Arduino Cloud, you will also need to have a service plan with your account.

      +
      +

      General Operation

      +

      The Arduino Cloud enables connectivity between an IoT/Edge Arduino enabled device and the cloud. The edge device updates data in the Arduino Cloud by updating variables or parameters attached to a cloud device.

      +

      In the Arduino Cloud, the edge device is represented by a Device which has a virtual Thing attached/associated with it. The Thing acts as a container for a list of parameters or variables which represent the data values received from the edge device. As values on the edge device update, they are transmitted to the Arduino Cloud.

      +

      For a SparkFun DataLogger IoT connected to an Arduino Cloud device, the output parameters of a device connected to the DataLogger are mapped to variables within the Arduino Cloud Device's Thing using a simple pattern of DeviceName_ParameterName for the name of the variable in the Arduino Cloud.

      +
      + Arduino Cloud Overview +
      + +

      Creating a Device in Arduino Cloud

      +

      The first step connecting to the Arduino Cloud is setting up a device within the cloud. A device is a logical element that represents a physical device.

      +

      Log into your Arduino Cloud account.

      + + +

      Click on the expand menu icon on the upper left (e.g. the three lines stacked like a "hamburger"; ☰) and select Devices. If your window is big enough, then it will show up on the navigation bar.

      +
      + Select Devices +
      + +

      This page lists your currently defined devices. If there are no defined devices, select the Add Device button. This will probably be at the bottom of the page. The location of this button will change once the page has a device (or if there is an update to Arduino's user interface).

      +
      + Add a Device +
      + +

      A device type selection dialog is then shown. Since we are connecting a DataLogger IoT board to the system, and not connected a known device, select DIY - Any Device to manually include the DataLogger IoT.

      +
      + Select DIY Device +
      + +

      Once selected, another dialog is presented. Just select Continue. At this point you can provide a name for your device. In this case we named it as "MyDataLoggerIoT."

      +
      + Name Device +
      + +

      The next screen is the critical step of the device creation process. This step is the one time the Device Secret Key is available. The provided Device ID and Device Secret Key values are needed to connect to the Arduino Cloud. Once this step is completed, the Secret Key is no longer available.

      +
      + Device Secret +
      + +

      The easiest way to capture these values is by downloading as a PDF file, which is offered on the setup page. Click on the download the PDF and save it to a safe location. When ready, click on the check box indicating that you have saved the values and select the Continue button.

      +

      Arduino Cloud API Keys

      +

      In addition to creating a device, to access the Arduino Cloud, the driver requires an API Key. This allows the DataLogger IoT's Arduino Cloud driver to access the web API of the Arduino Cloud. This API is used to setup the connection to the Arduino Cloud.

      +

      To create an API key, click on the menu bar to expand and select your Arduino account profile > Personal Settings.

      +
      + API Keys +
      + +

      This menu takes you to a list of existing API Keys. If you have not created one yet, the list will have nothing in it like the image below. From this page, select the CREATE API KEY button.

      +
      + Create Key +
      + +
      +

      Note

      +

      Users will need a service plan in order to take advantage of the API.

      +
      +

      In the presented dialog, enter a name for the API key. In this case, we named it "MyDataLoggerKey".

      +
      + API Key Name +
      + +

      Once the name is entered, click CONTINUE. A page with the new API key is presented. Like in Device Creation, this page contains a secret that is only available on this page during this process.

      +
      + API KEY NOW +
      + +

      Make note of the Client ID and Client Secret values on this page. The best method to capture these values is to download the PDF file offered on this page. Click on the download the PDF and save it to a safe location. When ready, click on the check box indicating that you have saved the values and select the DONE button.

      +

      At this point, the Arduino Cloud is setup for connection by the driver.

      +

      Arduino Cloud Configuration

      +

      To add an Arduino Cloud Device as a destination DataLogger IoT, the Arduino Cloud connection is enabled via the DataLogger menu system and the connection values, obtained from the Arduino Cloud (see above), are set in the connection properties.

      +

      The specifics for the Arduino Cloud must be configured. This includes the following:

      +
        +
      • Thing Name
      • +
      • Thing ID
      • +
      • API Client ID
      • +
      • API Secret
      • +
      • Device Secret
      • +
      • Device ID
      • +
      +
      +

      Note

      +

      The Thing Name does not necessarily need to be configured. However, there will be less confusion if you set this up before connecting the DataLogger IoT to the Cloud. The Thing ID will automatically be generated and saved once there is a connection available.

      +
      +

      Thing Name

      +

      The name of the Arduino Cloud Thing to use. If the Thing doesn't exist on startup, the driver will create a Thing and be named "Untitled" if you do not provide a name.

      +
      +

      Note

      +

      Note satisfied with the default "Untitled" as the Thing's name? You can rename the Thing Name after creating the Thing. Note that you will need to manually rename the Thing Name on the Arduino Cloud and DataLogger IoT.

      +
      +

      Thing ID

      +

      This is the ID of the Thing being used. This value is obtained by the following methods:

      +
        +
      • If the driver creates a new Thing, the ID is obtained and used.
      • +
      • If an existing Thing is connected to the DataLogger IoT, the driver retrieves it's ID.
      • +
      +
      +

      Note

      +

      In this case, the driver cannot create any new variables until the system is restarted.

      +
      +
        +
      • The user creates a new Thing using the web interface of Arduino Cloud, and provides the Thing Name and Thing ID .
      • +
      +

      API Client ID and Secret

      +

      These values are used to provide API access by the driver. This access allows for the creation/use of a Thing and Variables within the Arduino Cloud. These are obtained via the steps outlined earlier in this document.

      +

      Device Secret and ID

      +

      These values are used to identify the Arduino device that is connected to. These are obtained via the steps outlined earlier in this document.

      +

      Setting Properties

      +

      The above property values must be set in the DataLogger's Arduino Cloud driver before use. They can be manually by using the menu system like the previous MQTT example.

      +

      For users that are interested in using the menu system, open a Serial Terminal, connect to the COM port that your DataLogger enumerated to, and set it to 115200 baud. In this case, we connected to COM13. Press any key to enter the Main Menu. Type 1 to enter the Settings menu. Then type 16 to enter the Arduino IoT Menu. When the menu system for the Arduino IoT is presented, you will need to configure the property values as listed in the JSON file. Saving the values through the menu system will save the credentials to the ESP32's persistent memory. The following options are displayed:

      +
      + Arduino IoT Cloud Menu Options +
      + +

      The alternative to using the menu system is a JSON file. These values can be set using a JSON file that is loaded by the system at startup. For the DataLogger Arduino Cloud example outlined in this document, the entries in the setting's JSON file are as follows:

      +
      "Arduino IoT": {
      +    "Enabled": true,
      +    "Thing Name": "SparkFunThing1",
      +    "API Client ID": "MY_API_ID",
      +    "API Secret": "MY_API_SECRET",
      +    "Device Secret": "MY_DEVICE_SECRET",
      +    "Device ID": "MY_DEVICE_ID"            
      +  },
      +
      +

      You will need to update the API Client ID, API Secret, Device Secret, and Device ID with the values that were obtained earlier. Don't forget to enable Arduino Cloud service by setting the value to true. If the JSON file is saved in the microSD card, you will need to load the credentials to the DataLogger IoT.

      +
      +

      Tip

      +

      To load the values by the system at startup using a JSON file and microSD card, you will need to configure the Save Settings. This JSON file will be created with the "Save to Fallback" option. Make sure to enable the Arduino IoT as well.

      +
      +

      Operation

      +

      On startup, when the first values are written from the DataLogger IoT, the connection to the Arduino Cloud is made. During this connection, the system connects to the specified Thing and variables are mapped between the DataLogger Device values and Arduino Cloud Variables. If needed, variables can be created manually in the cloud.

      +

      While this initial setup takes seconds to complete, updates to the values on the Arduino Cloud are rapid as soon as there is a connection available.

      +

      Viewing Values

      +

      Once the DataLogger IoT device is configured and running, updates in Arduino Cloud are listed in the Things tab of the Arduino Cloud page. Clicking the target Thing provides access to the current variable values that are connected to the DataLogger IoT. Your mileage may vary depending on the compatible device that is connected to the DataLogger IoT. In this case, we were able to see the built-in sensors that were connected on the DataLogger IoT - 9DoF.

      +
      + Cloud Variables +
      + +
      +

      Note

      +

      Not seeing certain variables on your list? Check your connections to make sure that the compatible device is connected to the DataLogger IoT. You may also have certain outputs disabled (like the connected sensors or timestamp).

      +
      +
      +

      Note

      +

      Having problems connecting new variables with the DataLogger IoT? When swapping out compatible Qwiic enabled devices, you may need to delete previous cloud variables so that the DataLogger IoT is able re-initialize them on the next power cycle.

      +
      +

      Create a Dashboard

      +

      With the data now available in the Arduino Cloud as variables, it is a simple step create a dashboard that plots the data values.

      +

      The general steps to create a simple dashboard include:

      +
        +
      • Select the Dashboards section of the Arduino Cloud.
      • +
      • Select the Build Dashboard button. If you have a dashboard already built, the location of the button will change and the button will be renamed: Create.
      • +
      • Click the edit button (i.e. the icon that looks like a paper and pencil, this is next to the eye).
      • +
      • Add an element to the dashboard -- for this example select ADD ^ > Advanced Chart.
      • +
      • On the Chart's Widget Settings select Link Variables to add readings.
      • +
      • The DataLogger IoT Variables are listed - select the variable to link.
      • +
      • Continue this step until all the desired variables are linked to the chart. You can select up to 5x variables at a time. Click on the Link Variables button after selecting the variables.
      • +
      • This will bring you back to the Chart's Widget Settings window. Configure any preferences that to display (i.e. variable colors, labels, etc.). When all variables are linked and the Chart Widget Settings is configured, select Done.
      • +
      +
      + Linked Variables +
      + +

      The created dashboard then displays the values posted from the SparkFun DataLogger IoT. You can continue adding additional readings on the dashboard that you were not able to fit on graph or even rename the Dashboard view. In this case, we displayed accelerometer values and temperature in degrees Celsius from the DataLogger IoT - 9DoF.

      +
      + DataLogger IoT - 9DoF Dashboard +
      + +
      +

      Note

      +

      Not seeing any values on the LIVE view? Try clicking on the other time periods to see the values posted.

      +
      +

      Using compatible Qwiic enabled devices, you can also display additional readings that are not available with the built-in sensors. In this case, we were able to display humidity, temperature in degrees Fahrenheit, equivalent CO2, TVOC, and AQI with the DataLogger IoT and Environmental Combo Breakout (ENS160/BME280).

      +
      + DataLogger IoT with Qwiic Environmental Combo Breakout (ENS160/BME280) Dashboard +
      + +

      Viewing and Downloading Log Files using the IoT Web Server

      +

      As of firmware v01.02.00, log files can be viewed and downloaded over a WiFi network! This saves you time by allowing you to download the files without the need to disconnect the DataLogger IoT and manually remove microSD card.

      +

      The following is covered by this document:

      +
        +
      • How a user configures and uses the HTTP connection
      • +
      • Use examples
      • +
      +

      IoT Web Server Connection Setup

      +

      To connect to the ESP32's IoT Web Server, the following information is needed:

      +
        +
      • The server name/address
      • +
      • [optional] A username - if required
      • +
      • [optional] A password - if required
      • +
      +

      IoT Web Server Menu System

      +

      We'll need to adjust the settings for the IoT Web Server.

      +

      For users that are interested in using the menu system, open a Serial Terminal, connect to the COM port that your DataLogger enumerated to, and set it to 115200 baud. In this case, we connected to COM13. Press any key to enter the Main Menu. Type 1 to enter the Settings menu. Then type 17 to enter the IoT Web Server Menu. When the menu system for the IoT Web Server is presented, the following options are displayed:

      +
      + IoT Web Server Options +
      + +

      The options are:

      +
        +
      • Enable/Disable the connection
      • +
      • Username
      • +
      • Password
      • +
      • Enable/Disable mDNS support
      • +
      • mDNS name
      • +
      +

      At a minimum, you will just need to enable the connection. However, we recommend enabling mDNS support if it is supported in your network.

      +

      Once all these values are set, the system will serve the log files in your local 2.4GHz WiFi network following the JSON information structure noted below in this document.

      +

      JSON File Entries

      +

      If a JSON file is being used as an option to import settings into the DataLogger IoT, the following entries are used for the IoT web server:

      +
      "IoT Web Server": {
      +  "Enabled": false,
      +  "Username": "",
      +  "Password": "",
      +  "mDNS Support": false,
      +  "mDNS Name": "dataloggerAD6B8"
      +},
      +
      +

      Where:

      +
        +
      • Enabled - Set to true to enable the connection.
      • +
      • Username - Web server user name if being used.
      • +
      • Password - Web server password if being used.
      • +
      • mDNS Support - Set to true if multicast DNS is supported. This allows you to enter the address as "http://dataloggerXXXXX.local" (where XXXXX is generated from the last 5x characters from your board ID) rather than typing the exact IP address of the ESP32.
      • +
      • mDNS Name - Multicast DNS name. In this case, the default name was set to dataloggerAD6B8. This name will be different depending on your DataLogger IoT's board ID so AD6B8 will be different for your board.
      • +
      +
      +

      Tip

      +

      To load the values by the system at startup using a JSON file and microSD card, you will need to configure the Save Settings. This JSON file will be created with the "Save to Fallback" option. Make sure to enable the MQTT Client as well.

      +
      +

      Connect and Download Log File

      +
      +

      Note

      +

      You will need to make sure that the ESP32 is on the same network as your computer in order to access the log files.

      +
      +
      +

      Note

      +

      When authentication is enabled, some browsers might require a second login depending on user settings.

      +
      +

      Once the web server is enabled and the settings are saved, you will need to reboot the DataLogger IoT. As the DatLogger initializes, it will connect to your WiFi Network. Take note of the mDNS address, in this case, it was "http://dataloggerAD6B8.local".

      +
      + DataLogger IoT Initializing, WiFi Connected, Web Server Enabled +
      + +

      Once the DataLogger IoT has finished initializing, open web browser. Connect the DataLogger IoT by entering the address "http://dataloggerXXXXX.local", where XXXX is the last 5x characters of your board ID. You will be presented with the log files available on the microSD card. Click on a log file to download and save it to your computer.

      +
      + Viewing Available Log Files through a Web Browser +
      + +
      +

      Note

      +

      If mDNS is not supported, you can also enter the IP address of the Datalogger IoT into a web browser to view and download the log files. You can view the IP address when the DataLogger IoT is initializing. If you have administrative privileges to the WiFi Network, you can also view the IP address through your WiFi router as well.

      +

      + Viewing Available Log Files through a Web Browser using IP Address +

      +
      +

      Now that you have downloaded the log files, try graphing the data on a spreadsheet!

      +

      Example - How to Convert Comma Separated Values (CSV) to a Spreadsheet

      +

      The DataLogger IoT is great at displaying real time data with an IoT service whenever there is an Internet connection available. For those that want to use the DataLogger IoT without a WiFi connection and/or you just want to save data to a microSD card, you can import the comma separated values (CSV) from the text file into a spreadsheet to graph the values.

      +

      There are a few spreadsheet programs available that can take text files with CSV but for the scope of this tutorial, we will be using Google Sheets™ to convert the CSV output to a graph.

      +
      + Google Sheets Logo +
      + Image Courtesy of Google Sheets. +
      + +
      +

      Note

      +

      Google and Google Sheets are trademarks of Google LLC. This tutorial is not endorsed by or affiliated with Google in any way. We just thought it was a sweet tool to visualize all the data that was collected by your snazzy DataLogger IoT. 😉

      +
      +

      Log Some Data!

      +

      At this point, we will assume that you have configured connected your devices to the DataLogger IoT and configured its settings. Insert the microSD card into it's socket. Power the DataLogger IoT up and start logging some data! In this case, we were using the DataLogger IoT using the Qwiic Environmental Combo Breakout - ENS160/BME280. of course, you could have other compatible Qwiic-enabled devices connected depending on your setup. For simplicity, a WiFi connection was used to synchronize the clock to the NTP server and a computer's USB port was used to power everything.

      +
      +

      Tip

      +

      For users without an Internet connection to sync the clock to the NTP server, you may want to consider using a compatible Qwiic-enabled device like the Qwiic Real Time Clock (RTC) Module - RV-8803 or a Qwiic-enabled u-blox GNSS module. Note that you will need to configure the time to your area before logging any data. U-blox GNSS modules would also need to be able to view a few satellites before being able to synchronize to the UTC.

      +
      +
      +

      Note

      +

      For users that require a timestamp with their datasets, make sure to enable timestamp.

      +
      +

      Download the Log Files

      +

      Users can download the log files to your computer with the IoT Web Server. You will need to update firmware to v01.02.00 and enable this feature. For more information, check out the previous example to view and download log files using the IoT web server.

      + + +

      Of course, users can follow the old school method and manually grab the files using a microSD card reader. When ready, remove power from the DataLogger IoT and eject the microSD card from the socket. Insert the microSD card into an adapter and connect to your computer.

      +
      + + Memory Card Adapter and USB Reader for microSD cards +
      + +

      Importing CSV to a Spreadsheet

      +

      Log into your Google account and open Google Sheets to create a new spreadsheet.

      + + +

      Head to the menu and select: File > Import.

      +
      + Importing Google Sheets +
      + +

      A window will pop up with some options to import a file. Click the Upload tab. Click on the Browse button to choose the file. Or drag and drop the file into the upload area. In this case, the DataLogger IoT saved the comma separated values to a text file called sfe0003.txt.

      +
      + Select CSV File +
      + +
      +

      Note

      +

      Not seeing any data in the file or even a text file saved in the root directory? Make sure that the microSD card is formatted correctly and the DataLogger is configured properly. In the menu, make sure to have the SD Card Format enabled and set to the correct format. In this case, we are using the default CSV format.

      +
      +

      Another window will pop up asking how to import the file. From the drop down menu, select: Import location > Create new spreadsheet and Separator Type > Detect automatically. Since the file will include commas to separate each reading, Google Sheets should automatically separate text and values into each cell. Otherwise, you can select comma as the separator type.

      +
      + Separator Type +
      + +
      +

      Note

      +

      If you have the file open, you can also manually paste the CSV data into the spreadsheet. Select all the contents of the text file and copy the contents onto your clipboard. Right click the cell closest to the top and farthest to the left of the spreadsheet (i.e. A1). Then paste the data. One caveat is that Google Sheets may have problems where it only pastes the title of each column.

      +

      + Titles of Each Header Rows Only Pasted +

      +

      If you see this happen, you will need to select all but the header row from the text file. Then copy the contents onto your clipboard again. Right click on the next row the titles (i.e. A2) and paste the data.

      +

      + Manual Paste +

      +
      +
      +

      Tip

      +

      To separate the values to each column, highlight everything in the column. Then head to the menu and select: Data > Split text into columns

      +

      + Splitting Text to Columns +

      +
      +

      Graphing Your Datasets

      +

      Hold down the Shift button on your keyboard and select the columns that you would like to graph using your mouse. Once the data is highlighted, head to the menu and select: Insert > Chart.

      +
      + Select Datasets to Graph and then select Insert > Chart from menu +
      + +

      The values that were selected will be graphed. You will want to be careful about including too many datasets on the graph as it can be hard to read when they are not in the same range.

      +
      + Unformatted Chart +
      + +

      At this point, try formatting the data based on your preferences and export the graph. The graph below was formatted and exported to a PNG. Note that the values for the AQI were moved to the right of the graph for a better viewing since they were smaller than the datasets for TVOC and eCO2.

      +
      + Exported and Formatted Chart +
      + +
      +

      Note

      +

      There are additional features to help format your data based on your personal preferences! Select the column that you would like to format. Then head to the menu: Format > Number. Select the format that you would like to apply to the dataset. In this case, we adjusted the General Time with Custom Date and Time to show a 12-hour format before creating a new graph.

      +

      + Exported and Formatted Chart with 12-hour Format +

      +
      +

      Appendix - Supported Qwiic Devices

      +

      DataLogger IoT Supported Devices

      +

      The following lists the devices/boards supported by the DataLogger IoT boards. New drivers to support a device will be listed under the firmware version.

      +

      Version 01.01.00

      + +

      Version 01.00.00

      + +

      Troubleshooting

      +
      +

      Note

      +

      + Not working as expected and need help?

      +

      If you need technical assistance and more information on a product that is not working as you expected, we recommend heading on over to the SparkFun Technical Assistance page for some initial troubleshooting.

      +

      +

      If you don't find what you need there, the SparkFun Forums are a great place to find and ask for help. If this is your first visit, you'll need to create a Forum Account to search product forums and post questions.

      +

      +
      +

      Issues Connecting to IoT Service

      +

      Having trouble connecting your DataLogger IoT to an IoT service? Make sure to check your credentials and ensure that the configuration matches the IoT Service (such as your WiFi network, port, server, topic, certificates, keys, etc. to name a few). Make sure to also include the associated certificates and keys in the microSD card as well. You may see an output similar to the following errors below.

      +

      AWS IoT Error

      +

      The following error occurred when the DataLogger IoT was initializing with AWS.

      +
      [W] AWS IoT disconnected - reconnecting.......[E] AWS IoT: MQTT connection failed. Error Code: -2
      +
      +
      + + Configuration entered incorrectly, DataLogger not connecting to IoT Service +
      + +

      In this case, the DataLogger IoT failed to connect to AWS IoT service because the port was using the default value that was saved: 1883. Ensure that the port is set to 8883 for your IoT service (e.g. AWS IoT, Azure, and ThingSpeak) and saved in persistent memory in order for the DataLogger IoT to successfully connect. As of firmware v01.00.04, the default is 8883.

      +

      ThinkSpeak IoT Error

      +

      The following error occurred when the DataLogger IoT was initializing with ThingSpeak.

      +
      [I] ThingSpeak MQTT: connecting to MQTT endpoint mqtt3.thingspeak.com:8883 .......[E] ThingSpeak MQTT: Connection Error [4]
      +
      +
      + + Configuration entered incorrectly, DataLogger not connecting to IoT Service - ThingSpeak +
      + +

      In this case, the DataLogger IoT failed to connect to ThingSpeak service because the credentials were entered incorrectly. Ensure that the and saved in persistent memory in order for the DataLogger IoT to successfully connect.

      +

      Arduino Cloud Error 1

      +

      The following error was occurred when the DataLogger IoT was initializing with Arduino Cloud.

      +
      [W] ArduinoIoT - Thing Name not provided
      +.
      +.
      +.
      +[I] Arduino IoT: setup variables...[E] ArduinoIoT HTTP communication error [401] - token request
      +[E] Arduino IoT Cloud not available or account credentials incorrect
      +
      +
      + + Configuration entered incorrectly, DataLogger not connecting to IoT Service +
      + +

      In this case, the DataLogger IoT failed to connect to the Arduino Cloud service because the credentials were incorrect. Ensure that the credentials (i.e. API client ID, API secret, device secret, device ID) are entered correctly and saved in persistent memory in order for the DataLogger IoT to successfully connect.

      +

      Arduino Cloud Error 2

      +

      The following error was occurred when the DataLogger IoT was initializing with Arduino Cloud.

      +
      [I] Arduino IoT: setup variables...[E] Arduino IoT: return code=400, failed to decode request body with content type "application/json": uuid: incorrect UUID length 37 in string "a111aaa1-1111-1111-1a1a-1a11111a1111\r"
      +
      +

      In this case, the DataLogger IoT failed to connect to the Arduino Cloud service because the credentials were incorrect. The string was supposed to be the device ID. When copying and pasting the device ID from a PDF that was generated with the Arduino Cloud, a carriage return (\r) was also copied and entered in the serial terminal. By pasting the device ID into a text editor and then re-copying/pasting it into the serial terminal helped to ensure that the credentials were entered correctly.

      +
      +

      Note

      +

      The device ID in this example was a randomly generated string. You will need to check to make sure that your device matches the one that the Arduino Cloud generated specifically for your account.

      +
      +

      Arduino Cloud Error 3

      +

      The following error was occurred when the DataLogger IoT was initializing with Arduino Cloud.

      +
      [I] Arduino IoT: setup variables...ArduinoIoTCloudTCP::handle_ConnectMqttBroker could not connect to mqtts-up.iot.arduino.cc:8884
      +ArduinoIoTCloudTCP::handle_ConnectMqttBroker 1 connection attempt at tick time 301939
      +ArduinoIoTCloudTCP::handle_ConnectMqttBroker could not connect to mqtts-up.iot.arduino.cc:8884
      +ArduinoIoTCloudTCP::handle_ConnectMqttBroker 2 connection attempt at tick time 307420
      +[E] Arduino IoT: No Arduino Thing ID provided. Enter ID, delete Thing (SparkFunThing1) on Cloud, or enter new Thing Name.
      +
      +

      In this case, the DataLogger IoT failed to connect to the Arduino Cloud service because there was already a Thing that was created. By deleting the Thing in the Arduino Cloud, the DataLogger IoT was able to automatically create another Thing and setup the variables.

      +

      ThingSpeak Data Points Not Updating

      +

      If your DataLogger IoT is connected to ThingSpeak but you do not see any data, ensure that the device name matches the Qwiic device that is connect to the DataLogger IoT. For example, the DataLogger IoT and Qwiic-enabled ENS160 was able to connect to ThingSpeak as shown in the image on the bottom left. However, there were no data points in any of the graphs as shown on ThingSpeak as shown in the image on the bottom right.

      +
      + + + + +
      DataLogger Connected to ThingSpeakNo Data Points in ThingSpeak Channel
      +
      + +

      If you head back into the configuration menu for the DataLogger's ThingSpeak channel, make sure that the matches the connected Qwiic device's name that was shown during initialization. In this case, the device that was loaded and detected was ENS160. Then add the channel ID before saving the system settings.

      +
      + + Matching Device Name with Qwiic-Enabled ENS160 Breakout Board +
      + +
      +

      Note

      +

      Only one device can be loaded per channel! ThingSpeak is not able graph two different devices in the same channel.

      +
      +

      Head back to your ThingSpeak Channel to verify that data is being plotted on the graphs.

      +
      + + ThingSpeak Outputting ENS160 Sensor Data on Graphs +
      + +

      U-Blox I2C Device Disappears when IoT DataLogger Initializes

      +

      If you have issues where a u-blox device that is connected to the I2C port fails to connect a second time when the IoT DataLogger initializes, this is due to a bug in the firmware from an initial release. You may see an output similar to the following message and image shown below.

      +
      [W] GNSS::isConnected no traffic seen (first attempt)
      +
      +
      + + No Satellite Lock... Bug +
      + +

      If you see the following output and the IoT DataLogger not loading your u-blox device, you will need to update the firmware to v01.00.03 and above. For more information, make sure to check out the tutorial on updating your IoT DataLogger's firmware.

      +

      Resources

      +

      Now that you've successfully got your DataLogger IoT up and running, it's time to incorporate it into your own project! For more information, check out the resources below:

      + +

      Or check out these related blog posts.

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      +
      + + + +
      + + + +
      + + + +
      +
      +
      +
      + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/sitemap.xml b/sitemap.xml new file mode 100644 index 0000000..b8e545d --- /dev/null +++ b/sitemap.xml @@ -0,0 +1,119 @@ + + + + https://sparkfun.github.io/SparkFun_DataLogger/ + 2024-11-14 + + + https://sparkfun.github.io/SparkFun_DataLogger/configuration/ + 2024-11-14 + + + https://sparkfun.github.io/SparkFun_DataLogger/contribute/ + 2024-11-14 + + + https://sparkfun.github.io/SparkFun_DataLogger/example_CSV_to_spreadsheet/ + 2024-11-14 + + + https://sparkfun.github.io/SparkFun_DataLogger/example_arduino_iot_cloud/ + 2024-11-14 + + + https://sparkfun.github.io/SparkFun_DataLogger/example_aws/ + 2024-11-14 + + + https://sparkfun.github.io/SparkFun_DataLogger/example_azure/ + 2024-11-14 + + + https://sparkfun.github.io/SparkFun_DataLogger/example_http/ + 2024-11-14 + + + https://sparkfun.github.io/SparkFun_DataLogger/example_iot_web_server/ + 2024-11-14 + + + https://sparkfun.github.io/SparkFun_DataLogger/example_mqtt/ + 2024-11-14 + + + https://sparkfun.github.io/SparkFun_DataLogger/example_thingspeak/ + 2024-11-14 + + + https://sparkfun.github.io/SparkFun_DataLogger/example_timestamp/ + 2024-11-14 + + + https://sparkfun.github.io/SparkFun_DataLogger/factory_reset/ + 2024-11-14 + + + https://sparkfun.github.io/SparkFun_DataLogger/file_issue/ + 2024-11-14 + + + https://sparkfun.github.io/SparkFun_DataLogger/hard_copy/ + 2024-11-14 + + + https://sparkfun.github.io/SparkFun_DataLogger/hardware_hookup/ + 2024-11-14 + + + https://sparkfun.github.io/SparkFun_DataLogger/hardware_overview/ + 2024-11-14 + + + https://sparkfun.github.io/SparkFun_DataLogger/introduction/ + 2024-11-14 + + + https://sparkfun.github.io/SparkFun_DataLogger/prepare_your_microsd_card/ + 2024-11-14 + + + https://sparkfun.github.io/SparkFun_DataLogger/resources/ + 2024-11-14 + + + https://sparkfun.github.io/SparkFun_DataLogger/single_page/ + 2024-11-14 + + + https://sparkfun.github.io/SparkFun_DataLogger/supported_devices/ + 2024-11-14 + + + https://sparkfun.github.io/SparkFun_DataLogger/troubleshooting/ + 2024-11-14 + + + https://sparkfun.github.io/SparkFun_DataLogger/updating_firmware/ + 2024-11-14 + + + https://sparkfun.github.io/SparkFun_DataLogger/wifi_network/ + 2024-11-14 + + + https://sparkfun.github.io/SparkFun_DataLogger/javascript/ + 2024-11-14 + + + https://sparkfun.github.io/SparkFun_DataLogger/relnotes/ + 2024-11-14 + + + https://sparkfun.github.io/SparkFun_DataLogger/relnotes/rn_v010101/ + 2024-11-14 + + + https://sparkfun.github.io/SparkFun_DataLogger/relnotes/rn_v010200/ + 2024-11-14 + + \ No newline at end of file diff --git a/sitemap.xml.gz b/sitemap.xml.gz new file mode 100644 index 0000000..444a8b2 Binary files /dev/null and b/sitemap.xml.gz differ diff --git a/stylesheet/extra.css b/stylesheet/extra.css new file mode 100644 index 0000000..c10aa3e --- /dev/null +++ b/stylesheet/extra.css @@ -0,0 +1,522 @@ +/* ================================================================================== */ +/* General Customizations */ +/* ================================================================================== */ + +/* Sets page to use screens full width (can also use initial; instead of 100%) */ +.md-grid { + max-width: 100%; +} + +/* Center images in figure elements */ +.md-typeset figure { + text-align: -webkit-center; +} + + + +/* Sets the maximum height size of the code blocks */ +/* (code block becomes scrollable, but not compatible when lines are displayed) */ +/* .md-typeset pre > code { + max-height: var(--md-codeblock-height, 800px); +} */ + +/* Failed workaround (lines are not scrollable)*/ +/* .highlighttable .linenodiv { + max-height: var(--md-codeblock-height, 800px); +} */ + +/* Word wrap code blocks (no horizontal scroll) */ +pre code { + white-space : pre-wrap; +} + + +/* ================================================================================== */ +/* Icons */ +/* ================================================================================== */ + +/* Heart Animation ================================================================== */ +@keyframes heart { + 0%, 40%, 80%, 100% { + transform: scale(1); + } + 20%, 60% { + transform: scale(1.15); + } +} + +.heart { + animation: heart 1000ms infinite; +} + +/* Autodesk Icon ==================================================================== */ +.md-typeset .twemoji.autodesk svg { + display:inline-flex; + height: 1rem; + width: 100%; +} + + +/* ================================================================================== */ +/* Custom Admonitions */ +/* ================================================================================== */ + +/* SparkFun ========================================================================= */ +.md-typeset :is(.tip)>:is(.admonition-title,summary):before { + background-color: #ee2e22; +} + + +/* GitHub =========================================================================== */ +.md-typeset .admonition.github, +.md-typeset details.github { + border-color: white; +} +.md-typeset .github > .admonition-title, +.md-typeset .github > summary { + background-image: linear-gradient(to bottom, #8241f9, #4e277b); + /* background-color: rgb(110, 64, 201); */ +} +.md-typeset .github > .admonition-title::before, +.md-typeset .github > summary::before { + background-color: white; + -webkit-mask-image: var(--md-admonition-icon--github); + mask-image: var(--md-admonition-icon--github); +} + + +/* Arduino ========================================================================== */ +.md-typeset .admonition.arduino, +.md-typeset details.arduino { + border-color: #005c5f; +} +.md-typeset .arduino > .admonition-title, +.md-typeset .arduino > summary { + background-color: rgba(0, 92, 95, 0.1); +} +.md-typeset .arduino > .admonition-title::before, +.md-typeset .arduino > summary::before { + background-color: #005c5f; + -webkit-mask-image: var(--md-admonition-icon--arduino); + mask-image: var(--md-admonition-icon--arduino); +} + + +/* Python =========================================================================== */ +.md-typeset .admonition.python, +.md-typeset details.python { + border-color: #3776ab; +} +.md-typeset .python > .admonition-title, +.md-typeset .python > summary { + background-color: #1e415e; +} +.md-typeset .python > .admonition-title::before, +.md-typeset .python > summary::before { + background-color: #3776ab; + -webkit-mask-image: var(--md-admonition-icon--python); + mask-image: var(--md-admonition-icon--python); +} + + +/* Code ============================================================================= */ +.md-typeset .admonition.code, +.md-typeset details.code { + border-color: #9e9e9e; +} +.md-typeset .code > .admonition-title, +.md-typeset .code > summary { + background-color: rgba(158, 158, 158, 0.1); +} +.md-typeset .code > .admonition-title::before, +.md-typeset .code > summary::before { + background-color: #9e9e9e; + -webkit-mask-image: var(--md-admonition-icon--code); + mask-image: var(--md-admonition-icon--code); +} + + +/* Terminal ========================================================================= */ +.md-typeset .admonition.terminal, +.md-typeset details.terminal { + border-color: #9e9e9e; +} +.md-typeset .terminal > .admonition-title, +.md-typeset .terminal > summary { + background-color: rgba(158, 158, 158, 0.1); +} +.md-typeset .terminal > .admonition-title::before, +.md-typeset .terminal > summary::before { + background-color: #9e9e9e; + -webkit-mask-image: var(--md-admonition-icon--terminal); + mask-image: var(--md-admonition-icon--terminal); +} + + +/* Serial Monitor =================================================================== */ +.md-typeset .admonition.serial, +.md-typeset details.serial { + border-color: #9e9e9e; +} +.md-typeset .serial > .admonition-title, +.md-typeset .serial > summary { + background-color: rgba(158, 158, 158, 0.1); +} +.md-typeset .serial > .admonition-title::before, +.md-typeset .serial > summary::before { + background-color: #9e9e9e; + -webkit-mask-image: var(--md-admonition-icon--serial); + mask-image: var(--md-admonition-icon--serial); +} + + +/* Hot ============================================================================== */ +.md-typeset .admonition.hot, +.md-typeset details.hot { + border-color: #ff1744; +} +.md-typeset .hot > .admonition-title, +.md-typeset .hot > summary { + background-color: #ff17441a; +} +.md-typeset .hot > .admonition-title::before, +.md-typeset .hot > summary::before { + background-color: #ff1744; + -webkit-mask-image: var(--md-admonition-icon--hot); + mask-image: var(--md-admonition-icon--hot); +} + + + +/* ================================================================================== */ +/* Header Permalinks */ +/* ================================================================================== */ +/* https://github.com/squidfunk/mkdocs-material/discussions/3535 */ + +.headerlink { + --permalink-size: 16px; /* for font-relative sizes, 0.6em is a good choice */ + --permalink-spacing: 4px; + + width: calc(var(--permalink-size) + var(--permalink-spacing)); + height: var(--permalink-size); + vertical-align: middle; + background-color: var(--md-default-fg-color--lighter); + background-size: var(--permalink-size); + mask-size: var(--permalink-size); + -webkit-mask-size: var(--permalink-size); + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + visibility: visible; + -webkit-mask-image: var(--md-admonition-icon--link); + mask-image: var(--md-admonition-icon--link); +} + +[id]:target .headerlink { + background-color: var(--md-typeset-a-color); +} + +.headerlink:hover { + background-color: var(--md-accent-fg-color) !important; +} + +@media screen and (min-width: 76.25em) { + h1, h2, h3, h4, h5, h6 { + display: flex; + align-items: center; + flex-direction: row; + } + + /* Sets icon location to left of header */ + /* .headerlink { + order: -1; + margin-left: calc(var(--permalink-size) * -1 - var(--permalink-spacing)) !important; + } */ +} + + + + +/* ================================================================================== */ +/* Introduction */ +/* ================================================================================== */ + +/* Sets table width for introduction */ +.md-typeset table.desc { + width: 100%; + display: table; +} +.md-typeset table.desc label{ + font-size: .85rem; +} + + +/* ================================================================================== */ +/* Grid Cards */ +/* ================================================================================== */ + +/* Sets grid width for introduction */ +.grid.cards.desc { + grid-template-columns: 35% 65%; +} + +/* Sets images size in grid cards */ +.md-typeset .grid ul figure img { + width: 100%; + max-width: 200px; +} + +/* Add compatibility to mobile platforms - by setting minimum width for grid cards */ +@media (max-width: 750px) { + .grid.cards.desc { + grid-template-columns: 100%; + } +} + + +/* ================================================================================== */ +/* YoutTube Videos */ +/* ================================================================================== */ + +/* Auto adjust embedded youtube videos size */ +/* height: 47vw * (viewport/element) percentage */ +.video { + position: relative; + width: 100%; +} +.video iframe { + position: relative; + top: 0; + width: 90vh; + min-width: 200px; + height: 16.45vw; + min-height: 113px; + border: 0; + overflow: hidden; +} + +.video_desc { + position: relative; + width: 100%; +} +.video_desc iframe { + position: relative; + top: 0; + width: 90vh; + min-width: 200px; + /* height: 12.72vw; */ + height: calc(calc(calc(100vw - 48.4rem) * 0.35 - 2rem) * 0.9); + min-height: 113px; + border: 0; + overflow: hidden; +} + +/* Add compatibility for mobile devices */ +/* height: 47vw * (viewport) percentage */ + +/* Single Column */ +@media (max-width: 750px) { + .video iframe { + height: 47vw; + } + + .video_desc iframe { + /* 100vw * 0.9 * 56.25% (aspect ratio) */ + height: 50.625vw; + } +} + +/* Grid: 35/65 */ +@media (min-width: 751px) { + .video_desc iframe { + /* 100vw * 0.9 * 56.25% (aspect ratio) * 0.35 */ + height: 17.71875vw; + } +} + +/* Single ToC */ +@media (min-width: 960px) { + .video_desc iframe { + /* height: 12.7vw; */ + height: calc( ( (100vw - 24.2rem) * 0.35 - 2rem) * 0.9); + } +} + +/* Double ToC */ +@media (min-width: 1219px) { + .video_desc iframe { + /* height: 12.7vw; */ + height: calc( ( (100vw - 24.2rem * 2) * 0.35 - 2rem) * 0.9); + } +} + +/* ================================================================================== */ +/* Parameters for PDF Rendering */ +/* ================================================================================== */ + +/* Hides elemments with , , or on webpage (used for the generated PDF) */ +* + .pdf { + display: none; +} +.md-typeset *.qr { + display: none; +} +.md-typeset *.tinyqr { + display: none; +} + +.md-typeset ul.pdf { + display: none; +} + +@media print { + /* Shows elements with , , or on webpage (used for the generated PDF) */ + * + .pdf { + display: contents; + } + .md-typeset *.qr { + display: inline-flex; + align-items: center; + } + + .md-typeset *.tinyqr { + display: inline-flex; + vertical-align: middle; + width: 40px; + } + + .grid.cards.hide { + display: none; + } + + /* Removes borders on tables with */ + table + .pdf { + border-style:none; + } + + /* Limits images size in table */ + .pdf img { + width: 100%; + max-width: 200px; + } + + .md-typeset ul.pdf { + display: flow-root; + } + + /* Sets table to 100% of page width */ + .md-typeset table:not([class]) { + width: 100%; + display: table; + table-layout: fixed; + } + .md-typeset table.pdf { + width: 100%; + display: table; + table-layout: fixed; + } + + /* Sets images size in tables */ + .md-typeset table:not([class]) td img { + width: 100%; + max-height: 150px; + } + + + /* ================================================================================== */ + /* YoutTube Videos */ + /* ================================================================================== */ + + .video iframe { + display: none; + } + + + /* ================================================================================== */ + /* Grid Cards */ + /* ================================================================================== */ + + /* Column Spacing =================================================================== */ + + /* Two Column Setting */ + .md-typeset .grid.grid.cards.col-2 { + grid-template-columns: repeat(auto-fit,minmax(16rem,1fr)); + } + + /* Three Column Setting */ + .md-typeset .grid.grid.cards.col-3 { + grid-template-columns: repeat(auto-fit,minmax(12rem,1fr)); + } + + /* Four Column Setting */ + .md-typeset .grid.grid.cards.col-4 { + grid-template-columns: repeat(auto-fit,minmax(8rem,1fr)); + } + + /* Sets grid card width (not working) + Works for File > Print function, but not valid for PDF generator due to issue with Weazy + * Refer to https://github.com/Kozea/WeasyPrint/issues/543 + .md-typeset .grid { + display: grid; + grid-template-columns: repeat(4, 25%); + } */ + /* Shrinks card width to 25% and font to 8px (kind of works for PDF generator, but doesn't create columns) + .md-typeset :is(.cards) { + width: 25%; + font-size: 8px; + } */ + + + /* Sets maximum image size */ + .md-typeset img { + max-width: 200px; + max-height: 200px; + } + /* Fixes positioning of admonition icon */ + .md-typeset :is(.admonition-title,summary):before { + top: 0.6rem; + left: 0.6rem; + } + /* Fixes appearance of admonition icon */ + .md-typeset :is(.admonition,details)>:last-child { + background-color: transparent; + } + /* Fixes margin spacing for lists */ + .md-typeset ol,ul { + margin-left: 1.5rem; + } + + /* Adjusts page break for PDF generator */ + article h2,h3,h4,h5,h6,ol,ul { + page-break-before: avoid; + } + article div.admonition { + page-break-before: avoid; + } + article table,ol,ul { + page-break-inside: auto !important; + } + + /* Displays first tab (workaround to rendering issue) */ + * + .tabbed-content { + display: contents; + } + * + .tabbed-content .tabbed-block { + display: contents; + page-break-after: always; + } + + /* Prevents page break when tabs are embedded in an admonition*/ + article details.note { + display: inline-block; + overflow: hidden; + /* Hardcode max height to match code block size, when tabbed (need to find better alternative) */ + max-height: 138mm; + } + + /* Limits size of code blocks in admonitions (workaround to rendering issue) */ + .md-typeset :is(pre, code) { + display: inline-flexbox; + max-height: 120mm; + overflow: hidden; + } +} diff --git a/supported_devices/index.html b/supported_devices/index.html new file mode 100644 index 0000000..6058d08 --- /dev/null +++ b/supported_devices/index.html @@ -0,0 +1,1842 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Appendix - Supported Qwiic Devices - SparkFun DataLogger IoT Hookup Guide + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      + +
      + + + + + + +
      + + + + + + + + + + + +
      + +
      + + + + +
      +
      + + + +
      +
      +
      + + + + + + + + + + + + + +
      +
      +
      + + + +
      +
      +
      + + + +
      +
      +
      + + + +
      + + + + + + + +
      + + + + + + + + + + + + + + + + + + + + + + + + +

      DataLogger IoT Supported Devices

      +

      The following lists the devices/boards supported by the DataLogger IoT boards. New drivers to support a device will be listed under the firmware version.

      +

      Version 01.01.00

      + +

      Version 01.00.00

      + + + + + + + + + + + + + + + + + + + + + + + + + + +
      +
      + + + +
      + + + +
      + + + +
      +
      +
      +
      + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/troubleshooting/index.html b/troubleshooting/index.html new file mode 100644 index 0000000..782528c --- /dev/null +++ b/troubleshooting/index.html @@ -0,0 +1,2038 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Troubleshooting - SparkFun DataLogger IoT Hookup Guide + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      + +
      + + + + + + +
      + + + + + + + + + + + +
      + +
      + + + + +
      +
      + + + +
      +
      +
      + + + + + + + + + + + + + +
      +
      +
      + + + +
      +
      +
      + + + +
      +
      +
      + + + +
      + + + + + + + +
      + + + + + + + + + + + + + + + + + + + + + + + + +

      Troubleshooting

      + +
      +

      Note

      +

      + Not working as expected and need help?

      +

      If you need technical assistance and more information on a product that is not working as you expected, we recommend heading on over to the SparkFun Technical Assistance page for some initial troubleshooting.

      +

      +

      If you don't find what you need there, the SparkFun Forums are a great place to find and ask for help. If this is your first visit, you'll need to create a Forum Account to search product forums and post questions.

      +

      +
      +

      Issues Connecting to IoT Service

      +

      Having trouble connecting your DataLogger IoT to an IoT service? Make sure to check your credentials and ensure that the configuration matches the IoT Service (such as your WiFi network, port, server, topic, certificates, keys, etc. to name a few). Make sure to also include the associated certificates and keys in the microSD card as well. You may see an output similar to the following errors below.

      +

      AWS IoT Error

      +

      The following error occurred when the DataLogger IoT was initializing with AWS.

      +
      [W] AWS IoT disconnected - reconnecting.......[E] AWS IoT: MQTT connection failed. Error Code: -2
      +
      +
      + + Configuration entered incorrectly, DataLogger not connecting to IoT Service +
      + +

      In this case, the DataLogger IoT failed to connect to AWS IoT service because the port was using the default value that was saved: 1883. Ensure that the port is set to 8883 for your IoT service (e.g. AWS IoT, Azure, and ThingSpeak) and saved in persistent memory in order for the DataLogger IoT to successfully connect. As of firmware v01.00.04, the default is 8883.

      +

      ThinkSpeak IoT Error

      +

      The following error occurred when the DataLogger IoT was initializing with ThingSpeak.

      +
      [I] ThingSpeak MQTT: connecting to MQTT endpoint mqtt3.thingspeak.com:8883 .......[E] ThingSpeak MQTT: Connection Error [4]
      +
      +
      + + Configuration entered incorrectly, DataLogger not connecting to IoT Service - ThingSpeak +
      + +

      In this case, the DataLogger IoT failed to connect to ThingSpeak service because the credentials were entered incorrectly. Ensure that the and saved in persistent memory in order for the DataLogger IoT to successfully connect.

      +

      Arduino Cloud Error 1

      +

      The following error was occurred when the DataLogger IoT was initializing with Arduino Cloud.

      +
      [W] ArduinoIoT - Thing Name not provided
      +.
      +.
      +.
      +[I] Arduino IoT: setup variables...[E] ArduinoIoT HTTP communication error [401] - token request
      +[E] Arduino IoT Cloud not available or account credentials incorrect
      +
      +
      + + Configuration entered incorrectly, DataLogger not connecting to IoT Service +
      + +

      In this case, the DataLogger IoT failed to connect to the Arduino Cloud service because the credentials were incorrect. Ensure that the credentials (i.e. API client ID, API secret, device secret, device ID) are entered correctly and saved in persistent memory in order for the DataLogger IoT to successfully connect.

      +

      Arduino Cloud Error 2

      +

      The following error was occurred when the DataLogger IoT was initializing with Arduino Cloud.

      +
      [I] Arduino IoT: setup variables...[E] Arduino IoT: return code=400, failed to decode request body with content type "application/json": uuid: incorrect UUID length 37 in string "a111aaa1-1111-1111-1a1a-1a11111a1111\r"
      +
      +

      In this case, the DataLogger IoT failed to connect to the Arduino Cloud service because the credentials were incorrect. The string was supposed to be the device ID. When copying and pasting the device ID from a PDF that was generated with the Arduino Cloud, a carriage return (\r) was also copied and entered in the serial terminal. By pasting the device ID into a text editor and then re-copying/pasting it into the serial terminal helped to ensure that the credentials were entered correctly.

      +
      +

      Note

      +

      The device ID in this example was a randomly generated string. You will need to check to make sure that your device matches the one that the Arduino Cloud generated specifically for your account.

      +
      +

      Arduino Cloud Error 3

      +

      The following error was occurred when the DataLogger IoT was initializing with Arduino Cloud.

      +
      [I] Arduino IoT: setup variables...ArduinoIoTCloudTCP::handle_ConnectMqttBroker could not connect to mqtts-up.iot.arduino.cc:8884
      +ArduinoIoTCloudTCP::handle_ConnectMqttBroker 1 connection attempt at tick time 301939
      +ArduinoIoTCloudTCP::handle_ConnectMqttBroker could not connect to mqtts-up.iot.arduino.cc:8884
      +ArduinoIoTCloudTCP::handle_ConnectMqttBroker 2 connection attempt at tick time 307420
      +[E] Arduino IoT: No Arduino Thing ID provided. Enter ID, delete Thing (SparkFunThing1) on Cloud, or enter new Thing Name.
      +
      +

      In this case, the DataLogger IoT failed to connect to the Arduino Cloud service because there was already a Thing that was created. By deleting the Thing in the Arduino Cloud, the DataLogger IoT was able to automatically create another Thing and setup the variables.

      +

      ThingSpeak Data Points Not Updating

      +

      If your DataLogger IoT is connected to ThingSpeak but you do not see any data, ensure that the device name matches the Qwiic device that is connect to the DataLogger IoT. For example, the DataLogger IoT and Qwiic-enabled ENS160 was able to connect to ThingSpeak as shown in the image on the bottom left. However, there were no data points in any of the graphs as shown on ThingSpeak as shown in the image on the bottom right.

      +
      + + + + +
      DataLogger Connected to ThingSpeakNo Data Points in ThingSpeak Channel
      +
      + +

      If you head back into the configuration menu for the DataLogger's ThingSpeak channel, make sure that the matches the connected Qwiic device's name that was shown during initialization. In this case, the device that was loaded and detected was ENS160. Then add the channel ID before saving the system settings.

      +
      + + Matching Device Name with Qwiic-Enabled ENS160 Breakout Board +
      + +
      +

      Note

      +

      Only one device can be loaded per channel! ThingSpeak is not able graph two different devices in the same channel.

      +
      +

      Head back to your ThingSpeak Channel to verify that data is being plotted on the graphs.

      +
      + + ThingSpeak Outputting ENS160 Sensor Data on Graphs +
      + +

      U-Blox I2C Device Disappears when IoT DataLogger Initializes

      +

      If you have issues where a u-blox device that is connected to the I2C port fails to connect a second time when the IoT DataLogger initializes, this is due to a bug in the firmware from an initial release. You may see an output similar to the following message and image shown below.

      +
      [W] GNSS::isConnected no traffic seen (first attempt)
      +
      +
      + + No Satellite Lock... Bug +
      + +

      If you see the following output and the IoT DataLogger not loading your u-blox device, you will need to update the firmware to v01.00.03 and above. For more information, make sure to check out the tutorial on updating your IoT DataLogger's firmware.

      + + + + + + + + + + + + + + + + + + + + + + + + + +
      +
      + + + +
      + + + +
      + + + +
      +
      +
      +
      + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/updating_firmware/index.html b/updating_firmware/index.html new file mode 100644 index 0000000..f420872 --- /dev/null +++ b/updating_firmware/index.html @@ -0,0 +1,1909 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Updating Firmware - SparkFun DataLogger IoT Hookup Guide + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      + +
      + + + + + + +
      + + + + + + + + + + + +
      + +
      + + + + +
      +
      + + + +
      +
      +
      + + + + + + + + + + + + + +
      +
      +
      + + + +
      +
      +
      + + + +
      +
      +
      + + + +
      + + + + + + + + + +
      + + + + + + + + + + + + + + + + + + + + + + + + +

      Updating Firmware

      + +
      +

      Danger

      +

      Please think very carefully before uploading any Arduino sketches to your DataLogger IoT.

      +

      You will overwrite the DataLogger IoT firmware, leaving it unable to update or restore itself.

      +

      Each DataLogger IoT comes pre-programmed with amazing firmware which can do so much. It is designed to be able to update itself and restore itself if necessary. But it can not do that if you overwrite the firmware with any Arduino sketch. It is just like erasing the restore partition on your computer hard drive. Do not do it - unless you really know what you are doing.

      +

      Really. We mean it.

      +
      +

      Firmware Update - SD Card

      +

      This action enables the ability to update the onboard firmware to an image file contained an SD card. This user is presented a list of available firmware images files contained in root directory of the on-board SD card, and updates the board to the selected file.

      +

      This option is available on ESP32 devices that contained two update firmware (OTA type) partitions within the on-board device flash memory. Consult the specific products production and build system for further details.

      +

      To download the latest firmware and update through the microSD card, you will need to head to the GitHub repository containing the firmware. Select the firmware version and download.

      + + +

      Once downloaded, insert the microSD card into a card reader or USB adapter. Then move the files into the memory card's root directory. Below shows an image of v01.00.01 and v01.00.02 on a Windows OS.

      +
      + + microSD Card Firmware Files +
      + +

      Once the files are copied to the memory card, eject the microSD card from your computer. Insert the card back into the DataLogger IoT's microSD card socket. Connect the DataLogger IoT to your computer using a USB cable.

      +
      + + + + + +
      Insert MicroSD CardDataLogger IoT Connecting USB
      +
      + +
      +

      Note

      +

      What's going on here?!? This tutorial was updated for firmware version 01.01.00!!! You will notice this menu option has changed to 17 !!!

      +
      +

      Open a Serial Terminal, connect to the COM port that your DataLogger enumerated to, and set it to 115200 baud. In this case, we connected to COM13. Press any key to enter the Main Menu. Type 1 to enter the Settings menu. Then type 17 to enter the System Update Menu. Finally, type 3 to update the firmware from the microSD card. You should see an image similar to the output below.

      +
      + + Firmware Files +
      + +

      The system will search the root directory of the on-board SD card for available firmware files. The firmware files are selected using the following criteria:

      +
        +
      • The file is contained in the root "/" folder of the SD card.
      • +
      • The filename has a ".bin" extension.
      • +
      • The filename starts with a specified name prefix. The prefix is optional and is set by the developers at SparkFun using this action.
          +
        • For example, the DataLogger IoT boards use a prefix value of "SparkFun_DataLoggerIoT_".
        • +
        +
      • +
      +

      Once a file is selected, the system new firmware is read off the SD card and written to the device.

      +
      + + Updating +
      + +

      And once updated, the system is rebooted and starts using the new firmware image!

      +
      + + Reboot +
      + +

      Firmware Update - Over-the-Air (OTA)

      +

      This action enables the ability to update the onboard firmware to an image file contained on an update server, which is accessed via the WiFi network the system is connected to. This Over-The-Air (OTA) capability contacts the systems update server and searches for newer firmware (later version) for the specific board.

      +

      This option is available on ESP32 devices that contained two update firmware (OTA type) partitions within the on-board device flash memory. Consult the specific products production and build system for further details.

      +

      If you have not already, connect the DataLogger IoT to your computer using a USB cable.

      +
      + + + + +
      DataLogger IoT Connecting USB
      +
      + +

      Open a Serial Terminal, connect to the COM port that your DataLogger enumerated to, and set it to 115200 baud. In this case, we connected to COM13. Press any key to enter the Main Menu. Type 1 to enter the Settings menu. Then type 16 to enter the System Update Menu. Finally, type 4 to update the firmware over-the-air.

      +
      System Update Menu Options +
      + +

      When this option is selected, the system will contact the update server and search for available upgrade firmware, selecting the latest version available. If a newer version is found, a prompt is presented to confirm the upgrade.

      +
      + + Select OTA Update +
      + +
      +

      Note

      +

      For the upgrade option to occur, a the system must be connected to a network and have access to the firmware OTA server.

      +
      +

      Typing Y (or hitting enter) starts the update operation. As the firmware is downloaded, the percent complete status is updated. If connectivity fails during the download, the operation is halted and the update aborted.

      +
      + + OTA Update Downloading +
      + +

      Once the update file is downloaded, it is verified as being the correct file. Once verified, the system is rebooted and starts using the new firmware image! You will notice the firmware version change as the DataLogger IoT initializes.

      +
      + + Updated OTA and Rebooted +
      + + + + + + + + + + + + + + + + + + + + + + + + + +
      +
      + + + +
      + + + +
      + + + +
      +
      +
      +
      + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/wifi_network/index.html b/wifi_network/index.html new file mode 100644 index 0000000..51fe3b3 --- /dev/null +++ b/wifi_network/index.html @@ -0,0 +1,1746 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Connecting to a WiFi Network - SparkFun DataLogger IoT Hookup Guide + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      + +
      +
      + +
      + + + + + + +
      + + + + + + + + + + + +
      + +
      + + + + +
      +
      + + + +
      +
      +
      + + + + + + + + + + + + + +
      +
      +
      + + + +
      +
      +
      + + + +
      +
      +
      + + + +
      + + + + + + + + + +
      + + + + + + + + + + + + + + + + + + + + + + + + +

      Connecting to a WiFi Network

      + +
      +

      Note

      +

      The ESP32-WROOM can only connect to a 2.4GHz WiFi network. Unfortunately, 5GHz is not supported on the ESP32-WROOM module.

      +
      +

      To take advantage of syncing the DataLogger to the Network Time Protocol (NTP), logging data to an IoT service, or updating firmware OTA, you will need to connect to a 2.4GHz WiFi network.

      +

      Open a Serial Terminal, connect to the COM port that your DataLogger enumerated to, and set it to 115200 baud. In this case, we connected to COM13. Press any key to enter the Main Menu. Type 1 to enter the Settings menu. Then send a 4 to configure the WiFi settings.

      +
      + WiFi Network Menu Options +
      + +

      Send a 2 to set the WiFi Network Name. You'll be prompted to set the network name. In this case, the network name is sparkfun. Once you enter the name, hit the enter key.

      +
      + + Configure WiFi Network +
      + +

      Send a 2 to set the WiFi password. You'll be prompted to set the password. As you send the password, each character will be masked by a asterisk (i.e. *) Once you enter the name, hit the enter key.

      +
      + + Configure WiFi Password +
      + +

      Follow the prompts to exit out of the menu properly so that the DataLogger IoT saves the settings.

      +
      + Output Save Settings Confirmation +
      + +

      Once you see the message [I] Saving System Settings and data on the output, hit the reset button on the board. You can also use the menu to perform a device restart, however you will need to ensure that you receive the message indicating that the settings were saved before restarting the device.

      +

      Once the device has restarted, the DataLogger will provide an output as it is initializing. If the WiFi credentials are saved properly, you will receive a message indicating that your chosen network is connected to your WiFi network. If the time source is set to the default NTP client, you will also notice that the time will be synced to the latest date and time!

      +
      + + Configure WiFi Password +
      + + + + + + + + + + + + + + + + + + + + + + + + + +
      +
      + + + +
      + + + +
      + + + +
      +
      +
      +
      + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file