2020-04-26 08:43:52 +01:00
/*
xsns_68_windmeter . ino - Analog wind sensor support for Tasmota
2021-01-01 12:44:04 +00:00
Copyright ( C ) 2021 Matteo Albinola
2020-04-26 08:43:52 +01:00
( inspired by great works of Thomas Eckerstorfer , Norbert Richter , Maarten Damen and Theo Arends )
This program 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 .
This program 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 this program . If not , see < http : //www.gnu.org/licenses/>.
*/
# ifdef USE_WINDMETER
/*********************************************************************************************\
* WindMeter sensor ( speed )
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
# define XSNS_68 68
# define D_WINDMETER_NAME "WindMeter"
2023-10-06 09:53:38 +01:00
# define WINDMETER_DEF_RADIUS 61 // Cups' center rotation radius in millimeters (calculated by measuring the distance from the centre axis to the center of one of the cups)
# define WINDMETER_DEF_PULSES_X_ROT 1 // Number of pulses that occurs in a complete rotation
2020-04-26 08:43:52 +01:00
# define WINDMETER_DEF_PULSE_DEBOUNCE 10 // Pulse counter debounce time (milliseconds)
2023-10-06 09:53:38 +01:00
# define WINDMETER_DEF_SPEED_FACTOR 1.18 // Cup anemometer factor: a compensation factor that depends on the ratio of the cups to the cups center rotation radius
2020-04-28 13:02:05 +01:00
# define WINDMETER_DEF_TELE_PCHANGE 255 // Minimum percentage change between current and last reported speed in order to trigger a new tele message (0...100, 255 means off)
2020-04-26 08:43:52 +01:00
# define WINDMETER_WEIGHT_AVG_SAMPLE 150 // No of samples to take
2023-10-06 09:53:38 +01:00
# define WINDMETER_DEF_MEASURE_INTVL 1 // Speed measurement interval: speed value will be computed every X (seconds)
2020-04-26 08:43:52 +01:00
# ifdef USE_WEBSERVER
# define D_WINDMETER_WIND_AVG "∅"
# define D_WINDMETER_WIND_ANGLE "∠"
# define D_WINDMETER_WIND_DEGREE "°"
const char HTTP_SNS_WINDMETER [ ] PROGMEM =
" {s} " D_WINDMETER_NAME " " D_TX20_WIND_SPEED " {m}%s %s{e} "
# ifndef USE_WINDMETER_NOSTATISTICS
" {s} " D_WINDMETER_NAME " " D_TX20_WIND_SPEED " " D_WINDMETER_WIND_AVG " {m}%s %s{e} "
" {s} " D_WINDMETER_NAME " " D_TX20_WIND_SPEED_MIN " {m}%s %s{e} "
" {s} " D_WINDMETER_NAME " " D_TX20_WIND_SPEED_MAX " {m}%s %s{e} "
# endif // USE_WINDMETER_NOSTATISTICS
// "{s}WindMeter " D_TX20_WIND_DIRECTION "{m}%s %s" D_WINDMETER_WIND_DEGREE "{e}"
//#ifndef USE_WINDMETER_NOSTATISTICS
// "{s}WindMeter " D_TX20_WIND_DIRECTION " " D_WINDMETER_WIND_AVG "{m}%s %s" D_WINDMETER_WIND_DEGREE "{e}"
// "{s}WindMeter " D_TX20_WIND_DIRECTION " " D_WINDMETER_WIND_ANGLE "{m}%s" D_WINDMETER_WIND_DEGREE " (%s,%s)" D_WINDMETER_WIND_DEGREE;
//#endif // USE_WINDMETER_NOSTATISTICS
;
# endif // USE_WEBSERVER
// float saves 48 byte
float const windmeter_pi = 3.1415926535897932384626433 ; // Pi
float const windmeter_2pi = windmeter_pi * 2 ;
struct WINDMETER {
2020-06-06 08:25:00 +01:00
volatile uint32_t counter_time ;
volatile unsigned long counter = 0 ;
2023-10-06 09:53:38 +01:00
uint8_t measure_counter = 0 ;
uint32_t measure_time = 0 ;
2020-04-26 08:43:52 +01:00
float speed = 0 ;
2020-04-28 13:02:05 +01:00
float last_tele_speed = 0 ;
2020-04-26 08:43:52 +01:00
# ifndef USE_WINDMETER_NOSTATISTICS
float speed_min = 0 ;
float speed_max = 0 ;
float speed_avg = 0 ;
uint32_t samples_count = 0 ;
uint32_t avg_samples_no ;
# endif // USE_WINDMETER_NOSTATISTICS
} WindMeter ;
2021-04-02 16:14:08 +01:00
void IRAM_ATTR WindMeterUpdateSpeed ( void )
2020-04-26 08:43:52 +01:00
{
uint32_t time = micros ( ) ;
uint32_t time_diff = time - WindMeter . counter_time ;
2021-06-11 17:14:12 +01:00
if ( time_diff > Settings - > windmeter_pulse_debounce * 1000 ) {
2020-04-26 08:43:52 +01:00
WindMeter . counter_time = time ;
WindMeter . counter + + ;
2023-10-06 09:53:38 +01:00
//AddLog(LOG_LEVEL_DEBUG, PSTR("WMET: Counter %d"), WindMeter.counter);
2020-04-26 08:43:52 +01:00
}
}
/********************************************************************************************/
void WindMeterInit ( void )
{
2021-06-11 17:14:12 +01:00
if ( ! Settings - > flag2 . speed_conversion ) {
Settings - > flag2 . speed_conversion = 2 ; // 0 = none, 1 = m/s, 2 = km/h, 3 = kn, 4 = mph, 5 = ft/s, 6 = yd/s
2020-04-26 08:43:52 +01:00
}
2021-06-11 17:14:12 +01:00
if ( ! Settings - > windmeter_radius ) {
Settings - > windmeter_radius = WINDMETER_DEF_RADIUS ;
2020-04-26 08:43:52 +01:00
}
2021-06-11 17:14:12 +01:00
if ( ! Settings - > windmeter_pulses_x_rot ) {
Settings - > windmeter_pulses_x_rot = WINDMETER_DEF_PULSES_X_ROT ;
2020-04-26 08:43:52 +01:00
}
2021-06-11 17:14:12 +01:00
if ( ! Settings - > windmeter_pulse_debounce ) {
Settings - > windmeter_pulse_debounce = WINDMETER_DEF_PULSE_DEBOUNCE ;
2020-04-26 08:43:52 +01:00
}
2023-10-06 09:53:38 +01:00
if ( ! Settings - > windmeter_measure_intvl | | Settings - > windmeter_measure_intvl = = 0 ) {
Settings - > windmeter_measure_intvl = WINDMETER_DEF_MEASURE_INTVL ;
}
2021-06-11 17:14:12 +01:00
if ( ! Settings - > windmeter_speed_factor ) {
2023-10-06 09:53:38 +01:00
Settings - > windmeter_speed_factor = ( int16_t ) ( WINDMETER_DEF_SPEED_FACTOR * 1000 ) ;
2020-04-26 08:43:52 +01:00
}
2021-06-11 17:14:12 +01:00
if ( ! Settings - > windmeter_tele_pchange ) {
Settings - > windmeter_tele_pchange = WINDMETER_DEF_TELE_PCHANGE ;
2020-04-28 13:02:05 +01:00
}
2020-04-26 08:43:52 +01:00
# ifndef USE_WINDMETER_NOSTATISTICS
WindMeterResetStatData ( ) ;
WindMeterCheckSampleCount ( ) ;
# endif // USE_WINDMETER_NOSTATISTICS
2020-05-02 14:22:37 +01:00
pinMode ( Pin ( GPIO_WINDMETER_SPEED ) , INPUT_PULLUP ) ;
attachInterrupt ( Pin ( GPIO_WINDMETER_SPEED ) , WindMeterUpdateSpeed , FALLING ) ;
2020-04-26 08:43:52 +01:00
}
void WindMeterEverySecond ( void )
{
2023-10-06 09:53:38 +01:00
if ( WindMeter . measure_counter < ( Settings - > windmeter_measure_intvl - 1 ) ) {
WindMeter . measure_counter + + ;
} else {
uint32_t time = micros ( ) ;
uint32_t last_time = ( WindMeter . measure_time = = 0 ) ? ( Settings - > windmeter_measure_intvl * 1000000 ) : WindMeter . measure_time ;
WindMeter . measure_counter = 0 ;
WindMeter . measure_time = time ;
// speed = ( (pulses / pulses_per_rotation) * (2 * pi * radius) * anemometer_factor ) / delta_time
WindMeter . speed = ( ( ( WindMeter . counter / Settings - > windmeter_pulses_x_rot ) * ( windmeter_2pi * ( ( float ) Settings - > windmeter_radius / 1000 ) ) * ( ( float ) Settings - > windmeter_speed_factor / 1000 ) ) / ( ( float ) ( time - last_time ) / 1000000 ) ) ;
WindMeter . counter = 0 ;
//char speed_string[FLOATSZ];
//dtostrfd(WindMeter.speed, 2, speed_string);
//char uspeed_string[FLOATSZ];
//dtostrfd(ConvertSpeed(WindMeter.speed), 2, uspeed_string);
//AddLog(LOG_LEVEL_DEBUG, PSTR("WMET: Speed %s [m/s] - %s [unit]"), speed_string, uspeed_string);
2020-04-26 08:43:52 +01:00
# ifndef USE_WINDMETER_NOSTATISTICS
2023-10-06 09:53:38 +01:00
if ( WindMeter . speed < WindMeter . speed_min ) {
WindMeter . speed_min = WindMeter . speed ;
}
if ( WindMeter . speed > WindMeter . speed_max ) {
WindMeter . speed_max = WindMeter . speed ;
}
2020-04-26 08:43:52 +01:00
2023-10-06 09:53:38 +01:00
// exponentially weighted average is not quite as smooth as the arithmetic average
// but close enough to the moving average and does not require the regular reset
// of the divider with the associated jump in avg values after period is over
if ( WindMeter . samples_count < = WindMeter . avg_samples_no ) {
WindMeter . samples_count + + ;
}
WindMeter . speed_avg - = WindMeter . speed_avg / WindMeter . samples_count ;
WindMeter . speed_avg + = float ( WindMeter . speed ) / WindMeter . samples_count ;
2020-04-26 08:43:52 +01:00
2023-10-06 09:53:38 +01:00
WindMeterCheckSampleCount ( ) ;
if ( 0 = = Settings - > tele_period ) {
WindMeterResetStatData ( ) ;
}
2020-04-26 08:43:52 +01:00
# endif // USE_WINDMETER_NOSTATISTICS
2020-04-28 13:02:05 +01:00
2023-10-06 09:53:38 +01:00
if ( WindMeterShouldTriggerTele ( ) ) {
MqttPublishTeleperiodSensor ( ) ;
}
2020-04-28 13:02:05 +01:00
}
}
bool WindMeterShouldTriggerTele ( )
{
2021-06-11 17:14:12 +01:00
if ( Settings - > windmeter_tele_pchange > 100 ) {
2020-04-28 13:02:05 +01:00
return false ;
} else if ( WindMeter . last_tele_speed = = 0 ) {
return WindMeter . speed > 0 ;
} else {
float perc_change = ( WindMeter . speed / WindMeter . last_tele_speed ) - 1 ;
2021-06-11 17:14:12 +01:00
return ( perc_change * ( ( perc_change < 0 ) ? - 100 : 100 ) ) > = Settings - > windmeter_tele_pchange ;
2020-04-28 13:02:05 +01:00
}
2020-04-26 08:43:52 +01:00
}
void WindMeterResetStatData ( void )
{
WindMeter . speed_min = WindMeter . speed ;
WindMeter . speed_max = WindMeter . speed ;
//WindMeter.direction_min = WindMeter.direction;
//WindMeter.direction_max = WindMeter.direction;
}
void WindMeterCheckSampleCount ( void )
{
uint32_t prev_avg_samples_no = WindMeter . avg_samples_no ;
2021-06-11 17:14:12 +01:00
if ( Settings - > tele_period ) {
2020-04-26 08:43:52 +01:00
// number for avg samples = teleperiod value if set
2021-06-11 17:14:12 +01:00
WindMeter . avg_samples_no = Settings - > tele_period ;
2020-04-26 08:43:52 +01:00
} else {
// otherwise use default number of samples for this driver
WindMeter . avg_samples_no = WINDMETER_WEIGHT_AVG_SAMPLE ;
}
if ( prev_avg_samples_no ! = WindMeter . avg_samples_no ) {
WindMeter . speed_avg = WindMeter . speed ;
WindMeter . samples_count = 0 ;
}
}
void WindMeterShow ( bool json )
{
char speed_string [ FLOATSZ ] ;
dtostrfd ( ConvertSpeed ( WindMeter . speed ) , 2 , speed_string ) ;
# ifndef USE_WINDMETER_NOSTATISTICS
char speed_min_string [ FLOATSZ ] ;
dtostrfd ( ConvertSpeed ( WindMeter . speed_min ) , 2 , speed_min_string ) ;
char speed_max_string [ FLOATSZ ] ;
dtostrfd ( ConvertSpeed ( WindMeter . speed_max ) , 2 , speed_max_string ) ;
char speed_avg_string [ FLOATSZ ] ;
dtostrfd ( ConvertSpeed ( WindMeter . speed_avg ) , 2 , speed_avg_string ) ;
//char direction_avg_string[FLOATSZ];
//dtostrfd(WindMeter.direction_avg, 1, direction_avg_string);
//char direction_avg_cardinal_string[4];
//GetTextIndexed(direction_avg_cardinal_string, sizeof(direction_avg_cardinal_string), int((WindMeter.direction_avg/22.5f)+0.5f) % 16, kWindMeterDirections);
//char direction_range_string[FLOATSZ];
//dtostrfd(Tx2xNormalize(WindMeter.direction_max-WindMeter.direction_min)*22.5, 1, direction_range_string);
//char direction_min_string[FLOATSZ];
//dtostrfd(Tx2xNormalize(WindMeter.direction_min)*22.5, 1, direction_min_string);
//char direction_max_string[FLOATSZ];
//dtostrfd(Tx2xNormalize(WindMeter.direction_max)*22.5, 1, direction_max_string);
# endif // USE_WINDMETER_NOSTATISTICS
if ( json ) {
2020-04-28 13:02:05 +01:00
WindMeter . last_tele_speed = WindMeter . speed ;
2020-04-26 08:43:52 +01:00
# ifndef USE_WINDMETER_NOSTATISTICS
2020-05-12 20:12:16 +01:00
//ResponseAppend_P(PSTR(",\"" D_WINDMETER_NAME "\":{\"" D_JSON_SPEED "\":{\"Act\":%s,\"Avg\":%s,\"Min\":%s,\"Max\":%s},\"Dir\":{\"Card\":\"%s\",\"Deg\":%s,\"Avg\":%s,\"AvgCard\":\"%s\",\"Min\":%s,\"Max\":%s,\"Range\":%s}}"),
ResponseAppend_P ( PSTR ( " , \" " D_WINDMETER_NAME " \" :{ \" " D_JSON_SPEED " \" :{ \" Act \" :%s, \" Avg \" :%s, \" Min \" :%s, \" Max \" :%s}} " ) ,
2020-04-26 08:43:52 +01:00
speed_string ,
speed_avg_string ,
speed_min_string ,
2020-05-12 20:12:16 +01:00
speed_max_string
//direction_cardinal_string,
//direction_string,
//direction_avg_string,
//direction_avg_cardinal_string,
//direction_min_string,
//direction_max_string,
//direction_range_string
2020-04-26 08:43:52 +01:00
) ;
# else // USE_WINDMETER_NOSTATISTICS
2020-05-12 20:12:16 +01:00
//ResponseAppend_P(PSTR(",\"" D_WINDMETER_NAME "\":{\"" D_JSON_SPEED "\":{\"Act\":%s},\"Dir\":{\"Card\":\"%s\",\"Deg\":%s}}"),
ResponseAppend_P ( PSTR ( " , \" " D_WINDMETER_NAME " \" :{ \" " D_JSON_SPEED " \" :{ \" Act \" :%s}} " ) ,
speed_string
//wind_direction_cardinal_string,
//wind_direction_string
2020-04-26 08:43:52 +01:00
) ;
# endif // USE_WINDMETER_NOSTATISTICS
# ifdef USE_WEBSERVER
} else {
WSContentSend_PD ( HTTP_SNS_WINDMETER ,
speed_string ,
SpeedUnit ( ) . c_str ( ) ,
# ifndef USE_WINDMETER_NOSTATISTICS
speed_avg_string ,
SpeedUnit ( ) . c_str ( ) ,
speed_min_string ,
SpeedUnit ( ) . c_str ( ) ,
speed_max_string ,
SpeedUnit ( ) . c_str ( ) ,
# endif // USE_WINDMETER_NOSTATISTICS
" n/a " , //wind_direction_cardinal_string,
" n/a " //wind_direction_string
# ifndef USE_WINDMETER_NOSTATISTICS
, " n/a " , //,wind_direction_avg_cardinal_string,
" n/a " , //wind_direction_avg_string,
" n/a " , //wind_direction_range_string,
" n/a " , //wind_direction_min_string,
" n/a " //wind_direction_max_string
# endif // USE_WINDMETER_NOSTATISTICS
) ;
# endif // USE_WEBSERVER
}
}
/*********************************************************************************************\
* Commands
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
bool Xsns68Cmnd ( void )
{
2021-01-30 13:52:53 +00:00
if ( ArgC ( ) > 1 ) {
char argument [ XdrvMailbox . data_len ] ;
switch ( XdrvMailbox . payload ) {
case 1 :
2021-06-11 17:14:12 +01:00
Settings - > windmeter_radius = ( uint16_t ) strtol ( ArgV ( argument , 2 ) , nullptr , 10 ) ;
2021-01-30 13:52:53 +00:00
break ;
case 2 :
2021-06-11 17:14:12 +01:00
Settings - > windmeter_pulses_x_rot = ( uint8_t ) strtol ( ArgV ( argument , 2 ) , nullptr , 10 ) ;
2021-01-30 13:52:53 +00:00
break ;
case 3 :
2021-06-11 17:14:12 +01:00
Settings - > windmeter_pulse_debounce = ( uint16_t ) strtol ( ArgV ( argument , 2 ) , nullptr , 10 ) ;
2021-01-30 13:52:53 +00:00
break ;
case 4 :
2021-06-11 17:14:12 +01:00
Settings - > windmeter_speed_factor = ( int16_t ) ( CharToFloat ( ArgV ( argument , 2 ) ) * 1000 ) ;
2021-01-30 13:52:53 +00:00
break ;
case 5 :
2021-06-11 17:14:12 +01:00
Settings - > windmeter_tele_pchange = ( uint8_t ) strtol ( ArgV ( argument , 2 ) , nullptr , 10 ) ;
2021-01-30 13:52:53 +00:00
break ;
2023-10-06 09:53:38 +01:00
case 6 :
uint8_t measure_intvl = ( uint8_t ) strtol ( ArgV ( argument , 2 ) , nullptr , 10 ) ;
if ( measure_intvl = = 0 ) {
Settings - > windmeter_measure_intvl = WINDMETER_DEF_MEASURE_INTVL ;
} else {
Settings - > windmeter_measure_intvl = measure_intvl ;
}
break ;
2021-01-30 13:52:53 +00:00
}
2020-04-26 08:43:52 +01:00
}
2023-10-06 09:53:38 +01:00
float anemometer_factor = ( float ) Settings - > windmeter_speed_factor / 1000 ;
2021-01-30 13:52:53 +00:00
char tele_pchange_string [ 4 ] = " off " ;
2021-06-11 17:14:12 +01:00
if ( Settings - > windmeter_tele_pchange < = 100 ) {
itoa ( Settings - > windmeter_tele_pchange , tele_pchange_string , 10 ) ;
2020-04-26 08:43:52 +01:00
}
2023-10-06 09:53:38 +01:00
Response_P ( PSTR ( " { \" " D_WINDMETER_NAME " \" :{ \" Radius \" :%d, \" PulsesPerRot \" :%d, \" PulseDebounce \" :%d, \" SpeedFactor \" :%3_f, \" TeleTriggerMin%Change \" :%s, \" MeasureInterval \" :%d}} " ) ,
Settings - > windmeter_radius , Settings - > windmeter_pulses_x_rot , Settings - > windmeter_pulse_debounce , & anemometer_factor , tele_pchange_string , Settings - > windmeter_measure_intvl ) ;
2021-01-30 13:52:53 +00:00
return true ;
2020-04-26 08:43:52 +01:00
}
/*********************************************************************************************\
* Interface
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2022-11-11 09:44:56 +00:00
bool Xsns68 ( uint32_t function )
2020-04-26 08:43:52 +01:00
{
bool result = false ;
2020-05-02 14:22:37 +01:00
if ( PinUsed ( GPIO_WINDMETER_SPEED ) ) {
2020-04-26 08:43:52 +01:00
switch ( function ) {
case FUNC_INIT :
WindMeterInit ( ) ;
break ;
case FUNC_EVERY_SECOND :
WindMeterEverySecond ( ) ;
break ;
# ifndef USE_WINDMETER_NOSTATISTICS
case FUNC_AFTER_TELEPERIOD :
WindMeterResetStatData ( ) ;
break ;
# endif // USE_WINDMETER_NOSTATISTICS
case FUNC_JSON_APPEND :
WindMeterShow ( true ) ;
break ;
# ifdef USE_WEBSERVER
case FUNC_WEB_SENSOR :
WindMeterShow ( false ) ;
break ;
# endif // USE_WEBSERVER
case FUNC_COMMAND_SENSOR :
if ( XSNS_68 = = XdrvMailbox . index ) {
result = Xsns68Cmnd ( ) ;
}
}
}
return result ;
}
# endif // USE_WINDMETER