diff --git a/CMakeLists.txt b/CMakeLists.txt index cb76c4e..c767108 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -107,8 +107,11 @@ add_executable(fanpico src/i2c_bmp180.c src/i2c_bmp280.c src/i2c_dps310.c + src/i2c_lps22.c + src/i2c_lps25.c src/i2c_mcp9808.c src/i2c_pct2075.c + src/i2c_shtc3.c src/i2c_stts22h.c src/i2c_tmp102.c src/i2c_tmp117.c diff --git a/commands.md b/commands.md index 6186e0b..91b8637 100644 --- a/commands.md +++ b/commands.md @@ -68,6 +68,8 @@ Fanpico supports following commands: * [CONFigure:SENSORx:TEMPNominal?](#configuresensorxtempnominal-1) * [CONFigure:SENSORx:FILTER](#configuresensorxfilter) * [CONFigure:SENSORx:FILTER?](#configuresensorxfilter-1) +* [CONFigure:VSENSORS?](#systemvsensors) +* [CONFigure:VSENSORS:SOUrces?](#systemvsensorssources) * [CONFigure:VSENSORx:NAME](#configurevsensorxname) * [CONFigure:VSENSORx:NAME?](#configurevsensorxname-1) * [CONFigure:VSENSORx:SOUrce](#configurevsensorxsource) @@ -90,7 +92,10 @@ Fanpico supports following commands: * [MEASure:SENSORx?](#measuresensorx) * [MEASure:SENSORx:Read?](#measuresensorxread) * [MEASure:SENSORx:TEMP?](#measuresensorxtemp) +* [MEASure:VSENSORS?](#measurevsensors) * [MEASure:VSENSORx?](#measurevsensorx) +* [MEASure:VSENSORx:HUMidity?](#measurevsensorxhumidity) +* [MEASure:VSENSORx:PREssure?](#measurevsensorxpressure) * [MEASure:VSENSORx:Read?](#measurevsensorxread) * [MEASure:VSENSORx:TEMP?](#measurevsensorxtemp) * [Read?](#read) @@ -142,12 +147,20 @@ Fanpico supports following commands: * [SYStem:MQTT:INTerval:STATUS?](#systemmqttintervalstatus-1) * [SYStem:MQTT:INTerval:TEMP](#systemmqttintervaltemp) * [SYStem:MQTT:INTerval:TEMP?](#systemmqttintervaltemp-1) +* [SYStem:MQTT:INTerval:VSENsor](#systemmqttintervalvsensor) +* [SYStem:MQTT:INTerval:VSENsor?](#systemmqttintervalvsensor-1) * [SYStem:MQTT:INTerval:RPM](#systemmqttintervalrpm) * [SYStem:MQTT:INTerval:RPM?](#systemmqttintervalrpm-1) * [SYStem:MQTT:INTerval:PWM](#systemmqttintervalpwm) * [SYStem:MQTT:INTerval:PWM?](#systemmqttintervalpwm-1) * [SYStem:MQTT:MASK:TEMP](#systemmqttmasktemp) * [SYStem:MQTT:MASK:TEMP?](#systemmqttmasktemp-1) +* [SYStem:MQTT:MASK:VTEMP](#systemmqttmaskvtemp) +* [SYStem:MQTT:MASK:VTEMP?](#systemmqttmaskvtemp-1) +* [SYStem:MQTT:MASK:VHUMidity](#systemmqttmaskvhumidity) +* [SYStem:MQTT:MASK:VHUMidity?](#systemmqttmaskvhumidity-1) +* [SYStem:MQTT:MASK:VPREssure](#systemmqttmaskvpressure) +* [SYStem:MQTT:MASK:VPREssure?](#systemmqttmaskvpressure-1) * [SYStem:MQTT:MASK:FANRPM](#systemmqttmaskfanrpm) * [SYStem:MQTT:MASK:FANRPM?](#systemmqttmaskfanrpm-1) * [SYStem:MQTT:MASK:FANPWM](#systemmqttmaskfanpwm) @@ -164,6 +177,12 @@ Fanpico supports following commands: * [SYStem:MQTT:TOPIC:RESPonse?](#systemmqttopicresponse-1) * [SYStem:MQTT:TOPIC:TEMP](#systemmqtttopictemp) * [SYStem:MQTT:TOPIC:TEMP?](#systemmqttopictemp-1) +* [SYStem:MQTT:TOPIC:VTEMP](#systemmqtttopicvtemp) +* [SYStem:MQTT:TOPIC:VTEMP?](#systemmqttopicvtemp-1) +* [SYStem:MQTT:TOPIC:VHUMidity](#systemmqtttopicvhumidity) +* [SYStem:MQTT:TOPIC:VHUMidity?](#systemmqttopicvhumidity-1) +* [SYStem:MQTT:TOPIC:VPREssure](#systemmqtttopicvpressure) +* [SYStem:MQTT:TOPIC:VPREssure?](#systemmqttopicvpressure-1) * [SYStem:MQTT:TOPIC:FANRPM](#systemmqtttopicfanrpm) * [SYStem:MQTT:TOPIC:FANRPM?](#systemmqttopicfanrpm-1) * [SYStem:MQTT:TOPIC:FANPWM](#systemmqtttopicfanpwm) @@ -208,7 +227,6 @@ Fanpico supports following commands: * [SYStem:VREFadc](#systemvrefadc) * [SYStem:VREFadc?](#systemvrefadc-1) * [SYStem:VSENSORS?](#systemvsensors) -* [SYStem:VSENSORS:SOUrces?](#systemvsensorssources) * [SYStem:WIFI?](#systemwifi) * [SYStem:WIFI:COUntry](#systemwificountry) * [SYStem:WIFI:COUntry?](#systemwificountry-1) @@ -1063,6 +1081,30 @@ CONF:SENSOR1:FILTER? sma,10 ``` +#### CONFigure:VSENSORS? + +This is same as CONFigure::VSENSORS:SOUrces? command. + + +#### CONFigure:VSENSORS:SOUrces? +Return virtual sensor (source) configuration information for all +virtual sensors in CSV format. + +Format: ,,,,... + +Example: +``` +CONF:VSENSORS:SOURCES? +vsensor1,onewire,22cd991800000020 +vsensor2,i2c,0x48,TMP117 +vsensor3,i2c,0x37,PCT2075 +vsensor4,i2c,0x77,DPS310 +vsensor5,i2c,0x76,BMP280 +vsensor6,i2c,0x49,ADT7410 +vsensor7,i2c,0x38,AHT2x +vsensor8,manual,0.00,30 +``` + ### CONFigure:VSENSORx Commands VSENSORx (where x is the sensor number) commands are used to configure virtual temperature sensors. @@ -1134,8 +1176,11 @@ AS621x||AS621x series: AS6212 (0.2C), AC6214 (0.4C), AC6218 (0.8C) BMP180||16bit, 0.5C accuracy BMP280|0x76, 0x77|20bit, 0.5C accuracy DPS310|0x77, 0x76|24bit, 0.5C accuracy +LPS22|0x5d, 0x5c|Temperature and Pressure sensor +LPS25|0x5d, 0x5c|Temperature and Pressure sensor, 2C accuracy MCP9808||13bit, 0.25C accuracy PCT2075||11bit, 1C accuracy +SHTC3|0x70|Temperature and Humidity sensor, 0.2C accuracy STTS22H|0x38, 0x3c, 0x3e, 0x3f|16bit, 0.5C accuracy TMP102|0x48, 0x49, 0x4a, 0x4b|12bit, 2C accuracy TMP117|0x48, 0x49, 0x4a, 0x4b|16bit, 0.1C accuracy @@ -1460,6 +1505,26 @@ MEAS:SENSOR1:TEMP? 25 ``` + +#### MEASure:VSENSORS? +Return all measurements for all virtual sensors. + +Format: sensor,temperature_C,humidity_%,pressure_hPa + +Example: +``` +MEAS:VSENSORS? +vsensor1,"vsensor1",24.4,0,0 +vsensor2,"vsensor2",24.9,0,0 +vsensor3,"vsensor3",24.8,43,0 +vsensor4,"vsensor4",26.5,0,997 +vsensor5,"vsensor5",25.1,0,991 +vsensor6,"vsensor6",0.0,0,0 +vsensor7,"vsensor7",0.0,0,0 +vsensor8,"vsensor8",0.0,0,0 +``` + + #### MEASure:VENSORx? Return current temperature (C) measured by the sensor. @@ -1469,6 +1534,24 @@ MEAS:VSENSOR1? 25 ``` +#### MEASure:VSENSORx:HUMidity? +Return current humidity (%) measured by the sensor. + +Example: +``` +MEAS:VSENSOR1:HUM? +45 +``` + +#### MEASure:VSENSORx:PREssure? +Return current pressure (hPa) measured by the sensor. + +Example: +``` +MEAS:VSENSOR1:PRE? +1013 +``` + #### MEASure:VSENSORx:Read? Return current temperature (C) measured by the sensor. @@ -2210,6 +2293,29 @@ SYS:MQTT:INT:TEMP? ``` +#### SYStem:MQTT:INTerval:VSENsor +Configure how often unit will publish (send) virtual sensor status messages. +Set this to 0 (seconds) to disable publishing status updates. +Recommended values are 60 (seconds) or higher. + +Default: 0 (disabled) + +Example: +``` +SYS:MQTT:INT:VSEN 60 +``` + + +#### SYStem:MQTT:INTerval:VSENsor? +Query how often unit is setup to publish virtual sensor status messages. + +Example: +``` +SYS:MQTT:INT:VSENP? +60 +``` + + #### SYStem:MQTT:INTerval:RPM Configure how often unit will publish (send) RPM status updates for fans (and mbfans). @@ -2284,6 +2390,78 @@ SYS:MQTT:MASK:TEMP? ``` +#### SYStem:MQTT:MASK:VTEMP +Configure which virtual sensors should publish (send) temperature data to MQTT server. + +Sensors can be specified as comma separated list (2,3) or as range (1-3) +or as combination of both. + +Default: (do not publish data from any sensor) + +Example: +``` +SYS:MQTT:MASK:VTEMP 1,2,3,4 +``` + + +#### SYStem:MQTT:MASK:VTEMP? +Query which virtual sensors are configured to publish (send) temperature data to MQTT server. + +Example: +``` +SYS:MQTT:MASK:VTEMP? +1-4 +``` + + +#### SYStem:MQTT:MASK:VHUMidity +Configure which virtual sensors should publish (send) humidity data to MQTT server. + +Sensors can be specified as comma separated list (2,3) or as range (1-3) +or as combination of both. + +Default: (do not publish data from any sensor) + +Example: +``` +SYS:MQTT:MASK:VHUM 1,2 +``` + + +#### SYStem:MQTT:MASK:VHUMidity? +Query which virtual sensors are configured to publish (send) humidity data to MQTT server. + +Example: +``` +SYS:MQTT:MASK:VHUM? +1-2 +``` + + +#### SYStem:MQTT:MASK:VPREssure +Configure which virtual sensors should publish (send) pressure data to MQTT server. + +Sensors can be specified as comma separated list (2,3) or as range (1-3) +or as combination of both. + +Default: (do not publish data from any sensor) + +Example: +``` +SYS:MQTT:MASK:VPRE 1,2 +``` + + +#### SYStem:MQTT:MASK:VPREssure? +Query which virtual sensors are configured to publish (send) pressure data to MQTT server. + +Example: +``` +SYS:MQTT:MASK:VPRE? +1-2 +``` + + #### SYStem:MQTT:MASK:FANRPM Configure which fan ports should publish (send) RPM data to MQTT server. @@ -2458,7 +2636,7 @@ Default: Example: ``` -SYS:MQTT:TOPIC:TEMP musername/feeds/temp%d +SYS:MQTT:TOPIC:TEMP mysername/feeds/temp%d ``` @@ -2472,6 +2650,81 @@ myusername/feeds/temp%d ``` +#### SYStem:MQTT:TOPIC:VTEMP +Configure topic template for publishing virtual sensor temperature data to. +If this is left to empty, then unit won't send response to any commands. + +This is template string where ```%d``` should be used to mark the port number. + + +Default: + +Example: +``` +SYS:MQTT:TOPIC:VTEMP mysername/feeds/vtemp%d +``` + + +#### SYStem:MQTT:TOPIC:VTEMP? +Query currently set topic template for virtual sensor temperature data. + +Example: +``` +SYS:MQTT:TOPIC:VTEMP? +myusername/feeds/vtemp%d +``` + + +#### SYStem:MQTT:TOPIC:VHUMidity +Configure topic template for publishing virtual sensor humidity data to. +If this is left to empty, then unit won't send response to any commands. + +This is template string where ```%d``` should be used to mark the port number. + + +Default: + +Example: +``` +SYS:MQTT:TOPIC:VHUM mysername/feeds/humidity%d +``` + + +#### SYStem:MQTT:TOPIC:VHUMidity? +Query currently set topic template for virtual sensor humidity data. + +Example: +``` +SYS:MQTT:TOPIC:VHUM? +myusername/feeds/humidity%d +``` + + +#### SYStem:MQTT:TOPIC:VPREssure +Configure topic template for publishing virtual sensor pressure data to. +If this is left to empty, then unit won't send response to any commands. + +This is template string where ```%d``` should be used to mark the port number. + + +Default: + +Example: +``` +SYS:MQTT:TOPIC:VPRE mysername/feeds/pressure%d +``` + + +#### SYStem:MQTT:TOPIC:VPREssure? +Query currently set topic template for virtual sensor pressure data. + +Example: +``` +SYS:MQTT:TOPIC:VPRE? +myusername/feeds/pressure%d +``` + + #### SYStem:MQTT:TOPIC:FANRPM Configure topic template for publishing fan RPM data to. If this is left to empty, then unit won't send response to any commands. @@ -2984,26 +3237,6 @@ SYS:VSENSORS? 8 ``` -#### SYStem:VSENSORS:SOUrces? -Return virtual sensor (source) configuration information for all -virtual sensors in CSV format. - -Format: ,,,,... - -Example: -``` -SYS:VSENSORS:SOURCES? -vsensor1,onewire,22cd991800000020 -vsensor2,i2c,0x48,TMP117 -vsensor3,i2c,0x37,PCT2075 -vsensor4,i2c,0x77,DPS310 -vsensor5,i2c,0x76,BMP280 -vsensor6,i2c,0x49,ADT7410 -vsensor7,i2c,0x38,AHT2x -vsensor8,manual,0.00,30 -``` - - #### SYStem:VREFadc new: release v1.6.4 \ Set actual (measured with a volt meter) reference voltage (Vref) for ADC. diff --git a/src/command.c b/src/command.c index 248a60c..2f8c47a 100644 --- a/src/command.c +++ b/src/command.c @@ -660,6 +660,24 @@ int cmd_read(const char *cmd, const char *args, int query, struct prev_cmd_t *pr return 0; } +int cmd_vsensors_read(const char *cmd, const char *args, int query, struct prev_cmd_t *prev_cmd) +{ + int i; + + if (!query) + return 1; + + for (i = 0; i < VSENSOR_COUNT; i++) { + printf("vsensor%d,\"%s\",%.1lf,%.0f,%0.0f\n", i+1, + conf->vsensors[i].name, + st->vtemp[i], + st->vhumidity[i], + st->vpressure[i]); + } + + return 0; +} + int cmd_fan_name(const char *cmd, const char *args, int query, struct prev_cmd_t *prev_cmd) { int fan; @@ -2011,6 +2029,44 @@ int cmd_vsensor_temp(const char *cmd, const char *args, int query, struct prev_c return 1; } +int cmd_vsensor_humidity(const char *cmd, const char *args, int query, struct prev_cmd_t *prev_cmd) +{ + int sensor; + float d; + + if (!query) + return 1; + + sensor = get_prev_cmd_index(prev_cmd, 0) - 1; + if (sensor >= 0 && sensor < VSENSOR_COUNT) { + d = st->vhumidity[sensor]; + log_msg(LOG_DEBUG, "vsensor%d humidity = %f%%", sensor + 1, d); + printf("%.0f\n", d); + return 0; + } + + return 1; +} + +int cmd_vsensor_pressure(const char *cmd, const char *args, int query, struct prev_cmd_t *prev_cmd) +{ + int sensor; + float d; + + if (!query) + return 1; + + sensor = get_prev_cmd_index(prev_cmd, 0) - 1; + if (sensor >= 0 && sensor < VSENSOR_COUNT) { + d = st->vpressure[sensor]; + log_msg(LOG_DEBUG, "vsensor%d pressure = %fhPa", sensor + 1, d); + printf("%.0f\n", d); + return 0; + } + + return 1; +} + int cmd_vsensor_filter(const char *cmd, const char *args, int query, struct prev_cmd_t *prev_cmd) { int sensor; @@ -2219,6 +2275,12 @@ int cmd_mqtt_temp_interval(const char *cmd, const char *args, int query, struct &conf->mqtt_temp_interval, 0, (86400 * 30), "MQTT Publish Temp Interval"); } +int cmd_mqtt_vsensor_interval(const char *cmd, const char *args, int query, struct prev_cmd_t *prev_cmd) +{ + return uint32_setting(cmd, args, query, prev_cmd, + &conf->mqtt_vsensor_interval, 0, (86400 * 30), "MQTT Publish VSENSOR Interval"); +} + int cmd_mqtt_rpm_interval(const char *cmd, const char *args, int query, struct prev_cmd_t *prev_cmd) { return uint32_setting(cmd, args, query, prev_cmd, @@ -2265,6 +2327,27 @@ int cmd_mqtt_temp_topic(const char *cmd, const char *args, int query, struct pre sizeof(conf->mqtt_temp_topic), "MQTT Temperature Topic", NULL); } +int cmd_mqtt_vtemp_topic(const char *cmd, const char *args, int query, struct prev_cmd_t *prev_cmd) +{ + return string_setting(cmd, args, query, prev_cmd, + conf->mqtt_vtemp_topic, + sizeof(conf->mqtt_vtemp_topic), "MQTT VTemperature Topic", NULL); +} + +int cmd_mqtt_vhumidity_topic(const char *cmd, const char *args, int query, struct prev_cmd_t *prev_cmd) +{ + return string_setting(cmd, args, query, prev_cmd, + conf->mqtt_vhumidity_topic, + sizeof(conf->mqtt_vhumidity_topic), "MQTT VHumidity Topic", NULL); +} + +int cmd_mqtt_vpressure_topic(const char *cmd, const char *args, int query, struct prev_cmd_t *prev_cmd) +{ + return string_setting(cmd, args, query, prev_cmd, + conf->mqtt_vpressure_topic, + sizeof(conf->mqtt_vpressure_topic), "MQTT VPressure Topic", NULL); +} + int cmd_mqtt_fan_rpm_topic(const char *cmd, const char *args, int query, struct prev_cmd_t *prev_cmd) { return string_setting(cmd, args, query, prev_cmd, @@ -2307,6 +2390,27 @@ int cmd_mqtt_mask_temp(const char *cmd, const char *args, int query, struct prev 1, "MQTT Temperature Mask"); } +int cmd_mqtt_mask_vtemp(const char *cmd, const char *args, int query, struct prev_cmd_t *prev_cmd) +{ + return bitmask16_setting(cmd, args, query, prev_cmd, + &conf->mqtt_vtemp_mask, VSENSOR_MAX_COUNT, + 1, "MQTT VSENSOR Temperature Mask"); +} + +int cmd_mqtt_mask_vhumidity(const char *cmd, const char *args, int query, struct prev_cmd_t *prev_cmd) +{ + return bitmask16_setting(cmd, args, query, prev_cmd, + &conf->mqtt_vhumidity_mask, VSENSOR_MAX_COUNT, + 1, "MQTT VSENSOR Humidity Mask"); +} + +int cmd_mqtt_mask_vpressure(const char *cmd, const char *args, int query, struct prev_cmd_t *prev_cmd) +{ + return bitmask16_setting(cmd, args, query, prev_cmd, + &conf->mqtt_vpressure_mask, VSENSOR_MAX_COUNT, + 1, "MQTT VSENSOR Pressure Mask"); +} + int cmd_mqtt_mask_fan_rpm(const char *cmd, const char *args, int query, struct prev_cmd_t *prev_cmd) { return bitmask16_setting(cmd, args, query, prev_cmd, @@ -2808,6 +2912,9 @@ const struct cmd_t wifi_commands[] = { #ifdef WIFI_SUPPORT const struct cmd_t mqtt_mask_commands[] = { { "TEMP", 4, NULL, cmd_mqtt_mask_temp }, + { "VTEMP", 5, NULL, cmd_mqtt_mask_vtemp }, + { "VHUMidity", 4, NULL, cmd_mqtt_mask_vhumidity }, + { "VPREssure", 4, NULL, cmd_mqtt_mask_vpressure }, { "FANRPM", 6, NULL, cmd_mqtt_mask_fan_rpm }, { "FANPWM", 6, NULL, cmd_mqtt_mask_fan_duty }, { "MBFANRPM", 8, NULL, cmd_mqtt_mask_mbfan_rpm }, @@ -2818,6 +2925,7 @@ const struct cmd_t mqtt_mask_commands[] = { const struct cmd_t mqtt_interval_commands[] = { { "STATUS", 6, NULL, cmd_mqtt_status_interval }, { "TEMP", 4, NULL, cmd_mqtt_temp_interval }, + { "VSENsor", 4, NULL, cmd_mqtt_vsensor_interval }, { "RPM", 3, NULL, cmd_mqtt_rpm_interval }, { "PWM", 3, NULL, cmd_mqtt_duty_interval }, { 0, 0, 0, 0 } @@ -2828,6 +2936,9 @@ const struct cmd_t mqtt_topic_commands[] = { { "COMMand", 4, NULL, cmd_mqtt_cmd_topic }, { "RESPonse", 4, NULL, cmd_mqtt_resp_topic }, { "TEMP", 4, NULL, cmd_mqtt_temp_topic }, + { "VTEMP", 5, NULL, cmd_mqtt_vtemp_topic }, + { "VHUMidity", 4, NULL, cmd_mqtt_vhumidity_topic }, + { "VPREssure", 4, NULL, cmd_mqtt_vpressure_topic }, { "FANRPM", 6, NULL, cmd_mqtt_fan_rpm_topic }, { "FANPWM", 6, NULL, cmd_mqtt_fan_duty_topic }, { "MBFANRPM", 8, NULL, cmd_mqtt_mbfan_rpm_topic }, @@ -2890,11 +3001,6 @@ const struct cmd_t onewire_commands[] = { { 0, 0, 0, 0 } }; -const struct cmd_t vsensor_s_commands[] = { - { "SOUrce", 3, NULL, cmd_vsensors_sources }, - { 0, 0, 0, 0 } -}; - const struct cmd_t system_commands[] = { { "DEBUG", 5, NULL, cmd_debug }, /* Obsolete ? */ { "DISPlay", 4, display_commands, cmd_display_type }, @@ -2926,8 +3032,8 @@ const struct cmd_t system_commands[] = { { "UPGRADE", 7, NULL, cmd_usb_boot }, { "UPTIme", 4, NULL, cmd_uptime }, { "VERsion", 3, NULL, cmd_version }, - { "VSENSORS", 8, vsensor_s_commands, cmd_vsensors }, { "VREFadc", 4, NULL, cmd_sensor_adc_vref }, + { "VSENSORS", 8, NULL, cmd_vsensors }, { "WIFI", 4, wifi_commands, cmd_wifi }, { 0, 0, 0, 0 } }; @@ -2985,6 +3091,11 @@ const struct cmd_t vsensor_c_commands[] = { { 0, 0, 0, 0 } }; +const struct cmd_t vsensors_c_commands[] = { + { "SOUrce", 3, NULL, cmd_vsensors_sources }, + { 0, 0, 0, 0 } +}; + const struct cmd_t config_commands[] = { { "DELete", 3, NULL, cmd_delete_config }, { "FAN", 3, fan_c_commands, NULL }, @@ -2992,6 +3103,7 @@ const struct cmd_t config_commands[] = { { "Read", 1, NULL, cmd_print_config }, { "SAVe", 3, NULL, cmd_save_config }, { "SENSOR", 6, sensor_c_commands, NULL }, + { "VSENSORS", 8, vsensors_c_commands, cmd_vsensors_sources }, { "VSENSOR", 7, vsensor_c_commands, NULL }, { 0, 0, 0, 0 } }; @@ -3019,6 +3131,8 @@ const struct cmd_t sensor_commands[] = { }; const struct cmd_t vsensor_commands[] = { + { "HUMidity", 3, NULL, cmd_vsensor_humidity }, + { "PREssure", 3, NULL, cmd_vsensor_pressure }, { "Read", 1, NULL, cmd_vsensor_temp }, { "TEMP", 4, NULL, cmd_vsensor_temp }, { 0, 0, 0, 0 } @@ -3029,6 +3143,7 @@ const struct cmd_t measure_commands[] = { { "MBFAN", 5, mbfan_commands, cmd_mbfan_read }, { "Read", 1, NULL, cmd_read }, { "SENSOR", 6, sensor_commands, cmd_sensor_temp }, + { "VSENSORS", 8, NULL, cmd_vsensors_read }, { "VSENSOR", 7, vsensor_commands, cmd_vsensor_temp }, { 0, 0, 0, 0 } }; diff --git a/src/config.c b/src/config.c index 456f0ff..40e6504 100644 --- a/src/config.c +++ b/src/config.c @@ -555,17 +555,24 @@ void clear_config(struct fanpico_config *cfg) cfg->mqtt_cmd_topic[0] = 0; cfg->mqtt_resp_topic[0] = 0; cfg->mqtt_temp_mask = 0; + cfg->mqtt_vtemp_mask = 0; + cfg->mqtt_vhumidity_mask = 0; + cfg->mqtt_vpressure_mask = 0; cfg->mqtt_fan_rpm_mask = 0; cfg->mqtt_fan_duty_mask = 0; cfg->mqtt_mbfan_rpm_mask = 0; cfg->mqtt_mbfan_duty_mask = 0; cfg->mqtt_temp_topic[0] = 0; + cfg->mqtt_vtemp_topic[0] = 0; + cfg->mqtt_vhumidity_topic[0] = 0; + cfg->mqtt_vpressure_topic[0] = 0; cfg->mqtt_fan_rpm_topic[0] = 0; cfg->mqtt_fan_duty_topic[0] = 0; cfg->mqtt_mbfan_rpm_topic[0] = 0; cfg->mqtt_mbfan_duty_topic[0] = 0; cfg->mqtt_status_interval = DEFAULT_MQTT_STATUS_INTERVAL; cfg->mqtt_temp_interval = DEFAULT_MQTT_TEMP_INTERVAL; + cfg->mqtt_vsensor_interval = DEFAULT_MQTT_TEMP_INTERVAL; cfg->mqtt_rpm_interval = DEFAULT_MQTT_RPM_INTERVAL; cfg->mqtt_duty_interval = DEFAULT_MQTT_DUTY_INTERVAL; cfg->mqtt_ha_discovery_prefix[0] = 0; @@ -674,6 +681,9 @@ cJSON *config_to_json(const struct fanpico_config *cfg) if (cfg->mqtt_temp_interval != DEFAULT_MQTT_TEMP_INTERVAL) cJSON_AddItemToObject(config, "mqtt_temp_interval", cJSON_CreateNumber(cfg->mqtt_temp_interval)); + if (cfg->mqtt_vsensor_interval != DEFAULT_MQTT_TEMP_INTERVAL) + cJSON_AddItemToObject(config, "mqtt_vsensor_interval", + cJSON_CreateNumber(cfg->mqtt_vsensor_interval)); if (cfg->mqtt_rpm_interval != DEFAULT_MQTT_RPM_INTERVAL) cJSON_AddItemToObject(config, "mqtt_rpm_interval", cJSON_CreateNumber(cfg->mqtt_rpm_interval)); @@ -685,6 +695,21 @@ cJSON *config_to_json(const struct fanpico_config *cfg) cJSON_CreateString( bitmask_to_str(cfg->mqtt_temp_mask, SENSOR_MAX_COUNT, 1, true))); + if (cfg->mqtt_vtemp_mask) + cJSON_AddItemToObject(config, "mqtt_vtemp_mask", + cJSON_CreateString( + bitmask_to_str(cfg->mqtt_vtemp_mask, VSENSOR_MAX_COUNT, + 1, true))); + if (cfg->mqtt_vhumidity_mask) + cJSON_AddItemToObject(config, "mqtt_vhumidity_mask", + cJSON_CreateString( + bitmask_to_str(cfg->mqtt_vhumidity_mask, VSENSOR_MAX_COUNT, + 1, true))); + if (cfg->mqtt_vpressure_mask) + cJSON_AddItemToObject(config, "mqtt_vpressure_mask", + cJSON_CreateString( + bitmask_to_str(cfg->mqtt_vpressure_mask, VSENSOR_MAX_COUNT, + 1, true))); if (cfg->mqtt_fan_rpm_mask) cJSON_AddItemToObject(config, "mqtt_fan_rpm_mask", cJSON_CreateString( @@ -708,6 +733,15 @@ cJSON *config_to_json(const struct fanpico_config *cfg) if (strlen(cfg->mqtt_temp_topic) > 0) cJSON_AddItemToObject(config, "mqtt_temp_topic", cJSON_CreateString(cfg->mqtt_temp_topic)); + if (strlen(cfg->mqtt_vtemp_topic) > 0) + cJSON_AddItemToObject(config, "mqtt_vtemp_topic", + cJSON_CreateString(cfg->mqtt_vtemp_topic)); + if (strlen(cfg->mqtt_vhumidity_topic) > 0) + cJSON_AddItemToObject(config, "mqtt_vhumidity_topic", + cJSON_CreateString(cfg->mqtt_vhumidity_topic)); + if (strlen(cfg->mqtt_vpressure_topic) > 0) + cJSON_AddItemToObject(config, "mqtt_vpressure_topic", + cJSON_CreateString(cfg->mqtt_vpressure_topic)); if (strlen(cfg->mqtt_fan_rpm_topic) > 0) cJSON_AddItemToObject(config, "mqtt_fan_rpm_topic", cJSON_CreateString(cfg->mqtt_fan_rpm_topic)); @@ -1018,6 +1052,9 @@ int json_to_config(cJSON *config, struct fanpico_config *cfg) if ((ref = cJSON_GetObjectItem(config, "mqtt_temp_interval"))) { cfg->mqtt_temp_interval = cJSON_GetNumberValue(ref); } + if ((ref = cJSON_GetObjectItem(config, "mqtt_vsensor_interval"))) { + cfg->mqtt_vsensor_interval = cJSON_GetNumberValue(ref); + } if ((ref = cJSON_GetObjectItem(config, "mqtt_rpm_interval"))) { cfg->mqtt_rpm_interval = cJSON_GetNumberValue(ref); } @@ -1028,6 +1065,18 @@ int json_to_config(cJSON *config, struct fanpico_config *cfg) if (!str_to_bitmask(cJSON_GetStringValue(ref), SENSOR_MAX_COUNT, &m, 1)) cfg->mqtt_temp_mask = m; } + if ((ref = cJSON_GetObjectItem(config, "mqtt_vtemp_mask"))) { + if (!str_to_bitmask(cJSON_GetStringValue(ref), VSENSOR_MAX_COUNT, &m, 1)) + cfg->mqtt_vtemp_mask = m; + } + if ((ref = cJSON_GetObjectItem(config, "mqtt_vhumidity_mask"))) { + if (!str_to_bitmask(cJSON_GetStringValue(ref), VSENSOR_MAX_COUNT, &m, 1)) + cfg->mqtt_vhumidity_mask = m; + } + if ((ref = cJSON_GetObjectItem(config, "mqtt_vpressure_mask"))) { + if (!str_to_bitmask(cJSON_GetStringValue(ref), VSENSOR_MAX_COUNT, &m, 1)) + cfg->mqtt_vpressure_mask = m; + } if ((ref = cJSON_GetObjectItem(config, "mqtt_fan_rpm_mask"))) { if (!str_to_bitmask(cJSON_GetStringValue(ref), FAN_MAX_COUNT, &m, 1)) cfg->mqtt_fan_rpm_mask = m; @@ -1048,6 +1097,18 @@ int json_to_config(cJSON *config, struct fanpico_config *cfg) if ((val = cJSON_GetStringValue(ref))) strncopy(cfg->mqtt_temp_topic, val, sizeof(cfg->mqtt_temp_topic)); } + if ((ref = cJSON_GetObjectItem(config, "mqtt_vtemp_topic"))) { + if ((val = cJSON_GetStringValue(ref))) + strncopy(cfg->mqtt_vtemp_topic, val, sizeof(cfg->mqtt_vtemp_topic)); + } + if ((ref = cJSON_GetObjectItem(config, "mqtt_vhumidity_topic"))) { + if ((val = cJSON_GetStringValue(ref))) + strncopy(cfg->mqtt_vhumidity_topic, val, sizeof(cfg->mqtt_vhumidity_topic)); + } + if ((ref = cJSON_GetObjectItem(config, "mqtt_vpressure_topic"))) { + if ((val = cJSON_GetStringValue(ref))) + strncopy(cfg->mqtt_vpressure_topic, val, sizeof(cfg->mqtt_vpressure_topic)); + } if ((ref = cJSON_GetObjectItem(config, "mqtt_fan_rpm_topic"))) { if ((val = cJSON_GetStringValue(ref))) strncopy(cfg->mqtt_fan_rpm_topic, val, sizeof(cfg->mqtt_fan_rpm_topic)); diff --git a/src/fanpico.c b/src/fanpico.c index a70edd2..0f0a10d 100644 --- a/src/fanpico.c +++ b/src/fanpico.c @@ -262,6 +262,8 @@ static void clear_state(struct fanpico_state *s) s->vtemp[i] = 0.0; s->vtemp_prev[i] = 0.0; s->vtemp_updated[i] = from_us_since_boot(0); + s->vpressure[i] = -1.0; + s->vhumidity[i] = -1.0; } } diff --git a/src/fanpico.h b/src/fanpico.h index ac5d2ee..4846a8a 100644 --- a/src/fanpico.h +++ b/src/fanpico.h @@ -256,16 +256,23 @@ struct fanpico_config { char mqtt_cmd_topic[MQTT_MAX_TOPIC_LEN + 1]; char mqtt_resp_topic[MQTT_MAX_TOPIC_LEN + 1]; uint16_t mqtt_temp_mask; + uint16_t mqtt_vtemp_mask; + uint16_t mqtt_vhumidity_mask; + uint16_t mqtt_vpressure_mask; uint16_t mqtt_fan_rpm_mask; uint16_t mqtt_fan_duty_mask; uint16_t mqtt_mbfan_rpm_mask; uint16_t mqtt_mbfan_duty_mask; char mqtt_temp_topic[MQTT_MAX_TOPIC_LEN + 1]; + char mqtt_vtemp_topic[MQTT_MAX_TOPIC_LEN + 1]; + char mqtt_vhumidity_topic[MQTT_MAX_TOPIC_LEN + 1]; + char mqtt_vpressure_topic[MQTT_MAX_TOPIC_LEN + 1]; char mqtt_fan_rpm_topic[MQTT_MAX_TOPIC_LEN + 1]; char mqtt_fan_duty_topic[MQTT_MAX_TOPIC_LEN + 1]; char mqtt_mbfan_rpm_topic[MQTT_MAX_TOPIC_LEN + 1]; char mqtt_mbfan_duty_topic[MQTT_MAX_TOPIC_LEN + 1]; uint32_t mqtt_temp_interval; + uint32_t mqtt_vsensor_interval; uint32_t mqtt_rpm_interval; uint32_t mqtt_duty_interval; char mqtt_ha_discovery_prefix[32 + 1]; @@ -278,6 +285,8 @@ struct fanpico_config { #endif /* Non-config items */ float vtemp[VSENSOR_MAX_COUNT]; + float vhumidity[VSENSOR_MAX_COUNT]; + float vpressure[VSENSOR_MAX_COUNT]; absolute_time_t vtemp_updated[VSENSOR_MAX_COUNT]; void *i2c_context[VSENSOR_MAX_COUNT]; }; @@ -291,6 +300,8 @@ struct fanpico_state { float temp[SENSOR_MAX_COUNT]; float temp_prev[SENSOR_MAX_COUNT]; float vtemp[VSENSOR_MAX_COUNT]; + float vhumidity[VSENSOR_MAX_COUNT]; + float vpressure[VSENSOR_MAX_COUNT]; absolute_time_t vtemp_updated[VSENSOR_MAX_COUNT]; float vtemp_prev[VSENSOR_MAX_COUNT]; float onewire_temp[ONEWIRE_MAX_COUNT]; @@ -412,6 +423,7 @@ int fanpico_mqtt_client_active(); void fanpico_mqtt_reconnect(); void fanpico_mqtt_publish(); void fanpico_mqtt_publish_temp(); +void fanpico_mqtt_publish_vsensor(); void fanpico_mqtt_publish_rpm(); void fanpico_mqtt_publish_duty(); void fanpico_mqtt_scpi_command(); diff --git a/src/fanpico_fsdata.c b/src/fanpico_fsdata.c index 98a1688..fc622a7 100644 --- a/src/fanpico_fsdata.c +++ b/src/fanpico_fsdata.c @@ -942,11 +942,11 @@ static const unsigned char FSDATA_ALIGN_PRE data__index_shtml[] FSDATA_ALIGN_POS 0x28,0x68,0x74,0x74,0x70,0x73,0x3a,0x2f,0x2f,0x67,0x69,0x74,0x68,0x75,0x62,0x2e, 0x63,0x6f,0x6d,0x2f,0x74,0x6a,0x6b,0x6f,0x2f,0x66,0x61,0x6e,0x70,0x69,0x63,0x6f, 0x29,0x0d,0x0a, -/* "Last-Modified: Sat, 10 Aug 2024 04:42:49 GMT" +/* "Last-Modified: Fri, 20 Sep 2024 04:54:45 GMT" " (46+ bytes) */ -0x4c,0x61,0x73,0x74,0x2d,0x4d,0x6f,0x64,0x69,0x66,0x69,0x65,0x64,0x3a,0x20,0x53, -0x61,0x74,0x2c,0x20,0x31,0x30,0x20,0x41,0x75,0x67,0x20,0x32,0x30,0x32,0x34,0x20, -0x30,0x34,0x3a,0x34,0x32,0x3a,0x34,0x39,0x20,0x47,0x4d,0x54,0x0d,0x0a, +0x4c,0x61,0x73,0x74,0x2d,0x4d,0x6f,0x64,0x69,0x66,0x69,0x65,0x64,0x3a,0x20,0x46, +0x72,0x69,0x2c,0x20,0x32,0x30,0x20,0x53,0x65,0x70,0x20,0x32,0x30,0x32,0x34,0x20, +0x30,0x34,0x3a,0x35,0x34,0x3a,0x34,0x35,0x20,0x47,0x4d,0x54,0x0d,0x0a, /* "Content-Type: text/html Expires: Fri, 10 Apr 2008 14:00:00 GMT Pragma: no-cache @@ -958,7 +958,7 @@ Pragma: no-cache 0x30,0x38,0x20,0x31,0x34,0x3a,0x30,0x30,0x3a,0x30,0x30,0x20,0x47,0x4d,0x54,0x0d, 0x0a,0x50,0x72,0x61,0x67,0x6d,0x61,0x3a,0x20,0x6e,0x6f,0x2d,0x63,0x61,0x63,0x68, 0x65,0x0d,0x0a,0x0d,0x0a, -/* raw file data (2930 bytes) */ +/* raw file data (2939 bytes) */ 0x3c,0x68,0x74,0x6d,0x6c,0x3e,0x0a,0x20,0x20,0x3c,0x68,0x65,0x61,0x64,0x3e,0x0a, 0x20,0x20,0x20,0x20,0x3c,0x74,0x69,0x74,0x6c,0x65,0x3e,0x46,0x61,0x6e,0x50,0x69, 0x63,0x6f,0x3a,0x20,0x3c,0x21,0x2d,0x2d,0x23,0x6e,0x61,0x6d,0x65,0x2d,0x2d,0x3e, @@ -1096,53 +1096,53 @@ Pragma: no-cache 0x74,0x2d,0x73,0x69,0x7a,0x65,0x3a,0x20,0x31,0x34,0x70,0x78,0x3b,0x22,0x3e,0x3c, 0x74,0x68,0x3e,0x23,0x3c,0x2f,0x74,0x68,0x3e,0x3c,0x74,0x68,0x3e,0x4e,0x61,0x6d, 0x65,0x3c,0x2f,0x74,0x68,0x3e,0x3c,0x74,0x68,0x3e,0x54,0x65,0x6d,0x70,0x20,0x26, -0x23,0x78,0x32,0x36,0x30,0x30,0x3b,0x3c,0x2f,0x74,0x68,0x3e,0x3c,0x2f,0x74,0x72, +0x23,0x78,0x32,0x36,0x30,0x30,0x3b,0x3c,0x2f,0x74,0x68,0x3e,0x3c,0x74,0x68,0x3e, +0x4f,0x74,0x68,0x65,0x72,0x3c,0x2f,0x74,0x68,0x3e,0x0a,0x09,0x09,0x09,0x3c,0x74, +0x72,0x3e,0x3c,0x21,0x2d,0x2d,0x23,0x76,0x73,0x65,0x6e,0x72,0x6f,0x77,0x31,0x2d, +0x2d,0x3e,0x3c,0x2f,0x74,0x72,0x3e,0x0a,0x09,0x09,0x09,0x3c,0x74,0x72,0x3e,0x3c, +0x21,0x2d,0x2d,0x23,0x76,0x73,0x65,0x6e,0x72,0x6f,0x77,0x32,0x2d,0x2d,0x3e,0x3c, +0x2f,0x74,0x72,0x3e,0x0a,0x09,0x09,0x09,0x3c,0x74,0x72,0x3e,0x3c,0x21,0x2d,0x2d, +0x23,0x76,0x73,0x65,0x6e,0x72,0x6f,0x77,0x33,0x2d,0x2d,0x3e,0x3c,0x2f,0x74,0x72, 0x3e,0x0a,0x09,0x09,0x09,0x3c,0x74,0x72,0x3e,0x3c,0x21,0x2d,0x2d,0x23,0x76,0x73, -0x65,0x6e,0x72,0x6f,0x77,0x31,0x2d,0x2d,0x3e,0x3c,0x2f,0x74,0x72,0x3e,0x0a,0x09, +0x65,0x6e,0x72,0x6f,0x77,0x34,0x2d,0x2d,0x3e,0x3c,0x2f,0x74,0x72,0x3e,0x0a,0x09, 0x09,0x09,0x3c,0x74,0x72,0x3e,0x3c,0x21,0x2d,0x2d,0x23,0x76,0x73,0x65,0x6e,0x72, -0x6f,0x77,0x32,0x2d,0x2d,0x3e,0x3c,0x2f,0x74,0x72,0x3e,0x0a,0x09,0x09,0x09,0x3c, -0x74,0x72,0x3e,0x3c,0x21,0x2d,0x2d,0x23,0x76,0x73,0x65,0x6e,0x72,0x6f,0x77,0x33, +0x6f,0x77,0x35,0x2d,0x2d,0x3e,0x3c,0x2f,0x74,0x72,0x3e,0x0a,0x09,0x09,0x09,0x3c, +0x74,0x72,0x3e,0x3c,0x21,0x2d,0x2d,0x23,0x76,0x73,0x65,0x6e,0x72,0x6f,0x77,0x36, 0x2d,0x2d,0x3e,0x3c,0x2f,0x74,0x72,0x3e,0x0a,0x09,0x09,0x09,0x3c,0x74,0x72,0x3e, -0x3c,0x21,0x2d,0x2d,0x23,0x76,0x73,0x65,0x6e,0x72,0x6f,0x77,0x34,0x2d,0x2d,0x3e, +0x3c,0x21,0x2d,0x2d,0x23,0x76,0x73,0x65,0x6e,0x72,0x6f,0x77,0x37,0x2d,0x2d,0x3e, 0x3c,0x2f,0x74,0x72,0x3e,0x0a,0x09,0x09,0x09,0x3c,0x74,0x72,0x3e,0x3c,0x21,0x2d, -0x2d,0x23,0x76,0x73,0x65,0x6e,0x72,0x6f,0x77,0x35,0x2d,0x2d,0x3e,0x3c,0x2f,0x74, -0x72,0x3e,0x0a,0x09,0x09,0x09,0x3c,0x74,0x72,0x3e,0x3c,0x21,0x2d,0x2d,0x23,0x76, -0x73,0x65,0x6e,0x72,0x6f,0x77,0x36,0x2d,0x2d,0x3e,0x3c,0x2f,0x74,0x72,0x3e,0x0a, -0x09,0x09,0x09,0x3c,0x74,0x72,0x3e,0x3c,0x21,0x2d,0x2d,0x23,0x76,0x73,0x65,0x6e, -0x72,0x6f,0x77,0x37,0x2d,0x2d,0x3e,0x3c,0x2f,0x74,0x72,0x3e,0x0a,0x09,0x09,0x09, -0x3c,0x74,0x72,0x3e,0x3c,0x21,0x2d,0x2d,0x23,0x76,0x73,0x65,0x6e,0x72,0x6f,0x77, -0x38,0x2d,0x2d,0x3e,0x3c,0x2f,0x74,0x72,0x3e,0x0a,0x09,0x09,0x20,0x20,0x20,0x20, -0x20,0x20,0x3c,0x2f,0x74,0x61,0x62,0x6c,0x65,0x3e,0x0a,0x09,0x09,0x20,0x20,0x3c, -0x2f,0x74,0x64,0x3e,0x3c,0x2f,0x74,0x72,0x3e,0x0a,0x09,0x09,0x3c,0x2f,0x74,0x61, -0x62,0x6c,0x65,0x3e,0x0a,0x09,0x20,0x20,0x20,0x20,0x20,0x20,0x3c,0x2f,0x74,0x64, -0x3e,0x0a,0x0a,0x09,0x20,0x20,0x20,0x20,0x3c,0x2f,0x74,0x72,0x3e,0x0a,0x09,0x20, -0x20,0x3c,0x2f,0x74,0x61,0x62,0x6c,0x65,0x3e,0x0a,0x09,0x20,0x20,0x3c,0x62,0x72, -0x3e,0x0a,0x09,0x3c,0x2f,0x74,0x64,0x3e,0x0a,0x20,0x20,0x20,0x20,0x20,0x20,0x3c, -0x2f,0x74,0x72,0x3e,0x0a,0x20,0x20,0x20,0x20,0x20,0x20,0x3c,0x74,0x72,0x3e,0x0a, -0x09,0x3c,0x74,0x64,0x3e,0x0a,0x09,0x20,0x20,0x3c,0x74,0x61,0x62,0x6c,0x65,0x20, -0x77,0x69,0x64,0x74,0x68,0x3d,0x22,0x31,0x30,0x30,0x25,0x22,0x3e,0x0a,0x09,0x20, -0x20,0x20,0x20,0x3c,0x74,0x72,0x20,0x76,0x61,0x6c,0x69,0x67,0x6e,0x3d,0x22,0x62, -0x6f,0x74,0x74,0x6f,0x6d,0x22,0x3e,0x0a,0x09,0x20,0x20,0x20,0x20,0x20,0x20,0x3c, -0x74,0x64,0x20,0x77,0x69,0x64,0x74,0x68,0x3d,0x22,0x34,0x35,0x25,0x22,0x3e,0x3c, -0x66,0x6f,0x6e,0x74,0x20,0x73,0x69,0x7a,0x65,0x3d,0x2d,0x31,0x3e,0x46,0x61,0x6e, -0x50,0x69,0x63,0x6f,0x2d,0x3c,0x21,0x2d,0x2d,0x23,0x6d,0x6f,0x64,0x65,0x6c,0x2d, -0x2d,0x3e,0x20,0x76,0x3c,0x21,0x2d,0x2d,0x23,0x76,0x65,0x72,0x73,0x69,0x6f,0x6e, -0x2d,0x2d,0x3e,0x3c,0x2f,0x66,0x6f,0x6e,0x74,0x3e,0x3c,0x2f,0x74,0x64,0x3e,0x0a, -0x09,0x20,0x20,0x20,0x20,0x20,0x20,0x3c,0x74,0x64,0x20,0x77,0x69,0x64,0x74,0x68, -0x3d,0x22,0x31,0x30,0x25,0x22,0x3e,0x3c,0x21,0x2d,0x2d,0x23,0x77,0x61,0x74,0x63, -0x68,0x64,0x6f,0x67,0x2d,0x2d,0x3e,0x3c,0x2f,0x74,0x64,0x3e,0x0a,0x09,0x20,0x20, -0x20,0x20,0x20,0x20,0x3c,0x74,0x64,0x20,0x77,0x69,0x64,0x74,0x68,0x3d,0x22,0x34, -0x35,0x25,0x22,0x20,0x61,0x6c,0x69,0x67,0x6e,0x3d,0x22,0x72,0x69,0x67,0x68,0x74, -0x22,0x3e,0x3c,0x66,0x6f,0x6e,0x74,0x20,0x73,0x69,0x7a,0x65,0x3d,0x22,0x2d,0x31, -0x22,0x3e,0x3c,0x21,0x2d,0x2d,0x23,0x64,0x61,0x74,0x65,0x74,0x69,0x6d,0x65,0x2d, -0x2d,0x3e,0x3c,0x62,0x72,0x3e,0x55,0x70,0x74,0x69,0x6d,0x65,0x3a,0x20,0x3c,0x21, -0x2d,0x2d,0x23,0x75,0x70,0x74,0x69,0x6d,0x65,0x2d,0x2d,0x3e,0x3c,0x2f,0x66,0x6f, -0x6e,0x74,0x3e,0x3c,0x2f,0x74,0x64,0x3e,0x0a,0x09,0x20,0x20,0x20,0x20,0x3c,0x2f, -0x74,0x72,0x3e,0x0a,0x09,0x20,0x20,0x3c,0x2f,0x74,0x61,0x62,0x6c,0x65,0x3e,0x0a, -0x09,0x3c,0x2f,0x74,0x64,0x3e,0x0a,0x20,0x20,0x20,0x20,0x20,0x20,0x3c,0x2f,0x74, -0x72,0x3e,0x0a,0x20,0x20,0x20,0x20,0x3c,0x2f,0x74,0x61,0x62,0x6c,0x65,0x3e,0x0a, -0x20,0x20,0x3c,0x2f,0x62,0x6f,0x64,0x79,0x3e,0x0a,0x3c,0x2f,0x68,0x74,0x6d,0x6c, -0x3e,0x0a,}; +0x2d,0x23,0x76,0x73,0x65,0x6e,0x72,0x6f,0x77,0x38,0x2d,0x2d,0x3e,0x3c,0x2f,0x74, +0x72,0x3e,0x0a,0x09,0x09,0x20,0x20,0x20,0x20,0x20,0x20,0x3c,0x2f,0x74,0x61,0x62, +0x6c,0x65,0x3e,0x0a,0x09,0x09,0x20,0x20,0x3c,0x2f,0x74,0x64,0x3e,0x3c,0x2f,0x74, +0x72,0x3e,0x0a,0x09,0x09,0x3c,0x2f,0x74,0x61,0x62,0x6c,0x65,0x3e,0x0a,0x09,0x20, +0x20,0x20,0x20,0x20,0x20,0x3c,0x2f,0x74,0x64,0x3e,0x0a,0x0a,0x09,0x20,0x20,0x20, +0x20,0x3c,0x2f,0x74,0x72,0x3e,0x0a,0x09,0x20,0x20,0x3c,0x2f,0x74,0x61,0x62,0x6c, +0x65,0x3e,0x0a,0x09,0x20,0x20,0x3c,0x62,0x72,0x3e,0x0a,0x09,0x3c,0x2f,0x74,0x64, +0x3e,0x0a,0x20,0x20,0x20,0x20,0x20,0x20,0x3c,0x2f,0x74,0x72,0x3e,0x0a,0x20,0x20, +0x20,0x20,0x20,0x20,0x3c,0x74,0x72,0x3e,0x0a,0x09,0x3c,0x74,0x64,0x3e,0x0a,0x09, +0x20,0x20,0x3c,0x74,0x61,0x62,0x6c,0x65,0x20,0x77,0x69,0x64,0x74,0x68,0x3d,0x22, +0x31,0x30,0x30,0x25,0x22,0x3e,0x0a,0x09,0x20,0x20,0x20,0x20,0x3c,0x74,0x72,0x20, +0x76,0x61,0x6c,0x69,0x67,0x6e,0x3d,0x22,0x62,0x6f,0x74,0x74,0x6f,0x6d,0x22,0x3e, +0x0a,0x09,0x20,0x20,0x20,0x20,0x20,0x20,0x3c,0x74,0x64,0x20,0x77,0x69,0x64,0x74, +0x68,0x3d,0x22,0x34,0x35,0x25,0x22,0x3e,0x3c,0x66,0x6f,0x6e,0x74,0x20,0x73,0x69, +0x7a,0x65,0x3d,0x2d,0x31,0x3e,0x46,0x61,0x6e,0x50,0x69,0x63,0x6f,0x2d,0x3c,0x21, +0x2d,0x2d,0x23,0x6d,0x6f,0x64,0x65,0x6c,0x2d,0x2d,0x3e,0x20,0x76,0x3c,0x21,0x2d, +0x2d,0x23,0x76,0x65,0x72,0x73,0x69,0x6f,0x6e,0x2d,0x2d,0x3e,0x3c,0x2f,0x66,0x6f, +0x6e,0x74,0x3e,0x3c,0x2f,0x74,0x64,0x3e,0x0a,0x09,0x20,0x20,0x20,0x20,0x20,0x20, +0x3c,0x74,0x64,0x20,0x77,0x69,0x64,0x74,0x68,0x3d,0x22,0x31,0x30,0x25,0x22,0x3e, +0x3c,0x21,0x2d,0x2d,0x23,0x77,0x61,0x74,0x63,0x68,0x64,0x6f,0x67,0x2d,0x2d,0x3e, +0x3c,0x2f,0x74,0x64,0x3e,0x0a,0x09,0x20,0x20,0x20,0x20,0x20,0x20,0x3c,0x74,0x64, +0x20,0x77,0x69,0x64,0x74,0x68,0x3d,0x22,0x34,0x35,0x25,0x22,0x20,0x61,0x6c,0x69, +0x67,0x6e,0x3d,0x22,0x72,0x69,0x67,0x68,0x74,0x22,0x3e,0x3c,0x66,0x6f,0x6e,0x74, +0x20,0x73,0x69,0x7a,0x65,0x3d,0x22,0x2d,0x31,0x22,0x3e,0x3c,0x21,0x2d,0x2d,0x23, +0x64,0x61,0x74,0x65,0x74,0x69,0x6d,0x65,0x2d,0x2d,0x3e,0x3c,0x62,0x72,0x3e,0x55, +0x70,0x74,0x69,0x6d,0x65,0x3a,0x20,0x3c,0x21,0x2d,0x2d,0x23,0x75,0x70,0x74,0x69, +0x6d,0x65,0x2d,0x2d,0x3e,0x3c,0x2f,0x66,0x6f,0x6e,0x74,0x3e,0x3c,0x2f,0x74,0x64, +0x3e,0x0a,0x09,0x20,0x20,0x20,0x20,0x3c,0x2f,0x74,0x72,0x3e,0x0a,0x09,0x20,0x20, +0x3c,0x2f,0x74,0x61,0x62,0x6c,0x65,0x3e,0x0a,0x09,0x3c,0x2f,0x74,0x64,0x3e,0x0a, +0x20,0x20,0x20,0x20,0x20,0x20,0x3c,0x2f,0x74,0x72,0x3e,0x0a,0x20,0x20,0x20,0x20, +0x3c,0x2f,0x74,0x61,0x62,0x6c,0x65,0x3e,0x0a,0x20,0x20,0x3c,0x2f,0x62,0x6f,0x64, +0x79,0x3e,0x0a,0x3c,0x2f,0x68,0x74,0x6d,0x6c,0x3e,0x0a,}; #if FSDATA_FILE_ALIGNMENT==1 static const unsigned int dummy_align__status_csv = 5; diff --git a/src/httpd-fs/index.shtml b/src/httpd-fs/index.shtml index bdd0784..7fe2074 100644 --- a/src/httpd-fs/index.shtml +++ b/src/httpd-fs/index.shtml @@ -78,7 +78,7 @@
- + diff --git a/src/httpd.c b/src/httpd.c index f075635..e0698b9 100644 --- a/src/httpd.c +++ b/src/httpd.c @@ -81,11 +81,13 @@ u16_t csv_stats(char *insert, int insertlen, u16_t current_tag_part, u16_t *next } for (i = 0; i < VSENSOR_COUNT; i++) { pwm = sensor_get_duty(&cfg->vsensors[i].map, st->vtemp[i]); - snprintf(row, sizeof(row), "vsensor%d,\"%s\",%.1lf,%.1lf\n", + snprintf(row, sizeof(row), "vsensor%d,\"%s\",%.1lf,%.1lf,%.0f,%.0f\n", i+1, cfg->vsensors[i].name, st->vtemp[i], - pwm); + pwm, + st->vhumidity[i], + st->vpressure[i]); strncatenate(buf, row, BUF_LEN); } @@ -200,6 +202,10 @@ u16_t json_stats(char *insert, int insertlen, u16_t current_tag_part, u16_t *nex cJSON_AddItemToObject(o, "name", cJSON_CreateString(cfg->vsensors[i].name)); cJSON_AddItemToObject(o, "temperature", cJSON_CreateNumber(round_decimal(st->vtemp[i], 1))); cJSON_AddItemToObject(o, "duty_cycle", cJSON_CreateNumber(round_decimal(pwm, 1))); + if (st->vhumidity[i] >= 0.0) + cJSON_AddItemToObject(o, "humidity", cJSON_CreateNumber(round_decimal(st->vhumidity[i], 1))); + if (st->vpressure[i] >= 0.0) + cJSON_AddItemToObject(o, "pressure", cJSON_CreateNumber(round_decimal(st->vpressure[i], 1))); cJSON_AddItemToArray(array, o); } cJSON_AddItemToObject(json, "vsensors", array); @@ -316,10 +322,23 @@ u16_t fanpico_ssi_handler(const char *tag, char *insert, int insertlen, else if (!strncmp(tag, "vsenrow", 7)) { uint8_t i = tag[7] - '1'; if (i < VSENSOR_COUNT) { - printed = snprintf(insert, insertlen, "
#NameTemp ☀
#NameTemp ☀Other
%d%s%0.1f ℃", - i + 1, - cfg->vsensors[i].name, - st->vtemp[i]); + char other[24], tmp[12]; + + other[0] = 0; + if (st->vhumidity[i] >= 0.0) { + snprintf(tmp, sizeof(tmp), "%0.0f %%rh", st->vhumidity[i]); + strncatenate(other, tmp, sizeof(other)); + } + if (st->vpressure[i] >= 0.0) { + snprintf(tmp, sizeof(tmp), "%0.0f hPa", st->vpressure[i]); + if (strlen(other) > 0) + strncatenate(other, ", ", sizeof(other)); + strncatenate(other, tmp, sizeof(other)); + } + + printed = snprintf(insert, insertlen, + "%d%s%0.1f ℃%s", + i + 1, cfg->vsensors[i].name, st->vtemp[i], other); } } else if (!strncmp(tag, "csvstat", 7)) { diff --git a/src/i2c.c b/src/i2c.c index b0c5981..d2f697d 100644 --- a/src/i2c.c +++ b/src/i2c.c @@ -63,6 +63,16 @@ void* dps310_init(i2c_inst_t *i2c, uint8_t addr); int dps310_start_measurement(void *ctx); int dps310_get_measurement(void *ctx, float *temp, float *pressure, float *humidity); +/* i2c_lps22.c */ +void* lps22_init(i2c_inst_t *i2c, uint8_t addr); +int lps22_start_measurement(void *ctx); +int lps22_get_measurement(void *ctx, float *temp, float *pressure, float *humidity); + +/* i2c_lps25.c */ +void* lps25_init(i2c_inst_t *i2c, uint8_t addr); +int lps25_start_measurement(void *ctx); +int lps25_get_measurement(void *ctx, float *temp, float *pressure, float *humidity); + /* i2c_mcp9808.c */ void* mcp9808_init(i2c_inst_t *i2c, uint8_t addr); int mcp9808_start_measurement(void *ctx); @@ -73,6 +83,11 @@ void* pct2075_init(i2c_inst_t *i2c, uint8_t addr); int pct2075_start_measurement(void *ctx); int pct2075_get_measurement(void *ctx, float *temp, float *pressure, float *humidity); +/* i2c_shtc3.c */ +void* shtc3_init(i2c_inst_t *i2c, uint8_t addr); +int shtc3_start_measurement(void *ctx); +int shtc3_get_measurement(void *ctx, float *temp, float *pressure, float *humidity); + /* i2c_stts22h.c */ void* stts22h_init(i2c_inst_t *i2c, uint8_t addr); int stts22h_start_measurement(void *ctx); @@ -97,8 +112,11 @@ static const i2c_sensor_entry_t i2c_sensor_types[] = { { "BMP180", bmp180_init, bmp180_start_measurement, bmp180_get_measurement }, { "BMP280", bmp280_init, bmp280_start_measurement, bmp280_get_measurement }, { "DPS310", dps310_init, dps310_start_measurement, dps310_get_measurement }, + { "LPS22", lps22_init, lps22_start_measurement, lps22_get_measurement }, + { "LPS25", lps25_init, lps25_start_measurement, lps25_get_measurement }, { "MCP9808", mcp9808_init, mcp9808_start_measurement, mcp9808_get_measurement }, { "PCT2075", pct2075_init, pct2075_start_measurement, pct2075_get_measurement }, + { "SHTC3", shtc3_init, shtc3_start_measurement, shtc3_get_measurement }, { "STTS22H", stts22h_init, stts22h_start_measurement, stts22h_get_measurement }, { "TMP102", tmp102_init, tmp102_start_measurement, tmp102_get_measurement }, { "TMP117", tmp117_init, tmp117_start_measurement, tmp117_get_measurement }, @@ -208,20 +226,12 @@ int i2c_read_register_u24(i2c_inst_t *i2c, uint8_t addr, uint8_t reg, uint32_t * int res; DEBUG_PRINT("args=%p,%02x,%02x,%p\n", i2c, addr, reg, val); - res = i2c_write_timeout_us(i2c, addr, ®, 1, true, - I2C_WRITE_TIMEOUT(1)); - if (res < 1) { - DEBUG_PRINT("write failed (%d)\n", res); + res = i2c_read_register_block(i2c, addr, reg, buf, sizeof(buf)); + if (res) { + DEBUG_PRINT("failed to read register\n"); return -1; } - res = i2c_read_timeout_us(i2c, addr, buf, 3, false, - I2C_READ_TIMEOUT(3)); - if (res < 3) { - DEBUG_PRINT("read failed (%d)\n", res); - return -2; - } - *val = (buf[0] << 16) | (buf[1] << 8) | buf[2]; DEBUG_PRINT("read ok: [%02x %02x %02x] %08lx (%lu)\n", buf[0], buf[1], buf[2], *val, *val); @@ -235,20 +245,12 @@ int i2c_read_register_u16(i2c_inst_t *i2c, uint8_t addr, uint8_t reg, uint16_t * int res; DEBUG_PRINT("args=%p,%02x,%02x,%p\n", i2c, addr, reg, val); - res = i2c_write_timeout_us(i2c, addr, ®, 1, true, - I2C_WRITE_TIMEOUT(1)); - if (res < 1) { - DEBUG_PRINT("write failed (%d)\n", res); + res = i2c_read_register_block(i2c, addr, reg, buf, sizeof(buf)); + if (res) { + DEBUG_PRINT("failed to read register\n"); return -1; } - res = i2c_read_timeout_us(i2c, addr, buf, 2, false, - I2C_READ_TIMEOUT(2)); - if (res < 2) { - DEBUG_PRINT("read failed (%d)\n", res); - return -2; - } - *val = (buf[0] << 8) | buf[1]; DEBUG_PRINT("read ok: [%02x %02x] %04x (%u)\n", buf[0], buf[1], *val, *val); @@ -258,27 +260,50 @@ int i2c_read_register_u16(i2c_inst_t *i2c, uint8_t addr, uint8_t reg, uint16_t * int i2c_read_register_u8(i2c_inst_t *i2c, uint8_t addr, uint8_t reg, uint8_t *val) { - uint8_t buf; + uint8_t buf[1]; int res; DEBUG_PRINT("args=%p,%02x,%02x,%p\n", i2c, addr, reg, val); - res = i2c_write_timeout_us(i2c, addr, ®, 1, true, - I2C_WRITE_TIMEOUT(1)); - if (res < 1) { - DEBUG_PRINT("write failed (%d)\n", res); + res = i2c_read_register_block(i2c, addr, reg, buf, sizeof(buf)); + if (res) { + DEBUG_PRINT("failed to read register\n"); return -1; } - res = i2c_read_timeout_us(i2c, addr, &buf, 1, false, - I2C_READ_TIMEOUT(1)); - if (res < 1) { - DEBUG_PRINT("read failed (%d)\n", res); + *val = buf[0]; + DEBUG_PRINT("read ok: %02x (%u)\n", *val, *val); + + return 0; +} + + +int i2c_write_register_block(i2c_inst_t *i2c, uint8_t addr, uint8_t reg, const uint8_t *buf, size_t len) +{ + uint8_t tmp[128]; + int res; + + DEBUG_PRINT("args=%p,%02x,%02x,%p,%u\n", i2c, addr, reg, buf, len); + tmp[0] = reg; + if (len >= sizeof(tmp)) { + DEBUG_PRINT("too large buffer: %d\n", len); + return -1; + } + memcpy(&tmp[1], buf, len); + res = i2c_write_timeout_us(i2c, addr, buf, len + 1, false, + I2C_WRITE_TIMEOUT(len + 1)); + if (res < len + 1) { + DEBUG_PRINT("write register values failed (%d)\n", res); return -2; + } else { +#if I2C_DEBUG > 0 + DEBUG_PRINT("write ok: %d [", res); + for(int i = 0; i <= len; i++) { + printf(" %02x", tmp[i]); + } + printf(" ]\n"); +#endif } - *val = buf; - DEBUG_PRINT("read ok: %02x (%u)\n", *val, *val); - return 0; } @@ -326,6 +351,46 @@ int i2c_write_register_u8(i2c_inst_t *i2c, uint8_t addr, uint8_t reg, uint8_t va } +int i2c_read_raw(i2c_inst_t *i2c, uint8_t addr, uint8_t *buf, size_t len, bool nostop) +{ + int res; + + DEBUG_PRINT("args=%p,%02x,%p,%u\n", i2c, addr, buf, len); + + res = i2c_read_timeout_us(i2c, addr, buf, len, nostop, + I2C_READ_TIMEOUT(len)); + if (res < len) { + DEBUG_PRINT("read failed (%d)\n", res); + return -2; + } + + DEBUG_PRINT("read ok: %u\n", len); + + return 0; +} + + +int i2c_write_raw_u16(i2c_inst_t *i2c, uint8_t addr, uint16_t cmd, bool nostop) +{ + uint8_t buf[2]; + int res; + + buf[0] = cmd >> 8; + buf[1] = cmd & 0xff; + + DEBUG_PRINT("args=%p,%02x,%04x\n", i2c, addr, cmd); + + res = i2c_write_timeout_us(i2c, addr, buf, 2, nostop, + I2C_WRITE_TIMEOUT(2)); + if (res < 2) { + DEBUG_PRINT("write failed (%d)\n", res); + return -1; + } + + return 0; +} + + uint get_i2c_sensor_type(const char *name) { int type = -1; @@ -434,8 +499,8 @@ void setup_i2c_bus(struct fanpico_config *config) res = i2c_init_sensor(v->i2c_type, v->i2c_addr, &ctx); if (res) { - log_msg(LOG_NOTICE, "I2C Device %s (at 0x%02x): failed to initialize", - i2c_sensor_type_str(v->i2c_type), v->i2c_addr); + log_msg(LOG_NOTICE, "I2C Device %s (at 0x%02x): failed to initialize: %d", + i2c_sensor_type_str(v->i2c_type), v->i2c_addr, res); continue; } config->i2c_context[i] = ctx; @@ -501,9 +566,7 @@ int i2c_read_temps(struct fanpico_config *config) res = i2c_get_measurement(v->i2c_type, config->i2c_context[i], &temp, &pressure, &humidity); if (res == 0) { - if (pressure > 0.0 || humidity > 0.0 ) { - if (pressure > 0.0) - pressure /= 100.0; + if (pressure >= 0.0 || humidity >= 0.0 ) { log_msg(LOG_DEBUG, "vsensor%d: temp=%0.4fC, pressure=%0.2fhPa, humidity=%0.2f%%", i + 1, temp, pressure, humidity); } else { @@ -511,6 +574,8 @@ int i2c_read_temps(struct fanpico_config *config) } mutex_enter_blocking(config_mutex); config->vtemp[i] = temp; + config->vpressure[i] = pressure; + config->vhumidity[i] = humidity; config->vtemp_updated[i] = get_absolute_time(); mutex_exit(config_mutex); } else { diff --git a/src/i2c.h b/src/i2c.h index 318f86d..b38d4c7 100644 --- a/src/i2c.h +++ b/src/i2c.h @@ -73,8 +73,13 @@ int i2c_read_register_block(i2c_inst_t *i2c, uint8_t addr, uint8_t reg, uint8_t int i2c_read_register_u24(i2c_inst_t *i2c, uint8_t addr, uint8_t reg, uint32_t *val); int i2c_read_register_u16(i2c_inst_t *i2c, uint8_t i2c_addr, uint8_t reg, uint16_t *val); int i2c_read_register_u8(i2c_inst_t *i2c, uint8_t i2c_addr, uint8_t reg, uint8_t *val); +int i2c_write_register_block(i2c_inst_t *i2c, uint8_t addr, uint8_t reg, const uint8_t *buf, size_t len); int i2c_write_register_u16(i2c_inst_t *i2c, uint8_t addr, uint8_t reg, uint16_t val); int i2c_write_register_u8(i2c_inst_t *i2c, uint8_t addr, uint8_t reg, uint8_t val); + +int i2c_read_raw(i2c_inst_t *i2c, uint8_t addr, uint8_t *buf, size_t len, bool nostop); +int i2c_write_raw_u16(i2c_inst_t *i2c, uint8_t addr, uint16_t cmd, bool nostop); + int32_t twos_complement(uint32_t value, uint8_t bits); diff --git a/src/i2c_lps22.c b/src/i2c_lps22.c new file mode 100644 index 0000000..22f8427 --- /dev/null +++ b/src/i2c_lps22.c @@ -0,0 +1,168 @@ +/* i2c_lps22.c + Copyright (C) 2024 Timo Kokkonen + + SPDX-License-Identifier: GPL-3.0-or-later + + This file is part of FanPico. + + FanPico is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + FanPico is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with FanPico. If not, see . +*/ + +#include +#include +#include +#include "pico/stdlib.h" +#include "hardware/gpio.h" +#include "hardware/i2c.h" + +#include "i2c.h" + +/* LPS22 Registers */ +#define INTERRUPT_CFG 0x0b +#define THS_P_L 0x0c +#define THS_P_H 0x0d +#define IF_CTRL 0x0e +#define WHO_AM_I 0x0f +#define CTRL_REG1 0x10 +#define CTRL_REG2 0x11 +#define CTRL_REG3 0x12 +#define FIFO_CTRL 0x13 +#define FIFO_WTM 0x14 +#define REF_P_L 0x15 +#define REF_P_H 0x16 +#define RPDS_L 0x18 +#define RPDS_H 0x19 +#define INT_SOURCE 0x24 +#define FIFO_STATUS1 0x25 +#define FIFO_STATUS2 0x26 +#define STATUS 0x27 +#define PRES_OUT_XL 0x28 +#define PRES_OUT_L 0x29 +#define PRES_OUT_H 0x2a +#define TEMP_OUT_L 0x2b +#define TEMP_OUT_H 0x2c +#define FIFO_DATA_OUT_PRESS_XL 0x78 +#define FIFO_DATA_OUT_PRESS_L 0x79 +#define FIFO_DATA_OUT_PRESS_H 0x7a +#define FIFO_DATA_OUT_TEMP_L 0x7b +#define FIFO_DATA_OUT_TEMP_H 0x7c + +#define LPS22_DEVICE_ID 0xb1 + + +typedef struct lps22_context_t { + i2c_inst_t *i2c; + uint8_t addr; +} lps22_context_t; + + +void* lps22_init(i2c_inst_t *i2c, uint8_t addr) +{ + int res; + uint8_t val = 0; + lps22_context_t *ctx = calloc(1, sizeof(lps22_context_t)); + + if (!ctx) + return NULL; + ctx->i2c = i2c; + ctx->addr = addr; + + /* Read and verify device ID */ + res = i2c_read_register_u8(i2c, addr, WHO_AM_I, &val); + if (res || val != LPS22_DEVICE_ID) + goto panic; + + /* Reset Sensor */ + res = i2c_write_register_u8(i2c, addr, CTRL_REG2, 0x04); // SWRESET + if (res) + goto panic; + sleep_us(5); + res = i2c_write_register_u8(i2c, addr, CTRL_REG2, 0x92); // BOOT + if (res) + goto panic; + sleep_us(2300); + + /* Read configuration register */ + res = i2c_read_register_u8(i2c, addr, CTRL_REG2, &val); + if (res) + goto panic; + /* Check that boot is complete */ + if (val & 0x80) + goto panic; + + + /* Set continuous mode and output data rate (25Hz) */ + res = i2c_write_register_u8(i2c, addr, CTRL_REG1, 0x3c); + if (res) + goto panic; + + /* Set FIFO to Continuous mode */ + res = i2c_write_register_u8(i2c, addr, FIFO_CTRL, 0x03); + if (res) + goto panic; + + return ctx; + +panic: + free(ctx); + return NULL; +} + + +int lps22_start_measurement(void *ctx) +{ + /* Nothing to do, sensor is in continuous measurement mode... */ + + return 1000; /* measurement should be available after 1s */ +} + + +int lps22_get_measurement(void *ctx, float *temp, float *pressure, float *humidity) +{ + lps22_context_t *c = (lps22_context_t*)ctx; + int res; + uint8_t val; + uint8_t buf[5]; + int32_t t_raw, p_raw; + + + /* Read status register */ + res = i2c_read_register_u8(c->i2c, c->addr, STATUS, &val); + if (res) + return -1; + + /* Check P_DA and T_DA (data available) bits */ + if ((val & 0x03) != 0x03) + return 1; + + /* Get Measurement */ + res = i2c_read_register_block(c->i2c, c->addr, PRES_OUT_XL, buf, sizeof(buf)); + if (res) + return -2; + + p_raw = twos_complement((uint32_t)((buf[2] << 16) | (buf[1] << 8) | buf[0]), 24); + t_raw = twos_complement((uint32_t)(((buf[4] << 8) | buf[3])), 16); + + DEBUG_PRINT("t_raw = %ld, p_raw = %ld\n", t_raw, p_raw); + + *temp = t_raw / 100.0; + *pressure = p_raw / 4096.0; + *humidity = -1.0; + + DEBUG_PRINT("temp = %0.1f C, pressure = %0.1f hPa\n", *temp, *pressure); + + return 0; +} + + diff --git a/src/i2c_lps25.c b/src/i2c_lps25.c new file mode 100644 index 0000000..01d7d90 --- /dev/null +++ b/src/i2c_lps25.c @@ -0,0 +1,158 @@ +/* i2c_lps25.c + Copyright (C) 2024 Timo Kokkonen + + SPDX-License-Identifier: GPL-3.0-or-later + + This file is part of FanPico. + + FanPico is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + FanPico is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with FanPico. If not, see . +*/ + +#include +#include +#include +#include "pico/stdlib.h" +#include "hardware/gpio.h" +#include "hardware/i2c.h" + +#include "i2c.h" + +/* LPS25 Registers */ +#define WHO_AM_I 0x0f +#define RES_CONF 0x10 +#define CTRL_REG1 0x20 +#define CTRL_REG2 0x21 +#define CTRL_REG3 0x22 +#define CTRL_REG4 0x23 +#define STATUS_REG 0x27 +#define PRES_OUT_XL 0x28 +#define PRES_OUT_L 0x29 +#define PRES_OUT_H 0x2a +#define TEMP_OUT_L 0x2b +#define TEMP_OUT_H 0x2c +#define FIFO_CTRL 0x2e +#define FIFO_STATUS 0x2f + +#define LPS25_DEVICE_ID 0xbd + + +typedef struct lps25_context_t { + i2c_inst_t *i2c; + uint8_t addr; +} lps25_context_t; + + +void* lps25_init(i2c_inst_t *i2c, uint8_t addr) +{ + int res; + uint8_t val = 0; + lps25_context_t *ctx = calloc(1, sizeof(lps25_context_t)); + + if (!ctx) + return NULL; + ctx->i2c = i2c; + ctx->addr = addr; + + /* Read and verify device ID */ + res = i2c_read_register_u8(i2c, addr, WHO_AM_I, &val); + if (res || val != LPS25_DEVICE_ID) + goto panic; + + /* Reset Sensor */ + res = i2c_write_register_u8(i2c, addr, CTRL_REG2, 0x04); // SWRESET + if (res) + goto panic; + sleep_us(5); + res = i2c_write_register_u8(i2c, addr, CTRL_REG2, 0x80); // BOOT + if (res) + goto panic; + sleep_us(2300); + + /* Read configuration register */ + res = i2c_read_register_u8(i2c, addr, CTRL_REG2, &val); + if (res) + goto panic; + /* Check that boot is complete */ + if (val & 0x80) + goto panic; + + /* Set Pressure and temperature resolution */ + res = i2c_write_register_u8(i2c, addr, RES_CONF, 0x0f); + if (res) + goto panic; + + /* Set active mode and output data rate (12.5Hz) */ + res = i2c_write_register_u8(i2c, addr, CTRL_REG1, 0xb0); + if (res) + goto panic; + + /* Set FIFO to Mean mode (32-sample moving average) */ + res = i2c_write_register_u8(i2c, addr, FIFO_CTRL, 0xdf); + if (res) + goto panic; + + return ctx; + +panic: + free(ctx); + return NULL; +} + + +int lps25_start_measurement(void *ctx) +{ + /* Nothing to do, sensor is in continuous measurement mode... */ + + return 1000; /* measurement should be available after 1s */ +} + + +int lps25_get_measurement(void *ctx, float *temp, float *pressure, float *humidity) +{ + lps25_context_t *c = (lps25_context_t*)ctx; + int res; + uint8_t val; + uint8_t buf[5]; + int32_t t_raw, p_raw; + + + /* Read status register */ + res = i2c_read_register_u8(c->i2c, c->addr, STATUS_REG, &val); + if (res) + return -1; + + /* Check P_DA and T_DA (data available) bits */ + if ((val & 0x03) != 0x03) + return 1; + + /* Get Measurement */ + res = i2c_read_register_block(c->i2c, c->addr, PRES_OUT_XL | 0x80, buf, sizeof(buf)); + if (res) + return -2; + + p_raw = twos_complement((uint32_t)((buf[2] << 16) | (buf[1] << 8) | buf[0]), 24); + t_raw = twos_complement((uint32_t)(((buf[4] << 8) | buf[3])), 16); + + DEBUG_PRINT("t_raw = %ld, p_raw = %ld\n", t_raw, p_raw); + + *temp = 42.5 + t_raw / 480.0; + *pressure = p_raw / 4096.0; + *humidity = -1.0; + + DEBUG_PRINT("temp = %0.1f C, pressure = %0.1f hPa\n", *temp, *pressure); + + return 0; +} + + diff --git a/src/i2c_shtc3.c b/src/i2c_shtc3.c new file mode 100644 index 0000000..fda86f1 --- /dev/null +++ b/src/i2c_shtc3.c @@ -0,0 +1,201 @@ +/* i2c_shtc3.c + Copyright (C) 2024 Timo Kokkonen + + SPDX-License-Identifier: GPL-3.0-or-later + + This file is part of FanPico. + + FanPico is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + FanPico is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with FanPico. If not, see . +*/ + +#include +#include +#include +#include "pico/stdlib.h" +#include "hardware/gpio.h" +#include "hardware/i2c.h" + +#include "i2c.h" + +/* SHTC3 Commands */ +#define CMD_SLEEP 0xb098 +#define CMD_WAKEUP 0x3517 +#define CMD_MEASURE 0x58e0 +#define CMD_RESET 0x805d +#define CMD_ID 0xefc8 + +#define SHTC3_DEVICE_ID 0x0807 +#define SHTC3_DEVICE_ID_MASK 0x083f + + +typedef struct shtc3_context_t { + i2c_inst_t *i2c; + uint8_t addr; +} shtc3_context_t; + + +/* CRC-8 Lookup Table (Polymonial 0x31) */ +static const uint8_t shtc3_crc_lookup_table[] = { + 0x00,0x31,0x62,0x53,0xC4,0xF5,0xA6,0x97,0xB9,0x88,0xDB,0xEA,0x7D,0x4C,0x1F,0x2E, + 0x43,0x72,0x21,0x10,0x87,0xB6,0xE5,0xD4,0xFA,0xCB,0x98,0xA9,0x3E,0x0F,0x5C,0x6D, + 0x86,0xB7,0xE4,0xD5,0x42,0x73,0x20,0x11,0x3F,0x0E,0x5D,0x6C,0xFB,0xCA,0x99,0xA8, + 0xC5,0xF4,0xA7,0x96,0x01,0x30,0x63,0x52,0x7C,0x4D,0x1E,0x2F,0xB8,0x89,0xDA,0xEB, + 0x3D,0x0C,0x5F,0x6E,0xF9,0xC8,0x9B,0xAA,0x84,0xB5,0xE6,0xD7,0x40,0x71,0x22,0x13, + 0x7E,0x4F,0x1C,0x2D,0xBA,0x8B,0xD8,0xE9,0xC7,0xF6,0xA5,0x94,0x03,0x32,0x61,0x50, + 0xBB,0x8A,0xD9,0xE8,0x7F,0x4E,0x1D,0x2C,0x02,0x33,0x60,0x51,0xC6,0xF7,0xA4,0x95, + 0xF8,0xC9,0x9A,0xAB,0x3C,0x0D,0x5E,0x6F,0x41,0x70,0x23,0x12,0x85,0xB4,0xE7,0xD6, + 0x7A,0x4B,0x18,0x29,0xBE,0x8F,0xDC,0xED,0xC3,0xF2,0xA1,0x90,0x07,0x36,0x65,0x54, + 0x39,0x08,0x5B,0x6A,0xFD,0xCC,0x9F,0xAE,0x80,0xB1,0xE2,0xD3,0x44,0x75,0x26,0x17, + 0xFC,0xCD,0x9E,0xAF,0x38,0x09,0x5A,0x6B,0x45,0x74,0x27,0x16,0x81,0xB0,0xE3,0xD2, + 0xBF,0x8E,0xDD,0xEC,0x7B,0x4A,0x19,0x28,0x06,0x37,0x64,0x55,0xC2,0xF3,0xA0,0x91, + 0x47,0x76,0x25,0x14,0x83,0xB2,0xE1,0xD0,0xFE,0xCF,0x9C,0xAD,0x3A,0x0B,0x58,0x69, + 0x04,0x35,0x66,0x57,0xC0,0xF1,0xA2,0x93,0xBD,0x8C,0xDF,0xEE,0x79,0x48,0x1B,0x2A, + 0xC1,0xF0,0xA3,0x92,0x05,0x34,0x67,0x56,0x78,0x49,0x1A,0x2B,0xBC,0x8D,0xDE,0xEF, + 0x82,0xB3,0xE0,0xD1,0x46,0x77,0x24,0x15,0x3B,0x0A,0x59,0x68,0xFF,0xCE,0x9D,0xAC +}; + +static uint8_t crc8(uint8_t *buf, size_t len) +{ + uint8_t crc = 0xff; + + for (int i = 0; i < len; i++) { + crc = shtc3_crc_lookup_table[crc ^ buf[i]]; + } + + return crc; +} + + +static int shtc3_read_u16(i2c_inst_t *i2c, uint8_t addr, uint16_t *val, bool nostop) +{ + uint8_t buf[3], crc; + int res; + + DEBUG_PRINT("args=%p,%02x,%p\n", i2c, addr, val); + + res = i2c_read_raw(i2c, addr, buf, 3, nostop); + if (res) { + DEBUG_PRINT("read failed (%d)\n", res); + return -1; + } + + *val = (buf[0] << 8) | buf[1]; + crc = crc8(buf, 2); + DEBUG_PRINT("read ok: [%02x %02x (%02x)] %04x (%u), crc=%02x\n", buf[0], buf[1], buf[2], *val, *val, crc); + + if (crc != buf[2]) { + DEBUG_PRINT("checksum mismatch!\n"); + return -2; + } + + return 0; +} + + +void* shtc3_init(i2c_inst_t *i2c, uint8_t addr) +{ + int res; + uint16_t val = 0; + shtc3_context_t *ctx = calloc(1, sizeof(shtc3_context_t)); + + if (!ctx) + return NULL; + ctx->i2c = i2c; + ctx->addr = addr; + + + /* Read and verify device ID */ + res = i2c_write_raw_u16(i2c, addr, CMD_ID, false); + if (res) + goto panic; + sleep_us(10); + res = shtc3_read_u16(i2c, addr, &val, false); + if (res) + goto panic; + if ((val & SHTC3_DEVICE_ID_MASK) != SHTC3_DEVICE_ID) + goto panic; + + + /* Reset sensor */ + res = i2c_write_raw_u16(i2c, addr, CMD_RESET, false); + if (res) + goto panic; + sleep_us(250); + + + /* Read and verify device ID again... */ + res = i2c_write_raw_u16(i2c, addr, CMD_ID, false); + if (res) + goto panic; + sleep_us(10); + res = shtc3_read_u16(i2c, addr, &val, false); + if (res) + goto panic; + if ((val & SHTC3_DEVICE_ID_MASK) != SHTC3_DEVICE_ID) + goto panic; + + return ctx; + +panic: + free(ctx); + return NULL; +} + + +int shtc3_start_measurement(void *ctx) +{ + shtc3_context_t *c = (shtc3_context_t*)ctx; + int res; + + /* Initiate measurement */ + res = i2c_write_raw_u16(c->i2c, c->addr, CMD_MEASURE, false); + if (res) + return -1; + + return 13; /* measurement should be available after 12.1ms */ +} + + +int shtc3_get_measurement(void *ctx, float *temp, float *pressure, float *humidity) +{ + shtc3_context_t *c = (shtc3_context_t*)ctx; + int res; + uint8_t buf[6]; + uint16_t t_raw = 0; + uint16_t h_raw = 0; + + /* Get Measurement */ + res = i2c_read_raw(c->i2c, c->addr, buf, 6, false); + if (res) + return -1; + + /* Check CRC of received values */ + if (crc8(&buf[0], 2) != buf[2]) + return -2; + if (crc8(&buf[3], 2) != buf[5]) + return -3; + + h_raw = (buf[0] << 8) | buf[1]; + t_raw = (buf[3] << 8) | buf[4]; + DEBUG_PRINT("h_raw = %u, t_raw = %u\n", h_raw, t_raw); + + *temp = -45 + 175 * (double) t_raw / 65536; + *pressure = -1.0; + *humidity = 100 * (double) h_raw / 65536; + DEBUG_PRINT("temp: %0.1f, humidity: %0.1f\n", *temp, *humidity); + + return 0; +} + + diff --git a/src/mqtt.c b/src/mqtt.c index bb298f6..8c8529a 100644 --- a/src/mqtt.c +++ b/src/mqtt.c @@ -460,6 +460,24 @@ static char* json_ha_discovery_message(const char *type, int idx, int count) cJSON_AddItemToObject(json, "device_class", cJSON_CreateString("temperature")); cJSON_AddItemToObject(json, "unit_of_measurement", cJSON_CreateString("°C")); } + else if (!strncmp(type, "vtemp", 5)) { + snprintf(tmp, sizeof(tmp), "Sensor: %s", cfg->vsensors[idx - 1].name); + cJSON_AddItemToObject(json, "name", cJSON_CreateString(tmp)); + cJSON_AddItemToObject(json, "device_class", cJSON_CreateString("temperature")); + cJSON_AddItemToObject(json, "unit_of_measurement", cJSON_CreateString("°C")); + } + else if (!strncmp(type, "vhumidity", 9)) { + snprintf(tmp, sizeof(tmp), "Sensor: %s", cfg->vsensors[idx - 1].name); + cJSON_AddItemToObject(json, "name", cJSON_CreateString(tmp)); + cJSON_AddItemToObject(json, "device_class", cJSON_CreateString("humidity")); + cJSON_AddItemToObject(json, "unit_of_measurement", cJSON_CreateString("%")); + } + else if (!strncmp(type, "vpressure", 9)) { + snprintf(tmp, sizeof(tmp), "Sensor: %s", cfg->vsensors[idx - 1].name); + cJSON_AddItemToObject(json, "name", cJSON_CreateString(tmp)); + cJSON_AddItemToObject(json, "device_class", cJSON_CreateString("pressure")); + cJSON_AddItemToObject(json, "unit_of_measurement", cJSON_CreateString("hPa")); + } else if (!strncmp(type, "fanrpm", 6)) { snprintf(tmp, sizeof(tmp), "Fan: %s", cfg->fans[idx - 1].name); cJSON_AddItemToObject(json, "name", cJSON_CreateString(tmp)); @@ -559,6 +577,24 @@ static void fanpico_mqtt_ha_discovery() } } + for (int i = 0; i < VSENSOR_COUNT; i++) { + if (cfg->mqtt_vtemp_mask & (1 << i)) { + snprintf(topic, sizeof(topic), "%s_vt%d/config", mqtt_ha_base_topic, i + 1); + if (send_ha_discovery_msg(topic, "vtemp", i + 1, count++)) + return; + } + if (cfg->mqtt_vhumidity_mask & (1 << i)) { + snprintf(topic, sizeof(topic), "%s_vh%d/config", mqtt_ha_base_topic, i + 1); + if (send_ha_discovery_msg(topic, "vhumidity", i + 1, count++)) + return; + } + if (cfg->mqtt_vpressure_mask & (1 << i)) { + snprintf(topic, sizeof(topic), "%s_vp%d/config", mqtt_ha_base_topic, i + 1); + if (send_ha_discovery_msg(topic, "vpressure", i + 1, count++)) + return; + } + } + for (int i = 0; i < FAN_COUNT; i++) { if (cfg->mqtt_fan_rpm_mask & (1 << i)) { snprintf(topic, sizeof(topic), "%s_fr%d/config", mqtt_ha_base_topic, i + 1); @@ -602,6 +638,21 @@ static char* json_ha_state_message() } } + for (int i = 0; i < VSENSOR_COUNT; i++) { + if (cfg->mqtt_vtemp_mask & (1 << i)) { + snprintf(name, sizeof(name), "vtemp%d", i + 1); + cJSON_AddItemToObject(json, name, cJSON_CreateNumber(round_decimal(st->vtemp[i], 1))); + } + if (cfg->mqtt_vhumidity_mask & (1 << i)) { + snprintf(name, sizeof(name), "vhumidity%d", i + 1); + cJSON_AddItemToObject(json, name, cJSON_CreateNumber(round_decimal(st->vhumidity[i], 0))); + } + if (cfg->mqtt_vpressure_mask & (1 << i)) { + snprintf(name, sizeof(name), "vpressure%d", i + 1); + cJSON_AddItemToObject(json, name, cJSON_CreateNumber(round_decimal(st->vpressure[i], 0))); + } + } + for (int i = 0; i < FAN_COUNT; i++) { if (cfg->mqtt_fan_rpm_mask & (1 << i)) { float rpm = st->fan_freq[i] * 60 / cfg->fans[i].rpm_factor; @@ -770,6 +821,39 @@ void fanpico_mqtt_publish_temp() log_msg(LOG_DEBUG, "fanpico_mqtt_publish_temp(): end"); } +void fanpico_mqtt_publish_vsensor() +{ + const struct fanpico_state *st = fanpico_state; + char topic[MQTT_MAX_TOPIC_LEN + 8]; + char buf[64]; + + if (!mqtt_client) + return; + + log_msg(LOG_DEBUG, "fanpico_mqtt_publish_vsensor(): start"); + for (int i = 0; i < VSENSOR_COUNT; i++) { + if (strlen(cfg->mqtt_vtemp_topic) > 0 && cfg->mqtt_vtemp_mask & (1 << i)) { + snprintf(topic, sizeof(topic), cfg->mqtt_vtemp_topic, i + 1); + snprintf(buf, sizeof(buf), "%.1f", st->vtemp[i]); + mqtt_publish_message(topic, buf, strlen(buf), mqtt_qos, 0, + cfg->mqtt_vtemp_topic); + } + if (strlen(cfg->mqtt_vhumidity_topic) > 0 && cfg->mqtt_vhumidity_mask & (1 << i)) { + snprintf(topic, sizeof(topic), cfg->mqtt_vhumidity_topic, i + 1); + snprintf(buf, sizeof(buf), "%.0f", st->vhumidity[i]); + mqtt_publish_message(topic, buf, strlen(buf), mqtt_qos, 0, + cfg->mqtt_vhumidity_topic); + } + if (strlen(cfg->mqtt_vpressure_topic) > 0 && cfg->mqtt_vpressure_mask & (1 << i)) { + snprintf(topic, sizeof(topic), cfg->mqtt_vpressure_topic, i + 1); + snprintf(buf, sizeof(buf), "%.0f", st->vpressure[i]); + mqtt_publish_message(topic, buf, strlen(buf), mqtt_qos, 0, + cfg->mqtt_vpressure_topic); + } + } + log_msg(LOG_DEBUG, "fanpico_mqtt_publish_vsensor(): end"); +} + void fanpico_mqtt_publish_rpm() { const struct fanpico_state *st = fanpico_state; diff --git a/src/network.c b/src/network.c index 426d93d..134e611 100644 --- a/src/network.c +++ b/src/network.c @@ -242,6 +242,7 @@ void wifi_poll() static absolute_time_t ABSOLUTE_TIME_INITIALIZED_VAR(test_t, 0); static absolute_time_t ABSOLUTE_TIME_INITIALIZED_VAR(publish_status_t, 0); static absolute_time_t ABSOLUTE_TIME_INITIALIZED_VAR(publish_temp_t, 0); + static absolute_time_t ABSOLUTE_TIME_INITIALIZED_VAR(publish_vsensor_t, 0); static absolute_time_t ABSOLUTE_TIME_INITIALIZED_VAR(publish_rpm_t, 0); static absolute_time_t ABSOLUTE_TIME_INITIALIZED_VAR(publish_duty_t, 0); static absolute_time_t ABSOLUTE_TIME_INITIALIZED_VAR(command_t, 0); @@ -295,6 +296,11 @@ void wifi_poll() fanpico_mqtt_publish_temp(); } } + if (cfg->mqtt_vsensor_interval > 0) { + if (time_passed(&publish_vsensor_t, cfg->mqtt_vsensor_interval * 1000)) { + fanpico_mqtt_publish_vsensor(); + } + } if (cfg->mqtt_rpm_interval > 0) { if (time_passed(&publish_rpm_t, cfg->mqtt_rpm_interval * 1000)) { fanpico_mqtt_publish_rpm(); diff --git a/src/sensors.c b/src/sensors.c index 7329f3b..479aa0f 100644 --- a/src/sensors.c +++ b/src/sensors.c @@ -130,6 +130,8 @@ double get_vsensor(uint8_t i, struct fanpico_config *config, if (absolute_time_diff_us(config->vtemp_updated[i], state->vtemp_updated[i]) != 0) { t = config->vtemp[i]; state->vtemp_updated[i] = config->vtemp_updated[i]; + state->vpressure[i] = config->vpressure[i]; + state->vhumidity[i] = config->vhumidity[i]; } /* Check if should reset temperature back to default due to lack of updates... */ if (s->timeout > 0 && t != s->default_temp) { diff --git a/src/util_rp2040.c b/src/util_rp2040.c index 39811d3..799fd27 100644 --- a/src/util_rp2040.c +++ b/src/util_rp2040.c @@ -98,17 +98,29 @@ const char *rp2040_model_str() static char buf[32]; uint8_t version = 0; uint8_t known_chip = 0; - uint8_t chip_version = rp2040_chip_version(); + uint8_t chip_version = 0; + char *model; + char r; + +#if PICO_RP2350 + model = "2350"; + r = 'A'; + chip_version = rp2350_chip_version(); + if (chip_version <= 2) + known_chip = 1; + version = chip_version; +#else + model = "2040"; + r = 'B'; uint8_t rom_version = rp2040_rom_version(); - - + chip_version = rp2040_chip_version(); if (chip_version <= 2 && rom_version <= 3) known_chip = 1; - version = rom_version - 1; +#endif - snprintf(buf, sizeof(buf), "RP2040-B%d%s", - version, + snprintf(buf, sizeof(buf), "RP%s-%c%d%s", + model, r, version, (known_chip ? "" : " (?)")); return buf;