06-08-2017, 01:55 PM
Hi everybody,
I've bought a weatherstation sensor from Pearl branded as infactory wireless sensor for FWS-686 & FWS-665. It didn't work out of the box with pilight, but I've reverse engineered the protocol to some extend.
I've used the tfa source code to make some new draft protocol code. That's why I just renamed every occurrence of tfa to tfa2. Here is the tfa2.c:
It works so far, but up to now, I've only found out the meaning of some of the bits sent by the sensor, but not all.
Following, I post a couple of bit-streams from the sensor with corresponding temperature and humidity values. The last 3 digit is the long footer pulse.
I assume, that the first 8 bits are the id, since they change whenever I put out the batteries.
After that, there are 8 bits, I have no clue about. They change every telegram seemingly random, so it could be a checksum. On the other hand, Pearl claims, that this sensor also measures the pressure. While I actually doubt it, it could have to do something with that.
The next 8 bits represent the temperature+90.0°F in Fahrenheit as a binary number with one decimal place, so you need to subtract 90.0°F from it.
After that there are 8 bits for the humidity, divided in 2 groups of 4, each one presenting one digit of the decimal value in binary.
The next two bits could be the battery state.
The last two bits represent the channel 1-3, which can be selected by a switch.
Okay, I now need your help in deciphering the second group of 8 bits and further more, I need some hints as to how I get the code into a style, that it could appear in the official pilight version, and how to get it there.
Since this is my first post in this forum, please let me know, if I've violated some rules or whatever.
Cheers
I've bought a weatherstation sensor from Pearl branded as infactory wireless sensor for FWS-686 & FWS-665. It didn't work out of the box with pilight, but I've reverse engineered the protocol to some extend.
I've used the tfa source code to make some new draft protocol code. That's why I just renamed every occurrence of tfa to tfa2. Here is the tfa2.c:
Code:
/*
Copyright (C) 2014 CurlyMo
This file is part of pilight.
pilight 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.
pilight 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 pilight. If not, see <http://www.gnu.org/licenses/>
*/
#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 "../../core/gc.h"
#include "tfa2.h"
#define PULSE_MULTIPLIER 6
#define MIN_PULSE_LENGTH 460
#define MAX_PULSE_LENGTH 530
#define AVG_PULSE_LENGTH 471
#define RAW_LENGTH 82
typedef struct settings_t {
double id;
double channel;
double temp;
double humi;
struct settings_t *next;
} settings_t;
static struct settings_t *settings = NULL;
static int validate(void) {
if(tfa2->rawlen == RAW_LENGTH) {
if(tfa2->raw[tfa2->rawlen-1] >= (MIN_PULSE_LENGTH*PULSE_DIV) &&
tfa2->raw[tfa2->rawlen-1] <= (MAX_PULSE_LENGTH*PULSE_DIV)) {
return 0;
}
}
return -1;
}
static void parseCode(void) {
int binary[RAW_LENGTH/2];
int temp1 = 0;//, temp2 = 0, temp3 = 0;
int humi1 = 0, humi2 = 0;
int id = 0, battery = 0;
int channel = 0;
int i = 0, x = 0;
double humi_offset = 0.0, temp_offset = 0.0;
double temperature = 0.0, humidity = 0.0;
char binstr[RAW_LENGTH/2+1];
for(x=1;x<tfa2->rawlen-2;x+=2) {
if(tfa2->raw[x] > AVG_PULSE_LENGTH*PULSE_MULTIPLIER) {
binstr[i]='1';
binary[i++] = 1;
} else {
binstr[i]='0';
binary[i++] = 0;
}
}
binstr[i]='\0';
id = binToDecRev(binary, 0, 7);
channel = binToDecRev(binary, 38, 39);
temp1 = binToDecRev(binary, 16, 27);
/* Convert F to C */
temperature = (int)((float)(((temp1*10) - 9000) - 3200) * ((float)5/(float)9));
humi1 = binToDecRev(binary, 28, 31);
humi2 = binToDecRev(binary, 32, 35);
humidity = ((humi1*10)+(humi2));
if(binToDecRev(binary, 36, 37) > 1) {
battery = 0;
} else {
battery = 1;
}
struct settings_t *tmp = settings;
while(tmp) {
if(fabs(tmp->id-id) < EPSILON && fabs(tmp->channel-channel) < EPSILON) {
humi_offset = tmp->humi;
temp_offset = tmp->temp;
break;
}
tmp = tmp->next;
}
temperature += temp_offset;
humidity += humi_offset;
tfa2->message = json_mkobject();
json_append_member(tfa2->message, "id", json_mknumber(id, 0));
json_append_member(tfa2->message, "temperature", json_mknumber(temperature/100, 2));
json_append_member(tfa2->message, "humidity", json_mknumber(humidity, 2));
json_append_member(tfa2->message, "battery", json_mknumber(battery, 0));
json_append_member(tfa2->message, "channel", json_mknumber(channel, 0));
json_append_member(tfa2->message, "binary", json_mkstring(binstr));
}
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 channel = -1, id = -1;
int match = 0;
jchild = json_first_child(jid);
while(jchild) {
jchild1 = json_first_child(jchild);
while(jchild1) {
if(strcmp(jchild1->key, "channel") == 0) {
channel = jchild1->number_;
}
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 && fabs(tmp->channel-channel) < 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->channel = channel;
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 tfa2Init(void) {
protocol_register(&tfa2);
protocol_set_id(tfa2, "tfa2");
protocol_device_add(tfa2, "tfa2", "inFactory weather sensor");
tfa2->devtype = WEATHER;
tfa2->hwtype = RF433;
tfa2->maxgaplen = MAX_PULSE_LENGTH*PULSE_DIV;
tfa2->mingaplen = MIN_PULSE_LENGTH*PULSE_DIV;
tfa2->minrawlen = 82;
tfa2->maxrawlen = 82;
options_add(&tfa2->options, 't', "temperature", OPTION_HAS_VALUE, DEVICES_VALUE, JSON_NUMBER, NULL, "^[0-9]{1,3}$");
options_add(&tfa2->options, 'i', "id", OPTION_HAS_VALUE, DEVICES_ID, JSON_NUMBER, NULL, "[0-9]");
options_add(&tfa2->options, 'c', "channel", OPTION_HAS_VALUE, DEVICES_ID, JSON_NUMBER, NULL, "[0-9]");
options_add(&tfa2->options, 'h', "humidity", OPTION_HAS_VALUE, DEVICES_VALUE, JSON_NUMBER, NULL, "[0-9]");
options_add(&tfa2->options, 'b', "battery", OPTION_HAS_VALUE, DEVICES_VALUE, JSON_NUMBER, NULL, "^[01]$");
// options_add(&tfa2->options, 0, "decimals", OPTION_HAS_VALUE, DEVICES_SETTING, JSON_NUMBER, (void *)2, "[0-9]");
options_add(&tfa2->options, 0, "temperature-offset", OPTION_HAS_VALUE, DEVICES_SETTING, JSON_NUMBER, (void *)0, "[0-9]");
options_add(&tfa2->options, 0, "humidity-offset", OPTION_HAS_VALUE, DEVICES_SETTING, JSON_NUMBER, (void *)0, "[0-9]");
options_add(&tfa2->options, 0, "temperature-decimals", OPTION_HAS_VALUE, GUI_SETTING, JSON_NUMBER, (void *)2, "[0-9]");
options_add(&tfa2->options, 0, "humidity-decimals", OPTION_HAS_VALUE, GUI_SETTING, JSON_NUMBER, (void *)2, "[0-9]");
options_add(&tfa2->options, 0, "show-humidity", OPTION_HAS_VALUE, GUI_SETTING, JSON_NUMBER, (void *)1, "^[10]{1}$");
options_add(&tfa2->options, 0, "show-temperature", OPTION_HAS_VALUE, GUI_SETTING, JSON_NUMBER, (void *)1, "^[10]{1}$");
options_add(&tfa2->options, 0, "show-battery", OPTION_HAS_VALUE, GUI_SETTING, JSON_NUMBER, (void *)1, "^[10]{1}$");
tfa2->parseCode=&parseCode;
tfa2->checkValues=&checkValues;
tfa2->validate=&validate;
tfa2->gc=&gc;
}
#if defined(MODULE) && !defined(_WIN32)
void compatibility(struct module_t *module) {
module->name = "tfa2";
module->version = "1.0";
module->reqversion = "6.0";
module->reqcommit = "84";
}
void init(void) {
tfa2Init();
}
#endif
It works so far, but up to now, I've only found out the meaning of some of the bits sent by the sensor, but not all.
Following, I post a couple of bit-streams from the sensor with corresponding temperature and humidity values. The last 3 digit is the long footer pulse.
I assume, that the first 8 bits are the id, since they change whenever I put out the batteries.
After that, there are 8 bits, I have no clue about. They change every telegram seemingly random, so it could be a checksum. On the other hand, Pearl claims, that this sensor also measures the pressure. While I actually doubt it, it could have to do something with that.
The next 8 bits represent the temperature+90.0°F in Fahrenheit as a binary number with one decimal place, so you need to subtract 90.0°F from it.
After that there are 8 bits for the humidity, divided in 2 groups of 4, each one presenting one digit of the decimal value in binary.
The next two bits could be the battery state.
The last two bits represent the channel 1-3, which can be selected by a switch.
Code:
00010000100010010110011101100100001100013 - 24.1°C 43%
00010000000110010110011101010100010100013 - 24.0°C 45%
0001000001??10100110011010110100010000013 - 23.5°C 44%
00010000011010100110011010110?00010000013 - 23.5°C 44%
00010000110010100110010111000100010000013 - 22.6°C 44%
00010000111110100110001101100100011100013 - 20.5°C 47%
00010000000010100110001101100100100000013 - 20.5°C 48%
00010000011010100110001110010100100000013 - 20.7°C 48%
00010000001110100110001110000100100000013 - 20.6°C 48%
00010000000010100110001101100100100000013 - 20.5°C 48%
00010000101010100110001101000100100000013 - 20.4°C 48%
00010000011010100110001110010100100000013 - 20.7°C 48%
00010000111110100110001101100100011100013 - 20.5°C 47%
00010111010110000110001000110100100100013 - 19.5°C 49%
00010111011010000110000110100101000000013 - 19.0°C 66.2°F 50%
00010111111110000110000111010101000100013 - 19.1°C 66.5°F 51%
00010111000010000110001000000101000000013 - 19.3°C 66.8°F 50%
00010111110010000110001001100101000100013 - 19.6°C 67.4°F 51%
00010111111110000110001010000101000100013 - 19.7°C 67.6°F 51%
00010111000010000110001010110101000100013 - 19.9°C 67.9°F 51%
00010111001010000110001011100101000100013 - 20.1°C 68.2°F 51%
00010111010100010110001110010101001000013 - 20.7°C 69.3°F 52%
Okay, I now need your help in deciphering the second group of 8 bits and further more, I need some hints as to how I get the code into a style, that it could appear in the official pilight version, and how to get it there.
Since this is my first post in this forum, please let me know, if I've violated some rules or whatever.
Cheers