Clonare codice RF 433 MHz

Buongiorno a tutti,
mi son trovato a sperimentare con Arduino infinite strade per riuscire ad acquisire un segnale 433 MHz, pensando poi di ritrasmetterlo.
Praticamente - prima di arrivare ad Arduino - stavo clonando telecomandi autoleraning (presi da Amazon), e mi son reso conto che i miei sorgenti erano un pò afoni, quelli nuovi un pò sordi.... e così mi è venuta l'idea di operare con un passaggio indiretto, attraverso Arduino.
Perché??...: perché al modulino TX di Arduino l'antenna (17cm) la dispongo come meglio credo, e con una forma a spirale piana offro una grande superficie di ascolto al telecomando vergine in cui va trasferito il codice.

Il primo step è la ricezione / decodifica... impossibile nel mio caso.
Ho provato non so quante librerie, partendo da RCswitch e oltre, tutte senza successo. Dal momento che ho anche un RTL-SDR a disposizione, ho sperimentato anche la decodifica di SDRSharp, sempre senza successo! Però con il conforto di avere uno Spectrum Analyzer in casa che mi mostrava almeno l'esistenza di un segnale (com'è ovvio che sia). Poi il percorso è continuato lontano da Arduino dal momento che scopro l'esistenza di un altro grande SW: URH, che consente la registrazione del segnale modulato. Ovvero vedo finalmente che faccia ha ciò che non riesco a decodificare in nessun modo!

Mi spiego: ho il disegno degli impulsi di modulazione tra le mani...
IDEA :boom: perché non fare in modo che un Arduino non abbia lo stesso ... disegno tra i bits ???
E da qui ritorno ad Arduino (al futuro :crystal_ball: :smiley:) e parte l'avventura, rinunciando a qualsiasi tipo di libreria specifica per i 433MHz,
che tanto col mio segnale ho capito che non serve.

Cercando quasi a caso, scopro degli sketches Arduino autonomi, ovvero che non impiegano librerie.
Cerco di capire il funzionamento, ma anche io ho i miei limiti. Così saltando tra Arduino e URH, confronto i risultati di decodifica offerti da quegli sketches di Arduino, con quanto decodificato da URH.... ma ancora non ci siamo. Gli sketches interpretano il mio segnale come modulato Manchester, ed offrono un risultato decodificato, URH anche chiedendo di demodulare Manchester non porta allo stesso codice.
Non mi arrendo, perché comunque ho grandi strumenti tra le mani: i modulini (economici) di RX e TX a 433Mhz mi permettono di insistere a provare a ricevere qualcosa, che ha una forma nota grazie al supporto del ricevitore SDR ed URH.

Finalmente mi imbatto in questo sketch , e mi si apre il mondo:
Sniffer segnali RF

Si basa sul lavoro di ricezione di un modulino RF collegato al pin #2 di un Uno R3.
Il Millet ne ha fatti diversi di sketches autonomi, ognuno con una sua peculiarità. Questo in particolare ascolta il segnale e ne registra i timings.
Quegli stessi timings letti attentamente disegnano mentalmente il segnale che visto con URH - Record signal.
Adesso sono in grado di far acquisire ad Arduino qualcosa che si può tradurre nella codifica trasmessa da un tx, e visualizzato da URH.
Come?: integrando il lavoro di Millet con un algoritmo intelligente di analisi di quanto acquisito dal suo sketch.
Così si arriva a memorizzare una lunga parola "1010011...." corrispondente al 100% agli impulsi di modulazione del tx.
Perché? : se non riesco a decodificarlo, ci giro intorno, e mi accontento di demodularlo, il mio segnale, producendone la fotocopia!
E perché adesso ho in mano quella lunga parola da poter inviare al mio modulino TX, con Arduino. Ieri ho fatto acquisire ad un mio telecomando un pò sordo quel segnale, con molta meno fatica di un'acquisizione diretta tra telecomandi.
Poi con lo stesso telecomando, mi son fatto dire dal cancello del box, che è stato un buon lavoro! :wink: :wink: :wink:

Scusate la lungaggine, ora concludo. Prometto!
Devo solo mettere ordine nei 4 sketches, e poi pubblicherei il tutto, perché possa rendersi utile ad altri.
Devo chiedere l'intervento di qualche moderatore:
se tutto nasce da uno sketch di un altro (link sopra) poi posso pubblicare il mio lavoro, che include il suo, o commetto qualche violazione???
2°) il lavoro finito è composto da 4 sketches. Devo inserire 4 finestre codice???

Grazie
e buona domenica a tutti

Nessun probema, lascia sempre i copyright degli autori originali e tutto è in regola.

Se i codici sono 4, meglio identificarli con 4 boocchi codice.

Guglielmo

1 Like

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 ...:

  1. uint16_t timings[360], per ridurre la memoria dinamica impiegata, e lasciare spazio alle mie aggiunte
  2. 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 :stuck_out_tongue_winking_eye:

/*  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:

  1. enter URH Pulse Width in microsecondi
  2. scroll & enter index of VALID stream START
  3. 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 :stuck_out_tongue_winking_eye: :stuck_out_tongue_winking_eye: :stuck_out_tongue_winking_eye:

1 Like

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.