Implements callbacks tied to specific codes

- The new function register_callback allows to call a function when a
  given code is received. Allows to:

  - Register as many callbacks as needed

  - Optionally call wait_free_433() before calling callback

  - Provide a minimum delay betwen two consecutive calls of the
    callback, allowing to de-duplicate code reception (a very common
    situation with RF 433 Mhz code reception).
This commit is contained in:
Sébastien Millet 2021-07-11 18:59:25 +02:00
parent 386e52e1e2
commit f535d56cdb
8 changed files with 528 additions and 6 deletions

View File

@ -28,8 +28,8 @@
#include <Arduino.h>
#include <stdarg.h>
static char buffer[150];
static char progmem_reading_buffer[100];
static char buffer[120];
static char progmem_reading_buffer[70];
#ifdef __arm__
// should use uinstd.h to define sbrk but Due causes a conflict

View File

@ -40,6 +40,10 @@ for another example.
See [examples/03_react_on_code/03_react_on_code.ino](examples/03_react_on_code/03_react_on_code.ino)
for an example with code check.
See [examples/04_callback/04_callback.ino](examples/04_callback/04_callback.ino)
for an example with callback functions registered to be called when specific
codes are received.
More details
------------

View File

@ -1131,7 +1131,9 @@ byte Track::pin_number = 99;
Track::Track(int arg_pin_number, byte mood):
r_low(mood),
r_high(mood) {
r_high(mood),
head(nullptr),
opt_wait_free_433_before_calling_callbacks(false) {
pin_number = arg_pin_number;
treset();
}
@ -1563,6 +1565,7 @@ bool Track::do_events() {
dbgf("IH_max_pending_timings = %d", ih_get_max_pending_timings());
rawcode.debug_rawcode();
#endif
check_registered_callbacks();
return true;
}
return false;
@ -1748,6 +1751,84 @@ Decoder* Track::get_data(uint16_t filter, byte convention) {
return pdec0;
}
callback_t* Track::get_tail(const callback_t* h) {
const callback_t* pc = h;
if (pc) {
while (pc->next)
pc = pc->next;
}
return (callback_t*)pc;
}
void Track::setopt_wait_free_433_before_calling_callbacks(const bool val) {
opt_wait_free_433_before_calling_callbacks = val;
}
void Track::check_registered_callbacks() {
uint32_t t0 = millis();
bool flag_call_wait_free_433 = opt_wait_free_433_before_calling_callbacks;
Decoder *pdec0 = get_data(RF433ANY_FD_DECODED | RF433ANY_FD_DEDUP);
Decoder *pdec = pdec0;
while (pdec) {
const BitVector *pdata = pdec->get_pdata();
assert(pdata); // Must be the case (RF433ANY_FD_DECODED in the call to
// get_data() above).
for (callback_t *pc = head; pc; pc = pc->next) {
if (pc->encoding == RF433ANY_ID_ANY_ENCODING ||
pdec->get_id() == pc->encoding) {
if (!pdata->cmp(pc->pcode)) {
if (!pc->min_delay_between_two_calls ||
!pc->last_trigger ||
t0 >=
pc->last_trigger
+ pc->min_delay_between_two_calls
) {
if (flag_call_wait_free_433) {
wait_free_433();
flag_call_wait_free_433 = false;
}
pc->last_trigger = t0;
pc->func(pc->data);
}
}
}
}
pdec = pdec->get_next();
}
delete pdec0;
}
void Track::register_callback(byte encoding, const BitVector *pcode, void *data,
void (*func)(void *data), uint32_t min_delay_between_two_calls) {
assert(encoding == RF433ANY_ID_ANY_ENCODING ||
encoding == RF433ANY_ID_TRIBIT ||
encoding == RF433ANY_ID_TRIBIT_INV ||
encoding == RF433ANY_ID_MANCHESTER);
assert(pcode);
assert(func);
callback_t *pc = new callback_t;
pc->encoding = encoding;
pc->pcode = pcode;
pc->data = data;
pc->func = func;
pc->min_delay_between_two_calls = min_delay_between_two_calls;
pc->last_trigger = 0;
pc->next = nullptr;
callback_t *tail = get_tail(head);
if (tail) {
tail->next = pc;
} else {
head = pc;
}
}
#ifdef RF433ANY_DBG_TIMINGS
void Track::dbg_timings() const {
for (unsigned int i = 0; i + 1 < ih_dbg_pos; i += 2) {

View File

@ -328,6 +328,8 @@ enum class Signal {
// always produce a successful result.
#define RF433ANY_ID_END 5 // End of enumeration of real decoders
#define RF433ANY_ID_ANY_ENCODING 99
class Decoder {
private:
Decoder *next;
@ -596,6 +598,17 @@ struct IH_timing_t {
}
};
struct callback_t {
byte encoding;
const BitVector *pcode;
void *data;
void (*func)(void *data);
uint32_t min_delay_between_two_calls;
uint32_t last_trigger;
callback_t *next;
};
// NOTE - ABOUT STATIC MEMBER VARIABLES AND FUNCTIONS IN THE TRACK CLASS
// The class is designed so that one object is useful at a time. This comes
// from the fact that we attach interrupt handler to a static method (as is
@ -608,8 +621,6 @@ struct IH_timing_t {
typedef enum {TRK_WAIT, TRK_RECV, TRK_DATA} trk_t;
class Track {
private:
#ifdef RF433ANY_DBG_TIMINGS
static uint16_t ih_dbg_timings[40];
static uint16_t ih_dbg_exec[40];
@ -637,9 +648,14 @@ class Track {
RawCode rawcode;
callback_t *head;
bool opt_wait_free_433_before_calling_callbacks;
void reset_border_mgmt();
Decoder* get_data_core(byte convention);
callback_t* get_tail(const callback_t* h);
public:
Track(int arg_pin_number, byte mood = DEFAULT_RAIL_MOOD);
@ -670,6 +686,12 @@ class Track {
void wait_free_433();
Decoder* get_data(uint16_t filter, byte convention = RF433ANY_CONV0);
void setopt_wait_free_433_before_calling_callbacks(const bool val);
void register_callback(byte encoding, const BitVector *pcode,
void *data, void (*func)(void *data),
uint32_t min_delay_between_two_calls);
void check_registered_callbacks();
};
#endif // _RF433ANY_H

View File

@ -0,0 +1,62 @@
// 04_callback.ino
// Example sketch that comes along with RF433any library
// Shows how to trigger different actions depending on code received, by
// registering callback function.
/*
Copyright 2021 Sébastien Millet
`RF433any' is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
`RF433any' 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 Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this program. If not, see
<https://www.gnu.org/licenses>.
*/
//
// Schematic: Radio Frequencies RECEIVER plugged on D2
//
#include "RF433any.h"
#include <Arduino.h>
#define PIN_RFINPUT 2
Track track(PIN_RFINPUT);
byte dummy;
void on_call(void *data) {
byte n = (byte *)data - &dummy;
Serial.print("Received code number ");
Serial.print(n);
Serial.print("\n");
}
void setup() {
pinMode(PIN_RFINPUT, INPUT);
Serial.begin(115200);
track.register_callback(RF433ANY_ID_TRIBIT,
new BitVector(32, 4, 0xb9, 0x35, 0x6d, 0x00),
(void *)(&dummy + 1), on_call, 2000);
track.register_callback(RF433ANY_ID_TRIBIT,
new BitVector(32, 4, 0xb5, 0x35, 0x6d, 0x00),
(void *)(&dummy + 2), on_call, 2000);
}
void loop() {
track.treset();
while (!track.do_events())
delay(1);
}
// vim: ts=4:sw=4:tw=80:et

View File

@ -0,0 +1,10 @@
ARDUINO_DIR = /usr/share/arduino
ARDUINO_LIBS = RF433any
ARDMK_DIR = /home/sebastien/.arduino_mk
# USER_LIB_PATH = /home/sebastien/travail/cpp/seb/arduino/libraries
BOARD_TAG = uno
MCU = atmega328
include $(ARDMK_DIR)/Arduino.mk

343
examples/04_callback/am Executable file
View File

@ -0,0 +1,343 @@
#!/usr/bin/bash
# am
# Copyright 2019, 2020, 2021 Sébastien Millet
# Can perform the following:
# 1. Compile the code
# 2. Upload to Arduino
# 3. Read (continually) what is arriving from the USB port the Arduino is
# connected to
# Versions history (as of 1.3)
# 1.3 Output from Arduino is recorded in files named with numbers instead of
# date-time string.
# 1.4 Adds -t (--testplan) option, to set TESTPLAN macro
# 1.5 -t (or --testplan) now comes with a value, so as to manage multiple test
# plans.
# 1.6 Updated to work fine with Arch arduino package instead of the manually
# installed (from tar.gz source) package used so far.
# 1.7 Renames archlinux-arduino back to arduino, and created corresponding
# symlink (was cleaner to do s).
set -euo pipefail
VERSION=1.7
PORT=
BOARD=
SPEED=
FQBN=
BUILDDIR=
RECORDDIR=out
READSPEED=
RECORDFILE=
UPLOAD="no"
VERBOSE="no"
CATUSB="no"
STTY="no"
RECORDUSB="no"
COMPILE="yes"
TESTPLAN=
DISPLAYSEP=no
function finish {
if [ "${DISPLAYSEP}" == "yes" ]; then
echo "-----END ARDUINO OUTPUT-----" | tee -a "${RECORDFILE}"
fi
}
trap finish EXIT
function usage {
echo "Usage:"
echo " am [OPTIONS...] FILE"
echo "Compile FILE using arduino-builder."
echo "Example: am sketch.ino"
echo ""
echo "ENVIRONMENT VARIABLES"
echo " If ARDUINO_USER_LIBS is defined and non empty, then arduino-builder"
echo " is called with the supplementary option -libraries followed by"
echo " ARDUINO_USER_LIBS' value."
echo ""
echo "OPTIONS"
echo " -h --help Display this help screen"
echo " -V --version Output version information and quit"
echo " -v --verbose Be more talkative"
echo " -u --upload Upload compiled code into Arduino"
echo " -b --board Board, either 'uno' or 'nano'"
echo " -p --port Port, for ex. '/dev/ttyUSB0'"
echo " -s --speed Upload speed, for ex. 115200"
echo " Normally, speed is infered from device type:"
echo " 115200 for Uno, 57600 for Nano"
echo " -B --fqbn Board Fully Qualified Name, like 'arduino:avr:uno'"
echo " -d --builddir Build directory"
echo " -c --catusb Display (continually) what Arduino writes on USB"
echo " --stty Tune stty properly for later communication (implied"
echo " by --catusb)"
echo " -r --recordusb Write USB (continually) to a file (implies -c)"
echo " --recordfile Output file if -r option is set"
echo " -n --nocompile Don't compile code"
echo " --readspeed Read speed of USB. If not specified, this script"
echo " will try to infere it from source file. If it"
echo " fails, it'll fallback to 9600."
echo " This option is useful only if USB is read"
echo " (-c or --stty option set)"
echo " -t --testplan Set TESTPLAN macro value"
echo " (as if #define TESTPLAN VALUE)"
exit 1
}
function version {
echo "am version ${VERSION}"
exit
}
OPTS=$(getopt -o hVvub:p:s:B:d:crnt: --long help,version,verbose,upload,board:,port:,speed:,fqbn:,builddir:,catusb,stty,recordusb,nocompile,readspeed:,recordfile:,testplan: -n 'am' -- "$@")
eval set -- "$OPTS"
while true; do
case "$1" in
-h | --help ) usage; shift ;;
-V | --version ) version; shift ;;
-v | --verbose ) VERBOSE="yes"; shift ;;
-u | --upload ) UPLOAD="yes"; shift ;;
-b | --board ) BOARD="$2"; shift 2 ;;
-p | --port ) PORT="$2"; shift 2 ;;
-s | --speed ) SPEED="$2"; shift 2 ;;
-B | --fqbn ) FQBN="$2"; shift 2 ;;
-d | --builddir ) BUILDDIR="$2"; shift 2 ;;
-c | --catusb ) CATUSB="yes"; shift ;;
-r | --recordusb ) RECORDUSB="yes"; CATUSB="yes"; shift ;;
-n | --nocompile ) COMPILE="no"; shift ;;
--readspeed ) READSPEED="$2"; shift 2 ;;
--recordfile ) RECORDFILE="$2"; shift 2 ;;
--stty ) STTY="yes"; shift ;;
-t | --testplan ) TESTPLAN="$2"; shift 2 ;;
-- ) shift; break ;;
* ) break ;;
esac
done
FILE=${1:-}
TRAILINGOPTS=${2:-}
if [ -n "${TRAILINGOPTS}" ]; then
echo "Error: trailing options"
exit 1;
fi
if [ -z "${FILE}" ]; then
echo "Error: no input file"
exit 1;
fi
set +e
if [ -n "${BOARD}" ]; then
if [ "${BOARD}" != "uno" ] && [ "${BOARD}" != "nano" ]; then
echo "Error: board '${BOARD}' unknown"
exit 1
fi
fi
#ARDUINODIR=$(LANG='' type -a arduino \
# | tail -n 1 \
# | sed 's/\S\+\sis\s//')
#ARDUINODIR=$(readlink -f "${ARDUINODIR}")
#ARDUINODIR=$(dirname "${ARDUINODIR}")
ARDUINODIR=/usr/share/arduino
COUNTUNO=$(compgen -G '/dev/ttyACM*' | wc -l)
COUNTNANO=$(compgen -G '/dev/ttyUSB*' | wc -l)
if [ -z "${BOARD}" ]; then
if [ "${COUNTUNO}" -ge 1 ] && [ "${COUNTNANO}" -ge 1 ]; then
echo "Error: cannot guess board, found ${COUNTUNO} uno(s), ${COUNTNANO} nano(s)"
exit 10
fi
if [ "${COUNTUNO}" -ge 1 ]; then
BOARD=uno
elif [ "${COUNTNANO}" -ge 1 ]; then
BOARD=nano
fi
if [ -z "${BOARD}" ]; then
echo "Error: cannot guess board, none found";
exit 10
fi
fi
if [ "${UPLOAD}" == "yes" ] || [ "${CATUSB}" == "yes" ]; then
if [ -z "${PORT}" ]; then
if [ "${BOARD}" == "uno" ]; then
COUNT=${COUNTUNO}
PORT=$(compgen -G '/dev/ttyACM*')
elif [ "${BOARD}" == "nano" ]; then
COUNT=${COUNTNANO}
PORT=$(compgen -G '/dev/ttyUSB*')
else
echo "FATAL #001, CHECK THIS CODE"
exit 99
fi
if [ "${COUNT}" -ge 2 ]; then
echo "Error: cannot guess port, more than 1 board '${BOARD}' found"
exit 10
fi
if [ -z "${PORT}" ]; then
echo "Error: cannot guess port, none found"
exit 10
fi
fi
if [ -z "${SPEED}" ]; then
if [ "${BOARD}" == "uno" ]; then
SPEED=115200
elif [ "${BOARD}" == "nano" ]; then
SPEED=57600
else
echo "FATAL #002, CHECK THIS CODE"
exit 99
fi
fi
if [ ! -e "${PORT}" ]; then
echo "Error: port not found"
exit 10
fi
fi
if [ -z "${FQBN}" ]; then
if [ "${BOARD}" == "uno" ]; then
FQBN="arduino:avr:uno"
elif [ "${BOARD}" == "nano" ]; then
FQBN="arduino:avr:nano:cpu=atmega328old"
else
echo "FATAL #003, CHECK THIS CODE"
exit 99
fi
fi
if [ -z "${BUILDDIR}" ]; then
if [[ "${FILE}" == */* ]]; then
BUILDDIR=${FILE%/*}
BUILDDIR="${BUILDDIR%/}/build"
else
BUILDDIR=build
fi
fi
if [ "${RECORDUSB}" == "yes" ]; then
if [ -z "${RECORDFILE}" ]; then
V=${FILE##*/}
V=${V%.*}
V=${V:-out}
PREV=
for i in {15..00}; do
F="${RECORDDIR}/${V}-$i.txt"
if [ -e "${F}" ] && [ -n "${PREV}" ]; then
mv "${F}" "${PREV}"
fi
PREV="${F}"
done
RECORDFILE="${F}"
mkdir -p "${RECORDDIR}"
fi
else
RECORDFILE="/dev/null"
fi
if [ "${VERBOSE}" == "yes" ]; then
echo "-- Settings"
echo "Arduino dir: ${ARDUINODIR}"
echo "Board: ${BOARD}"
echo "Port: ${PORT}"
echo "Speed: ${SPEED}"
echo "Fqbn: ${FQBN}"
echo "Upload: ${UPLOAD}"
echo "Catusb: ${CATUSB}"
echo "Recordusb: ${RECORDUSB}"
echo "Record file: ${RECORDFILE}"
echo "Verbose: ${VERBOSE}"
echo "File: ${FILE}"
echo "Build dir: ${BUILDDIR}"
fi
set -e
if [ "${COMPILE}" == "yes" ]; then
echo "-- Compile"
mkdir -p "${BUILDDIR}"
OPT_LIB=
TMP_ULIB=${ARDUINO_USER_LIBS:-}
if [ -n "${TMP_ULIB}" ]; then
OPT_LIB="-libraries ""${TMP_ULIB}"""
fi
TESTPLAN_OPT=""
if [ -n "${TESTPLAN}" ]; then
TESTPLAN_OPT="-prefs=build.extra_flags=-DTESTPLAN=${TESTPLAN}"
fi
# shellcheck disable=SC2086
# (We don't want to quote OPT_LIB as it can contain multiple options.)
"${ARDUINODIR}/arduino-builder" \
-hardware "${ARDUINODIR}/hardware" \
-tools "${ARDUINODIR}/hardware/tools/avr" \
-tools "${ARDUINODIR}/tools-builder" \
-built-in-libraries "${ARDUINODIR}/libraries" \
${OPT_LIB} \
-fqbn "${FQBN}" \
-build-path "${BUILDDIR}" \
${TESTPLAN_OPT} \
"${FILE}"
fi
FILEBASENAME=${FILE##*/}
if [ "${UPLOAD}" == "yes" ]; then
echo "-- Upload"
"/usr/bin/avrdude" \
-q -q -patmega328p -carduino -P"${PORT}" -b"${SPEED}" -D \
-Uflash:w:"${BUILDDIR}/${FILEBASENAME}".hex:i
fi
if [ "${CATUSB}" == "yes" ] || [ "${STTY}" == "yes" ]; then
if [ -z "${READSPEED}" ]; then
TFILE=$(mktemp)
gcc -fpreprocessed -dD -x c++ -E "${FILE}" > "${TFILE}"
for sp in 9600 19200 28800 38400 57600 115200; do
if grep ${sp} "${TFILE}" > /dev/null; then
READSPEED=${sp}
fi
done
READSPEED=${READSPEED:-9600}
rm "${TFILE}"
fi
stty -F "${PORT}" -hupcl -echo "${READSPEED}"
echo "-- usb setup with speed ${READSPEED}"
fi
if [ "${CATUSB}" == "yes" ]; then
echo "-- Read usb (Ctrl-C to quit)"
DISPLAYSEP=yes
{
echo "speed=${READSPEED}"
echo "fqbn=${FQBN}"
echo "port=${PORT}"
echo "file=${FILE}"
echo "filedate=$(date +"%Y-%m-%dT%H:%M:%SZ" -d @$(stat -c '%Y' "${FILE}"))"
echo "date=$(date +'%Y-%m-%dT%H:%M:%SZ')"
echo ""
echo "-----BEGIN ARDUINO OUTPUT-----"
} | tee "${RECORDFILE}"
tee -a "${RECORDFILE}" < "${PORT}"
fi

View File

@ -1,5 +1,5 @@
name=RF433any
version=0.6.0
version=0.7.0
author=Sébastien Millet
maintainer=Sébastien Millet <milletseb@laposte.net>
sentence=A library to decode any protocol received on a 433 Mhz Radio Frequencies receiver