Merge pull request #124 from vestom/generic_demod
New packet based pulse demodulation infrastructure
This commit is contained in:
commit
d96f8e3814
14 changed files with 701 additions and 145 deletions
|
@ -35,6 +35,7 @@ if(CMAKE_COMPILER_IS_GNUCC AND NOT WIN32)
|
|||
ADD_DEFINITIONS(-Wno-unused)
|
||||
ADD_DEFINITIONS(-Wsign-compare)
|
||||
ADD_DEFINITIONS(-g3 -O0)
|
||||
ADD_DEFINITIONS(-std=gnu99)
|
||||
#http://gcc.gnu.org/wiki/Visibility
|
||||
add_definitions(-fvisibility=hidden)
|
||||
endif()
|
||||
|
|
47
include/bitbuffer.h
Normal file
47
include/bitbuffer.h
Normal file
|
@ -0,0 +1,47 @@
|
|||
/**
|
||||
* Bit buffer
|
||||
*
|
||||
* A two-dimensional bit buffer consisting of bytes
|
||||
*
|
||||
* Copyright (C) 2015 Tommy Vestermark
|
||||
* 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 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*/
|
||||
|
||||
#ifndef INCLUDE_BITBUFFER_H_
|
||||
#define INCLUDE_BITBUFFER_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define BITBUF_COLS 34 // Number of bytes in a column
|
||||
#define BITBUF_ROWS 50
|
||||
|
||||
|
||||
/// Bit buffer
|
||||
typedef struct {
|
||||
int row_index; // Number of active rows - 1
|
||||
int bit_col_index; // Bit index into byte (0 is MSB, 7 is LSB)
|
||||
int16_t bits_per_row[BITBUF_ROWS];
|
||||
uint8_t bits_buffer[BITBUF_ROWS][BITBUF_COLS];
|
||||
} bitbuffer_t;
|
||||
|
||||
|
||||
/// Clear the content of the bitbuffer
|
||||
void bitbuffer_clear(bitbuffer_t *bits);
|
||||
|
||||
/// Add a single bit at the end of the bitbuffer (MSB first)
|
||||
void bitbuffer_add_bit(bitbuffer_t *bits, int bit);
|
||||
|
||||
/// Add a new row to the bitbuffer
|
||||
void bitbuffer_add_row(bitbuffer_t *bits);
|
||||
|
||||
/// Invert all bits in the bitbuffer (do not invert the empty bits)
|
||||
//void bitbuffer_invert(bitbuffer_t *bits);
|
||||
|
||||
/// Print the content of the bitbuffer
|
||||
void bitbuffer_print(const bitbuffer_t *bits);
|
||||
|
||||
|
||||
#endif /* INCLUDE_BITBUFFER_H_ */
|
46
include/pulse_demod.h
Normal file
46
include/pulse_demod.h
Normal file
|
@ -0,0 +1,46 @@
|
|||
/**
|
||||
* Pulse demodulation functions
|
||||
*
|
||||
* Binary demodulators (PWM/PPM/Manchester/...) using a pulse data structure as input
|
||||
*
|
||||
* Copyright (C) 2015 Tommy Vestermark
|
||||
* 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 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*/
|
||||
|
||||
#ifndef INCLUDE_PULSE_DEMOD_H_
|
||||
#define INCLUDE_PULSE_DEMOD_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include "pulse_detect.h"
|
||||
#include "rtl_433.h"
|
||||
|
||||
|
||||
/// Demodulate a Pulse Width Modulation signal
|
||||
///
|
||||
/// Demodulate a Pulse Width Modulation (PWM) signal consisting of short and long high pulses.
|
||||
/// Gap between pulses may be of fixed size or variable (e.g. fixed period)
|
||||
/// - Short pulse will add a 1 bit
|
||||
/// - Long pulse will add a 0 bit
|
||||
/// @param start_bit = 0: Do not remove any startbits
|
||||
/// @param start_bit = 1: First bit in each message is considered a startbit and not stored in bitbuffer
|
||||
/// @return number of events processed
|
||||
int pulse_demod_pwm(const pulse_data_t *pulses, struct protocol_state *device, int start_bit);
|
||||
|
||||
|
||||
/// Demodulate a Manchester encoded signal with a hardcoded zerobit in front
|
||||
///
|
||||
/// Demodulate a Manchester encoded signal where first rising edge is counted as a databit
|
||||
/// and therefore always will be zero (Most likely a hardcoded Oregon Scientific peculiarity)
|
||||
///
|
||||
/// Clock is recovered from the data based on pulse width. When time since last bit is more
|
||||
/// than 1.5 times the clock half period (short_width) it is declared a data edge where:
|
||||
/// - Rising edge means bit = 0
|
||||
/// - Falling edge means bit = 1
|
||||
/// @return number of events processed
|
||||
int pulse_demod_manchester_zerobit(const pulse_data_t *pulses, struct protocol_state *device);
|
||||
|
||||
|
||||
#endif /* INCLUDE_PULSE_DEMOD_H_ */
|
47
include/pulse_detect.h
Normal file
47
include/pulse_detect.h
Normal file
|
@ -0,0 +1,47 @@
|
|||
/**
|
||||
* Pulse detection functions
|
||||
*
|
||||
* Copyright (C) 2015 Tommy Vestermark
|
||||
* 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 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*/
|
||||
|
||||
#ifndef INCLUDE_PULSE_DETECT_H_
|
||||
#define INCLUDE_PULSE_DETECT_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define PD_MAX_PULSES 1000 // Maximum number of pulses before forcing End Of Package
|
||||
#define PD_MAX_GAP_RATIO 10 // Ratio gap/pulse width to exceed to declare End Of Package (heuristic)
|
||||
#define PD_MAX_PULSE_LENGTH 25000 // Pulse width to exceed to declare End Of Package (e.g. for non OOK packages)
|
||||
|
||||
/// Data for a compact representation of generic pulse train
|
||||
typedef struct {
|
||||
unsigned int num_pulses;
|
||||
unsigned int pulse[PD_MAX_PULSES]; // Contains width of a pulse (high)
|
||||
unsigned int gap[PD_MAX_PULSES]; // Width of gaps between pulses (low)
|
||||
} pulse_data_t;
|
||||
|
||||
|
||||
/// Clear the content of a pulse_data_t structure
|
||||
void pulse_data_clear(pulse_data_t *data); // Clear the struct
|
||||
|
||||
/// Print the content of a pulse_data_t structure (for debug)
|
||||
void pulse_data_print(const pulse_data_t *data);
|
||||
|
||||
|
||||
/// Demodulate On/Off Keying from an envelope signal
|
||||
///
|
||||
/// Function is stateful and can be called with chunks of input data
|
||||
/// @return 0 if all input data is processed
|
||||
/// @return 1 if package is detected (but data is still not completely processed)
|
||||
int detect_pulse_package(const int16_t *envelope_data, uint32_t len, int16_t level_limit, pulse_data_t *pulses);
|
||||
|
||||
|
||||
/// Analyze and print result
|
||||
void pulse_analyzer(const pulse_data_t *data);
|
||||
|
||||
|
||||
#endif /* INCLUDE_PULSE_DETECT_H_ */
|
|
@ -37,10 +37,41 @@
|
|||
/* Supported modulation types */
|
||||
#define OOK_PWM_D 1 /* Pulses are of the same length, the distance varies (PPM) */
|
||||
#define OOK_PWM_P 2 /* The length of the pulses varies */
|
||||
#define OOK_MANCHESTER 3 /* Manchester code */
|
||||
#define OOK_PWM_RAW 4 /* Pulse Width Modulation. No startbit removal. Short pulses = 1, Long = 0 */
|
||||
#define OOK_PULSE_PWM_STARTBIT 3 // Pulse Width Modulation. Startbit removal. Short pulses = 1, Long = 0
|
||||
#define OOK_PULSE_PWM_RAW 4 // Pulse Width Modulation. No startbit removal. Short pulses = 1, Long = 0
|
||||
#define OOK_PULSE_MANCHESTER_ZEROBIT 5 // Manchester encoding. Hardcoded zerobit. Rising Edge = 0, Falling edge = 1
|
||||
|
||||
extern int debug_output;
|
||||
int debug_callback(uint8_t buffer[BITBUF_ROWS][BITBUF_COLS], int16_t bits_per_row[BITBUF_ROWS]);
|
||||
|
||||
|
||||
struct protocol_state {
|
||||
int (*callback)(uint8_t bits_buffer[BITBUF_ROWS][BITBUF_COLS], int16_t bits_per_row[BITBUF_ROWS]);
|
||||
|
||||
/* bits state */
|
||||
int bits_col_idx;
|
||||
int bits_row_idx;
|
||||
int bits_bit_col_idx;
|
||||
uint8_t bits_buffer[BITBUF_ROWS][BITBUF_COLS];
|
||||
int16_t bits_per_row[BITBUF_ROWS];
|
||||
int bit_rows;
|
||||
unsigned int modulation;
|
||||
|
||||
/* demod state */
|
||||
int pulse_length;
|
||||
int pulse_count;
|
||||
int pulse_distance;
|
||||
int sample_counter;
|
||||
int start_c;
|
||||
|
||||
int packet_present;
|
||||
int pulse_start;
|
||||
int real_bits;
|
||||
int start_bit;
|
||||
/* pwm limits */
|
||||
int short_limit;
|
||||
int long_limit;
|
||||
int reset_limit;
|
||||
};
|
||||
|
||||
#endif /* INCLUDE_RTL_433_H_ */
|
||||
|
|
|
@ -18,6 +18,9 @@
|
|||
########################################################################
|
||||
add_executable(rtl_433
|
||||
rtl_433.c
|
||||
bitbuffer.c
|
||||
pulse_demod.c
|
||||
pulse_detect.c
|
||||
devices/silvercrest.c
|
||||
devices/rubicson.c
|
||||
devices/prologue.c
|
||||
|
|
110
src/bitbuffer.c
Normal file
110
src/bitbuffer.c
Normal file
|
@ -0,0 +1,110 @@
|
|||
/**
|
||||
* Bit buffer
|
||||
*
|
||||
* A two-dimensional bit buffer consisting of bytes
|
||||
*
|
||||
* Copyright (C) 2015 Tommy Vestermark
|
||||
* 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 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*/
|
||||
|
||||
#include "bitbuffer.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
|
||||
void bitbuffer_clear(bitbuffer_t *bits) {
|
||||
bits->row_index = 0;
|
||||
bits->bit_col_index = 0;
|
||||
memset(bits->bits_per_row, 0, BITBUF_ROWS*2);
|
||||
memset(bits->bits_buffer, 0, BITBUF_ROWS * BITBUF_COLS);
|
||||
}
|
||||
|
||||
|
||||
void bitbuffer_add_bit(bitbuffer_t *bits, int bit) {
|
||||
uint16_t col_index = bits->bits_per_row[bits->row_index]/8;
|
||||
if((col_index < BITBUF_COLS)
|
||||
&& (bits->row_index < BITBUF_ROWS)
|
||||
) {
|
||||
bits->bits_buffer[bits->row_index][col_index] |= bit << (7-bits->bit_col_index);
|
||||
bits->bit_col_index++;
|
||||
bits->bit_col_index %= 8; // Wrap around
|
||||
bits->bits_per_row[bits->row_index]++;
|
||||
}
|
||||
else {
|
||||
fprintf(stderr, "ERROR: bitbuffer:: Could not add more columns\n");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void bitbuffer_add_row(bitbuffer_t *bits) {
|
||||
if(bits->row_index < BITBUF_ROWS) {
|
||||
bits->row_index++;
|
||||
bits->bit_col_index = 0;
|
||||
}
|
||||
else {
|
||||
fprintf(stderr, "ERROR: bitbuffer:: Could not add more rows\n");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void bitbuffer_print(const bitbuffer_t *bits) {
|
||||
fprintf(stderr, "bitbuffer:: row_index: %d, bit_col_index: %d\n", bits->row_index, bits->bit_col_index);
|
||||
for (int row = 0; row <= bits->row_index; ++row) {
|
||||
fprintf(stderr, "[%02d] {%d} ", row, bits->bits_per_row[row]);
|
||||
for (int col = 0; col < (bits->bits_per_row[row]+7)/8; ++col) {
|
||||
fprintf(stderr, "%02x ", bits->bits_buffer[row][col]);
|
||||
}
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Test code
|
||||
// gcc -I include/ -std=gnu11 -D _TEST src/bitbuffer.c
|
||||
#ifdef _TEST
|
||||
int main(int argc, char **argv) {
|
||||
fprintf(stderr, "bitbuffer:: test\n");
|
||||
|
||||
bitbuffer_t bits = {0};
|
||||
|
||||
fprintf(stderr, "TEST: bitbuffer:: The empty buffer\n");
|
||||
bitbuffer_print(&bits);
|
||||
|
||||
fprintf(stderr, "TEST: bitbuffer:: Add 1 bit\n");
|
||||
bitbuffer_add_bit(&bits, 1);
|
||||
bitbuffer_print(&bits);
|
||||
|
||||
fprintf(stderr, "TEST: bitbuffer:: Add 1 new row\n");
|
||||
bitbuffer_add_row(&bits);
|
||||
bitbuffer_print(&bits);
|
||||
|
||||
fprintf(stderr, "TEST: bitbuffer:: Fill row\n");
|
||||
for (int i=0; i < BITBUF_COLS*8; ++i) {
|
||||
bitbuffer_add_bit(&bits, i%2);
|
||||
}
|
||||
bitbuffer_print(&bits);
|
||||
|
||||
fprintf(stderr, "TEST: bitbuffer:: Add row and fill 1 column too many\n");
|
||||
bitbuffer_add_row(&bits);
|
||||
for (int i=0; i <= BITBUF_COLS*8; ++i) {
|
||||
bitbuffer_add_bit(&bits, i%2);
|
||||
}
|
||||
bitbuffer_print(&bits);
|
||||
|
||||
fprintf(stderr, "TEST: bitbuffer:: Clear\n");
|
||||
bitbuffer_clear(&bits);
|
||||
bitbuffer_print(&bits);
|
||||
|
||||
fprintf(stderr, "TEST: bitbuffer:: Add 1 row too many\n");
|
||||
for (int i=0; i <= BITBUF_ROWS; ++i) {
|
||||
bitbuffer_add_row(&bits);
|
||||
}
|
||||
bitbuffer_add_bit(&bits, 1);
|
||||
bitbuffer_print(&bits);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif /* _TEST */
|
|
@ -147,7 +147,7 @@ ambient_weather_callback (uint8_t bb[BITBUF_ROWS][BITBUF_COLS], int16_t bits_per
|
|||
r_device ambient_weather = {
|
||||
/* .id = */ 14,
|
||||
/* .name = */ "Ambient Weather Temperature Sensor",
|
||||
/* .modulation = */ OOK_MANCHESTER,
|
||||
/* .modulation = */ OOK_PULSE_MANCHESTER_ZEROBIT,
|
||||
/* .short_limit = */ 125,
|
||||
/* .long_limit = */ 0, // not used
|
||||
/* .reset_limit = */ 600,
|
||||
|
|
|
@ -61,10 +61,10 @@ static int fineoffset_WH2_callback(uint8_t bb[BITBUF_ROWS][BITBUF_COLS], int16_t
|
|||
// Validate package
|
||||
if (bits_per_row[0] >= 48 && // Dont waste time on a short package
|
||||
bb[0][0] == 0xFF && // Preamble
|
||||
bb[0][5] == crc8(&bb[0][1], 4, polynomial) // CRC (excluding preamble)
|
||||
)
|
||||
bb[0][5] == crc8(&bb[0][1], 4, polynomial) // CRC (excluding preamble)
|
||||
)
|
||||
{
|
||||
// Nibble 3,4 contains ID
|
||||
// Nibble 3,4 contains ID
|
||||
ID = ((bb[0][1]&0x0F) << 4) | ((bb[0][2]&0xF0) >> 4);
|
||||
|
||||
// Nible 5,6,7 contains 12 bits of temperature
|
||||
|
@ -99,7 +99,7 @@ static int fineoffset_WH2_callback(uint8_t bb[BITBUF_ROWS][BITBUF_COLS], int16_t
|
|||
r_device fineoffset_WH2 = {
|
||||
/* .id = */ 12,
|
||||
/* .name = */ "Fine Offset Electronics, WH-2 Sensor",
|
||||
/* .modulation = */ OOK_PWM_RAW,
|
||||
/* .modulation = */ OOK_PULSE_PWM_RAW,
|
||||
/* .short_limit = */ 200, // Short pulse 136, long pulse 381, fixed gap 259
|
||||
/* .long_limit = */ 700, // Maximum pulse period (long pulse + fixed gap)
|
||||
/* .reset_limit = */ 700, // We just want 1 package
|
||||
|
|
|
@ -207,9 +207,11 @@ static int lacrossetx_callback(uint8_t bb[BITBUF_ROWS][BITBUF_COLS],
|
|||
r_device lacrossetx = {
|
||||
/* .id = */11,
|
||||
/* .name = */"LaCrosse TX Temperature / Humidity Sensor",
|
||||
/* .modulation = */OOK_PWM_P,
|
||||
///* .modulation = */OOK_PWM_P,
|
||||
/* .modulation = */OOK_PULSE_PWM_STARTBIT,
|
||||
/* .short_limit = */238,
|
||||
/* .long_limit = */750,
|
||||
/* .reset_limit = */8000,
|
||||
///* .reset_limit = */8000,
|
||||
/* .reset_limit = */2000,
|
||||
/* .json_callback = */&lacrossetx_callback,
|
||||
/* .disabled = */0, };
|
||||
|
|
|
@ -343,7 +343,7 @@ static int oregon_scientific_callback(uint8_t bb[BITBUF_ROWS][BITBUF_COLS], int1
|
|||
r_device oregon_scientific = {
|
||||
/* .id = */ 11,
|
||||
/* .name = */ "Oregon Scientific Weather Sensor",
|
||||
/* .modulation = */ OOK_MANCHESTER,
|
||||
/* .modulation = */ OOK_PULSE_MANCHESTER_ZEROBIT,
|
||||
/* .short_limit = */ 125,
|
||||
/* .long_limit = */ 0, // not used
|
||||
/* .reset_limit = */ 600,
|
||||
|
|
95
src/pulse_demod.c
Normal file
95
src/pulse_demod.c
Normal file
|
@ -0,0 +1,95 @@
|
|||
/**
|
||||
* Pulse demodulation functions
|
||||
*
|
||||
* Binary demodulators (PWM/PPM/Manchester/...) using a pulse data structure as input
|
||||
*
|
||||
* Copyright (C) 2015 Tommy Vestermark
|
||||
* 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 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*/
|
||||
|
||||
#include "pulse_demod.h"
|
||||
#include "bitbuffer.h"
|
||||
#include <stdio.h>
|
||||
|
||||
int pulse_demod_pwm(const pulse_data_t *pulses, struct protocol_state *device, int start_bit) {
|
||||
int events = 0;
|
||||
int start_bit_detected = 0;
|
||||
bitbuffer_t bits = {0};
|
||||
|
||||
for(unsigned n = 0; n < pulses->num_pulses; ++n) {
|
||||
|
||||
// Should we disregard startbit?
|
||||
if(start_bit == 1 && start_bit_detected == 0) {
|
||||
start_bit_detected = 1;
|
||||
} else {
|
||||
// Detect pulse width
|
||||
if(pulses->pulse[n] <= (unsigned)device->short_limit) {
|
||||
bitbuffer_add_bit(&bits, 1);
|
||||
} else {
|
||||
bitbuffer_add_bit(&bits, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// End of Message?
|
||||
if(pulses->gap[n] > (unsigned)device->reset_limit) {
|
||||
if (device->callback) {
|
||||
events += device->callback(bits.bits_buffer, bits.bits_per_row);
|
||||
bitbuffer_clear(&bits);
|
||||
start_bit_detected = 0;
|
||||
} else {
|
||||
bitbuffer_print(&bits);
|
||||
}
|
||||
// Check for new packet in multipacket
|
||||
} else if(pulses->gap[n] > (unsigned)device->long_limit) {
|
||||
bitbuffer_add_row(&bits);
|
||||
start_bit_detected = 0;
|
||||
}
|
||||
}
|
||||
return events;
|
||||
}
|
||||
|
||||
|
||||
int pulse_demod_manchester_zerobit(const pulse_data_t *pulses, struct protocol_state *device) {
|
||||
int events = 0;
|
||||
unsigned time_since_last = 0;
|
||||
bitbuffer_t bits = {0};
|
||||
|
||||
// First rising edge is allways counted as a zero (Seems to be hardcoded policy for the Oregon Scientific sensors...)
|
||||
bitbuffer_add_bit(&bits, 0);
|
||||
|
||||
for(unsigned n = 0; n < pulses->num_pulses; ++n) {
|
||||
// Falling edge is on end of pulse
|
||||
if(pulses->pulse[n] + time_since_last > (unsigned)(device->short_limit + (device->short_limit>>1))) {
|
||||
// Last bit was recorded more than short_limit*1.5 samples ago
|
||||
// so this pulse start must be a data edge (falling data edge means bit = 1)
|
||||
bitbuffer_add_bit(&bits, 1);
|
||||
time_since_last = 0;
|
||||
} else {
|
||||
time_since_last += pulses->pulse[n];
|
||||
}
|
||||
|
||||
// End of Message?
|
||||
if(pulses->gap[n] > (unsigned)device->reset_limit) {
|
||||
if (device->callback) {
|
||||
events += device->callback(bits.bits_buffer, bits.bits_per_row);
|
||||
bitbuffer_clear(&bits);
|
||||
bitbuffer_add_bit(&bits, 0); // Prepare for new message with hardcoded 0
|
||||
time_since_last = 0;
|
||||
} else {
|
||||
bitbuffer_print(&bits);
|
||||
}
|
||||
// Rising edge is on end of gap
|
||||
} else if(pulses->gap[n] + time_since_last > (unsigned)(device->short_limit + (device->short_limit>>1))) {
|
||||
// Last bit was recorded more than short_limit*1.5 samples ago
|
||||
// so this pulse end is a data edge (rising data edge means bit = 0)
|
||||
bitbuffer_add_bit(&bits, 0);
|
||||
time_since_last = 0;
|
||||
} else {
|
||||
time_since_last += pulses->gap[n];
|
||||
}
|
||||
}
|
||||
return events;
|
||||
}
|
275
src/pulse_detect.c
Normal file
275
src/pulse_detect.c
Normal file
|
@ -0,0 +1,275 @@
|
|||
/**
|
||||
* Pulse detection functions
|
||||
*
|
||||
* Copyright (C) 2015 Tommy Vestermark
|
||||
* 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 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*/
|
||||
|
||||
#include "pulse_detect.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
void pulse_data_clear(pulse_data_t *data) {
|
||||
data->num_pulses = 0;
|
||||
for(unsigned n = 0; n < PD_MAX_PULSES; ++n) {
|
||||
data->pulse[n] = 0;
|
||||
data->gap[n] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void pulse_data_print(const pulse_data_t *data) {
|
||||
fprintf(stderr, "Pulse data: %u pulses\n", data->num_pulses);
|
||||
for(unsigned n = 0; n < data->num_pulses; ++n) {
|
||||
fprintf(stderr, "[%3u] Pulse: %4u, Gap: %4u\n", n, data->pulse[n], data->gap[n]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Internal state data for detect_pulse_package()
|
||||
typedef struct {
|
||||
enum {
|
||||
PD_STATE_IDLE = 0,
|
||||
PD_STATE_PULSE = 1,
|
||||
PD_STATE_GAP = 2
|
||||
} state;
|
||||
unsigned int pulse_length; // Counter for internal pulse detection
|
||||
unsigned int max_pulse; // Size of biggest pulse detected
|
||||
unsigned int max_gap; // Size of biggest gap detected
|
||||
|
||||
unsigned int data_counter; // Counter for how much of data chunck is processed
|
||||
} pulse_state_t;
|
||||
static pulse_state_t pulse_state;
|
||||
|
||||
|
||||
int detect_pulse_package(const int16_t *envelope_data, uint32_t len, int16_t level_limit, pulse_data_t *pulses) {
|
||||
|
||||
pulse_state_t *s = &pulse_state;
|
||||
|
||||
// Process all new samples
|
||||
while(s->data_counter < len) {
|
||||
switch (s->state) {
|
||||
case PD_STATE_IDLE:
|
||||
s->pulse_length = 0;
|
||||
s->max_pulse = 0;
|
||||
s->max_gap = 0;
|
||||
if (envelope_data[s->data_counter] > level_limit) {
|
||||
s->state = PD_STATE_PULSE;
|
||||
}
|
||||
break;
|
||||
case PD_STATE_PULSE:
|
||||
s->pulse_length++;
|
||||
// End of pulse detected?
|
||||
if (envelope_data[s->data_counter] < level_limit) { // Gap?
|
||||
pulses->pulse[pulses->num_pulses] = s->pulse_length; // Store pulse width
|
||||
|
||||
// EOP if pulse is too long
|
||||
if (s->pulse_length > PD_MAX_PULSE_LENGTH) {
|
||||
pulses->num_pulses++; // Store last pulse (with no gap)
|
||||
s->state = PD_STATE_IDLE;
|
||||
return 1; // End Of Package!!
|
||||
}
|
||||
|
||||
// Find largest pulse
|
||||
if(s->pulse_length > s->max_pulse) {
|
||||
s->max_pulse = s->pulse_length;
|
||||
}
|
||||
s->pulse_length = 0;
|
||||
s->state = PD_STATE_GAP;
|
||||
}
|
||||
break;
|
||||
case PD_STATE_GAP:
|
||||
s->pulse_length++;
|
||||
// New pulse detected?
|
||||
if (envelope_data[s->data_counter] > level_limit) { // New pulse?
|
||||
pulses->gap[pulses->num_pulses] = s->pulse_length; // Store gap width
|
||||
pulses->num_pulses++; // Next pulse
|
||||
|
||||
// EOP if too many pulses
|
||||
if (pulses->num_pulses >= PD_MAX_PULSES) {
|
||||
s->state = PD_STATE_IDLE;
|
||||
return 1; // End Of Package!!
|
||||
}
|
||||
|
||||
// Find largest gap
|
||||
if(s->pulse_length > s->max_gap) {
|
||||
s->max_gap = s->pulse_length;
|
||||
}
|
||||
s->pulse_length = 0;
|
||||
s->state = PD_STATE_PULSE;
|
||||
}
|
||||
|
||||
// EOP if gap is too long
|
||||
if ((s->pulse_length > (s->max_pulse * PD_MAX_GAP_RATIO))
|
||||
// || (s->pulse_length > (s->max_gap * PD_MAX_GAP_RATIO) && s->max_gap !=0)
|
||||
) {
|
||||
pulses->gap[pulses->num_pulses] = s->pulse_length; // Store gap width
|
||||
pulses->num_pulses++; // Store last pulse
|
||||
s->state = PD_STATE_IDLE;
|
||||
return 1; // End Of Package!!
|
||||
}
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "demod_OOK(): Unknown state!!\n");
|
||||
s->state = PD_STATE_IDLE;
|
||||
} // switch
|
||||
// Todo: check for too many pulses
|
||||
s->data_counter++;
|
||||
} // while
|
||||
|
||||
s->data_counter = 0;
|
||||
return 0; // Out of data
|
||||
}
|
||||
|
||||
#define MAX_HIST_BINS 16
|
||||
|
||||
/// Histogram data for single bin
|
||||
typedef struct {
|
||||
unsigned count;
|
||||
unsigned sum;
|
||||
unsigned mean;
|
||||
unsigned min;
|
||||
unsigned max;
|
||||
} hist_bin_t;
|
||||
|
||||
/// Histogram data for all bins
|
||||
typedef struct {
|
||||
unsigned bins_count;
|
||||
hist_bin_t bins[MAX_HIST_BINS];
|
||||
} histogram_t;
|
||||
|
||||
|
||||
// Helper macros
|
||||
#define max(a,b) ((a) > (b) ? (a) : (b))
|
||||
#define min(a,b) ((a) < (b) ? (a) : (b))
|
||||
|
||||
|
||||
/// Generate a histogram (unsorted)
|
||||
void histogram_sum(histogram_t *hist, const unsigned *data, unsigned len, float tolerance) {
|
||||
unsigned bin; // Iterator will be used outside for!
|
||||
|
||||
for(unsigned n = 0; n < len; ++n) {
|
||||
for(bin = 0; bin < hist->bins_count; ++bin) {
|
||||
int bn = data[n];
|
||||
int bm = hist->bins[bin].mean;
|
||||
if (abs(bn - bm) < (tolerance * max(bn, bm))) {
|
||||
hist->bins[bin].count++;
|
||||
hist->bins[bin].sum += data[n];
|
||||
hist->bins[bin].mean = hist->bins[bin].sum / hist->bins[bin].count;
|
||||
hist->bins[bin].min = min(data[n], hist->bins[bin].min);
|
||||
hist->bins[bin].max = max(data[n], hist->bins[bin].max);
|
||||
break; // Match found!
|
||||
}
|
||||
}
|
||||
// No match found?
|
||||
if(bin == hist->bins_count && bin < MAX_HIST_BINS) {
|
||||
hist->bins[bin].count = 1;
|
||||
hist->bins[bin].sum = data[n];
|
||||
hist->bins[bin].mean = data[n];
|
||||
hist->bins[bin].min = data[n];
|
||||
hist->bins[bin].max = data[n];
|
||||
hist->bins_count++;
|
||||
} // for bin
|
||||
} // for data
|
||||
}
|
||||
|
||||
|
||||
/// Fuse histogram bins with means within tolerance
|
||||
void histogram_fuse_bins(histogram_t *hist, float tolerance) {
|
||||
hist_bin_t zerobin = {0};
|
||||
if (hist->bins_count < 2) return; // Avoid underflow
|
||||
// Compare all bins
|
||||
for(unsigned n = 0; n < hist->bins_count-1; ++n) {
|
||||
for(unsigned m = n+1; m < hist->bins_count; ++m) {
|
||||
int bn = hist->bins[n].mean;
|
||||
int bm = hist->bins[m].mean;
|
||||
if (abs(bn - bm) < (tolerance * max(bn, bm))) {
|
||||
// Fuse data for bin[n] and bin[m]
|
||||
hist->bins[n].count += hist->bins[m].count;
|
||||
hist->bins[n].sum += hist->bins[m].sum;
|
||||
hist->bins[n].mean = hist->bins[n].sum / hist->bins[n].count;
|
||||
hist->bins[n].min = min(hist->bins[n].min, hist->bins[m].min);
|
||||
hist->bins[n].max = max(hist->bins[n].max, hist->bins[m].max);
|
||||
// Delete bin[m]
|
||||
for(unsigned l = m; l < hist->bins_count-1; ++l) {
|
||||
hist->bins[l] = hist->bins[l+1];
|
||||
}
|
||||
hist->bins_count--;
|
||||
hist->bins[hist->bins_count] = zerobin;
|
||||
m--; // Compare new bin in same place!
|
||||
} // if within tolerance
|
||||
} // for m
|
||||
} // for n
|
||||
}
|
||||
|
||||
|
||||
/// Print a histogram
|
||||
void histogram_print(const histogram_t *hist) {
|
||||
for(unsigned n = 0; n < hist->bins_count; ++n) {
|
||||
fprintf(stderr, " [%2u] mean: %4u (%u/%u),\t count: %3u\n", n,
|
||||
hist->bins[n].mean,
|
||||
hist->bins[n].min,
|
||||
hist->bins[n].max,
|
||||
hist->bins[n].count);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#define TOLERANCE (0.2) // 20% tolerance should still discern between the pulse widths: 0.33, 0.66, 1.0
|
||||
|
||||
/// Analyze the statistics of a pulse data structure and print result
|
||||
void pulse_analyzer(const pulse_data_t *data)
|
||||
{
|
||||
// Generate pulse period data
|
||||
pulse_data_t pulse_periods = {0};
|
||||
pulse_periods.num_pulses = data->num_pulses;
|
||||
for(unsigned n = 0; n < pulse_periods.num_pulses; ++n) {
|
||||
pulse_periods.pulse[n] = data->pulse[n] + data->gap[n];
|
||||
}
|
||||
|
||||
histogram_t hist_pulses = {0};
|
||||
histogram_t hist_gaps = {0};
|
||||
histogram_t hist_periods = {0};
|
||||
|
||||
// Generate statistics
|
||||
histogram_sum(&hist_pulses, data->pulse, data->num_pulses, TOLERANCE);
|
||||
histogram_sum(&hist_gaps, data->gap, data->num_pulses-1, TOLERANCE); // Leave out last gap (end)
|
||||
histogram_sum(&hist_periods, pulse_periods.pulse, pulse_periods.num_pulses-1, TOLERANCE); // Leave out last gap (end)
|
||||
|
||||
// Fuse overlapping bins
|
||||
histogram_fuse_bins(&hist_pulses, TOLERANCE);
|
||||
histogram_fuse_bins(&hist_gaps, TOLERANCE);
|
||||
histogram_fuse_bins(&hist_periods, TOLERANCE);
|
||||
|
||||
fprintf(stderr, "\nAnalyzing pulses...\n");
|
||||
fprintf(stderr, "Total number of pulses: %u\n", data->num_pulses);
|
||||
fprintf(stderr, "Pulse width distribution:\n");
|
||||
histogram_print(&hist_pulses);
|
||||
fprintf(stderr, "Gap width distribution:\n");
|
||||
histogram_print(&hist_gaps);
|
||||
fprintf(stderr, "Pulse period distribution:\n");
|
||||
histogram_print(&hist_periods);
|
||||
|
||||
fprintf(stderr, "Guessing modulation: ");
|
||||
if(data->num_pulses == 1) {
|
||||
fprintf(stderr, "Single pulse detected. Probably Frequency Shift Keying or just noise...\n");
|
||||
} else if(hist_pulses.bins_count == 1 && hist_gaps.bins_count == 2 && hist_periods.bins_count == 2) {
|
||||
fprintf(stderr, "Pulse Position Modulation with fixed pulse width\n");
|
||||
} else if(hist_pulses.bins_count == 2 && hist_gaps.bins_count == 2 && hist_periods.bins_count == 1) {
|
||||
fprintf(stderr, "Pulse Width Modulation with fixed period\n");
|
||||
} else if(hist_pulses.bins_count == 2 && hist_gaps.bins_count == 1 && hist_periods.bins_count == 2) {
|
||||
fprintf(stderr, "Pulse Width Modulation with fixed gap\n");
|
||||
} else if(hist_pulses.bins_count == 2 && hist_gaps.bins_count == 2 && hist_periods.bins_count == 3) {
|
||||
fprintf(stderr, "Manchester coding\n");
|
||||
} else if(hist_pulses.bins_count == 3 && hist_gaps.bins_count == 3 && hist_periods.bins_count == 1) {
|
||||
fprintf(stderr, "Pulse Width Modulation with startbit/delimiter\n");
|
||||
} else {
|
||||
fprintf(stderr, "No clue...\n");
|
||||
}
|
||||
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
|
169
src/rtl_433.c
169
src/rtl_433.c
|
@ -22,6 +22,9 @@
|
|||
|
||||
#include "rtl-sdr.h"
|
||||
#include "rtl_433.h"
|
||||
#include "pulse_detect.h"
|
||||
#include "pulse_demod.h"
|
||||
|
||||
|
||||
static int do_exit = 0;
|
||||
static int do_exit_async = 0, frequencies = 0, events = 0;
|
||||
|
@ -93,37 +96,6 @@ int debug_callback(uint8_t bb[BITBUF_ROWS][BITBUF_COLS], int16_t bits_per_row[BI
|
|||
return 0;
|
||||
}
|
||||
|
||||
struct protocol_state {
|
||||
int (*callback)(uint8_t bits_buffer[BITBUF_ROWS][BITBUF_COLS], int16_t bits_per_row[BITBUF_ROWS]);
|
||||
|
||||
/* bits state */
|
||||
int bits_col_idx;
|
||||
int bits_row_idx;
|
||||
int bits_bit_col_idx;
|
||||
uint8_t bits_buffer[BITBUF_ROWS][BITBUF_COLS];
|
||||
int16_t bits_per_row[BITBUF_ROWS];
|
||||
int bit_rows;
|
||||
unsigned int modulation;
|
||||
|
||||
/* demod state */
|
||||
int pulse_length;
|
||||
int pulse_count;
|
||||
int pulse_distance;
|
||||
int sample_counter;
|
||||
int start_c;
|
||||
|
||||
int packet_present;
|
||||
int pulse_start;
|
||||
int real_bits;
|
||||
int start_bit;
|
||||
/* pwm limits */
|
||||
int short_limit;
|
||||
int long_limit;
|
||||
int reset_limit;
|
||||
|
||||
|
||||
};
|
||||
|
||||
struct dm_state {
|
||||
FILE *file;
|
||||
int save_data;
|
||||
|
@ -145,6 +117,7 @@ struct dm_state {
|
|||
int r_dev_num;
|
||||
struct protocol_state *r_devs[MAX_PROTOCOLS];
|
||||
|
||||
pulse_data_t pulse_data;
|
||||
};
|
||||
|
||||
void usage(r_device *devices) {
|
||||
|
@ -756,105 +729,6 @@ static void pwm_p_decode(struct dm_state *demod, struct protocol_state* p, int16
|
|||
}
|
||||
}
|
||||
|
||||
/* Machester Decode for Oregon Scientific Weather Sensors
|
||||
Decode data streams sent by Oregon Scientific v2.1, and v3 weather sensors.
|
||||
With manchester encoding, both the pulse width and pulse distance vary. Clock sync
|
||||
is recovered from the data stream based on pulse widths and distances exceeding a
|
||||
minimum threashold (short limit* 1.5).
|
||||
*/
|
||||
static void manchester_decode(struct dm_state *demod, struct protocol_state* p, int16_t *buf, uint32_t len) {
|
||||
unsigned int i;
|
||||
|
||||
if (p->sample_counter == 0)
|
||||
p->sample_counter = p->short_limit*2;
|
||||
|
||||
for (i=0 ; i<len ; i++) {
|
||||
|
||||
if (p->start_c)
|
||||
p->sample_counter++; /* For this decode type, sample counter is count since last data bit recorded */
|
||||
|
||||
if (!p->pulse_count && (buf[i] > demod->level_limit)) { /* Pulse start (rising edge) */
|
||||
p->pulse_count = 1;
|
||||
if (p->sample_counter > (p->short_limit + (p->short_limit>>1))) {
|
||||
/* Last bit was recorded more than short_limit*1.5 samples ago */
|
||||
/* so this pulse start must be a data edge (rising data edge means bit = 0) */
|
||||
demod_add_bit(p, 0);
|
||||
p->sample_counter=1;
|
||||
p->start_c++; // start_c counts number of bits received
|
||||
}
|
||||
}
|
||||
if (p->pulse_count && (buf[i] <= demod->level_limit)) { /* Pulse end (falling edge) */
|
||||
if (p->sample_counter > (p->short_limit + (p->short_limit>>1))) {
|
||||
/* Last bit was recorded more than "short_limit*1.5" samples ago */
|
||||
/* so this pulse end is a data edge (falling data edge means bit = 1) */
|
||||
demod_add_bit(p, 1);
|
||||
p->sample_counter=1;
|
||||
p->start_c++;
|
||||
}
|
||||
p->pulse_count = 0;
|
||||
}
|
||||
|
||||
if (p->sample_counter > p->reset_limit) {
|
||||
//fprintf(stderr, "manchester_decode number of bits received=%d\n",p->start_c);
|
||||
if (p->callback)
|
||||
events+=p->callback(p->bits_buffer, p->bits_per_row);
|
||||
else
|
||||
demod_print_bits_packet(p);
|
||||
demod_reset_bits_packet(p);
|
||||
p->sample_counter = p->short_limit*2;
|
||||
p->start_c = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Pulse Width Modulation. No startbit removal */
|
||||
static void pwm_raw_decode(struct dm_state *demod, struct protocol_state* p, int16_t *buf, uint32_t len) {
|
||||
unsigned int i;
|
||||
for (i = 0; i < len; i++) {
|
||||
if (p->start_c) p->sample_counter++;
|
||||
|
||||
// Detect Pulse Start (leading edge)
|
||||
if (!p->pulse_start && (buf[i] > demod->level_limit)) {
|
||||
p->pulse_start = 1;
|
||||
p->sample_counter = 0;
|
||||
// Check for first bit in sequence
|
||||
if(!p->start_c) {
|
||||
p->start_c = 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Detect Pulse End (trailing edge)
|
||||
if (p->pulse_start && (buf[i] < demod->level_limit)) {
|
||||
p->pulse_start = 0;
|
||||
if (p->sample_counter <= p->short_limit) {
|
||||
demod_add_bit(p, 1);
|
||||
} else {
|
||||
demod_add_bit(p, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Detect Pulse period overrun
|
||||
if (p->sample_counter == p->long_limit) {
|
||||
demod_next_bits_packet(p);
|
||||
}
|
||||
|
||||
// Detect Pulse exceeding reset limit
|
||||
if (p->sample_counter > p->reset_limit) {
|
||||
p->sample_counter = 0;
|
||||
p->start_c = 0;
|
||||
p->pulse_start = 0;
|
||||
|
||||
if (p->callback)
|
||||
events+=p->callback(p->bits_buffer, p->bits_per_row);
|
||||
else
|
||||
demod_print_bits_packet(p);
|
||||
|
||||
demod_reset_bits_packet(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** Something that might look like a IIR lowpass filter
|
||||
*
|
||||
|
@ -923,6 +797,7 @@ static void rtlsdr_callback(unsigned char *buf, uint32_t len, void *ctx) {
|
|||
if (demod->analyze) {
|
||||
pwm_analyze(demod, demod->f_buf, len / 2);
|
||||
} else {
|
||||
// Loop through all demodulators for all samples (CPU intensive!)
|
||||
for (i = 0; i < demod->r_dev_num; i++) {
|
||||
switch (demod->r_devs[i]->modulation) {
|
||||
case OOK_PWM_D:
|
||||
|
@ -931,16 +806,40 @@ static void rtlsdr_callback(unsigned char *buf, uint32_t len, void *ctx) {
|
|||
case OOK_PWM_P:
|
||||
pwm_p_decode(demod, demod->r_devs[i], demod->f_buf, len / 2);
|
||||
break;
|
||||
case OOK_MANCHESTER:
|
||||
manchester_decode(demod, demod->r_devs[i], demod->f_buf, len/2);
|
||||
break;
|
||||
case OOK_PWM_RAW:
|
||||
pwm_raw_decode(demod, demod->r_devs[i], demod->f_buf, len / 2);
|
||||
// Add pulse demodulators here
|
||||
case OOK_PULSE_PWM_STARTBIT:
|
||||
case OOK_PULSE_PWM_RAW:
|
||||
case OOK_PULSE_MANCHESTER_ZEROBIT:
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "Unknown modulation %d in protocol!\n", demod->r_devs[i]->modulation);
|
||||
}
|
||||
}
|
||||
// Detect a package and loop through demodulators with pulse data
|
||||
while(detect_pulse_package(demod->f_buf, len/2, demod->level_limit, &demod->pulse_data)) {
|
||||
for (i = 0; i < demod->r_dev_num; i++) {
|
||||
switch (demod->r_devs[i]->modulation) {
|
||||
// Old style decoders
|
||||
case OOK_PWM_D:
|
||||
case OOK_PWM_P:
|
||||
break;
|
||||
case OOK_PULSE_PWM_STARTBIT:
|
||||
pulse_demod_pwm(&demod->pulse_data, demod->r_devs[i], 1);
|
||||
break;
|
||||
case OOK_PULSE_PWM_RAW:
|
||||
pulse_demod_pwm(&demod->pulse_data, demod->r_devs[i], 0);
|
||||
break;
|
||||
case OOK_PULSE_MANCHESTER_ZEROBIT:
|
||||
pulse_demod_manchester_zerobit(&demod->pulse_data, demod->r_devs[i]);
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "Unknown modulation %d in protocol!\n", demod->r_devs[i]->modulation);
|
||||
}
|
||||
} // for demodulators
|
||||
// if(debug_output) pulse_data_print(&demod->pulse_data);
|
||||
if(debug_output) pulse_analyzer(&demod->pulse_data);
|
||||
pulse_data_clear(&demod->pulse_data);
|
||||
}
|
||||
}
|
||||
|
||||
if (demod->save_data) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue