262 lines
14 KiB
C
262 lines
14 KiB
C
//#######################################################################################################
|
|
//## This Plugin is only for use with the RFLink software package ##
|
|
//## Plugin-30 AlectoV1 ##
|
|
//#######################################################################################################
|
|
/*********************************************************************************************\
|
|
* This plugin takes care of decoding the protocol used for outdoor sensors of the Alecto weather stations
|
|
* following protocol version 1
|
|
* This Plugin works at least with: Alecto WS3500, Silvercrest, Otio sht-10, Otio sht-20
|
|
* Auriol H13726, Ventus WS155, Hama EWS 1500, Meteoscan W155/W160
|
|
* Alecto WS4500, Ventus W044, Balance RF-WS105
|
|
*
|
|
* Author : StuntTeam
|
|
* Support : http://sourceforge.net/projects/rflink/
|
|
* License : This code is free for use in any open source project when this header is included.
|
|
* Usage of any parts of this code in a commercial application is prohibited!
|
|
*********************************************************************************************
|
|
* Technische informatie:
|
|
* Message Format: (9 nibbles, 36 bits):
|
|
*
|
|
* Format for Temperature Humidity
|
|
* AAAAAAAA BBBB CCCC CCCC CCCC DDDDDDDD EEEE
|
|
* 00110110 1000 1011 0111 0000 00011100 0110
|
|
* RC Type Temperature___ Humidity Checksum
|
|
* A = Rolling Code (includes channel number on low 2 bits of nibble1 (10=ch1 01=ch2 11=ch3) )
|
|
* B = Message type (xyyx = temp/humidity if yy <> '11') => only accepting yy = 00 for negative temperatures for now
|
|
* 4 bits: bit 0 = battery state 0=OK, 1= below 2.6 volt
|
|
* bit 1&2 = 00/01/10 = temp/hum is transmitted, 11=non temp is transmitted
|
|
* bit 3 = 0=scheduled transmission, 1=requested transmission (button press)
|
|
* C = Temperature (two's complement)
|
|
* D = Humidity BCD format
|
|
* E = Checksum
|
|
*
|
|
* Sample:
|
|
* 20;F4;DEBUG;Pulses=74;Pulses(uSec)=450,1900,350,1900,350,3975,350,3975,350,1900,350,3975,350,3975,350,1900,350,3975,350,1900,350,1900,350,1900,350,3975,350,1900,350,3975,350,3975,350,1900,350,3975,350,3975,350,3975,350,1900,350,1900,350,1900,350,1900,350,1900,350,1900,350,1900,350,3975,350,3975,350,3975,350,1900,350,1900,350,1900,350,3975,350,3975,350,2025,350;
|
|
* 20;F5;Alecto V1;ID=006c;TEMP=00ed;HUM=38;
|
|
*
|
|
* Format for Rain
|
|
* AAAAAAAA BBBB CCCC DDDD DDDD DDDD DDDD EEEE
|
|
* 01100001 0110 1100 1010 1011 0010 0000 0010
|
|
* RC Type Rain Checksum
|
|
* A = Rolling Code
|
|
* B = Message type (xyyx = NON temp/humidity data if yy = '11')
|
|
* C = fixed to 1100
|
|
* D = Rain (bitvalue * 0.25 mm)
|
|
* E = Checksum
|
|
*
|
|
* Sample:
|
|
* 20;A8;DEBUG;Pulses=74;Pulses(uSec)=550,1925,425,4100,425,4100,425,1975,425,1975,425,1975,425,1975,425,4100,400,2000,425,4100,425,4100,425,1975,425,4100,425,4100,425,1975,425,1975,425,4100,425,1975,425,4100,400,1975,425,4100,425,1975,425,4100,425,4100,425,1975,450,1975,425,4100,450,1950,450,1950,450,1950,425,1975,450,1950,450,1950,475,1925,500,4025,475,1950,475;
|
|
* 20;A9;Alecto V1;ID=0086;RAIN=04d5;
|
|
*
|
|
* Format for Windspeed
|
|
* AAAAAAAA BBBB CCCC CCCC CCCC DDDDDDDD EEEE
|
|
* RC Type Windspd Checksum
|
|
* A = Rolling Code
|
|
* B = Message type (xyyx = NON temp/humidity data if yy = '11')
|
|
* C = Fixed to 1000 0000 0000
|
|
* D = Windspeed (bitvalue * 0.2 m/s, correction for webapp = 3600/1000 * 0.2 * 100 = 72)
|
|
* E = Checksum
|
|
*
|
|
* Format for Winddirection & Windgust
|
|
* AAAAAAAA BBBB CCCD DDDD DDDD EEEEEEEE FFFF
|
|
01110000 0000 1111 1011 0000 00000000 0101
|
|
* RC Type Winddir Windgust Checksum
|
|
* A = Rolling Code
|
|
* B = Message type (xyyx = NON temp/humidity data if yy = '11')
|
|
* C = Fixed to 111x
|
|
* D = Wind direction 0-511 in 0.7 degree steps?
|
|
* E = Windgust (bitvalue * 0.2 m/s, correction for webapp = 3600/1000 * 0.2 * 100 = 72)
|
|
* F = Checksum
|
|
*
|
|
* Sample:
|
|
* 20;53;DEBUG;Pulses=74;Pulses(uSec)=425,3800,350,1825,350,1825,325,1825,350,1825,325,3800,350,3800,350,1825,325,3800,350,1825,325,1800,350,1825,350,1825,325,1825,325,3800,325,1825,350,1800,350,1825,325,3825,325,3800,325,1825,325,1825,325,1800,325,1825,350,3800,325,1825,325,3800,350,1800,350,1800,350,3800,350,1825,325,1825,325,1825,325,1825,350,1825,325,1925,325;
|
|
\*********************************************************************************************/
|
|
#define WS3500_PULSECOUNT 74
|
|
|
|
#ifdef PLUGIN_030
|
|
boolean Plugin_030(byte function, char *string) {
|
|
if (RawSignal.Number != WS3500_PULSECOUNT) return false;
|
|
unsigned long bitstream=0L;
|
|
byte nibble0=0;
|
|
byte nibble1=0;
|
|
byte nibble2=0;
|
|
byte nibble3=0;
|
|
byte nibble4=0;
|
|
byte nibble5=0;
|
|
byte nibble6=0;
|
|
byte nibble7=0;
|
|
byte checksum=0;
|
|
int temperature=0;
|
|
byte humidity=0;
|
|
unsigned int rain=0;
|
|
byte windspeed=0;
|
|
byte windgust=0;
|
|
int winddirection=0;
|
|
byte checksumcalc = 0;
|
|
byte rc=0;
|
|
byte battery=0;
|
|
//==================================================================================
|
|
for(byte x=2; x<=64; x=x+2) {
|
|
if (RawSignal.Pulses[x+1]*RawSignal.Multiply > 700) return false; // in between pulses should be short
|
|
if (RawSignal.Pulses[x]*RawSignal.Multiply > 2560) {
|
|
bitstream = ((bitstream >> 1) |(0x1L << 31));
|
|
} else {
|
|
bitstream = (bitstream >> 1);
|
|
}
|
|
}
|
|
for(byte x=66; x<=72; x=x+2) {
|
|
if (RawSignal.Pulses[x]*RawSignal.Multiply > 2560) {
|
|
checksum = ((checksum >> 1) |(0x1L << 3));
|
|
} else {
|
|
checksum = (checksum >> 1);
|
|
}
|
|
}
|
|
//==================================================================================
|
|
if (bitstream == 0) return false; // Perform a sanity check
|
|
//==================================================================================
|
|
// Prevent repeating signals from showing up
|
|
//==================================================================================
|
|
if( (SignalHash!=SignalHashPrevious) || ((RepeatingTimer+1000<millis()) && (SignalCRC != bitstream)) || (SignalCRC != bitstream) ) {
|
|
// not seen the RF packet recently
|
|
SignalCRC=bitstream;
|
|
} else {
|
|
// already seen the RF packet recently
|
|
return true;
|
|
}
|
|
//==================================================================================
|
|
// Sort nibbles
|
|
nibble7 = (bitstream >> 28) & 0xf;
|
|
nibble6 = (bitstream >> 24) & 0xf;
|
|
nibble5 = (bitstream >> 20) & 0xf;
|
|
nibble4 = (bitstream >> 16) & 0xf;
|
|
nibble3 = (bitstream >> 12) & 0xf;
|
|
nibble2 = (bitstream >> 8) & 0xf;
|
|
nibble1 = (bitstream >> 4) & 0xf;
|
|
nibble0 = bitstream & 0xf;
|
|
//==================================================================================
|
|
// Perform checksum calculations, Alecto checksums are Rollover Checksums by design!
|
|
if ((nibble2 & 0x6) != 6) { // temperature packet
|
|
checksumcalc = (0xf - nibble0 - nibble1 - nibble2 - nibble3 - nibble4 - nibble5 - nibble6 - nibble7) & 0xf;
|
|
} else {
|
|
if ((nibble3 & 0x7) == 3) { // Rain packet
|
|
checksumcalc = (0x7 + nibble0 + nibble1 + nibble2 + nibble3 + nibble4 + nibble5 + nibble6 + nibble7) & 0xf;
|
|
} else { // Wind packet
|
|
checksumcalc = (0xf - nibble0 - nibble1 - nibble2 - nibble3 - nibble4 - nibble5 - nibble6 - nibble7) & 0xf;
|
|
}
|
|
}
|
|
if (checksum != checksumcalc) return false;
|
|
//==================================================================================
|
|
battery=(nibble2)&0x1; // get battery indicator
|
|
nibble2=(nibble2)&0x06; // prepare nibble to contain only the needed bits
|
|
nibble3=(nibble3)&0x07; // prepare nibble to contain only the needed bits
|
|
//==================================================================================
|
|
rc = bitstream & 0xff;
|
|
|
|
if ((nibble2) != 6) { // nibble 2 needs to be set to something other than 'x11x' to be a temperature packet
|
|
// Temperature packet
|
|
temperature = (bitstream >> 12) & 0xfff;
|
|
//fix 12 bit signed number conversion
|
|
if ((temperature & 0x800) == 0x800) {
|
|
if ((nibble2 & 0x6) != 0) return false; // reject alecto v4 on alecto v1... (causing high negative temperatures with valid checksums)
|
|
temperature=4096-temperature; // fix for minus temperatures
|
|
if (temperature > 0x258) return false; // temperature out of range ( > -60.0 degrees)
|
|
temperature=temperature | 0x8000; // turn highest bit on for minus values
|
|
} else {
|
|
if (temperature > 0x258) return false; // temperature out of range ( > 60.0 degrees)
|
|
}
|
|
humidity = (16 * nibble7) + nibble6;
|
|
if (humidity > 0x99) return false; // Humidity out of range, assume ALL data is bad?
|
|
//==================================================================================
|
|
// Output
|
|
// ----------------------------------
|
|
sprintf(pbuffer, "20;%02X;", PKSequenceNumber++); // Node and packet number
|
|
Serial.print( pbuffer );
|
|
// ----------------------------------
|
|
Serial.print(F("Alecto V1;")); // Label
|
|
sprintf(pbuffer, "ID=%02x%02x;", (rc &0x03), (rc &0xfc) ); // ID is split into channel number and rolling code
|
|
Serial.print( pbuffer );
|
|
sprintf(pbuffer, "TEMP=%04x;", temperature);
|
|
Serial.print( pbuffer );
|
|
if (humidity < 0x99) { // Some AlectoV1 devices actually lack the humidity sensor and always report 99%
|
|
sprintf(pbuffer, "HUM=%02x;", humidity); // Only report humidity when it is below 99%
|
|
Serial.print( pbuffer );
|
|
}
|
|
if (battery==0) {
|
|
Serial.print("BAT=OK;");
|
|
} else {
|
|
Serial.print("BAT=LOW;");
|
|
}
|
|
Serial.println();
|
|
//==================================================================================
|
|
RawSignal.Repeats=true; // suppress repeats of the same RF packet
|
|
RawSignal.Number=0;
|
|
return true;
|
|
} else {
|
|
if ((nibble3) == 3) { // Rain packet
|
|
rain = ((bitstream >> 16) & 0xffff);
|
|
//==================================================================================
|
|
// Output
|
|
// ----------------------------------
|
|
sprintf(pbuffer, "20;%02X;", PKSequenceNumber++); // Node and packet number
|
|
Serial.print( pbuffer );
|
|
// ----------------------------------
|
|
Serial.print(F("Alecto V1;")); // Label
|
|
sprintf(pbuffer, "ID=00%02x;", rc); // ID
|
|
Serial.print( pbuffer );
|
|
sprintf(pbuffer, "RAIN=%04x;", rain);
|
|
Serial.print( pbuffer );
|
|
Serial.println();
|
|
//==================================================================================
|
|
RawSignal.Repeats=true; // suppress repeats of the same RF packet
|
|
RawSignal.Number=0;
|
|
return true;
|
|
}
|
|
if ((nibble3) == 1) { // windspeed packet
|
|
windspeed = ((bitstream >> 24) & 0xff);
|
|
windspeed = windspeed*72;
|
|
//==================================================================================
|
|
// Output
|
|
// ----------------------------------
|
|
sprintf(pbuffer, "20;%02X;", PKSequenceNumber++); // Node and packet number
|
|
Serial.print( pbuffer );
|
|
// ----------------------------------
|
|
Serial.print(F("Alecto V1;")); // Label
|
|
sprintf(pbuffer, "ID=00%02x;", rc); // ID
|
|
Serial.print( pbuffer );
|
|
sprintf(pbuffer, "WINSP=%04x;", windspeed);
|
|
Serial.print( pbuffer );
|
|
Serial.println();
|
|
//==================================================================================
|
|
RawSignal.Repeats=true; // suppress repeats of the same RF packet
|
|
RawSignal.Number=0;
|
|
return true;
|
|
}
|
|
if ((nibble3) == 7) { // winddir packet
|
|
winddirection = ((bitstream >> 15) & 0x1ff) / 45; // ???
|
|
winddirection = winddirection & 0x0f;
|
|
windgust = ((bitstream >> 24) & 0xff);
|
|
windgust = windgust*72;
|
|
//==================================================================================
|
|
// Output
|
|
// ----------------------------------
|
|
sprintf(pbuffer, "20;%02X;", PKSequenceNumber++); // Node and packet number
|
|
Serial.print( pbuffer );
|
|
// ----------------------------------
|
|
Serial.print(F("Alecto V1;")); // Label
|
|
sprintf(pbuffer, "ID=00%02x;", rc); // ID
|
|
Serial.print( pbuffer );
|
|
sprintf(pbuffer, "WINDIR=%04d;", winddirection);
|
|
Serial.print( pbuffer );
|
|
sprintf(pbuffer, "WINGS=%04x;", windgust);
|
|
Serial.print( pbuffer );
|
|
Serial.println();
|
|
//==================================================================================
|
|
RawSignal.Repeats=true; // suppress repeats of the same RF packet
|
|
RawSignal.Number=0;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
#endif // PLUGIN_030
|