Ottimo, allora posso pubblicare la materia.
Il "RF433snif.ino" di Millet l'ho modificato, spostando tutte le sue definizioni di variabili al modulo principale: "rf433snif_repeat.ino".
Inoltre "void setup" è stato soppresso; "void loop" è stato trasformato nella funzione "timings_rec()".
Infine, rispetto al progetto di Millet, ho dovuto modificare anche ...:
- uint16_t timings[360], per ridurre la memoria dinamica impiegata, e lasciare spazio alle mie aggiunte
- per timings L > IDS_MIN_INITSEQ, enfatizzare l'evento con serial_printf("...\n"); per facilitare la lettura del serial monitor.
// rf433snif.ino (modified*)
// * all variable definitions transferred to the main module
// Copyright Sébastien Millet 2019, 2020, 2021
// Raw recording and display of RF433 status switch timings.
// UPGD timings pulse demod and bit stream transmission
// Schematic:
// RF433 RECEIVER plugged on D2
//
// Below: IDS_ stands for "IDentify Signal"
// _d stands for "duration" (between a transition from low to high or
// high to low).
// Durations are in microseconds.
// IDS_MIN_INITSEQ Minimum initial sequence duration to start identifying a signal
// Maximum value of max_d / min_d, expressed as a power of 2.
// max_d is the maximum duration seen so far, min_d the minimum. 'Duration'
// means the duration between two transitions (signal going low to high or
// high to low).
// That means, during the first IDS_BE_STRICT_DURING_FIRST_N transitions,
// this inequality must be verified:
// (max_d / min_d) < (2 ^ IS_MAX_SCALE)
// IDS_D_MIN Minimum transition duration
// Check signal consistency during the first IDS_BE_STRICT_DURING_FIRST_N
// transitions. After that number, keep recording no matter the signal.
// In a tri-coded signal ('low-low-high' versus 'low-high-high'), 20
// transitions make 10 bits (if Manchester-coded it is variable...)
// SCALE_FACTOR - Leave it to 2 unless you know what you are doing.
// micros() counter produces multiple of 4, therefore we can divide times by
// 4 without loosing accuracy, and this allows us to store bigger values.
// Manage printf-like output to Serial
void serial_printf(const char* msg, ...)
__attribute__((format(printf, 1, 2)));
// NOTE
// Assume Serial has been initialized (Serial.begin(...))
void serial_printf(const char* msg, ...) {
va_list args;
va_start(args, msg);
vsnprintf(serial_printf_buffer, sizeof(serial_printf_buffer), msg, args);
va_end(args);
Serial.print(serial_printf_buffer);
}
void timings_rec (){ //upgd line
serial_printf("Waiting for signal...\n");
recording = 0; //upgd line
unsigned int timing_pos;
unsigned long d_min;
unsigned long d_max;
int val;
unsigned long previous_loop_d = 0;
while (1) {
if (!recording) {
initseq_d = previous_loop_d;
if (!initseq_d) {
unsigned long t0 = micros();
while((initseq_d = (micros() - t0)) < MAX_DURATION
&& digitalRead(PIN_RFINPUT) == LOW)
;
}
if (initseq_d >= IDS_MIN_INITSEQ || CONTINUOUS_RECORD) {
timing_pos = 0;
val = HIGH;
start_t = micros();
d_min = 0;
d_max = 0;
recording = 1;
}
previous_loop_d = 0;
continue;
}
unsigned long d;
unsigned long last_t = micros();
while ((d = (micros() - last_t)) < MAX_DURATION
&& digitalRead(PIN_RFINPUT) == val)
;
if (!d_min)
d_min = d;
else if (d < d_min)
d_min = d;
if (d > d_max)
d_max = d;
if (timing_pos < IDS_BE_STRICT_DURING_FIRST_N && !CONTINUOUS_RECORD) {
if ((d_min << IDS_MAX_SCALE) < d_max || d < IDS_D_MIN) {
// If ever we come upon a LOW value that has proper
// duration, maybe it was the initseq of a signal just
// starting.
// Should that be the case, we don't want to discard it
// => it'll be analyzed during next loop.
previous_loop_d = (val == LOW ? d : 0);
recording = 0;
continue;
}
}
timings[timing_pos++] = (d >> SCALE_FACTOR);
if (timing_pos >= sizeof(timings) / sizeof(*timings))
break;
val = (val == LOW ? HIGH : LOW);
}
unsigned long last_end_t = end_t;
end_t = micros();
unsigned long signal_d = end_t - start_t;
if (last_end_t)
serial_printf("^\n|---> < %lu us > elapsed without recording\nv\n",
start_t - last_end_t);
serial_printf("Signal duration: %lu us\n", signal_d);
serial_printf(" N %6s,%6s\n", "HIGH", "LOW");
serial_printf("-----BEGIN RF433 HIGH LOW SEQUENCE-----\n");
serial_printf(" %6s,%6lu\n", "", initseq_d);
for(unsigned int i = 0; i + 1 < timing_pos; i += 2) {
serial_printf("%03i\t%lu\t%lu\n", i >> 1,
(unsigned long)timings[i] << SCALE_FACTOR,
(unsigned long)timings[i + 1] << SCALE_FACTOR);
if ((timings[i+1] << SCALE_FACTOR) > IDS_MIN_INITSEQ) //new line
serial_printf("...\n"); //new line
}
serial_printf("-----END RF433 HIGH LOW SEQUENCE-----\n");
}
i commenti che trovate sopra sono suoi (di Millet) con poche aggiunte mie.
La versione originale - integrale la potete reperire al link postato all'inizio dell'argomento.
Il (mio) modulo principale "rf433snif_repeat.ino" è il centro di selezione delle scelte dell'utente.
Come si intuisce il progetto ambisce a sniffare il codice (Millet) e ritrasmetterlo col "transmit.ino" finale. In mezzo c'è questo distributore di compiti che dalla scelta dell'utente avvia il processo adeguato.
// rf433 sniff & repeat, based on sniffing performance of Millet sketch and
// integrated by clever timing analysis to deduct code modulation pulses: the
// process ends by storing entire bit stream, fully ready for acquired code transmission
// global vars follow
#define PIN_RFINPUT 2
#define FS1000A_DATA_PIN 3 //new line
#include <Arduino.h>
#define CONTINUOUS_RECORD 0
#define IDS_MIN_INITSEQ 4000
#define IDS_MAX_SCALE 5
#define IDS_D_MIN 64
#define IDS_BE_STRICT_DURING_FIRST_N 20
uint16_t timings[360]; //upgd line
unsigned long start_t;
unsigned long end_t = 0;
unsigned long initseq_d;
bool recording; //new line
char stream [250]; //new line
uint16_t pulseW = 0; //new line
uint16_t stream_delay; //new line
#define SCALE_FACTOR 2
#define MAX_DURATION (50000 << SCALE_FACTOR)
#include <stdarg.h>
char serial_printf_buffer[80];
// global vars end
/* once 'r' is selected, it is forever, until arduino restart,
* pure Millet rf433snif's timings_rec() function is invoked,
* and no other part of sketch will come into play, until reset
*
* case 's' stands for single time receive, until timings buffer full
* and then passes acquired timings to ANALYZER, for code demodulation;
* the process ends forcing to execute 'default' case on next loop
*
* case 't', at beginning of transmission (stream_delay == 0),
* as default (beginning) value for delay, initseq_d will be
* taken in account because of its meaning as low pause time
* between bit streams (Millet considerations about rf433snif);
* due to sketch cyclings, an amount of about 750 us must be subtracted
* from desired delay to make arduino work with right pause time.
* During the process the user is informed about defined pulse width and
* deducted (supposed)stream delay: keep in mind that this is adjustable!:
* during transmission, you may change the delay value, to fine adjust parameter
* during (loop) repetition of 't', each delay change will show as adjusted to...
*
* 'default': sketch shows available options, you MUST decide which one to choose
*/
char modo_ope;
void setup() {
pinMode(PIN_RFINPUT, INPUT);
pinMode(FS1000A_DATA_PIN, OUTPUT);
Serial.begin(115200);
}
void loop() {
// only for case 's', op mode will be masked to 'x' to allow next selection
switch (modo_ope) {
case 'r':
timings_rec();
break;
case 's':
timings_rec();
stream_delay=0;
stream_analyzer();
modo_ope = 'x';
break;
case 't':
if (stream_delay == 0){
stream_delay = initseq_d - 750;
Serial.print(F("\nURH Pulse Width - us "));
Serial.println(pulseW);
Serial.print(F("(supposed) stream repetition delay - us "));
Serial.println(stream_delay);
Serial.print(F("bit stream data... "));
Serial.println(stream);
Serial.println(F("\n*** CONTINUOUS TRANSMISSION ON AIR ***\n"));
Serial.println(F("you can re-adjust repetition delay (any time) by entering new value,"));
Serial.println(F("(subtract 750 us from official URH 'Pause', as valid resultant delay)"));
} else {
while (Serial.available()){
stream_delay = Serial.parseInt();
Serial.print(F("stream repetition delay adjusted to - us "));
Serial.println(stream_delay);
}
}
stream_send(); // invoke transmit function
break; // because of loop, transmission repeats infinitely
default:
Serial.println(F("\nr) continuous receiver"));
Serial.println(F("s) SINGLE SEQ receiver & analisys"));
if (modo_ope == 'x') Serial.println(F("t) continuous stream transmitter"));
while (!Serial.available()){}
modo_ope=Serial.read();
break;
}; // end of switch selection
}
// prg 6918 byte (21% of avail.); glob vars 1488 byte (72% of avail.)
è lungo perché tutte le definizioni di variabili di tutti gli altri moduli sono finite qui.
Il principio di funzionamento è descritto nei commenti nello sketch stesso. Negli sketch, tutti i commenti li ho messi in inglese.
Sostanzialmente il progetto e l'utente rivestono un ruolo interattivo, attraverso il serial monitor:
lo sketch chiede all'utente cosa vuole fare, sul monitor, l'utente risponde 'r' 's' 't' con la seriale.
Se scegliete 'r' va in funzionamento permanente lo sketch di Millet, e nient'altro.
't' inizialmente non è disponibile, perché Arduino non ha ancora acquisito un Bit Stream da trasmettere!
Per non ammazzare la memoria, ci sono dei limiti anche per char stream [250];
Tradotto: il Bit Stream di "10110010....." non può essere più lungo di 250 bytes/valori/uni-zeri...
Nel commento finale il peso dell'intero progetto: 21% della mem di programma e 72% della dinamica.
Quando l'IDE compila uno sketch che impiega > 75% di memoria dinamica, avverte del pericolo instabilità! Ecco il perché delle varie limitazioni.
Inoltre quando switch (modo_ope) esegue il (case) default:, la scelta è obbligata while (!Serial.available()){}
Per sfruttare le potenzialità di snif e repeat, fulcro del progetto, si deve scegliere 's' = single...
In questo caso viene eseguita una sola volta timings_rec, fino a riempimento del buffer timings[360].
Poi è il turno di stream_analyzer()!, che compie un'analisi intelligente dei timings acquisiti... ma ha bisogno di una mano 
/* FIRST: you must define and enter actual (URH) Pulse Width in microseconds,
* then scroll up and down serial monitor, searching for valid timings suquence
* and enter index (N) of VALID stream START, and index of SAME stream END
* then, with these parameters, the algorithm starts its work:
* comparing single timings[i] to odd multiples (x1 x3 x5) of half pulseW,
* we can build bit per bit entire bit stream of coded modulation word.
* i.e. with this criteria we draw the transmission modulation, without decoding it!
* NOTE that this corresponds to minimum requirements to reproduce the same signal!!
*/
void stream_analyzer (){
Serial.print(F("\nenter URH Pulse Width - us "));
while (!Serial.available()){}
pulseW = Serial.parseInt();
Serial.println(pulseW);
Serial.print(F("scroll & enter index of VALID stream START "));
while (!Serial.available()){}
unsigned int beg_stream = 2*Serial.parseInt();
Serial.print(F("(doubled) "));
Serial.println(beg_stream);
Serial.print(F("enter index of SAME stream END "));
while (!Serial.available()){}
unsigned int end_stream = 2*Serial.parseInt();
Serial.print(F("(doubled) "));
Serial.println(end_stream);
Serial.print("\n");
stream[0] = 0; // ensure to reset char array stream [200] for process repetition
for(unsigned int i = beg_stream; i + 1 <= end_stream + 1; i += 2) {
if ((timings[i] << SCALE_FACTOR)>(pulseW/2)) strcat(stream, "1");
if ((timings[i] << SCALE_FACTOR)>(3*pulseW/2)) strcat(stream, "1");
if ((timings[i] << SCALE_FACTOR)>(5*pulseW/2)) strcat(stream, "1");
if ((timings[i+1] << SCALE_FACTOR) < IDS_MIN_INITSEQ){
if ((timings[i+1] << SCALE_FACTOR)>(pulseW/2)) strcat(stream, "0");
if ((timings[i+1] << SCALE_FACTOR)>(3*pulseW/2)) strcat(stream, "0");
if ((timings[i+1] << SCALE_FACTOR)>(5*pulseW/2)) strcat(stream, "0");
}
}
for (uint8_t i = 0; i<(strlen(stream)); i++)
{
if (i==50 || i==100 || i==150 || i==200) Serial.print ("\n");
Serial.print(stream[i]);
}
Serial.print(F("\n\nThe bit stream is composed from "));
Serial.print(strlen(stream));
Serial.println(F(" timings of defined URH Pulse Width"));
}
Appena si avvia stream_analyzer (sopra) chiede collaborazione all'utilizzatore:
- enter URH Pulse Width in microsecondi
- scroll & enter index of VALID stream START
- enter index of SAME stream END
è necessario conoscere la dimensione del pulse width più stretto, ovvero l'unità di misura (larghezza) dell'impulso base, rilevabile da URH - Records.
Ovvero per impiegare bene questo progetto è necessario avere il supporto di una chiave SDR e URH.
Ciò non toglie che si possa conoscere (non so come) quel Pulse Width, anche senza quei mezzi.
#2 e #3 significano solo: scorri il contenuto del monitor, trova inizio e fine di una sequenza valida, e dammi i riferimenti indice.
Es.: nel mio caso ho 600us di Pulse Width, la prima sequenza valida inizia a 000 e termina a 057.
Consegnati quei valori ad Arduino, li passa a stream_analyzer () che per ogni timing acquisito ne confronta il valore (tempo) al Pulse Width /2... per multipli dispari crescenti.
Torno all'esempio: PW=600 us; PW/2=300us... 1xPW/2=300; 3xPW/2=900; 5xPW/2=1500 us.
Pertanto il 1° timing alto >(pulseW/2) registrerà in stream [250] un "1"; ma se lo stesso timing
è anche > 3xPW/2, allora aggiunge subito un "1"; e se è anche > 5xPW/2, aggiunge ancora "1"...
poi passo all'altro timing della stessa riga indice, relativo allo 0, con lo stesso criterio.
Poi si passa alla riga indice successiva, ripetendo esattamente lo stesso procedimento.
E l'analisi si svolge sulla base del PW=600 us e tra inizio e fine della sequenza indicata dall'operatore.
Alla fine dell'analisi abbiamo riempito il buffer stream [250] di uni e zeri di modulazione RF.
Se non fosse chiaro: non abbiamo decodificato il segnale, ma l'abbiamo demodulato
Ed il buffer stream [250] ha le informazioni necessarie per replicare la trasmissione.
Conclusa l'analisi, il modo_ope viene depistato ad 'x', così da indurre di nuovo il case default, però con l'opzione 't' in più!
Se si sceglie 't' lo sketch ricorda all'utente il PWidth definito, e suggerisce uno stream repetition delay - us dedotto dal parametro initseq_d dello sketch iniziale di Millet.
Lo stream repetition delay è il 2° parametro che l'utente deve essere in grado di consegnare ad Arduino: corrisponde alla pausa di silenzio "L" tra due treni di codice consecutivi.
Con URH la si rileva molto bene, con pochi ragionamenti. La stima basata su initseq_d è molto veritiera.
/*
* sending sequence of pulses that is described by single bit digit:
* this description is contained in the stream[i] buffer, stored from
* analyzer process, that builds it by means of sniffed timings, and
* a couple of time parameters from user
*
* keep in mind that stream[i] contains a very long sequence of 1s and 0s!
* note that every settled level will not be changed from break, allowing
* to attach next bit with same level, modulating transmission as a continuous level.
* After last bit of stream has been sent, stream_delay comes into play; then
* rf433 sniff & repeat regains flow control, check for delay updates, and
* returns to trasmission again, indefinitely.
*/
void sendhexdigit(char c , uint16_t pulseW)
{
switch (c) {
// bit = 0, send 1 low pulse
case '0':
digitalWrite(FS1000A_DATA_PIN, LOW);
delayMicroseconds(pulseW);
break;
// bit = 1, send 1 high pulse
case '1':
digitalWrite(FS1000A_DATA_PIN, HIGH);
delayMicroseconds(pulseW);
break;
default:
digitalWrite(FS1000A_DATA_PIN, LOW);
break;
}; // end of switch selection
} // end of sendhexdigit function
void stream_send (){
uint8_t i;
for (i = 0; i<(sizeof(stream)); i++)
{
sendhexdigit(stream[i], pulseW);
}
digitalWrite(FS1000A_DATA_PIN, LOW);
// pause
delayMicroseconds(stream_delay);
}
Quando si va in trasmissione, ....
Serial.println(F("\n*** CONTINUOUS TRANSMISSION ON AIR ***\n")); avvisa del processo
senza fine, finché non spegnete/scollegate Arduino.
C'è tutto il tempo che serve per appoggiare un autolearning alla chiocciolina, e fargli imparare il codice
