xsns_37_rfsensor.ino 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695
  1. /*
  2. xsns_37_rfsensor.ino - RF sensor receiver for Sonoff-Tasmota
  3. Copyright (C) 2018 Theo Arends
  4. This program is free software: you can redistribute it and/or modify
  5. it under the terms of the GNU General Public License as published by
  6. the Free Software Foundation, either version 3 of the License, or
  7. (at your option) any later version.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. GNU General Public License for more details.
  12. You should have received a copy of the GNU General Public License
  13. along with this program. If not, see <http://www.gnu.org/licenses/>.
  14. */
  15. #ifdef USE_RF_SENSOR
  16. /*********************************************************************************************\
  17. * RF receiver based on work by Paul Tonkes (www.nodo-domotica.nl)
  18. *
  19. * Supported 434MHz receiver is Aurel RX-4M50RR30SF
  20. * Supported 868MHz receiver is Aurel RX-AM8SF
  21. *
  22. * Connect one of above receivers with a 330 Ohm resistor to any GPIO
  23. *
  24. * USE_THEO_V2 Add support for 434MHz Theo V2 sensors as documented on https://sidweb.nl
  25. * USE_ALECTO_V2 Add support for 868MHz Alecto V2 sensors like ACH2010, WS3000 and DKW2012 weather stations
  26. \*********************************************************************************************/
  27. #define XSNS_37 37
  28. //#define USE_THEO_V2 // Add support for 434MHz Theo V2 sensors as documented on https://sidweb.nl
  29. //#define USE_ALECTO_V2 // Add support for 868MHz Alecto V2 sensors like ACH2010, WS3000 and DKW2012
  30. #define RFSNS_VALID_WINDOW 1800 // Number of seconds for sensor to respond (1800 = 30 minutes)
  31. #define RFSNS_LOOPS_PER_MILLI 1900 // (345 voor 16MHz ATMega) Voor 80MHz NodeMCU (ESP-12E). Getest met TheoV2 Protocol.
  32. #define RFSNS_RAW_BUFFER_SIZE 180 // (256) Maximum number of RF pulses that can be captured
  33. #define RFSNS_MIN_RAW_PULSES 112 // (16) =8 bits. Minimaal aantal ontvangen bits*2 alvorens cpu tijd wordt besteed aan decodering, etc.
  34. // Zet zo hoog mogelijk om CPU-tijd te sparen en minder 'onzin' te ontvangen.
  35. #define RFSNS_MIN_PULSE_LENGTH 300 // (50) Pulsen korter dan deze tijd uSec. worden als stoorpulsen beschouwd.
  36. #define RFSNS_RAWSIGNAL_SAMPLE 50 // Sample grootte / Resolutie in uSec waarmee ontvangen Rawsignalen pulsen worden opgeslagen
  37. #define RFSNS_SIGNAL_TIMEOUT 10 // Pulse timings in mSec. Beyond this value indicate end of message
  38. #define RFSNS_SIGNAL_REPEAT_TIME 500 // (500) Tijd in mSec. waarbinnen hetzelfde event niet nogmaals via RF mag binnenkomen. Onderdrukt ongewenste herhalingen van signaal
  39. typedef struct RawSignalStruct // Variabelen geplaatst in struct zodat deze later eenvoudig kunnen worden weggeschreven naar SDCard
  40. {
  41. int Number; // aantal bits, maal twee omdat iedere bit een mark en een space heeft.
  42. byte Repeats; // Aantal maal dat de pulsreeks verzonden moet worden bij een zendactie.
  43. byte Multiply; // Pulses[] * Multiply is de echte tijd van een puls in microseconden
  44. unsigned long Time; // Tijdstempel wanneer signaal is binnengekomen (millis())
  45. byte 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.
  46. // Om legacy redenen zit de eerste puls in element 1. Element 0 wordt dus niet gebruikt.
  47. } raw_signal_t;
  48. raw_signal_t *rfsns_raw_signal = NULL;
  49. uint8_t rfsns_rf_bit;
  50. uint8_t rfsns_rf_port;
  51. uint8_t rfsns_any_sensor = 0;
  52. /*********************************************************************************************\
  53. * Fetch signals from RF pin
  54. \*********************************************************************************************/
  55. bool RfSnsFetchSignal(byte DataPin, bool StateSignal)
  56. {
  57. uint8_t Fbit = digitalPinToBitMask(DataPin);
  58. uint8_t Fport = digitalPinToPort(DataPin);
  59. uint8_t FstateMask = (StateSignal ? Fbit : 0);
  60. if ((*portInputRegister(Fport) & Fbit) == FstateMask) { // Als er signaal is
  61. const unsigned long LoopsPerMilli = RFSNS_LOOPS_PER_MILLI;
  62. // Als het een herhalend signaal is, dan is de kans groot dat we binnen hele korte tijd weer in deze
  63. // routine terugkomen en dan midden in de volgende herhaling terecht komen. Daarom wordt er in dit
  64. // geval gewacht totdat de pulsen voorbij zijn en we met het capturen van data beginnen na een korte
  65. // rust tussen de signalen. Op deze wijze wordt het aantal zinloze captures teruggebracht.
  66. unsigned long PulseLength = 0;
  67. if (rfsns_raw_signal->Time) { // Eerst een snelle check, want dit bevindt zich in een tijdkritisch deel...
  68. if (rfsns_raw_signal->Repeats && (rfsns_raw_signal->Time + RFSNS_SIGNAL_REPEAT_TIME) > millis()) { // ...want deze check duurt enkele micro's langer!
  69. PulseLength = micros() + RFSNS_SIGNAL_TIMEOUT *1000; // Wachttijd
  70. while (((rfsns_raw_signal->Time + RFSNS_SIGNAL_REPEAT_TIME) > millis()) && (PulseLength > micros())) {
  71. if ((*portInputRegister(Fport) & Fbit) == FstateMask) {
  72. PulseLength = micros() + RFSNS_SIGNAL_TIMEOUT *1000;
  73. }
  74. }
  75. while (((rfsns_raw_signal->Time + RFSNS_SIGNAL_REPEAT_TIME) > millis()) && ((*portInputRegister(Fport) & Fbit) != FstateMask));
  76. }
  77. }
  78. int RawCodeLength = 1; // We starten bij 1, dit om legacy redenen. Vroeger had element 0 een speciaal doel.
  79. bool Ftoggle = false;
  80. unsigned long numloops = 0;
  81. unsigned long maxloops = RFSNS_SIGNAL_TIMEOUT * LoopsPerMilli;
  82. rfsns_raw_signal->Multiply = RFSNS_RAWSIGNAL_SAMPLE; // Ingestelde sample groote.
  83. do { // lees de pulsen in microseconden en plaats deze in de tijdelijke buffer rfsns_raw_signal
  84. numloops = 0;
  85. while(((*portInputRegister(Fport) & Fbit) == FstateMask) ^ Ftoggle) { // while() loop *A*
  86. if (numloops++ == maxloops) { break; } // timeout opgetreden
  87. }
  88. PulseLength = (numloops *1000) / LoopsPerMilli; // Bevat nu de pulslengte in microseconden
  89. if (PulseLength < RFSNS_MIN_PULSE_LENGTH) { break; }
  90. Ftoggle = !Ftoggle;
  91. rfsns_raw_signal->Pulses[RawCodeLength++] = PulseLength / (unsigned long)rfsns_raw_signal->Multiply; // sla op in de tabel rfsns_raw_signal
  92. }
  93. while(RawCodeLength < RFSNS_RAW_BUFFER_SIZE && numloops <= maxloops); // Zolang nog ruimte in de buffer, geen timeout en geen stoorpuls
  94. if ((RawCodeLength >= RFSNS_MIN_RAW_PULSES) && (RawCodeLength < RFSNS_RAW_BUFFER_SIZE -1)) {
  95. rfsns_raw_signal->Repeats = 0; // Op dit moment weten we nog niet het type signaal, maar de variabele niet ongedefinieerd laten.
  96. rfsns_raw_signal->Number = RawCodeLength -1; // Aantal ontvangen tijden (pulsen *2)
  97. rfsns_raw_signal->Pulses[rfsns_raw_signal->Number] = 0; // Laatste element bevat de timeout. Niet relevant.
  98. rfsns_raw_signal->Time = millis();
  99. return true;
  100. }
  101. else
  102. rfsns_raw_signal->Number = 0;
  103. }
  104. return false;
  105. }
  106. #ifdef USE_THEO_V2
  107. /*********************************************************************************************\
  108. * Theo V2 protocol
  109. * Dit protocol zorgt voor ontvangst van Theo sensoren met protocol V2
  110. *
  111. * Auteur : Theo Arends
  112. * Support : www.sidweb.nl
  113. * Datum : 17 Apr 2014
  114. * Versie : 0.1 - Initiele versie
  115. **********************************************************************************************
  116. * Technische informatie:
  117. *
  118. * Theo Sensor V2 type 1 Message Format (7 Bytes, 57 bits):
  119. * Checksum Type Chl BsVoltag Temperature Light
  120. * S AAAAAAAA BBBBBCCC DEFFFFFF GGGGGGGG GGGGGGGG HHHHHHHH HHHHHHHH
  121. * idx: 0 1 2 3 4 5 6
  122. *
  123. * Theo Sensor V2 type 2 Message Format (7 Bytes, 57 bits):
  124. * Checksum Type Chl BsVoltag Temperature Humidity
  125. * S AAAAAAAA BBBBBCCC DEFFFFFF GGGGGGGG GGGGGGGG HHHHHHHH HHHHHHHH
  126. * idx: 0 1 2 3 4 5 6
  127. \*********************************************************************************************/
  128. #define RFSNS_THEOV2_MAX_CHANNEL 2 // Max number of ATTiny sensor channels supported
  129. #define RFSNS_THEOV2_PULSECOUNT 114
  130. #define RFSNS_THEOV2_RF_PULSE_MID 1000 // PWM: Pulsen langer zijn '1'
  131. typedef struct {
  132. uint32_t time;
  133. int16_t temp;
  134. uint16_t lux;
  135. uint8_t volt;
  136. } theo_v2_t1_t;
  137. typedef struct {
  138. uint32_t time;
  139. int16_t temp;
  140. uint16_t hum;
  141. uint8_t volt;
  142. } theo_v2_t2_t;
  143. theo_v2_t1_t *rfsns_theo_v2_t1 = NULL;
  144. theo_v2_t2_t *rfsns_theo_v2_t2 = NULL;
  145. void RfSnsInitTheoV2(void)
  146. {
  147. rfsns_theo_v2_t1 = (theo_v2_t1_t*)malloc(RFSNS_THEOV2_MAX_CHANNEL * sizeof(theo_v2_t1_t));
  148. rfsns_theo_v2_t2 = (theo_v2_t2_t*)malloc(RFSNS_THEOV2_MAX_CHANNEL * sizeof(theo_v2_t2_t));
  149. rfsns_any_sensor++;
  150. }
  151. void RfSnsAnalyzeTheov2(void)
  152. {
  153. if (rfsns_raw_signal->Number != RFSNS_THEOV2_PULSECOUNT) { return; }
  154. byte Checksum; // 8 bits Checksum over following bytes
  155. byte Channel; // 3 bits channel
  156. byte Type; // 5 bits type
  157. byte Voltage; // 8 bits Vcc like 45 = 4.5V, bit 8 is batt low
  158. int Payload1; // 16 bits
  159. int Payload2; // 16 bits
  160. byte b, bytes, bits, id;
  161. byte idx = 3;
  162. byte chksum = 0;
  163. for (bytes = 0; bytes < 7; bytes++) {
  164. b = 0;
  165. for (bits = 0; bits <= 7; bits++)
  166. {
  167. if ((rfsns_raw_signal->Pulses[idx] * rfsns_raw_signal->Multiply) > RFSNS_THEOV2_RF_PULSE_MID) {
  168. b |= 1 << bits;
  169. }
  170. idx += 2;
  171. }
  172. if (bytes > 0) { chksum += b; } // bereken checksum
  173. switch (bytes) {
  174. case 0:
  175. Checksum = b;
  176. break;
  177. case 1:
  178. id = b;
  179. Channel = b & 0x7;
  180. Type = (b >> 3) & 0x1f;
  181. break;
  182. case 2:
  183. Voltage = b;
  184. break;
  185. case 3:
  186. Payload1 = b;
  187. break;
  188. case 4:
  189. Payload1 = (b << 8) | Payload1;
  190. break;
  191. case 5:
  192. Payload2 = b;
  193. break;
  194. case 6:
  195. Payload2 = (b << 8) | Payload2;
  196. break;
  197. }
  198. }
  199. if (Checksum != chksum) { return; }
  200. if ((Channel == 0) || (Channel > RFSNS_THEOV2_MAX_CHANNEL)) { return; }
  201. Channel--;
  202. rfsns_raw_signal->Repeats = 1; // het is een herhalend signaal. Bij ontvangst herhalingen onderdukken
  203. int Payload3 = Voltage & 0x3f;
  204. switch (Type) {
  205. case 1: // Temp / Lux
  206. rfsns_theo_v2_t1[Channel].time = LocalTime();
  207. rfsns_theo_v2_t1[Channel].volt = Payload3;
  208. rfsns_theo_v2_t1[Channel].temp = Payload1;
  209. rfsns_theo_v2_t1[Channel].lux = Payload2;
  210. break;
  211. case 2: // Temp / Hum
  212. rfsns_theo_v2_t2[Channel].time = LocalTime();
  213. rfsns_theo_v2_t2[Channel].volt = Payload3;
  214. rfsns_theo_v2_t2[Channel].temp = Payload1;
  215. rfsns_theo_v2_t2[Channel].hum = Payload2;
  216. break;
  217. }
  218. snprintf_P(log_data, sizeof(log_data), PSTR("RFS: TheoV2, ChkCalc %d, Chksum %d, id %d, Type %d, Ch %d, Volt %d, BattLo %d, Pld1 %d, Pld2 %d"),
  219. chksum, Checksum, id, Type, Channel +1, Payload3, (Voltage & 0x80) >> 7, Payload1, Payload2);
  220. AddLog(LOG_LEVEL_DEBUG);
  221. }
  222. void RfSnsTheoV2Show(bool json)
  223. {
  224. bool sensor_once = false;
  225. for (uint8_t i = 0; i < RFSNS_THEOV2_MAX_CHANNEL; i++) {
  226. if (rfsns_theo_v2_t1[i].time) {
  227. char sensor[10];
  228. snprintf_P(sensor, sizeof(sensor), PSTR("TV2T1C%d"), i +1);
  229. char voltage[33];
  230. dtostrfd((float)rfsns_theo_v2_t1[i].volt / 10, 1, voltage);
  231. if (rfsns_theo_v2_t1[i].time < LocalTime() - RFSNS_VALID_WINDOW) {
  232. if (json) {
  233. snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s,\"%s\":{\"" D_JSON_RFRECEIVED "\":\"%s\",\"" D_JSON_VOLTAGE "\":%s}"),
  234. mqtt_data, sensor, GetDT(rfsns_theo_v2_t1[i].time).c_str(), voltage);
  235. }
  236. } else {
  237. char temperature[33];
  238. dtostrfd(ConvertTemp((float)rfsns_theo_v2_t1[i].temp / 100), Settings.flag2.temperature_resolution, temperature);
  239. if (json) {
  240. snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s,\"%s\":{\"" D_JSON_TEMPERATURE "\":%s,\"" D_JSON_ILLUMINANCE "\":%d,\"" D_JSON_VOLTAGE "\":%s}"),
  241. mqtt_data, sensor, temperature, rfsns_theo_v2_t1[i].lux, voltage);
  242. #ifdef USE_DOMOTICZ
  243. if ((0 == tele_period) && !sensor_once) {
  244. DomoticzSensor(DZ_TEMP, temperature);
  245. DomoticzSensor(DZ_ILLUMINANCE, rfsns_theo_v2_t1[i].lux);
  246. sensor_once = true;
  247. }
  248. #endif // USE_DOMOTICZ
  249. #ifdef USE_WEBSERVER
  250. } else {
  251. snprintf_P(mqtt_data, sizeof(mqtt_data), HTTP_SNS_TEMP, mqtt_data, sensor, temperature, TempUnit());
  252. snprintf_P(mqtt_data, sizeof(mqtt_data), HTTP_SNS_ILLUMINANCE, mqtt_data, sensor, rfsns_theo_v2_t1[i].lux);
  253. #endif // USE_WEBSERVER
  254. }
  255. }
  256. }
  257. }
  258. sensor_once = false;
  259. for (uint8_t i = 0; i < RFSNS_THEOV2_MAX_CHANNEL; i++) {
  260. if (rfsns_theo_v2_t2[i].time) {
  261. char sensor[10];
  262. snprintf_P(sensor, sizeof(sensor), PSTR("TV2T2C%d"), i +1);
  263. char voltage[33];
  264. dtostrfd((float)rfsns_theo_v2_t2[i].volt / 10, 1, voltage);
  265. if (rfsns_theo_v2_t2[i].time < LocalTime() - RFSNS_VALID_WINDOW) {
  266. if (json) {
  267. snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s,\"%s\":{\"" D_JSON_RFRECEIVED" \":\"%s\",\"" D_JSON_VOLTAGE "\":%s}"),
  268. mqtt_data, sensor, GetDT(rfsns_theo_v2_t2[i].time).c_str(), voltage);
  269. }
  270. } else {
  271. float temp = ConvertTemp((float)rfsns_theo_v2_t2[i].temp / 100);
  272. float humi = (float)rfsns_theo_v2_t2[i].hum / 100;
  273. char temperature[33];
  274. dtostrfd(temp, Settings.flag2.temperature_resolution, temperature);
  275. char humidity[33];
  276. dtostrfd(humi, Settings.flag2.humidity_resolution, humidity);
  277. if (json) {
  278. snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s,\"%s\":{\"" D_JSON_TEMPERATURE "\":%s,\"" D_JSON_HUMIDITY "\":%s,\"" D_JSON_VOLTAGE "\":%s}"),
  279. mqtt_data, sensor, temperature, humidity, voltage);
  280. if ((0 == tele_period) && !sensor_once) {
  281. #ifdef USE_DOMOTICZ
  282. DomoticzTempHumSensor(temperature, humidity); //
  283. #endif // USE_DOMOTICZ
  284. #ifdef USE_KNX
  285. KnxSensor(KNX_TEMPERATURE, temp);
  286. KnxSensor(KNX_HUMIDITY, humi);
  287. #endif // USE_KNX
  288. sensor_once = true;
  289. }
  290. #ifdef USE_WEBSERVER
  291. } else {
  292. snprintf_P(mqtt_data, sizeof(mqtt_data), HTTP_SNS_TEMP, mqtt_data, sensor, temperature, TempUnit());
  293. snprintf_P(mqtt_data, sizeof(mqtt_data), HTTP_SNS_HUM, mqtt_data, sensor, humidity);
  294. #endif // USE_WEBSERVER
  295. }
  296. }
  297. }
  298. }
  299. }
  300. #endif // USE_THEO_V2 ************************************************************************
  301. #ifdef USE_ALECTO_V2
  302. /*********************************************************************************************\
  303. * Alecto V2 protocol
  304. * Dit protocol zorgt voor ontvangst van Alecto weerstation buitensensoren
  305. *
  306. * Auteur : Nodo-team (Martinus van den Broek) www.nodo-domotica.nl
  307. * Support ACH2010 en code optimalisatie door forumlid: Arendst
  308. * Support : www.nodo-domotica.nl
  309. * Datum : 25 Jan 2013
  310. * Versie : 1.3
  311. **********************************************************************************************
  312. * Technische informatie:
  313. * DKW2012 Message Format: (11 Bytes, 88 bits):
  314. * AAAAAAAA AAAABBBB BBBB__CC CCCCCCCC DDDDDDDD EEEEEEEE FFFFFFFF GGGGGGGG GGGGGGGG HHHHHHHH IIIIIIII
  315. * Temperature Humidity Windspd_ Windgust Rain____ ________ Winddir Checksum
  316. * A = start/unknown, first 8 bits are always 11111111
  317. * B = Rolling code
  318. * C = Temperature (10 bit value with -400 base)
  319. * D = Humidity
  320. * E = windspeed (* 0.3 m/s, correction for webapp = 3600/1000 * 0.3 * 100 = 108))
  321. * F = windgust (* 0.3 m/s, correction for webapp = 3600/1000 * 0.3 * 100 = 108))
  322. * G = Rain ( * 0.3 mm)
  323. * H = winddirection (0 = north, 4 = east, 8 = south 12 = west)
  324. * I = Checksum, calculation is still under investigation
  325. *
  326. * WS3000 and ACH2010 systems have no winddirection, message format is 8 bit shorter
  327. * Message Format: (10 Bytes, 80 bits):
  328. * AAAAAAAA AAAABBBB BBBB__CC CCCCCCCC DDDDDDDD EEEEEEEE FFFFFFFF GGGGGGGG GGGGGGGG HHHHHHHH
  329. * Temperature Humidity Windspd_ Windgust Rain____ ________ Checksum
  330. *
  331. * DCF Time Message Format: (NOT DECODED!)
  332. * AAAAAAAA BBBBCCCC DDDDDDDD EFFFFFFF GGGGGGGG HHHHHHHH IIIIIIII JJJJJJJJ KKKKKKKK LLLLLLLL MMMMMMMM
  333. * 11 Hours Minutes Seconds Year Month Day ? Checksum
  334. * B = 11 = DCF
  335. * C = ?
  336. * D = ?
  337. * E = ?
  338. * F = Hours BCD format (7 bits only for this byte, MSB could be '1')
  339. * G = Minutes BCD format
  340. * H = Seconds BCD format
  341. * I = Year BCD format (only two digits!)
  342. * J = Month BCD format
  343. * K = Day BCD format
  344. * L = ?
  345. * M = Checksum
  346. \*********************************************************************************************/
  347. #define RFSNS_DKW2012_PULSECOUNT 176
  348. #define RFSNS_ACH2010_MIN_PULSECOUNT 160 // reduce this value (144?) in case of bad reception
  349. #define RFSNS_ACH2010_MAX_PULSECOUNT 160
  350. #define D_ALECTOV2 "AlectoV2"
  351. const char kAlectoV2Directions[] PROGMEM = D_TX20_NORTH "|"
  352. D_TX20_NORTH D_TX20_NORTH D_TX20_EAST "|"
  353. D_TX20_NORTH D_TX20_EAST "|"
  354. D_TX20_EAST D_TX20_NORTH D_TX20_EAST "|"
  355. D_TX20_EAST "|"
  356. D_TX20_EAST D_TX20_SOUTH D_TX20_EAST "|"
  357. D_TX20_SOUTH D_TX20_EAST "|"
  358. D_TX20_SOUTH D_TX20_SOUTH D_TX20_EAST "|"
  359. D_TX20_SOUTH "|"
  360. D_TX20_SOUTH D_TX20_SOUTH D_TX20_WEST "|"
  361. D_TX20_SOUTH D_TX20_WEST "|"
  362. D_TX20_WEST D_TX20_SOUTH D_TX20_WEST "|"
  363. D_TX20_WEST "|"
  364. D_TX20_WEST D_TX20_NORTH D_TX20_WEST "|"
  365. D_TX20_NORTH D_TX20_WEST "|"
  366. D_TX20_NORTH D_TX20_NORTH D_TX20_WEST;
  367. typedef struct {
  368. uint32_t time;
  369. float temp;
  370. float rain;
  371. float wind;
  372. float gust;
  373. uint8_t type;
  374. uint8_t humi;
  375. uint8_t wdir;
  376. } alecto_v2_t;
  377. alecto_v2_t *rfsns_alecto_v2 = NULL;
  378. uint16_t rfsns_alecto_rain_base = 0;
  379. void RfSnsInitAlectoV2(void)
  380. {
  381. rfsns_alecto_v2 = (alecto_v2_t*)malloc(sizeof(alecto_v2_t));
  382. rfsns_any_sensor++;
  383. }
  384. void RfSnsAnalyzeAlectov2()
  385. {
  386. if (!(((rfsns_raw_signal->Number >= RFSNS_ACH2010_MIN_PULSECOUNT) &&
  387. (rfsns_raw_signal->Number <= RFSNS_ACH2010_MAX_PULSECOUNT)) || (rfsns_raw_signal->Number == RFSNS_DKW2012_PULSECOUNT))) { return; }
  388. byte c = 0;
  389. byte rfbit;
  390. byte data[9] = { 0 };
  391. byte msgtype = 0;
  392. byte rc = 0;
  393. int temp;
  394. byte checksum = 0;
  395. byte checksumcalc = 0;
  396. byte maxidx = 8;
  397. unsigned long atime;
  398. float factor;
  399. char buf1[16];
  400. if (rfsns_raw_signal->Number > RFSNS_ACH2010_MAX_PULSECOUNT) { maxidx = 9; }
  401. // Get message back to front as the header is almost never received complete for ACH2010
  402. byte idx = maxidx;
  403. for (byte x = rfsns_raw_signal->Number; x > 0; x = x-2) {
  404. if (rfsns_raw_signal->Pulses[x-1] * rfsns_raw_signal->Multiply < 0x300) {
  405. rfbit = 0x80;
  406. } else {
  407. rfbit = 0;
  408. }
  409. data[idx] = (data[idx] >> 1) | rfbit;
  410. c++;
  411. if (c == 8) {
  412. if (idx == 0) { break; }
  413. c = 0;
  414. idx--;
  415. }
  416. }
  417. checksum = data[maxidx];
  418. checksumcalc = RfSnsAlectoCRC8(data, maxidx);
  419. msgtype = (data[0] >> 4) & 0xf;
  420. rc = (data[0] << 4) | (data[1] >> 4);
  421. if (checksum != checksumcalc) { return; }
  422. if ((msgtype != 10) && (msgtype != 5)) { return; }
  423. rfsns_raw_signal->Repeats = 1; // het is een herhalend signaal. Bij ontvangst herhalingen onderdukken
  424. // Test set
  425. // rfsns_raw_signal->Number = RFSNS_DKW2012_PULSECOUNT; // DKW2012
  426. // data[8] = 11; // WSW
  427. factor = 1.22; // (1.08)
  428. // atime = rfsns_raw_signal->Time - rfsns_alecto_time;
  429. // if ((atime > 10000) && (atime < 60000)) factor = (float)60000 / atime;
  430. // rfsns_alecto_time = rfsns_raw_signal->Time;
  431. // Serial.printf("atime %d, rfsns_alecto_time %d\n", atime, rfsns_alecto_time);
  432. rfsns_alecto_v2->time = LocalTime();
  433. rfsns_alecto_v2->type = (RFSNS_DKW2012_PULSECOUNT == rfsns_raw_signal->Number);
  434. rfsns_alecto_v2->temp = (float)(((data[1] & 0x3) * 256 + data[2]) - 400) / 10;
  435. rfsns_alecto_v2->humi = data[3];
  436. uint16_t rain = (data[6] * 256) + data[7];
  437. // check if rain unit has been reset!
  438. if (rain < rfsns_alecto_rain_base) { rfsns_alecto_rain_base = rain; }
  439. if (rfsns_alecto_rain_base > 0) {
  440. rfsns_alecto_v2->rain += ((float)rain - rfsns_alecto_rain_base) * 0.30;
  441. }
  442. rfsns_alecto_rain_base = rain;
  443. rfsns_alecto_v2->wind = (float)data[4] * factor;
  444. rfsns_alecto_v2->gust = (float)data[5] * factor;
  445. if (rfsns_alecto_v2->type) {
  446. rfsns_alecto_v2->wdir = data[8] & 0xf;
  447. }
  448. snprintf_P(log_data, sizeof(log_data), PSTR("RFS: " D_ALECTOV2 ", ChkCalc %d, Chksum %d, rc %d, Temp %d, Hum %d, Rain %d, Wind %d, Gust %d, Dir %d, Factor %s"),
  449. 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));
  450. AddLog(LOG_LEVEL_DEBUG);
  451. }
  452. void RfSnsAlectoResetRain(void)
  453. {
  454. if ((RtcTime.hour == 0) && (RtcTime.minute == 0) && (RtcTime.second == 5)) {
  455. rfsns_alecto_v2->rain = 0; // Reset Rain
  456. }
  457. }
  458. /*********************************************************************************************\
  459. * Calculates CRC-8 checksum
  460. * reference http://lucsmall.com/2012/04/29/weather-station-hacking-part-2/
  461. * http://lucsmall.com/2012/04/30/weather-station-hacking-part-3/
  462. * https://github.com/lucsmall/WH2-Weather-Sensor-Library-for-Arduino/blob/master/WeatherSensorWH2.cpp
  463. \*********************************************************************************************/
  464. uint8_t RfSnsAlectoCRC8(uint8_t *addr, uint8_t len)
  465. {
  466. uint8_t crc = 0;
  467. while (len--) {
  468. uint8_t inbyte = *addr++;
  469. for (uint8_t i = 8; i; i--) {
  470. uint8_t mix = (crc ^ inbyte) & 0x80;
  471. crc <<= 1;
  472. if (mix) { crc ^= 0x31; }
  473. inbyte <<= 1;
  474. }
  475. }
  476. return crc;
  477. }
  478. #ifdef USE_WEBSERVER
  479. const char HTTP_SNS_ALECTOV2[] PROGMEM = "%s"
  480. "{s}" D_ALECTOV2 " " D_RAIN "{m}%s " D_UNIT_MILLIMETER "{e}"
  481. "{s}" D_ALECTOV2 " " D_TX20_WIND_SPEED "{m}%s " D_UNIT_KILOMETER_PER_HOUR "{e}"
  482. "{s}" D_ALECTOV2 " " D_TX20_WIND_SPEED_MAX "{m}%s " D_UNIT_KILOMETER_PER_HOUR "{e}";
  483. const char HTTP_SNS_ALECTOV2_WDIR[] PROGMEM = "%s"
  484. "{s}" D_ALECTOV2 " " D_TX20_WIND_DIRECTION "{m}%s{e}";
  485. #endif
  486. void RfSnsAlectoV2Show(bool json)
  487. {
  488. if (rfsns_alecto_v2->time) {
  489. if (rfsns_alecto_v2->time < LocalTime() - RFSNS_VALID_WINDOW) {
  490. if (json) {
  491. snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s,\"" D_ALECTOV2 "\":{\"" D_JSON_RFRECEIVED "\":\"%s\"}"),
  492. mqtt_data, GetDT(rfsns_alecto_v2->time).c_str());
  493. }
  494. } else {
  495. float temp = ConvertTemp(rfsns_alecto_v2->temp);
  496. char temperature[33];
  497. dtostrfd(temp, Settings.flag2.temperature_resolution, temperature);
  498. float humi = (float)rfsns_alecto_v2->humi;
  499. char humidity[33];
  500. dtostrfd(humi, Settings.flag2.humidity_resolution, humidity);
  501. char rain[33];
  502. dtostrfd(rfsns_alecto_v2->rain, 2, rain);
  503. char wind[33];
  504. dtostrfd(rfsns_alecto_v2->wind, 2, wind);
  505. char gust[33];
  506. dtostrfd(rfsns_alecto_v2->gust, 2, gust);
  507. char wdir[4];
  508. char direction[20];
  509. if (rfsns_alecto_v2->type) {
  510. GetTextIndexed(wdir, sizeof(wdir), rfsns_alecto_v2->wdir, kAlectoV2Directions);
  511. snprintf_P(direction, sizeof(direction), PSTR(",\"Direction\":\"%s\""), wdir);
  512. }
  513. if (json) {
  514. snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s,\"" D_ALECTOV2 "\":{\"" D_JSON_TEMPERATURE "\":%s,\"" D_JSON_HUMIDITY "\":%s,\"Rain\":%s,\"Wind\":%s,\"Gust\":%s%s}"),
  515. mqtt_data, temperature, humidity, rain, wind, gust, (rfsns_alecto_v2->type) ? direction : "");
  516. if (0 == tele_period) {
  517. #ifdef USE_DOMOTICZ
  518. // Use a rules to send data to Domoticz where also a local BMP280 is connected:
  519. // 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
  520. // 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"}
  521. // on tele-alectov2#rain do publish domoticz/in {"idx":70,"svalue":"0;%value%"} endon
  522. #endif // USE_DOMOTICZ
  523. }
  524. #ifdef USE_WEBSERVER
  525. } else {
  526. snprintf_P(mqtt_data, sizeof(mqtt_data), HTTP_SNS_TEMP, mqtt_data, D_ALECTOV2, temperature, TempUnit());
  527. snprintf_P(mqtt_data, sizeof(mqtt_data), HTTP_SNS_HUM, mqtt_data, D_ALECTOV2, humidity);
  528. snprintf_P(mqtt_data, sizeof(mqtt_data), HTTP_SNS_ALECTOV2, mqtt_data, rain, wind, gust);
  529. if (rfsns_alecto_v2->type) {
  530. snprintf_P(mqtt_data, sizeof(mqtt_data), HTTP_SNS_ALECTOV2_WDIR, mqtt_data, wdir);
  531. }
  532. #endif // USE_WEBSERVER
  533. }
  534. }
  535. }
  536. }
  537. #endif // USE_ALECTO_V2 **********************************************************************
  538. void RfSnsInit(void)
  539. {
  540. rfsns_raw_signal = (raw_signal_t*)(malloc(sizeof(raw_signal_t)));
  541. if (rfsns_raw_signal) {
  542. memset(rfsns_raw_signal, 0, sizeof(raw_signal_t)); // Init defaults to 0
  543. #ifdef USE_THEO_V2
  544. RfSnsInitTheoV2();
  545. #endif
  546. #ifdef USE_ALECTO_V2
  547. RfSnsInitAlectoV2();
  548. #endif
  549. if (rfsns_any_sensor) {
  550. rfsns_rf_bit = digitalPinToBitMask(pin[GPIO_RF_SENSOR]);
  551. rfsns_rf_port = digitalPinToPort(pin[GPIO_RF_SENSOR]);
  552. pinMode(pin[GPIO_RF_SENSOR], INPUT);
  553. } else {
  554. free(rfsns_raw_signal);
  555. rfsns_raw_signal = NULL;
  556. }
  557. }
  558. }
  559. void RfSnsAnalyzeRawSignal(void)
  560. {
  561. snprintf_P(log_data, sizeof(log_data), PSTR("RFS: Pulses %d"), (int)rfsns_raw_signal->Number);
  562. AddLog(LOG_LEVEL_DEBUG);
  563. #ifdef USE_THEO_V2
  564. RfSnsAnalyzeTheov2();
  565. #endif
  566. #ifdef USE_ALECTO_V2
  567. RfSnsAnalyzeAlectov2();
  568. #endif
  569. }
  570. void RfSnsEverySecond(void)
  571. {
  572. #ifdef USE_ALECTO_V2
  573. RfSnsAlectoResetRain();
  574. #endif
  575. }
  576. void RfSnsShow(bool json)
  577. {
  578. #ifdef USE_THEO_V2
  579. RfSnsTheoV2Show(json);
  580. #endif
  581. #ifdef USE_ALECTO_V2
  582. RfSnsAlectoV2Show(json);
  583. #endif
  584. }
  585. /*********************************************************************************************\
  586. * Interface
  587. \*********************************************************************************************/
  588. boolean Xsns37(byte function)
  589. {
  590. bool result = false;
  591. if ((pin[GPIO_RF_SENSOR] < 99) && (FUNC_INIT == function)) {
  592. RfSnsInit();
  593. }
  594. else if (rfsns_raw_signal) {
  595. switch (function) {
  596. case FUNC_LOOP:
  597. if ((*portInputRegister(rfsns_rf_port) &rfsns_rf_bit) == rfsns_rf_bit) {
  598. if (RfSnsFetchSignal(pin[GPIO_RF_SENSOR], HIGH)) {
  599. RfSnsAnalyzeRawSignal();
  600. }
  601. }
  602. sleep = 0;
  603. break;
  604. case FUNC_EVERY_SECOND:
  605. RfSnsEverySecond();
  606. break;
  607. case FUNC_JSON_APPEND:
  608. RfSnsShow(1);
  609. break;
  610. #ifdef USE_WEBSERVER
  611. case FUNC_WEB_APPEND:
  612. RfSnsShow(0);
  613. break;
  614. #endif // USE_WEBSERVER
  615. }
  616. }
  617. return result;
  618. }
  619. #endif // USE_RF_SENSOR