Skip to content

Commit

Permalink
TFA 2017 protocol with unit test (#426)
Browse files Browse the repository at this point in the history
  • Loading branch information
behrisch authored and CurlyMoo committed Jan 13, 2019
1 parent 9888f67 commit d191c08
Show file tree
Hide file tree
Showing 4 changed files with 304 additions and 0 deletions.
239 changes: 239 additions & 0 deletions libs/pilight/protocols/433.92/tfa2017.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
/*
Copyright (C) 2014, 2018 CurlyMo & DonBernos & Michael Behrisch
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#include "../../core/pilight.h"
#include "../../core/common.h"
#include "../../core/dso.h"
#include "../../core/log.h"
#include "../protocol.h"
#include "../../core/binary.h"
#include "tfa2017.h"

#define MIN_PULSE_LENGTH 250
#define AVG_PULSE 750
#define MIN_RAW_LENGTH 200
#define MAX_RAW_LENGTH 400
#define MESSAGE_LENGTH 48

typedef struct settings_t {
double id;
double temp;
double humi;
struct settings_t *next;
} settings_t;

static struct settings_t *settings = NULL;

static int validate(void) {
if(tfa2017->rawlen >= MIN_RAW_LENGTH && tfa2017->rawlen <= MAX_RAW_LENGTH) {
if(tfa2017->raw[tfa2017->rawlen-1] >= (MIN_PULSE_LENGTH*PULSE_DIV)) {
return 0;
}
}
return -1;
}

static void parseCode(char **message) {
int i = 0, x = 0, short_pulse = 0, prev = 0, long_pulse = 0;
int s = 0, start[3], m = 0, binary[MAX_RAW_LENGTH];
int msg[MESSAGE_LENGTH], channel = 0;
double humidity = 0.0, temperature = 0.0;

memset(*message, 0, 255);
if(tfa2017->rawlen > MAX_RAW_LENGTH) {
logprintf(LOG_ERR, "tfa2017: parsecode - invalid parameter passed %d", tfa2017->rawlen);
return;
}
for(x=0;x<tfa2017->rawlen;x++) {
if(tfa2017->raw[x] > AVG_PULSE) {
binary[i++] = 0;
if(short_pulse > 0) {
prev = short_pulse;
short_pulse = 0;
}
long_pulse++;
} else {
short_pulse++;
if(short_pulse%2 == 0) {
binary[i++] = 1;
}
long_pulse = 0;
}
if(long_pulse == 4 && (prev == 20 || (x > 7 && prev == x-1))) {
if(s == 0 || i > start[s-1]+MESSAGE_LENGTH) {
start[s++] = i-2;
prev = 0;
}
}
}
// The protocol sends the message three times in a row.
// If we find two identical ones, we consider it valid.
if(s < 2) {
return;
}
if(i > start[1]+MESSAGE_LENGTH && memcmp(&binary[start[0]], &binary[start[1]], MESSAGE_LENGTH) == 0) {
m=start[0];
} else if(s > 2 && i > start[2]+MESSAGE_LENGTH &&
(memcmp(&binary[start[0]], &binary[start[2]], MESSAGE_LENGTH) == 0 ||
memcmp(&binary[start[1]], &binary[start[2]], MESSAGE_LENGTH) == 0)) {
m=start[2];
} else {
return;
}

// decode manchester
prev = 1;
for(x=0;x<MESSAGE_LENGTH;x++) {
if(binary[x+m] == 0) {
prev = !prev;
}
msg[x] = prev;
}
// According to http://www.osengr.org/WxShield/Downloads/Weather-Sensor-RF-Protocols.pdf
// the first byte is a fixed id (0x45), the second is a rolling code which changes on
// battery replacement (both are not used here).
// Of the next four bits the first is unused, the next three encode the channel.
channel = binToDecRev(msg, 17, 19)+1;
// The next twelve bits encode the temperature T
// in tenth of degree Fahrenheit with an offset of 40.
// The following is a simplification of F=T/10-40 and C=(F-32)*5/9.
temperature = (double)binToDecRev(msg, 20, 31)/18.-40.;
// The next byte has the relative humidity in percent.
humidity = (double)binToDecRev(msg, 32, 39);
// The last byte contains a checksum which is not used here.

struct settings_t *tmp = settings;
while(tmp) {
if(fabs(tmp->id-channel) < EPSILON){
temperature += tmp->temp;
humidity += tmp->humi;
break;
}
tmp = tmp->next;
}
if(humidity < 0 || humidity > 100) {
return;
}

snprintf((*message), 255,
"{\"id\":%d,\"temperature\":%.2f,\"humidity\":%.2f}",
channel, temperature, humidity
);
}

static int checkValues(struct JsonNode *jvalues) {
struct JsonNode *jid = NULL;

if((jid = json_find_member(jvalues, "id"))) {
struct settings_t *snode = NULL;
struct JsonNode *jchild = NULL;
struct JsonNode *jchild1 = NULL;
double id = -1;
int match = 0;

jchild = json_first_child(jid);
while(jchild) {
jchild1 = json_first_child(jchild);
while(jchild1) {
if(strcmp(jchild1->key, "id") == 0) {
id = jchild1->number_;
}
jchild1 = jchild1->next;
}
jchild = jchild->next;
}

struct settings_t *tmp = settings;
while(tmp) {
if(fabs(tmp->id-id) < EPSILON) {
match = 1;
break;
}
tmp = tmp->next;
}

if(match == 0) {
if((snode = MALLOC(sizeof(struct settings_t))) == NULL) {
fprintf(stderr, "out of memory\n");
exit(EXIT_FAILURE);
}
snode->id = id;
snode->temp = 0;
snode->humi = 0;

json_find_number(jvalues, "temperature-offset", &snode->temp);
json_find_number(jvalues, "humidity-offset", &snode->humi);

snode->next = settings;
settings = snode;
}
}
return 0;
}

static void gc(void) {
struct settings_t *tmp = NULL;
while(settings) {
tmp = settings;
settings = settings->next;
FREE(tmp);
}
if(settings != NULL) {
FREE(settings);
}
}

#if !defined(MODULE) && !defined(_WIN32)
__attribute__((weak))
#endif
void tfa2017Init(void) {
protocol_register(&tfa2017);
protocol_set_id(tfa2017, "tfa2017");
protocol_device_add(tfa2017, "tfa2017", "TFA 30.X Temp Hum Sensor Revision 08/2017");
tfa2017->devtype = WEATHER;
tfa2017->hwtype = RF433;
tfa2017->minrawlen = MIN_RAW_LENGTH;
tfa2017->maxrawlen = MAX_RAW_LENGTH;
tfa2017->maxgaplen = AVG_PULSE*PULSE_DIV;
tfa2017->mingaplen = MIN_PULSE_LENGTH*PULSE_DIV;

options_add(&tfa2017->options, "t", "temperature", OPTION_HAS_VALUE, DEVICES_VALUE, JSON_NUMBER, NULL, "^[0-9]{1,3}$");
options_add(&tfa2017->options, "i", "id", OPTION_HAS_VALUE, DEVICES_ID, JSON_NUMBER, NULL, "[0-9]");
options_add(&tfa2017->options, "h", "humidity", OPTION_HAS_VALUE, DEVICES_VALUE, JSON_NUMBER, NULL, "^[0-9]{1,3}$");

// options_add(&tfa2017->options, "0", "decimals", OPTION_HAS_VALUE, DEVICES_SETTING, JSON_NUMBER, (void *)1, "[0-9]");
options_add(&tfa2017->options, "0", "temperature-decimals", OPTION_HAS_VALUE, GUI_SETTING, JSON_NUMBER, (void *)1, "[0-9]");
options_add(&tfa2017->options, "0", "humidity-decimals", OPTION_HAS_VALUE, GUI_SETTING, JSON_NUMBER, (void *)1, "[0-9]");
options_add(&tfa2017->options, "0", "humidity-offset", OPTION_HAS_VALUE, DEVICES_SETTING, JSON_NUMBER, (void *)0, "[0-9]");
options_add(&tfa2017->options, "0", "temperature-offset", OPTION_HAS_VALUE, DEVICES_SETTING, JSON_NUMBER, (void *)0, "[0-9]");
options_add(&tfa2017->options, "0", "show-humidity", OPTION_HAS_VALUE, GUI_SETTING, JSON_NUMBER, (void *)1, "^[10]{1}$");
options_add(&tfa2017->options, "0", "show-temperature", OPTION_HAS_VALUE, GUI_SETTING, JSON_NUMBER, (void *)1, "^[10]{1}$");

tfa2017->parseCode=&parseCode;
tfa2017->checkValues=&checkValues;
tfa2017->validate=&validate;
tfa2017->gc=&gc;
}

#ifdef MODULAR
void compatibility(const char **version, const char **commit) {
module->name = "tfa2017";
module->version = "1.0";
module->reqversion = "6.0";
module->reqcommit = "84";
}

void init(void) {
tfa2017Init();
}
#endif
17 changes: 17 additions & 0 deletions libs/pilight/protocols/433.92/tfa2017.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/*
Copyright (C) 2014, 2018 CurlyMo & DonBernos & Michael Behrisch
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

#ifndef _PROTOCOL_TFA2017_H_
#define _PROTOCOL_TFA2017_H_

#include "../protocol.h"

struct protocol_t *tfa2017;
void tfa2017Init(void);

#endif
45 changes: 45 additions & 0 deletions tests/protocols/tfa2017.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
static const int tfa2017_nrtests = 5;
static const struct raw_t tfa2017_tests[] = {
{
"4752 1032 912 1039 436 613 841 594 373 1056 431 532 443 535 436 537 927 552 415 558 418 561 416 1037 920 1033 922 555 420 557 416 1034 923 557 424 546 436 1014 453 516 944 535 435 546 444 998 473 506 944 1012 943 1007 475 496 962 526 446 526 446 996 486 490 488 489 962 517 465 516 457 514 466 510 465 985 495 497 490 472 498 476 504 488 491 473 495 475 507 467 509 473 505 465 507 496 961 973 976 966 987 496 488 486 496 946 1012 940 536 438 1027 453 526 921 547 436 540 426 568 408 1051 435 534 445 517 455 530 921 1027 931 1014 465 526 447 528 921 1034 446 526 455 516 931 547 428 1025 461 515 465 514 934 558 414 1045 908 1015 940 540 435 1023 458 509 480 505 931 556 414 567 416 1023 455 528 448 525 456 517 456 517 932 547 436 535 435 557 417 558 425 546 426 546 435 536 441 534 445 539 434 537 445 1004 942 1035 917 1030 459 511 457 517 933 1015 941 537 445 1004 468 517 933 547 435 554 419 543 434 1023 469 500 470 499 476 504 942 1025 924 1030 451 516 466 505 942 1013 466 506 476 496 952 536 435 1017 468 510 468 506 941 528 447 1015 940 1003 959 521 446 1013 465 518 456 517 933 537 446 530 448 1001 477 499 475 509 472 496 476 507 506 297579",
"{\"id\":2,\"temperature\":20.78,\"humidity\":53.00}",
NULL,
0
},
{
"519 377 566 413 561 417 557 902 1044 939 1008 911 564 417 564 413 1035 915 1030 937 1018 921 1032 921 557 422 557 426 1021 930 546 434 1012 940 535 447 1000 949 530 454 527 445 1007 957 985 970 514 457 517 461 989 965 505 477 978 495 476 980 971 981 505 476 969 984 963 516 462 991 484 498 476 507 468 509 466 507 474 507 463 517 464 511 928 559 422 551 424 560 411 581 394 585 393 582 393 567 415 567 407 575 406 575 394 1052 905 1067 887 1074 397 572 407 557 894 1063 894 1052 909 1055 889 1060 421 551 427 535 911 1045 435 545 905 1037 449 525 921 1052 423 557 425 546 902 1044 913 1038 444 526 448 525 920 1044 434 547 905 556 423 1032 920 1046 443 520 931 1038 904 574 405 1041 437 537 447 534 436 535 447 541 437 519 466 514 455 525 921 569 405 569 413 556 415 557 428 553 420 588 385 557 421 546 436 539 438 548 434 1024 919 1032 922 1031 450 521 452 526 929 1020 929 1034 917 1032 925 1019 465 520 453 520 930 1011 474 517 921 1034 446 527 929 1022 456 523 458 515 933 1021 932 1023 458 522 447 534 920 1015 476 506 944 532 436 1019 957 1004 451 546 911 1034 918 535 437 1017 463 513 464 506 474 506 471 510 461 516 458 532 454 506 515 241790",
"{\"id\":3,\"temperature\":21.39,\"humidity\":52.00}",
NULL,
0
},
{
"390 493 479 499 465 981 971 997 944 1065 409 537 440 557 894 1055 901 583 393 637 346 1041 436 533 444 546 895 629 346 549 171 4250 427 537 431 543 912 1033 914 583 404 577 95 4283 903 1023 937 1010 946 555 448 535 404 1047 902 570 404 612 366 567 414 549 420 561 416 1044 923 1044 435 569 411 534 435 509 468 518 927 605 378 545 429 576 397 588 382 585 402 546 426 557 427 547 424 571 403 547 434 1028 934 1028 930 1017 448 541 435 541 920 1013 942 545 453 534 410 1055 427 521 463 516 936 564 422 538 420 1017 471 498 476 487 493 509 462 516 454 536 912 1017 941 558 423 537 443 539 438 530 435 558 430 1033 914 1015 943 1035 923 534 426 541 443 1016 936 547 424 547 440 527 456 526 447 526 445 1010 948 997 484 499 469 516 464 520 458 506 947 535 438 551 428 536 497 485 443 517 449 537 438 541 438 527 455 516 468 523 445 1013 954 1019 930 999 466 511 468 506 935 1013 942 558 417 539 436 1016 483 496 476 496 952 549 434 536 437 1016 466 494 470 516 465 503 476 498 485 491 956 1004 935 538 446 538 434 528 447 548 433 567 420 1001 949 998 947 1012 934 537 452 528 454 994 964 521 442 535 447 522 450 536 454 510 461 987 978 976 489 516 482 467 480 517 469 498 530 657637",
"{\"id\":1,\"temperature\":2.50,\"humidity\":93.00}",
NULL,
0
},
{
"351 555 416 556 413 560 417 565 893 1058 903 1048 910 555 415 570 414 1021 926 1013 946 1013 459 516 458 519 942 528 453 860 111 1003 951 526 448 997 483 490 980 979 954 530 450 517 464 991 979 981 952 516 459 987 971 509 468 511 462 988 488 485 967 519 457 985 976 505 465 986 974 983 975 992 980 511 443 506 465 533 442 546 438 503 472 979 491 489 495 475 500 477 495 480 496 490 484 489 489 487 487 487 496 479 497 477 993 971 965 982 982 497 468 508 464 986 975 983 965 987 496 478 495 479 972 509 467 508 468 984 975 508 464 984 503 475 969 981 969 506 470 507 472 984 963 986 976 506 467 984 984 503 462 507 467 983 489 492 966 510 471 982 969 505 471 995 959 984 967 994 960 517 464 499 475 507 477 496 476 496 478 973 507 469 507 469 507 470 507 466 510 467 578 401 510 465 519 455 518 458 525 447 1005 942 1013 936 1024 450 554 421 547 894 1097 856 1136 814 1768 193 1143 333 651 353 597 829 1120 354 622 827 641 337 1106 859 1094 376 599 378 608 841 1098 864 1086 394 591 855 1088 394 591 382 589 861 619 357 1089 395 580 871 1088 405 563 871 1076 881 1067 882 1078 404 568 406 569 413 568 405 570 416 559 441 1191986",
"",
NULL,
0
},
{
"445 463 544 414 551 436 520 927 1025 937 990 986 490 494 468 545 905 1085 859 629 334 1142 820 608 373 1057 889 1105 852 1050 905 1055 131 176 597 1035 895 1078 421 565 416 547 885 1057 912 580 394 1100 381 539 448 548 413 573 401 566 882 576 406 1055 427 538 434 568 414 561 894 556 417 1024 463 528 922 572 433 975 948 1042 442 576 394 556 417 531 926 569 405 583 385 571 415 591 377 567 406 580 405 558 427 539 428 568 419 557 410 1034 924 1017 937 1042 460 494 457 536 916 1034 924 537 427 1071 881 570 410 1033 923 1026 929 1045 931 1008 916 1039 933 1014 455 541 447 498 1013 946 923 560 423 1044 437 519 464 531 446 540 427 510 948 546 417 1046 442 527 459 500 477 510 937 556 418 1016 461 509 947 555 405 1029 940 1024 448 519 467 510 466 518 932 548 424 537 449 527 439 565 408 554 423 540 445 527 446 548 438 536 447 519 447 1014 934 1025 946 994 468 505 469 509 937 1038 934 518 454 1001 944 547 435 1011 940 1015 942 1016 935 1007 944 1015 933 1028 455 508 465 520 933 1025 926 546 449 997 475 498 476 496 470 520 459 508 945 537 445 1006 466 506 471 515 468 508 937 528 446 1016 464 518 955 512 446 1006 948 1005 486 486 481 505 469 508 531 200366",
"{\"id\":6,\"temperature\":21.78,\"humidity\":48.00}",
NULL,
0
},
{ // error raw length too short
"461 497 486 487 963 1054 151 3693 962 544 424 533 442 537 456 521 436 565 420 575 396 535 466 529 424 540 438 537 446 994 964 1011 920 1030 465 557 416 503 954 1004 952 992 963 980 959 1046 424 575 415 525 924 993 486 528 915 1024 464 507 954 1026 449 553 417 506 960 993 944 1007 473 508 944 1004 479 507 938 1009 467 506 488 488 483 507 470 495 484 495 944 1007 953 1031 433 536 435 530 936 1003 466 550 445 518 446 496 476 520 515 15736",
NULL,
NULL,
-1
},
{ // error only one message
"1335 125 184 3988 584 744 691 402 1034 384 619 417 552 409 655 332 564 416 548 894 570 416 1050 892 1074 884 583 671 325 374 578 408 1058 891 593 390 562 417 549 311 4545 508 464 533 434 533 441 538 355 4032 428 573 403 598 376 579 403 531 447 576 395 549 435 1033 901 1029 924 1111 380 536 443 526 923 1034 916 545 434 558 419 1016 462 527 457 521 919 623 355 566 415 1025 449 525 457 516 468 517 465 511 457 504 952 556 410 1024 456 507 476 517 454 526 459 487 486 499 951 553 413 1024 957 1040 890 577 397 580 405 558 416 1014 934 547 438 544 436 558 402 554 426 537 458 994 470 515 460 507 468 527 458 494 477 509 944 552 434 525 440 545 437 536 432 538 439 537 440 535 446 550 434 537 436 530 458 1014 911 1034 938 1002 486 485 479 502 946 1023 933 527 458 528 454 986 489 516 468 484 960 515 459 523 453 1020 505 450 467 506 520 475 465 509 459 528 921 547 437 1005 475 501 477 496 487 498 469 505 465 522 931 515 469 997 965 1005 941 515 451 534 450 527 447 1008 950 516 456 527 456 538 436 537 443 509 472 990 474 507 481 496 467 497 493 493 474 511 531 10085",
"",
NULL,
0
}
};
3 changes: 3 additions & 0 deletions tests/protocols_433.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include "../libs/pilight/protocols/433.92/arctech_dimmer.h"
#include "../libs/pilight/protocols/433.92/arctech_dusk.h"
#include "../libs/pilight/protocols/433.92/livolo_switch.h"
#include "../libs/pilight/protocols/433.92/tfa2017.h"

#include "alltests.h"

Expand All @@ -45,6 +46,7 @@ const struct test_t {
#include "protocols/arctech_dimmer.h"
#include "protocols/arctech_dusk.h"
#include "protocols/livolo_switch.h"
#include "protocols/tfa2017.h"

static const struct test_t tests[] = {
{ &alectoWS1700Init, &alecto_ws1700, alecto_ws1700_tests, &alecto_ws1700_nrtests },
Expand All @@ -53,6 +55,7 @@ static const struct test_t tests[] = {
{ &arctechDimmerInit, &arctech_dimmer, arctech_dimmer_tests, &arctech_dimmer_nrtests },
{ &arctechDuskInit, &arctech_dusk, arctech_dusk_tests, &arctech_dusk_nrtests },
{ &livoloSwitchInit, &livolo_switch, livolo_switch_tests, &livolo_switch_nrtests },
{ &tfa2017Init, &tfa2017, tfa2017_tests, &tfa2017_nrtests },
};

static void test_protocols_433(CuTest *tc) {
Expand Down

0 comments on commit d191c08

Please sign in to comment.