diff --git a/Makefile b/Makefile index 5ab28c0..18e2ddb 100644 --- a/Makefile +++ b/Makefile @@ -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 \ diff --git a/simulations/serial_rtl433_ok.msg b/simulations/serial_rtl433_ok.msg new file mode 100644 index 0000000..1f70d34 --- /dev/null +++ b/simulations/serial_rtl433_ok.msg @@ -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,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, diff --git a/src/main.cc b/src/main.cc index 6d194c8..8beda7f 100644 --- a/src/main.cc +++ b/src/main.cc @@ -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()); diff --git a/src/wmbus.cc b/src/wmbus.cc index 9853390..2801c7d 100644 --- a/src/wmbus.cc +++ b/src/wmbus.cc @@ -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 }; diff --git a/src/wmbus.h b/src/wmbus.h index 16acbed..2c145d6 100644 --- a/src/wmbus.h +++ b/src/wmbus.h @@ -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 openRawTTY(string device, int baudrate, SerialCommunicationMan unique_ptr serial_override); unique_ptr openRTLWMBUS(string device, SerialCommunicationManager *manager, std::function on_exit, unique_ptr serial_override); +unique_ptr openRTL433(string device, SerialCommunicationManager *manager, std::function on_exit, + unique_ptr serial_override); unique_ptr openCUL(string device, SerialCommunicationManager *manager, unique_ptr serial_override); unique_ptr openD1TC(string device, SerialCommunicationManager *manager, diff --git a/src/wmbus_rtl433.cc b/src/wmbus_rtl433.cc new file mode 100644 index 0000000..a16b1cd --- /dev/null +++ b/src/wmbus_rtl433.cc @@ -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 . +*/ + +#include"wmbus.h" +#include"wmbus_utils.h" +#include"serial.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 serial, SerialCommunicationManager *manager); + +private: + unique_ptr serial_; + vector read_buffer_; + vector received_payload_; + bool warning_dll_len_printed_ {}; + + FrameStatus checkRTL433Frame(vector &data, + size_t *hex_frame_length, + int *hex_payload_len_out, + int *hex_payload_offset); + void handleMessage(vector &frame); + + string setup_; + SerialCommunicationManager *manager_ {}; +}; + +unique_ptr openRTL433(string command, SerialCommunicationManager *manager, + function on_exit, unique_ptr serial_override) +{ + vector args; + vector envs; + args.push_back("-c"); + args.push_back(command); + if (serial_override) + { + WMBusRTL433 *imp = new WMBusRTL433(std::move(serial_override), manager); + return unique_ptr(imp); + } + auto serial = manager->createSerialDeviceCommand("/bin/sh", args, envs, on_exit); + WMBusRTL433 *imp = new WMBusRTL433(std::move(serial), manager); + return unique_ptr(imp); +} + +WMBusRTL433::WMBusRTL433(unique_ptr 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 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 payload; + if (hex_payload_len > 0) + { + vector 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 &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()) + { + 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; +} diff --git a/src/wmbus_rtlwmbus.cc b/src/wmbus_rtlwmbus.cc index 06c09d3..b015939 100644 --- a/src/wmbus_rtlwmbus.cc +++ b/src/wmbus_rtlwmbus.cc @@ -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; } diff --git a/tests/test_stdin_and_file.sh b/tests/test_stdin_and_file.sh index 1ba2fa6..bc4fd30 100755 --- a/tests/test_stdin_and_file.sh +++ b/tests/test_stdin_and_file.sh @@ -117,4 +117,57 @@ then fi fi +######################################################## +TESTNAME="Reading rtl433 formatted telegrams from stdin" +TESTRESULT="ERROR" + +cat > $TEST/test_expected.txt < $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 < $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