Added support for rtl_433 to decode wmbus radio signals.

pull/143/head
Fredrik Öhrström 2020-08-10 23:00:41 +02:00
rodzic 555cfa7466
commit 7e6c57ad48
8 zmienionych plików z 404 dodań i 1 usunięć

Wyświetl plik

@ -150,6 +150,7 @@ METER_OBJS:=\
$(BUILD)/wmbus_cul.o \
$(BUILD)/wmbus_d1tc.o \
$(BUILD)/wmbus_rtlwmbus.o \
$(BUILD)/wmbus_rtl433.o \
$(BUILD)/wmbus_simulator.o \
$(BUILD)/wmbus_rawtty.o \
$(BUILD)/wmbus_wmb13u.o \

Wyświetl plik

@ -0,0 +1,3 @@
time,msg,codes,model,button,id,channel,battery_ok,temperature_C,mic,subtype,rid,humidity,state,status,brand,rain_rate,rain_rate_mm_h,rain_rate_in_h,rain_total,rain_mm,rain_in,gust,average,direction,wind_max_m_s,wind_avg_m_s,wind_dir_deg,pressure_hPa,uv,power_W,energy_kWh,radio_clock,sequence,unit,group_call,command,dim,dim_value,wind_speed,wind_gust,wind_direction,wind_avg_km_h,wind_max_km_h,dipswitch,rbutton,device,temperature_F,setpoint_C,switch,cmd,cmd_id,tristate,direction_deg,speed,rain,msg_type,signal,sensor_code,uv_status,uv_index,lux,wm,seq,rainfall_mm,wind_speed_ms,gust_speed_ms,current,interval,learn,message_type,sensor_id,sequence_num,battery_low,wind_speed_mph,wind_speed_kph,wind_avg_mi_h,rain_inch,rc,gust_speed_mph,wind_max_mi_h,wind_approach,flags,maybetemp,binding_countdown,depth,depth_cm,dev_id,sensor_type,power0,power1,power2,power0_W,power1_W,power2_W,node,ct1,ct2,ct3,ct4,Vrms/batt,batt_Vrms,temp1_C,temp2_C,temp3_C,temp4_C,temp5_C,temp6_C,pulse,address,button1,button2,button3,button4,data,sid,group,transmit,moisture,type,pressure_PSI,battery_mV,pressure_kPa,pulses,pulsecount,energy,len,to,from,payload,motion,opened,tamper,water,event,contact_open,reed_open,alarm,heartbeat,temperature1_C,temperature2_C,temperature_1_C,temperature_2_C,test,probe,humidity_1,ptemperature_C,phumidity,newbattery,heating,heating_temp,uvi,light_lux,pm2_5_ug_m3,pm10_0_ug_m3,counter,code,repeat,maybe_battery,delay,device_type,raw_message,switch1,switch2,switch3,switch4,switch5,extradata,house_id,module_id,sensor_count,alarms,sensor_value,battery_voltage,mode,version,type_string,CI,AC,ST,CW,sn,knx_ctrl,src,dst,l_npci,tpci,apci,crc,failed,class,alert,secret_knock,relay,wind_dev_deg,exposure_mins,transmit_s,button_id,button_name,pulses_per_kwh,cumulative_kWh,effect,encrypted,misc,current_A,voltage_V,pairing,connected,gap,impulses,triggered,storage,boost,ad_raw,power_idx,power_max,timestamp,physical_tamper,ert_type,encoder_tamper,consumption_data,else,restore,supervised,spidernet,repeater,ProtocolID,EndpointType,EndpointID,Consumption,Tamper,PacketCRC,MeterType,battery_oktemperature_C,from_id,to_id,msg_type_str,extended,hops_max,hops_left,formatted,cmd_dat,PacketTypeID,PacketLength,HammingCode,ApplicationVersion,ERTType,ERTSerialNumber,ConsumptionIntervalCount,ModuleProgrammingState,Unknown_field_1,LastGenerationCount,Unknown_field_2,TamperCounters,AsynchronousCounters,PowerOutageFlags,LastConsumptionCount,DifferentialConsumptionIntervals,TransmitTimeOffset,MeterIdCRC
2020-08-10 20:40:47,,,Wireless-MBus,,00010203,,,,CRC,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2e44333003020100071b7a634820252f2f0265840842658308820165950802fb1aae0142fb1aae018201fb1aa9012f,,,,,22,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,C,27,Cold Water,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
2020-08-10 20:42:21,,,Wireless-MBus,,11772288,,,,CRC,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,5744b40988227711101b7ab20800000265a00842658f088201659f08226589081265a0086265510852652b0902fb1aba0142fb1ab0018201fb1abd0122fb1aa90112fb1aba0162fb1aa60152fb1af501066d3b3bb36b2a00,,,,,22,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,C,27,Cold Water,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,

Wyświetl plik

@ -231,6 +231,42 @@ bool startUsingCommandline(Configuration *config)
std::move(serial_override));
break;
}
case DEVICE_RTL433:
{
string command;
if (!settings.override_tty)
{
command = config->device_extra;
string freq = "868.95M";
string prefix = "";
if (isFrequency(command)) {
freq = command;
command = "";
}
if (config->daemon) {
prefix = "/usr/bin/";
if (command == "")
{
// Default command is used, check that the binaries are in place.
if (!checkFileExists("/usr/bin/rtl_433"))
{
error("(rtl433) error: when starting as daemon, wmbusmeters expects /usr/bin/rtl_433 to exist!\n");
}
}
}
if (command == "") {
command = prefix+"rtl_433 -F csv -f "+freq;
}
verbose("(rtl433) using command: %s\n", command.c_str());
}
wmbus = openRTL433(command, manager.get(),
[command](){
warning("(rtl433) child process exited! "
"Command was: \"%s\"\n", command.c_str());
},
std::move(serial_override));
break;
}
case DEVICE_CUL:
{
verbose("(cul) on %s\n", settings.devicefile.c_str());

Wyświetl plik

@ -626,6 +626,7 @@ Detected detectImstAmberCul(string devicefile,
cul
d1tc
rtlwmbus: the devicefile produces rtlwmbus messages, ie. T1;1;1;2019-04-03 19:00:42.000;97;148;88888888;0x6e440106...ae03a77
rtl433: the devicefile produces rtl433 csv lines, ie. 2020-08-10 20:40:47,,,Wireless-MBus,,22232425,,,,CRC, ,25442d...23411d,,,,
simulation: assume the devicefile produces telegram=|....|+xx lines. This can also pace the simulated telegrams in time.
a baud rate like 38400: assume the devicefile is a raw tty character device.
*/
@ -650,6 +651,12 @@ Detected detectWMBusDeviceSetting(string devicefile,
return { DEVICE_RTLWMBUS, "", false };
}
if (devicefile == "rtl433")
{
debug("(detect) driver: rtl433\n");
return { DEVICE_RTL433, "", false };
}
// Is it a file named simulation_xxx.txt ?
if (checkIfSimulationFile(devicefile.c_str()))
{
@ -675,6 +682,7 @@ Detected detectWMBusDeviceSetting(string devicefile,
if (suffix == "im871a") return { DEVICE_IM871A, devicefile, 0, override_tty };
if (suffix == "rfmrx2") return { DEVICE_RFMRX2, devicefile, 0, override_tty };
if (suffix == "rtlwmbus") return { DEVICE_RTLWMBUS, devicefile, 0, override_tty };
if (suffix == "rtl433") return { DEVICE_RTL433, devicefile, 0, override_tty };
if (suffix == "cul") return { DEVICE_CUL, devicefile, 0, override_tty };
if (suffix == "d1tc") return { DEVICE_D1TC, devicefile, 0, override_tty };
if (suffix == "wmb13u") return { DEVICE_WMB13U, devicefile, 0, override_tty };

Wyświetl plik

@ -399,6 +399,7 @@ struct Meter;
X(DEVICE_RFMRX2)\
X(DEVICE_SIMULATOR)\
X(DEVICE_RTLWMBUS)\
X(DEVICE_RTL433)\
X(DEVICE_RAWTTY)\
X(DEVICE_WMB13U)
@ -451,6 +452,8 @@ unique_ptr<WMBus> openRawTTY(string device, int baudrate, SerialCommunicationMan
unique_ptr<SerialDevice> serial_override);
unique_ptr<WMBus> openRTLWMBUS(string device, SerialCommunicationManager *manager, std::function<void()> on_exit,
unique_ptr<SerialDevice> serial_override);
unique_ptr<WMBus> openRTL433(string device, SerialCommunicationManager *manager, std::function<void()> on_exit,
unique_ptr<SerialDevice> serial_override);
unique_ptr<WMBus> openCUL(string device, SerialCommunicationManager *manager,
unique_ptr<SerialDevice> serial_override);
unique_ptr<WMBus> openD1TC(string device, SerialCommunicationManager *manager,

300
src/wmbus_rtl433.cc 100644
Wyświetl plik

@ -0,0 +1,300 @@
/*
Copyright (C) 2019-2020 Fredrik Öhrström
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/>.
*/
#include"wmbus.h"
#include"wmbus_utils.h"
#include"serial.h"
#include<assert.h>
#include<fcntl.h>
#include<grp.h>
#include<pthread.h>
#include<semaphore.h>
#include<string.h>
#include<errno.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<unistd.h>
using namespace std;
struct WMBusRTL433 : public virtual WMBusCommonImplementation
{
bool ping();
uint32_t getDeviceId();
LinkModeSet getLinkModes();
void setLinkModes(LinkModeSet lms);
LinkModeSet supportedLinkModes() {
return
C1_bit |
T1_bit;
}
int numConcurrentLinkModes() { return 2; }
bool canSetLinkModes(LinkModeSet lms)
{
if (!supportedLinkModes().supports(lms)) return false;
// The rtl433 listens to both modes always.
return true;
}
void processSerialData();
SerialDevice *serial() { return NULL; }
void simulate();
bool reset();
WMBusRTL433(unique_ptr<SerialDevice> serial, SerialCommunicationManager *manager);
private:
unique_ptr<SerialDevice> serial_;
vector<uchar> read_buffer_;
vector<uchar> received_payload_;
bool warning_dll_len_printed_ {};
FrameStatus checkRTL433Frame(vector<uchar> &data,
size_t *hex_frame_length,
int *hex_payload_len_out,
int *hex_payload_offset);
void handleMessage(vector<uchar> &frame);
string setup_;
SerialCommunicationManager *manager_ {};
};
unique_ptr<WMBus> openRTL433(string command, SerialCommunicationManager *manager,
function<void()> on_exit, unique_ptr<SerialDevice> serial_override)
{
vector<string> args;
vector<string> envs;
args.push_back("-c");
args.push_back(command);
if (serial_override)
{
WMBusRTL433 *imp = new WMBusRTL433(std::move(serial_override), manager);
return unique_ptr<WMBus>(imp);
}
auto serial = manager->createSerialDeviceCommand("/bin/sh", args, envs, on_exit);
WMBusRTL433 *imp = new WMBusRTL433(std::move(serial), manager);
return unique_ptr<WMBus>(imp);
}
WMBusRTL433::WMBusRTL433(unique_ptr<SerialDevice> serial, SerialCommunicationManager *manager) :
WMBusCommonImplementation(DEVICE_RTL433), serial_(std::move(serial)), manager_(manager)
{
manager_->listenTo(serial_.get(),call(this,processSerialData));
serial_->open(true);
}
bool WMBusRTL433::ping()
{
return true;
}
uint32_t WMBusRTL433::getDeviceId()
{
return 0x11111111;
}
LinkModeSet WMBusRTL433::getLinkModes()
{
return Any_bit;
}
void WMBusRTL433::setLinkModes(LinkModeSet lm)
{
}
void WMBusRTL433::simulate()
{
}
void WMBusRTL433::processSerialData()
{
vector<uchar> data;
// Receive and accumulated serial data until a full frame has been received.
serial_->receive(&data);
read_buffer_.insert(read_buffer_.end(), data.begin(), data.end());
size_t frame_length;
int hex_payload_len, hex_payload_offset;
for (;;)
{
FrameStatus status = checkRTL433Frame(read_buffer_, &frame_length, &hex_payload_len, &hex_payload_offset);
if (status == PartialFrame)
{
break;
}
if (status == TextAndNotFrame)
{
// The buffer has already been printed by serial cmd.
read_buffer_.erase(read_buffer_.begin(), read_buffer_.begin()+frame_length);
if (read_buffer_.size() == 0)
{
break;
}
else
{
// Decode next line.
continue;
}
}
if (status == ErrorInFrame)
{
debug("(rtl433) error in received message.\n");
read_buffer_.erase(read_buffer_.begin(), read_buffer_.begin()+frame_length);
if (read_buffer_.size() == 0)
{
break;
}
else
{
// Decode next line.
continue;
}
}
if (status == FullFrame)
{
vector<uchar> payload;
if (hex_payload_len > 0)
{
vector<uchar> hex;
hex.insert(hex.end(), read_buffer_.begin()+hex_payload_offset, read_buffer_.begin()+hex_payload_offset+hex_payload_len);
bool ok = hex2bin(hex, &payload);
if (!ok)
{
if (hex.size() % 2 == 1)
{
payload.clear();
warning("(rtl433) warning: the hex string is not an even multiple of two! Dropping last char.\n");
hex.pop_back();
ok = hex2bin(hex, &payload);
}
if (!ok)
{
warning("(rtl433) warning: the hex string contains bad characters! Decode stopped partway.\n");
}
}
}
read_buffer_.erase(read_buffer_.begin(), read_buffer_.begin()+frame_length);
if (payload.size() > 0)
{
if (payload[0] != payload.size()-1)
{
if (!warning_dll_len_printed_)
{
warning("(rtl433) dll_len adjusted to %d from %d. Fix rtl_433? This warning will not be printed again.\n", payload.size()-1, payload[0]);
warning_dll_len_printed_ = true;
}
payload[0] = payload.size()-1;
}
}
handleTelegram(payload);
}
}
}
FrameStatus WMBusRTL433::checkRTL433Frame(vector<uchar> &data,
size_t *hex_frame_length,
int *hex_payload_len_out,
int *hex_payload_offset)
{
// 2020-08-10 20:40:47,,,Wireless-MBus,,22232425,,,,CRC,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,25442d2c252423221b168d209f38810821c3f371825d5c25b5bdea9821786aec9e2d,,,,,22,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,C,27,Cold Water,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
if (data.size() == 0) return PartialFrame;
if (isDebugEnabled())
{
string msg = safeString(data);
debug("(rtl433) checkRTL433Frame \"%s\"\n", msg.c_str());
}
int payload_len = 0;
size_t eolp = 0;
// Look for end of line
for (; eolp < data.size(); ++eolp)
{
if (data[eolp] == '\n')
{
data[eolp] = 0;
break;
}
}
if (eolp >= data.size())
{
return PartialFrame;
}
*hex_frame_length = eolp+1;
const char *needle = strstr((const char*)&data[0], "Wireless-MBus");
if (needle == NULL)
{
// rtl_433 found some other protocol on 868.95Mhz
return TextAndNotFrame;
}
// Look for start of telegram ,..44..........,
// This works right now because wmbusmeters currently only listens for 44 SND_NR
size_t i = 0;
for (; i+4 < data.size(); ++i) {
if (data[i] == ',' && data[i+3] == '4' && data[i+4] == '4')
{
size_t j = i+1;
for (; j<data.size(); ++j)
{
if (data[j] == ',') break;
}
if (j-i < 20)
{
// Oups, too short sequence of hex chars, this cannot be a proper telegram.
continue;
}
break;
}
}
if (i+4 >= data.size())
{
return ErrorInFrame; // No , 44 found, then discard the frame.
}
i+=1; // Skip the comma ,
// Look for end of line or semicolon.
size_t nextcomma = i;
for (; nextcomma < data.size(); ++nextcomma) {
if (data[nextcomma] == ',') break;
}
if (nextcomma >= data.size())
{
return PartialFrame;
}
payload_len = nextcomma-i;
*hex_payload_len_out = payload_len;
*hex_payload_offset = i;
return FullFrame;
}
bool WMBusRTL433::reset()
{
return false;
}

Wyświetl plik

@ -150,7 +150,6 @@ void WMBusRTLWMBUS::processSerialData()
if (status == ErrorInFrame)
{
debug("(rtlwmbus) error in received message.\n");
string msg = bin2hex(read_buffer_);
read_buffer_.clear();
break;
}

Wyświetl plik

@ -117,4 +117,57 @@ then
fi
fi
########################################################
TESTNAME="Reading rtl433 formatted telegrams from stdin"
TESTRESULT="ERROR"
cat > $TEST/test_expected.txt <<EOF
{"media":"room sensor","meter":"lansenth","name":"Rummet1","id":"00010203","current_temperature_c":21.8,"current_relative_humidity_rh":43,"average_temperature_1h_c":21.79,"average_relative_humidity_1h_rh":43,"average_temperature_24h_c":21.97,"average_relative_humidity_24h_rh":42.5,"timestamp":"1111-11-11T11:11:11Z"}
{"media":"room sensor","meter":"rfmamb","name":"Rummet2","id":"11772288","current_temperature_c":22.08,"average_temperature_1h_c":21.91,"average_temperature_24h_c":22.07,"maximum_temperature_1h_c":22.08,"minimum_temperature_1h_c":21.85,"maximum_temperature_24h_c":23.47,"minimum_temperature_24h_c":21.29,"current_relative_humidity_rh":44.2,"average_relative_humidity_1h_rh":43.2,"average_relative_humidity_24h_rh":44.5,"minimum_relative_humidity_1h_rh":42.2,"maximum_relative_humidity_1h_rh":50.1,"maximum_relative_humidity_24h_rh":0,"minimum_relative_humidity_24h_rh":0,"device_date_time":"2019-10-11 19:59","timestamp":"1111-11-11T11:11:11Z"}
EOF
cat simulations/serial_rtl433_ok.msg | \
$PROG --format=json --listento=any stdin:rtl433 \
Rummet1 lansenth 00010203 "" \
Rummet2 rfmamb 11772288 "" \
| grep Rummet > $TEST/test_output.txt
if [ "$?" = "0" ]
then
cat $TEST/test_output.txt | sed 's/"timestamp":"....-..-..T..:..:..Z"/"timestamp":"1111-11-11T11:11:11Z"/' > $TEST/test_responses.txt
diff $TEST/test_expected.txt $TEST/test_responses.txt
if [ "$?" = "0" ]
then
echo "OK: $TESTNAME"
TESTRESULT="OK"
fi
fi
if [ "$TESTRESULT" = "ERROR" ]; then echo ERROR: $TESTNAME; exit 1; fi
########################################################
TESTNAME="Reading rtl433 formatted telegrams from file"
TESTRESULT="ERROR"
cat > $TEST/test_expected.txt <<EOF
{"media":"room sensor","meter":"lansenth","name":"Rummet1","id":"00010203","current_temperature_c":21.8,"current_relative_humidity_rh":43,"average_temperature_1h_c":21.79,"average_relative_humidity_1h_rh":43,"average_temperature_24h_c":21.97,"average_relative_humidity_24h_rh":42.5,"timestamp":"1111-11-11T11:11:11Z"}
{"media":"room sensor","meter":"rfmamb","name":"Rummet2","id":"11772288","current_temperature_c":22.08,"average_temperature_1h_c":21.91,"average_temperature_24h_c":22.07,"maximum_temperature_1h_c":22.08,"minimum_temperature_1h_c":21.85,"maximum_temperature_24h_c":23.47,"minimum_temperature_24h_c":21.29,"current_relative_humidity_rh":44.2,"average_relative_humidity_1h_rh":43.2,"average_relative_humidity_24h_rh":44.5,"minimum_relative_humidity_1h_rh":42.2,"maximum_relative_humidity_1h_rh":50.1,"maximum_relative_humidity_24h_rh":0,"minimum_relative_humidity_24h_rh":0,"device_date_time":"2019-10-11 19:59","timestamp":"1111-11-11T11:11:11Z"}
EOF
$PROG --format=json --listento=any simulations/serial_rtl433_ok.msg:rtl433 \
Rummet1 lansenth 00010203 "" \
Rummet2 rfmamb 11772288 "" \
| grep Rummet > $TEST/test_output.txt
if [ "$?" = "0" ]
then
cat $TEST/test_output.txt | sed 's/"timestamp":"....-..-..T..:..:..Z"/"timestamp":"1111-11-11T11:11:11Z"/' > $TEST/test_responses.txt
diff $TEST/test_expected.txt $TEST/test_responses.txt
if [ "$?" = "0" ]
then
echo "OK: $TESTNAME"
TESTRESULT="OK"
fi
fi
if [ "$TESTRESULT" = "ERROR" ]; then echo ERROR: $TESTNAME; exit 1; fi