2018-12-11 13:24:52 +00:00
/*
2019-10-27 10:13:24 +00:00
xsns_37_rfsensor . ino - RF sensor receiver for Tasmota
2018-12-11 13:24:52 +00:00
2019-12-31 13:23:34 +00:00
Copyright ( C ) 2020 Theo Arends
2018-12-11 13:24:52 +00:00
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_RF_SENSOR
/*********************************************************************************************\
2018-12-17 17:06:19 +00:00
* RF receiver based on work by Paul Tonkes ( www . nodo - domotica . nl )
*
* Supported 434 MHz receiver is Aurel RX - 4 M50RR30SF
* Supported 868 MHz receiver is Aurel RX - AM8SF
*
* Connect one of above receivers with a 330 Ohm resistor to any GPIO
2018-12-11 13:24:52 +00:00
*
* USE_THEO_V2 Add support for 434 MHz Theo V2 sensors as documented on https : //sidweb.nl
2018-12-17 17:06:19 +00:00
* USE_ALECTO_V2 Add support for 868 MHz Alecto V2 sensors like ACH2010 , WS3000 and DKW2012 weather stations
2018-12-11 13:24:52 +00:00
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
# define XSNS_37 37
//#define USE_THEO_V2 // Add support for 434MHz Theo V2 sensors as documented on https://sidweb.nl
//#define USE_ALECTO_V2 // Add support for 868MHz Alecto V2 sensors like ACH2010, WS3000 and DKW2012
# define RFSNS_VALID_WINDOW 1800 // Number of seconds for sensor to respond (1800 = 30 minutes)
# define RFSNS_LOOPS_PER_MILLI 1900 // (345 voor 16MHz ATMega) Voor 80MHz NodeMCU (ESP-12E). Getest met TheoV2 Protocol.
# define RFSNS_RAW_BUFFER_SIZE 180 // (256) Maximum number of RF pulses that can be captured
# define RFSNS_MIN_RAW_PULSES 112 // (16) =8 bits. Minimaal aantal ontvangen bits*2 alvorens cpu tijd wordt besteed aan decodering, etc.
// Zet zo hoog mogelijk om CPU-tijd te sparen en minder 'onzin' te ontvangen.
# define RFSNS_MIN_PULSE_LENGTH 300 // (50) Pulsen korter dan deze tijd uSec. worden als stoorpulsen beschouwd.
# define RFSNS_RAWSIGNAL_SAMPLE 50 // Sample grootte / Resolutie in uSec waarmee ontvangen Rawsignalen pulsen worden opgeslagen
# define RFSNS_SIGNAL_TIMEOUT 10 // Pulse timings in mSec. Beyond this value indicate end of message
# define RFSNS_SIGNAL_REPEAT_TIME 500 // (500) Tijd in mSec. waarbinnen hetzelfde event niet nogmaals via RF mag binnenkomen. Onderdrukt ongewenste herhalingen van signaal
2018-12-17 17:06:19 +00:00
typedef struct RawSignalStruct // Variabelen geplaatst in struct zodat deze later eenvoudig kunnen worden weggeschreven naar SDCard
2018-12-11 13:24:52 +00:00
{
int Number ; // aantal bits, maal twee omdat iedere bit een mark en een space heeft.
2019-01-28 13:08:33 +00:00
uint8_t Repeats ; // Aantal maal dat de pulsreeks verzonden moet worden bij een zendactie.
uint8_t Multiply ; // Pulses[] * Multiply is de echte tijd van een puls in microseconden
2018-12-11 13:24:52 +00:00
unsigned long Time ; // Tijdstempel wanneer signaal is binnengekomen (millis())
2019-01-28 13:08:33 +00:00
uint8_t Pulses [ RFSNS_RAW_BUFFER_SIZE + 2 ] ; // Tabel met de gemeten pulsen in microseconden gedeeld door rfsns_raw_signal->Multiply. Dit scheelt helft aan RAM geheugen.
2018-12-11 13:24:52 +00:00
// Om legacy redenen zit de eerste puls in element 1. Element 0 wordt dus niet gebruikt.
2018-12-17 17:06:19 +00:00
} raw_signal_t ;
2018-12-11 13:24:52 +00:00
2019-03-26 17:26:50 +00:00
raw_signal_t * rfsns_raw_signal = nullptr ;
2018-12-11 13:24:52 +00:00
uint8_t rfsns_rf_bit ;
uint8_t rfsns_rf_port ;
2018-12-17 17:06:19 +00:00
uint8_t rfsns_any_sensor = 0 ;
2018-12-11 13:24:52 +00:00
/*********************************************************************************************\
* Fetch signals from RF pin
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2019-01-28 13:08:33 +00:00
bool RfSnsFetchSignal ( uint8_t DataPin , bool StateSignal )
2018-12-11 13:24:52 +00:00
{
uint8_t Fbit = digitalPinToBitMask ( DataPin ) ;
uint8_t Fport = digitalPinToPort ( DataPin ) ;
uint8_t FstateMask = ( StateSignal ? Fbit : 0 ) ;
if ( ( * portInputRegister ( Fport ) & Fbit ) = = FstateMask ) { // Als er signaal is
const unsigned long LoopsPerMilli = RFSNS_LOOPS_PER_MILLI ;
// Als het een herhalend signaal is, dan is de kans groot dat we binnen hele korte tijd weer in deze
// routine terugkomen en dan midden in de volgende herhaling terecht komen. Daarom wordt er in dit
// geval gewacht totdat de pulsen voorbij zijn en we met het capturen van data beginnen na een korte
// rust tussen de signalen. Op deze wijze wordt het aantal zinloze captures teruggebracht.
unsigned long PulseLength = 0 ;
2018-12-17 17:06:19 +00:00
if ( rfsns_raw_signal - > Time ) { // Eerst een snelle check, want dit bevindt zich in een tijdkritisch deel...
if ( rfsns_raw_signal - > Repeats & & ( rfsns_raw_signal - > Time + RFSNS_SIGNAL_REPEAT_TIME ) > millis ( ) ) { // ...want deze check duurt enkele micro's langer!
2018-12-11 13:24:52 +00:00
PulseLength = micros ( ) + RFSNS_SIGNAL_TIMEOUT * 1000 ; // Wachttijd
2018-12-17 17:06:19 +00:00
while ( ( ( rfsns_raw_signal - > Time + RFSNS_SIGNAL_REPEAT_TIME ) > millis ( ) ) & & ( PulseLength > micros ( ) ) ) {
2018-12-11 13:24:52 +00:00
if ( ( * portInputRegister ( Fport ) & Fbit ) = = FstateMask ) {
PulseLength = micros ( ) + RFSNS_SIGNAL_TIMEOUT * 1000 ;
}
}
2018-12-17 17:06:19 +00:00
while ( ( ( rfsns_raw_signal - > Time + RFSNS_SIGNAL_REPEAT_TIME ) > millis ( ) ) & & ( ( * portInputRegister ( Fport ) & Fbit ) ! = FstateMask ) ) ;
2018-12-11 13:24:52 +00:00
}
}
int RawCodeLength = 1 ; // We starten bij 1, dit om legacy redenen. Vroeger had element 0 een speciaal doel.
bool Ftoggle = false ;
unsigned long numloops = 0 ;
unsigned long maxloops = RFSNS_SIGNAL_TIMEOUT * LoopsPerMilli ;
2018-12-17 17:06:19 +00:00
rfsns_raw_signal - > Multiply = RFSNS_RAWSIGNAL_SAMPLE ; // Ingestelde sample groote.
2018-12-11 13:24:52 +00:00
do { // lees de pulsen in microseconden en plaats deze in de tijdelijke buffer rfsns_raw_signal
numloops = 0 ;
while ( ( ( * portInputRegister ( Fport ) & Fbit ) = = FstateMask ) ^ Ftoggle ) { // while() loop *A*
if ( numloops + + = = maxloops ) { break ; } // timeout opgetreden
}
PulseLength = ( numloops * 1000 ) / LoopsPerMilli ; // Bevat nu de pulslengte in microseconden
if ( PulseLength < RFSNS_MIN_PULSE_LENGTH ) { break ; }
Ftoggle = ! Ftoggle ;
2018-12-17 17:06:19 +00:00
rfsns_raw_signal - > Pulses [ RawCodeLength + + ] = PulseLength / ( unsigned long ) rfsns_raw_signal - > Multiply ; // sla op in de tabel rfsns_raw_signal
2018-12-11 13:24:52 +00:00
}
2018-12-17 17:06:19 +00:00
while ( RawCodeLength < RFSNS_RAW_BUFFER_SIZE & & numloops < = maxloops ) ; // Zolang nog ruimte in de buffer, geen timeout en geen stoorpuls
2018-12-11 13:24:52 +00:00
if ( ( RawCodeLength > = RFSNS_MIN_RAW_PULSES ) & & ( RawCodeLength < RFSNS_RAW_BUFFER_SIZE - 1 ) ) {
2018-12-17 17:06:19 +00:00
rfsns_raw_signal - > Repeats = 0 ; // Op dit moment weten we nog niet het type signaal, maar de variabele niet ongedefinieerd laten.
rfsns_raw_signal - > Number = RawCodeLength - 1 ; // Aantal ontvangen tijden (pulsen *2)
rfsns_raw_signal - > Pulses [ rfsns_raw_signal - > Number ] = 0 ; // Laatste element bevat de timeout. Niet relevant.
rfsns_raw_signal - > Time = millis ( ) ;
2018-12-11 13:24:52 +00:00
return true ;
}
else
2018-12-17 17:06:19 +00:00
rfsns_raw_signal - > Number = 0 ;
2018-12-11 13:24:52 +00:00
}
return false ;
}
# ifdef USE_THEO_V2
/*********************************************************************************************\
* Theo V2 protocol
* Dit protocol zorgt voor ontvangst van Theo sensoren met protocol V2
*
* Auteur : Theo Arends
* Support : www . sidweb . nl
* Datum : 17 Apr 2014
* Versie : 0.1 - Initiele versie
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Technische informatie :
*
* Theo Sensor V2 type 1 Message Format ( 7 Bytes , 57 bits ) :
* Checksum Type Chl BsVoltag Temperature Light
* S AAAAAAAA BBBBBCCC DEFFFFFF GGGGGGGG GGGGGGGG HHHHHHHH HHHHHHHH
* idx : 0 1 2 3 4 5 6
*
* Theo Sensor V2 type 2 Message Format ( 7 Bytes , 57 bits ) :
* Checksum Type Chl BsVoltag Temperature Humidity
* S AAAAAAAA BBBBBCCC DEFFFFFF GGGGGGGG GGGGGGGG HHHHHHHH HHHHHHHH
* idx : 0 1 2 3 4 5 6
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
# define RFSNS_THEOV2_MAX_CHANNEL 2 // Max number of ATTiny sensor channels supported
# define RFSNS_THEOV2_PULSECOUNT 114
# define RFSNS_THEOV2_RF_PULSE_MID 1000 // PWM: Pulsen langer zijn '1'
typedef struct {
uint32_t time ;
int16_t temp ;
uint16_t lux ;
uint8_t volt ;
} theo_v2_t1_t ;
typedef struct {
uint32_t time ;
int16_t temp ;
uint16_t hum ;
uint8_t volt ;
} theo_v2_t2_t ;
2019-03-26 17:26:50 +00:00
theo_v2_t1_t * rfsns_theo_v2_t1 = nullptr ;
theo_v2_t2_t * rfsns_theo_v2_t2 = nullptr ;
2018-12-17 17:06:19 +00:00
void RfSnsInitTheoV2 ( void )
{
rfsns_theo_v2_t1 = ( theo_v2_t1_t * ) malloc ( RFSNS_THEOV2_MAX_CHANNEL * sizeof ( theo_v2_t1_t ) ) ;
rfsns_theo_v2_t2 = ( theo_v2_t2_t * ) malloc ( RFSNS_THEOV2_MAX_CHANNEL * sizeof ( theo_v2_t2_t ) ) ;
rfsns_any_sensor + + ;
}
2018-12-11 13:24:52 +00:00
2018-12-17 17:06:19 +00:00
void RfSnsAnalyzeTheov2 ( void )
2018-12-11 13:24:52 +00:00
{
2018-12-17 17:06:19 +00:00
if ( rfsns_raw_signal - > Number ! = RFSNS_THEOV2_PULSECOUNT ) { return ; }
2018-12-11 13:24:52 +00:00
2019-01-28 13:08:33 +00:00
uint8_t Checksum ; // 8 bits Checksum over following bytes
uint8_t Channel ; // 3 bits channel
uint8_t Type ; // 5 bits type
uint8_t Voltage ; // 8 bits Vcc like 45 = 4.5V, bit 8 is batt low
2018-12-11 13:24:52 +00:00
int Payload1 ; // 16 bits
int Payload2 ; // 16 bits
2019-01-28 13:08:33 +00:00
uint8_t b , bytes , bits , id ;
2018-12-11 13:24:52 +00:00
2019-01-28 13:08:33 +00:00
uint8_t idx = 3 ;
uint8_t chksum = 0 ;
2018-12-11 13:24:52 +00:00
for ( bytes = 0 ; bytes < 7 ; bytes + + ) {
b = 0 ;
for ( bits = 0 ; bits < = 7 ; bits + + )
{
2018-12-17 17:06:19 +00:00
if ( ( rfsns_raw_signal - > Pulses [ idx ] * rfsns_raw_signal - > Multiply ) > RFSNS_THEOV2_RF_PULSE_MID ) {
2018-12-11 13:24:52 +00:00
b | = 1 < < bits ;
}
idx + = 2 ;
}
if ( bytes > 0 ) { chksum + = b ; } // bereken checksum
switch ( bytes ) {
case 0 :
Checksum = b ;
break ;
case 1 :
id = b ;
Channel = b & 0x7 ;
Type = ( b > > 3 ) & 0x1f ;
break ;
case 2 :
Voltage = b ;
break ;
case 3 :
Payload1 = b ;
break ;
case 4 :
Payload1 = ( b < < 8 ) | Payload1 ;
break ;
case 5 :
Payload2 = b ;
break ;
case 6 :
Payload2 = ( b < < 8 ) | Payload2 ;
break ;
}
}
2018-12-17 17:06:19 +00:00
if ( Checksum ! = chksum ) { return ; }
if ( ( Channel = = 0 ) | | ( Channel > RFSNS_THEOV2_MAX_CHANNEL ) ) { return ; }
Channel - - ;
2018-12-11 13:24:52 +00:00
2018-12-17 17:06:19 +00:00
rfsns_raw_signal - > Repeats = 1 ; // het is een herhalend signaal. Bij ontvangst herhalingen onderdukken
2018-12-11 13:24:52 +00:00
int Payload3 = Voltage & 0x3f ;
switch ( Type ) {
case 1 : // Temp / Lux
rfsns_theo_v2_t1 [ Channel ] . time = LocalTime ( ) ;
rfsns_theo_v2_t1 [ Channel ] . volt = Payload3 ;
rfsns_theo_v2_t1 [ Channel ] . temp = Payload1 ;
rfsns_theo_v2_t1 [ Channel ] . lux = Payload2 ;
break ;
case 2 : // Temp / Hum
rfsns_theo_v2_t2 [ Channel ] . time = LocalTime ( ) ;
rfsns_theo_v2_t2 [ Channel ] . volt = Payload3 ;
rfsns_theo_v2_t2 [ Channel ] . temp = Payload1 ;
rfsns_theo_v2_t2 [ Channel ] . hum = Payload2 ;
break ;
}
2020-11-06 16:09:13 +00:00
AddLog_P ( LOG_LEVEL_DEBUG , PSTR ( " RFS: TheoV2, ChkCalc %d, Chksum %d, id %d, Type %d, Ch %d, Volt %d, BattLo %d, Pld1 %d, Pld2 %d " ) ,
2018-12-11 13:24:52 +00:00
chksum , Checksum , id , Type , Channel + 1 , Payload3 , ( Voltage & 0x80 ) > > 7 , Payload1 , Payload2 ) ;
}
2018-12-17 17:06:19 +00:00
void RfSnsTheoV2Show ( bool json )
2018-12-11 13:24:52 +00:00
{
bool sensor_once = false ;
2019-06-30 15:44:36 +01:00
for ( uint32_t i = 0 ; i < RFSNS_THEOV2_MAX_CHANNEL ; i + + ) {
2018-12-11 13:24:52 +00:00
if ( rfsns_theo_v2_t1 [ i ] . time ) {
char sensor [ 10 ] ;
snprintf_P ( sensor , sizeof ( sensor ) , PSTR ( " TV2T1C%d " ) , i + 1 ) ;
2018-12-21 15:17:06 +00:00
char voltage [ 33 ] ;
2018-12-11 13:24:52 +00:00
dtostrfd ( ( float ) rfsns_theo_v2_t1 [ i ] . volt / 10 , 1 , voltage ) ;
if ( rfsns_theo_v2_t1 [ i ] . time < LocalTime ( ) - RFSNS_VALID_WINDOW ) {
if ( json ) {
2019-03-23 16:57:31 +00:00
ResponseAppend_P ( PSTR ( " , \" %s \" :{ \" " D_JSON_RFRECEIVED " \" : \" %s \" , \" " D_JSON_VOLTAGE " \" :%s} " ) ,
sensor , GetDT ( rfsns_theo_v2_t1 [ i ] . time ) . c_str ( ) , voltage ) ;
2018-12-11 13:24:52 +00:00
}
} else {
2018-12-21 15:17:06 +00:00
char temperature [ 33 ] ;
2018-12-11 13:24:52 +00:00
dtostrfd ( ConvertTemp ( ( float ) rfsns_theo_v2_t1 [ i ] . temp / 100 ) , Settings . flag2 . temperature_resolution , temperature ) ;
if ( json ) {
2019-03-23 16:57:31 +00:00
ResponseAppend_P ( PSTR ( " , \" %s \" :{ \" " D_JSON_TEMPERATURE " \" :%s, \" " D_JSON_ILLUMINANCE " \" :%d, \" " D_JSON_VOLTAGE " \" :%s} " ) ,
sensor , temperature , rfsns_theo_v2_t1 [ i ] . lux , voltage ) ;
2018-12-11 13:24:52 +00:00
# ifdef USE_DOMOTICZ
2020-10-29 12:37:09 +00:00
if ( ( 0 = = TasmotaGlobal . tele_period ) & & ! sensor_once ) {
2018-12-11 13:24:52 +00:00
DomoticzSensor ( DZ_TEMP , temperature ) ;
DomoticzSensor ( DZ_ILLUMINANCE , rfsns_theo_v2_t1 [ i ] . lux ) ;
sensor_once = true ;
}
# endif // USE_DOMOTICZ
# ifdef USE_WEBSERVER
} else {
2019-03-19 16:31:43 +00:00
WSContentSend_PD ( HTTP_SNS_TEMP , sensor , temperature , TempUnit ( ) ) ;
WSContentSend_PD ( HTTP_SNS_ILLUMINANCE , sensor , rfsns_theo_v2_t1 [ i ] . lux ) ;
2018-12-11 13:24:52 +00:00
# endif // USE_WEBSERVER
}
}
}
}
sensor_once = false ;
2019-06-30 15:44:36 +01:00
for ( uint32_t i = 0 ; i < RFSNS_THEOV2_MAX_CHANNEL ; i + + ) {
2018-12-11 13:24:52 +00:00
if ( rfsns_theo_v2_t2 [ i ] . time ) {
char sensor [ 10 ] ;
snprintf_P ( sensor , sizeof ( sensor ) , PSTR ( " TV2T2C%d " ) , i + 1 ) ;
2018-12-21 15:17:06 +00:00
char voltage [ 33 ] ;
2018-12-11 13:24:52 +00:00
dtostrfd ( ( float ) rfsns_theo_v2_t2 [ i ] . volt / 10 , 1 , voltage ) ;
if ( rfsns_theo_v2_t2 [ i ] . time < LocalTime ( ) - RFSNS_VALID_WINDOW ) {
if ( json ) {
2019-03-23 16:57:31 +00:00
ResponseAppend_P ( PSTR ( " , \" %s \" :{ \" " D_JSON_RFRECEIVED " \" : \" %s \" , \" " D_JSON_VOLTAGE " \" :%s} " ) ,
sensor , GetDT ( rfsns_theo_v2_t2 [ i ] . time ) . c_str ( ) , voltage ) ;
2018-12-11 13:24:52 +00:00
}
} else {
float temp = ConvertTemp ( ( float ) rfsns_theo_v2_t2 [ i ] . temp / 100 ) ;
2019-04-15 17:12:42 +01:00
float humi = ConvertHumidity ( ( float ) rfsns_theo_v2_t2 [ i ] . hum / 100 ) ;
2018-12-11 13:24:52 +00:00
if ( json ) {
2020-03-17 15:29:59 +00:00
ResponseAppend_P ( PSTR ( " , \" %s \" :{ " ) , sensor ) ;
ResponseAppendTHD ( temp , humi ) ;
ResponseAppend_P ( PSTR ( " , \" " D_JSON_VOLTAGE " \" :%s} " ) , voltage ) ;
2020-10-29 12:37:09 +00:00
if ( ( 0 = = TasmotaGlobal . tele_period ) & & ! sensor_once ) {
2018-12-11 13:24:52 +00:00
# ifdef USE_DOMOTICZ
2020-03-17 15:29:59 +00:00
DomoticzTempHumPressureSensor ( temp , humi ) ; //
2018-12-11 13:24:52 +00:00
# endif // USE_DOMOTICZ
# ifdef USE_KNX
KnxSensor ( KNX_TEMPERATURE , temp ) ;
KnxSensor ( KNX_HUMIDITY , humi ) ;
# endif // USE_KNX
sensor_once = true ;
}
# ifdef USE_WEBSERVER
} else {
2020-03-17 15:29:59 +00:00
WSContentSend_THD ( sensor , temp , humi ) ;
2018-12-11 13:24:52 +00:00
# endif // USE_WEBSERVER
}
}
}
}
}
# endif // USE_THEO_V2 ************************************************************************
# ifdef USE_ALECTO_V2
/*********************************************************************************************\
* Alecto V2 protocol
* Dit protocol zorgt voor ontvangst van Alecto weerstation buitensensoren
*
* Auteur : Nodo - team ( Martinus van den Broek ) www . nodo - domotica . nl
* Support ACH2010 en code optimalisatie door forumlid : Arendst
* Support : www . nodo - domotica . nl
* Datum : 25 Jan 2013
* Versie : 1.3
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Technische informatie :
* DKW2012 Message Format : ( 11 Bytes , 88 bits ) :
* AAAAAAAA AAAABBBB BBBB__CC CCCCCCCC DDDDDDDD EEEEEEEE FFFFFFFF GGGGGGGG GGGGGGGG HHHHHHHH IIIIIIII
* Temperature Humidity Windspd_ Windgust Rain____ ________ Winddir Checksum
* A = start / unknown , first 8 bits are always 11111111
* B = Rolling code
* C = Temperature ( 10 bit value with - 400 base )
* D = Humidity
* E = windspeed ( * 0.3 m / s , correction for webapp = 3600 / 1000 * 0.3 * 100 = 108 ) )
* F = windgust ( * 0.3 m / s , correction for webapp = 3600 / 1000 * 0.3 * 100 = 108 ) )
* G = Rain ( * 0.3 mm )
* H = winddirection ( 0 = north , 4 = east , 8 = south 12 = west )
* I = Checksum , calculation is still under investigation
*
* WS3000 and ACH2010 systems have no winddirection , message format is 8 bit shorter
* Message Format : ( 10 Bytes , 80 bits ) :
* AAAAAAAA AAAABBBB BBBB__CC CCCCCCCC DDDDDDDD EEEEEEEE FFFFFFFF GGGGGGGG GGGGGGGG HHHHHHHH
* Temperature Humidity Windspd_ Windgust Rain____ ________ Checksum
*
* DCF Time Message Format : ( NOT DECODED ! )
* AAAAAAAA BBBBCCCC DDDDDDDD EFFFFFFF GGGGGGGG HHHHHHHH IIIIIIII JJJJJJJJ KKKKKKKK LLLLLLLL MMMMMMMM
* 11 Hours Minutes Seconds Year Month Day ? Checksum
* B = 11 = DCF
* C = ?
* D = ?
* E = ?
* F = Hours BCD format ( 7 bits only for this byte , MSB could be ' 1 ' )
* G = Minutes BCD format
* H = Seconds BCD format
* I = Year BCD format ( only two digits ! )
* J = Month BCD format
* K = Day BCD format
* L = ?
* M = Checksum
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
# define RFSNS_DKW2012_PULSECOUNT 176
# define RFSNS_ACH2010_MIN_PULSECOUNT 160 // reduce this value (144?) in case of bad reception
# define RFSNS_ACH2010_MAX_PULSECOUNT 160
2018-12-11 17:00:12 +00:00
# define D_ALECTOV2 "AlectoV2"
const char kAlectoV2Directions [ ] PROGMEM = D_TX20_NORTH " | "
D_TX20_NORTH D_TX20_NORTH D_TX20_EAST " | "
D_TX20_NORTH D_TX20_EAST " | "
D_TX20_EAST D_TX20_NORTH D_TX20_EAST " | "
D_TX20_EAST " | "
D_TX20_EAST D_TX20_SOUTH D_TX20_EAST " | "
D_TX20_SOUTH D_TX20_EAST " | "
D_TX20_SOUTH D_TX20_SOUTH D_TX20_EAST " | "
D_TX20_SOUTH " | "
D_TX20_SOUTH D_TX20_SOUTH D_TX20_WEST " | "
D_TX20_SOUTH D_TX20_WEST " | "
D_TX20_WEST D_TX20_SOUTH D_TX20_WEST " | "
D_TX20_WEST " | "
D_TX20_WEST D_TX20_NORTH D_TX20_WEST " | "
D_TX20_NORTH D_TX20_WEST " | "
D_TX20_NORTH D_TX20_NORTH D_TX20_WEST ;
2018-12-11 13:24:52 +00:00
typedef struct {
uint32_t time ;
float temp ;
float rain ;
float wind ;
float gust ;
uint8_t type ;
uint8_t humi ;
uint8_t wdir ;
} alecto_v2_t ;
2019-03-26 17:26:50 +00:00
alecto_v2_t * rfsns_alecto_v2 = nullptr ;
2018-12-11 13:24:52 +00:00
uint16_t rfsns_alecto_rain_base = 0 ;
2018-12-17 17:06:19 +00:00
void RfSnsInitAlectoV2 ( void )
{
rfsns_alecto_v2 = ( alecto_v2_t * ) malloc ( sizeof ( alecto_v2_t ) ) ;
rfsns_any_sensor + + ;
}
void RfSnsAnalyzeAlectov2 ( )
2018-12-11 13:24:52 +00:00
{
2018-12-17 17:06:19 +00:00
if ( ! ( ( ( rfsns_raw_signal - > Number > = RFSNS_ACH2010_MIN_PULSECOUNT ) & &
( rfsns_raw_signal - > Number < = RFSNS_ACH2010_MAX_PULSECOUNT ) ) | | ( rfsns_raw_signal - > Number = = RFSNS_DKW2012_PULSECOUNT ) ) ) { return ; }
2018-12-11 13:24:52 +00:00
2019-01-28 13:08:33 +00:00
uint8_t c = 0 ;
uint8_t rfbit ;
uint8_t data [ 9 ] = { 0 } ;
uint8_t msgtype = 0 ;
uint8_t rc = 0 ;
2018-12-11 13:24:52 +00:00
int temp ;
2019-01-28 13:08:33 +00:00
uint8_t checksum = 0 ;
uint8_t checksumcalc = 0 ;
uint8_t maxidx = 8 ;
2018-12-11 13:24:52 +00:00
unsigned long atime ;
float factor ;
char buf1 [ 16 ] ;
2018-12-17 17:06:19 +00:00
if ( rfsns_raw_signal - > Number > RFSNS_ACH2010_MAX_PULSECOUNT ) { maxidx = 9 ; }
2018-12-11 13:24:52 +00:00
// Get message back to front as the header is almost never received complete for ACH2010
2019-01-28 13:08:33 +00:00
uint8_t idx = maxidx ;
2019-06-30 15:44:36 +01:00
for ( uint32_t x = rfsns_raw_signal - > Number ; x > 0 ; x = x - 2 ) {
2018-12-17 17:06:19 +00:00
if ( rfsns_raw_signal - > Pulses [ x - 1 ] * rfsns_raw_signal - > Multiply < 0x300 ) {
2018-12-11 13:24:52 +00:00
rfbit = 0x80 ;
} else {
rfbit = 0 ;
}
data [ idx ] = ( data [ idx ] > > 1 ) | rfbit ;
c + + ;
if ( c = = 8 ) {
if ( idx = = 0 ) { break ; }
c = 0 ;
idx - - ;
}
}
checksum = data [ maxidx ] ;
checksumcalc = RfSnsAlectoCRC8 ( data , maxidx ) ;
msgtype = ( data [ 0 ] > > 4 ) & 0xf ;
rc = ( data [ 0 ] < < 4 ) | ( data [ 1 ] > > 4 ) ;
2018-12-17 17:06:19 +00:00
if ( checksum ! = checksumcalc ) { return ; }
if ( ( msgtype ! = 10 ) & & ( msgtype ! = 5 ) ) { return ; }
2018-12-11 13:24:52 +00:00
2018-12-17 17:06:19 +00:00
rfsns_raw_signal - > Repeats = 1 ; // het is een herhalend signaal. Bij ontvangst herhalingen onderdukken
2018-12-11 13:24:52 +00:00
2018-12-11 17:00:12 +00:00
// Test set
2018-12-17 17:06:19 +00:00
// rfsns_raw_signal->Number = RFSNS_DKW2012_PULSECOUNT; // DKW2012
2018-12-11 17:00:12 +00:00
// data[8] = 11; // WSW
2018-12-11 13:24:52 +00:00
factor = 1.22 ; // (1.08)
2018-12-17 17:06:19 +00:00
// atime = rfsns_raw_signal->Time - rfsns_alecto_time;
2018-12-11 13:24:52 +00:00
// if ((atime > 10000) && (atime < 60000)) factor = (float)60000 / atime;
2018-12-17 17:06:19 +00:00
// rfsns_alecto_time = rfsns_raw_signal->Time;
2018-12-11 13:24:52 +00:00
// Serial.printf("atime %d, rfsns_alecto_time %d\n", atime, rfsns_alecto_time);
2018-12-17 17:06:19 +00:00
rfsns_alecto_v2 - > time = LocalTime ( ) ;
rfsns_alecto_v2 - > type = ( RFSNS_DKW2012_PULSECOUNT = = rfsns_raw_signal - > Number ) ;
rfsns_alecto_v2 - > temp = ( float ) ( ( ( data [ 1 ] & 0x3 ) * 256 + data [ 2 ] ) - 400 ) / 10 ;
rfsns_alecto_v2 - > humi = data [ 3 ] ;
2018-12-11 13:24:52 +00:00
uint16_t rain = ( data [ 6 ] * 256 ) + data [ 7 ] ;
// check if rain unit has been reset!
if ( rain < rfsns_alecto_rain_base ) { rfsns_alecto_rain_base = rain ; }
if ( rfsns_alecto_rain_base > 0 ) {
2018-12-17 17:06:19 +00:00
rfsns_alecto_v2 - > rain + = ( ( float ) rain - rfsns_alecto_rain_base ) * 0.30 ;
2018-12-11 13:24:52 +00:00
}
rfsns_alecto_rain_base = rain ;
2018-12-17 17:06:19 +00:00
rfsns_alecto_v2 - > wind = ( float ) data [ 4 ] * factor ;
rfsns_alecto_v2 - > gust = ( float ) data [ 5 ] * factor ;
if ( rfsns_alecto_v2 - > type ) {
rfsns_alecto_v2 - > wdir = data [ 8 ] & 0xf ;
2018-12-11 13:24:52 +00:00
}
2020-11-06 16:09:13 +00:00
AddLog_P ( LOG_LEVEL_DEBUG , PSTR ( " RFS: " D_ALECTOV2 " , ChkCalc %d, Chksum %d, rc %d, Temp %d, Hum %d, Rain %d, Wind %d, Gust %d, Dir %d, Factor %s " ) ,
2018-12-11 13:24:52 +00:00
checksumcalc , checksum , rc , ( ( data [ 1 ] & 0x3 ) * 256 + data [ 2 ] ) - 400 , data [ 3 ] , ( data [ 6 ] * 256 ) + data [ 7 ] , data [ 4 ] , data [ 5 ] , data [ 8 ] & 0xf , dtostrfd ( factor , 3 , buf1 ) ) ;
}
void RfSnsAlectoResetRain ( void )
{
if ( ( RtcTime . hour = = 0 ) & & ( RtcTime . minute = = 0 ) & & ( RtcTime . second = = 5 ) ) {
2018-12-17 17:06:19 +00:00
rfsns_alecto_v2 - > rain = 0 ; // Reset Rain
2018-12-11 13:24:52 +00:00
}
}
/*********************************************************************************************\
* Calculates CRC - 8 checksum
* reference http : //lucsmall.com/2012/04/29/weather-station-hacking-part-2/
* http : //lucsmall.com/2012/04/30/weather-station-hacking-part-3/
* https : //github.com/lucsmall/WH2-Weather-Sensor-Library-for-Arduino/blob/master/WeatherSensorWH2.cpp
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
uint8_t RfSnsAlectoCRC8 ( uint8_t * addr , uint8_t len )
{
uint8_t crc = 0 ;
while ( len - - ) {
uint8_t inbyte = * addr + + ;
2019-06-30 15:44:36 +01:00
for ( uint32_t i = 8 ; i ; i - - ) {
2018-12-11 13:24:52 +00:00
uint8_t mix = ( crc ^ inbyte ) & 0x80 ;
crc < < = 1 ;
if ( mix ) { crc ^ = 0x31 ; }
inbyte < < = 1 ;
}
}
return crc ;
}
2018-12-11 17:00:12 +00:00
# ifdef USE_WEBSERVER
2019-03-19 16:31:43 +00:00
const char HTTP_SNS_ALECTOV2 [ ] PROGMEM =
2018-12-11 17:00:12 +00:00
" {s} " D_ALECTOV2 " " D_RAIN " {m}%s " D_UNIT_MILLIMETER " {e} "
" {s} " D_ALECTOV2 " " D_TX20_WIND_SPEED " {m}%s " D_UNIT_KILOMETER_PER_HOUR " {e} "
" {s} " D_ALECTOV2 " " D_TX20_WIND_SPEED_MAX " {m}%s " D_UNIT_KILOMETER_PER_HOUR " {e} " ;
2019-03-19 16:31:43 +00:00
const char HTTP_SNS_ALECTOV2_WDIR [ ] PROGMEM =
2018-12-11 17:00:12 +00:00
" {s} " D_ALECTOV2 " " D_TX20_WIND_DIRECTION " {m}%s{e} " ;
# endif
2018-12-17 17:06:19 +00:00
void RfSnsAlectoV2Show ( bool json )
2018-12-11 13:24:52 +00:00
{
2018-12-17 17:06:19 +00:00
if ( rfsns_alecto_v2 - > time ) {
if ( rfsns_alecto_v2 - > time < LocalTime ( ) - RFSNS_VALID_WINDOW ) {
2018-12-11 13:24:52 +00:00
if ( json ) {
2019-03-23 16:57:31 +00:00
ResponseAppend_P ( PSTR ( " , \" " D_ALECTOV2 " \" :{ \" " D_JSON_RFRECEIVED " \" : \" %s \" } " ) , GetDT ( rfsns_alecto_v2 - > time ) . c_str ( ) ) ;
2018-12-11 13:24:52 +00:00
}
} else {
2018-12-17 17:06:19 +00:00
float temp = ConvertTemp ( rfsns_alecto_v2 - > temp ) ;
2019-04-15 17:12:42 +01:00
float humi = ConvertHumidity ( ( float ) rfsns_alecto_v2 - > humi ) ;
2020-03-17 15:29:59 +00:00
2018-12-21 15:17:06 +00:00
char rain [ 33 ] ;
2018-12-17 17:06:19 +00:00
dtostrfd ( rfsns_alecto_v2 - > rain , 2 , rain ) ;
2018-12-21 15:17:06 +00:00
char wind [ 33 ] ;
2018-12-17 17:06:19 +00:00
dtostrfd ( rfsns_alecto_v2 - > wind , 2 , wind ) ;
2018-12-21 15:17:06 +00:00
char gust [ 33 ] ;
2018-12-17 17:06:19 +00:00
dtostrfd ( rfsns_alecto_v2 - > gust , 2 , gust ) ;
2018-12-11 17:00:12 +00:00
char wdir [ 4 ] ;
char direction [ 20 ] ;
2018-12-17 17:06:19 +00:00
if ( rfsns_alecto_v2 - > type ) {
GetTextIndexed ( wdir , sizeof ( wdir ) , rfsns_alecto_v2 - > wdir , kAlectoV2Directions ) ;
2018-12-11 17:00:12 +00:00
snprintf_P ( direction , sizeof ( direction ) , PSTR ( " , \" Direction \" : \" %s \" " ) , wdir ) ;
}
2018-12-11 13:24:52 +00:00
if ( json ) {
2020-03-17 15:29:59 +00:00
ResponseAppend_P ( PSTR ( " , \" " D_ALECTOV2 " \" :{ " ) ) ;
ResponseAppendTHD ( temp , humi ) ;
ResponseAppend_P ( PSTR ( " , \" Rain \" :%s, \" Wind \" :%s, \" Gust \" :%s%s} " ) , rain , wind , gust , ( rfsns_alecto_v2 - > type ) ? direction : " " ) ;
2020-10-29 12:37:09 +00:00
if ( 0 = = TasmotaGlobal . tele_period ) {
2018-12-11 13:24:52 +00:00
# ifdef USE_DOMOTICZ
2018-12-17 17:06:19 +00:00
// Use a rules to send data to Domoticz where also a local BMP280 is connected:
// on tele-alectov2#temperature do var1 %value% endon on tele-alectov2#humidity do var2 %value% endon on tele-bmp280#pressure do publish domoticz/in {"idx":68,"svalue":"%var1%;%var2%;0;%value%;0"} endon
// on tele-alectov2#wind do var1 %value% endon on tele-alectov2#gust do publish domoticz/in {"idx":69,"svalue":"0;N;%var1%;%value%;22;24"} endon"}
// on tele-alectov2#rain do publish domoticz/in {"idx":70,"svalue":"0;%value%"} endon
2018-12-11 13:24:52 +00:00
# endif // USE_DOMOTICZ
}
# ifdef USE_WEBSERVER
} else {
2020-03-17 15:29:59 +00:00
WSContentSend_THD ( D_ALECTOV2 , temp , humi ) ;
2019-03-19 16:31:43 +00:00
WSContentSend_PD ( HTTP_SNS_ALECTOV2 , rain , wind , gust ) ;
2018-12-17 17:06:19 +00:00
if ( rfsns_alecto_v2 - > type ) {
2019-03-19 16:31:43 +00:00
WSContentSend_PD ( HTTP_SNS_ALECTOV2_WDIR , wdir ) ;
2018-12-11 17:00:12 +00:00
}
2018-12-11 13:24:52 +00:00
# endif // USE_WEBSERVER
}
}
}
}
# endif // USE_ALECTO_V2 **********************************************************************
void RfSnsInit ( void )
{
2018-12-17 17:06:19 +00:00
rfsns_raw_signal = ( raw_signal_t * ) ( malloc ( sizeof ( raw_signal_t ) ) ) ;
if ( rfsns_raw_signal ) {
2018-12-21 15:17:06 +00:00
memset ( rfsns_raw_signal , 0 , sizeof ( raw_signal_t ) ) ; // Init defaults to 0
2018-12-17 17:06:19 +00:00
# ifdef USE_THEO_V2
RfSnsInitTheoV2 ( ) ;
# endif
# ifdef USE_ALECTO_V2
RfSnsInitAlectoV2 ( ) ;
# endif
if ( rfsns_any_sensor ) {
2020-04-27 15:47:29 +01:00
rfsns_rf_bit = digitalPinToBitMask ( Pin ( GPIO_RF_SENSOR ) ) ;
rfsns_rf_port = digitalPinToPort ( Pin ( GPIO_RF_SENSOR ) ) ;
pinMode ( Pin ( GPIO_RF_SENSOR ) , INPUT ) ;
2018-12-17 17:06:19 +00:00
} else {
free ( rfsns_raw_signal ) ;
2019-03-26 17:26:50 +00:00
rfsns_raw_signal = nullptr ;
2018-12-17 17:06:19 +00:00
}
}
2018-12-11 13:24:52 +00:00
}
void RfSnsAnalyzeRawSignal ( void )
{
2020-11-06 16:09:13 +00:00
AddLog_P ( LOG_LEVEL_DEBUG , PSTR ( " RFS: Pulses %d " ) , ( int ) rfsns_raw_signal - > Number ) ;
2018-12-11 13:24:52 +00:00
# ifdef USE_THEO_V2
RfSnsAnalyzeTheov2 ( ) ;
# endif
# ifdef USE_ALECTO_V2
RfSnsAnalyzeAlectov2 ( ) ;
# endif
}
void RfSnsEverySecond ( void )
{
# ifdef USE_ALECTO_V2
RfSnsAlectoResetRain ( ) ;
# endif
}
2018-12-17 17:06:19 +00:00
void RfSnsShow ( bool json )
2018-12-11 13:24:52 +00:00
{
# ifdef USE_THEO_V2
RfSnsTheoV2Show ( json ) ;
# endif
# ifdef USE_ALECTO_V2
RfSnsAlectoV2Show ( json ) ;
# endif
}
/*********************************************************************************************\
* Interface
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2019-01-28 13:08:33 +00:00
bool Xsns37 ( uint8_t function )
2018-12-11 13:24:52 +00:00
{
2018-12-17 17:06:19 +00:00
bool result = false ;
2018-12-11 13:24:52 +00:00
2020-04-27 15:47:29 +01:00
if ( PinUsed ( GPIO_RF_SENSOR ) & & ( FUNC_INIT = = function ) ) {
2018-12-17 17:06:19 +00:00
RfSnsInit ( ) ;
}
else if ( rfsns_raw_signal ) {
2018-12-11 13:24:52 +00:00
switch ( function ) {
case FUNC_LOOP :
if ( ( * portInputRegister ( rfsns_rf_port ) & rfsns_rf_bit ) = = rfsns_rf_bit ) {
2020-04-27 15:47:29 +01:00
if ( RfSnsFetchSignal ( Pin ( GPIO_RF_SENSOR ) , HIGH ) ) {
2018-12-11 13:24:52 +00:00
RfSnsAnalyzeRawSignal ( ) ;
}
}
2020-10-29 15:16:34 +00:00
TasmotaGlobal . sleep = 0 ;
2018-12-11 13:24:52 +00:00
break ;
case FUNC_EVERY_SECOND :
RfSnsEverySecond ( ) ;
break ;
case FUNC_JSON_APPEND :
RfSnsShow ( 1 ) ;
break ;
# ifdef USE_WEBSERVER
2019-03-19 16:31:43 +00:00
case FUNC_WEB_SENSOR :
2018-12-11 13:24:52 +00:00
RfSnsShow ( 0 ) ;
break ;
# endif // USE_WEBSERVER
}
}
return result ;
}
# endif // USE_RF_SENSOR