Go Down

Topic: PJON - Multi-master, multi-media network protocol stack  (Read 73436 times) previous topic - next topic

_nabla_

Praticamente stai reinventando Ethernet, TCP e IP :D

Scherzi a parte, quel che voglio dire è che stai facendo un lavoro molto promettente e sicuramente arricchirà molto il tuo bagaglio culturale.
Quindi, dal punto di vista "teorico" meriti sicuramente un encomio.

Dal lato strettamente pratico però devi tenere presente che sarà ben raro che una rete "casalinga" necessiti di centinaia di nodi.
Se proprio ad un progettista toccasse un simile problema, con ogni probabilità si affiderebbe a protocolli e bus fisici stra-noti e affidabili come quelli che ho citato, anche perché è un investimento lato hardware non indifferente e poter contare su parti "di serie" convincerebbe meglio il committente.

Stai rischiando di scoprire l'acqua calda o peggio di perdere tantissime ore a reinventare la ruota dandogli solo un altro nome.
Finché lo fai per cultura personale non ho niente da dire, ma non ti aspettare un grande successo del tuo protocollo quando la rete è così estesa. Successo che ovviamente ti auguro, sia chiaro, ma non ti nascondo il mio scetticismo riguardo il numero di possibili progetti che lo coinvolgeranno.

Anche se facessi tutto alla perfezione, il bus è relativamente lento: ottimo perché più è lento, meno errori di comunicazione hai. Ma quando pensi a centinaia di oggetti interconnessi, i tempi per inviare il messaggio si allungano (perché si presume che più oggetti interconnessi, più sia alta l'occupazione media del bus, mica saranno tutti moduli in attesa di istruzioni quelli che aggiungi). Non puoi premere un pulsante e accendere la lampadina 10 secondi dopo...

Oggigiorno tra reti cablate o radio già molto ben strutturate, in cui si sono già ben affrontati tutti i problemi di affidabilità della comunicazione, criptazione, auto routing in caso di nodi spenti, resilienza ai disturbi ecc ecc c'è più di una scelta "professionale" a costi relativamente contenuti.

Quel che manca è un modo di fare in modo economico, affidabile e "moderno" piccole reti di microcontrollori.

Ti suggerirei quindi di approfondire il mondo delle piccole reti. A PJON serve un layer software per l'auto indirizzamento delle periferiche, servono numeri reali (indicando esattametne le condizioni di test) sul reale transfer rate (hai indicato quasi tutti numeri teorici e hai un solo impianto di test a quanto pare), forse serve un sistema di correzione delle derive del clock, puoi potenziare la parte di checksum e auto retrasmit dei dati corrotti...  Insomma, il prototipo è perfetto ma di lavoro ce n'è ancora tanto.
Va benissimo riservarsi qualche byte nel protocollo per future espansioni come router ecc, ma lascia il tutto proprio solo a livello di spazio per future espansioni.

Per farla breve, date a Ethernet quel che è di Ethernet e date a PJON quei che è di MODBUS :D
 

gioscarab

Ok, ho capito. Non sei il primo che me lo dice.
In ogni caso fare in modo che supporti una rete di reti non e' cosi' complicato al massimo scrivero' un layer a parte dello stack in futuro  :). In ogni caso, cosa ne pensi dell'implementazione radio che ti ho linkato? Credo sia una discreta alternativa a VirtualWire!  :)

_nabla_

Ho un paio di quei modulini in un cassetto, nel prossimo weekend farò un test.

Mi pare molto promettente, ed è appunto quel che intendevo quando ti suggerivo di focalizzarti sulle piccole reti a basso costo.

Con una libreria del genere si riesce a far fare cose a quei modulini che fanno quasi solo gli xbee, ma ad una frazione del prezzo totale

gioscarab

Alcuni utenti mi richiedono l'auto addressing dei nodi, tu cosa ne pensi? soprattutto per la versione radio sarebbe molto utile.

_nabla_

C'è più di un modo di farlo e devi valutare bene quale strada sia migliore.

Indirizzamento automatico significa sapere gestire i conflitti sugli indirizzi, specie se non dedichi un nodo ad essere un unico coordinatore degli indirizzi assegnati.

Secondo me potresti pensare a una qualche forma di auto indirizzamento minimale, con minimi impatti sulla libreria e lasciare il grosso del codice ad una applicazione di esempio. Così solo chi vuole la implementa nel suo codice.

Ad esempio attivare un comando "ping" che consumi pochi byte sul bus e a cui la libreria risponda sempre in automatico (senza che l'utilizzatore della libreria debba scrivere la parte software per la risposta).

Poi nella libreria fornisci una implementazione di esempio:

Al setup verifichi l'indirizzo presente in eeprom
- se è 0x00 o 0xFF allora vuole dire che la eeprom è intonsa (mai attribuìto un indirizzo). Parte allora un ciclo in cui si "pinga" il bus da 0x01 a 0xFE. Al primo ping che va in timeout, allora significa che quell'indirizzo è libero. Lo inserisce in eeprom.

- se non lo è, recupera l'indirizzo. Poi prova a pingarlo (la libreria deve evitare di auto-rispondersi, ma deve poter trasmettere fuori il proprio stesso indirizzo). Se nessuno risponde allora vuole dire che quell'indirizzo è ancora libero e continuerà ad usarlo. Se qualcuno risponde, allora significa che c'è un conflitto degli indirizzi (ad esempio un modulo è stato acceso mentre questo era spento). Con il ciclo di prima recuperi un nuovo indirizzo libero. Preferibilmente informi il bus (con un messaggio broadcast) che c'è stato un conflitto (indicando vecchio indirizzo e nuovo indirizzo), così i moduli che ad esempio si aspettavano un sensore di temperatura all'indirizzo 0x03 ora sanno che se lo troveranno al 0x05)

In realtà ci sono metodi anche molto più evoluti per gestire indirizzi e conflitti, ma questo mi pare abbastanza minimale da essere adatto al tipo di rete.

Una alternativa potrebbe essere che ogni modulo conserva una copia della lista degli indirizzi assegnati, quindi ciascuno sa sempre qual è il primo indirizzo libero: all'accensione fai il ping in loop, ammettiamo che il 0x01 non risponda, tu continui con il 0x02. Questo risponde al ping e ti dice che il primo libero è 0x05. Tu ti prendi 0x05 e con una chiamata broadcast dici che 0x05 è assegnato ( e tutti i moduli si segnano che non è più libero). Così hai evitato un conflitto su 0x01, 0x03, 0x04: moduli esistenti ma spenti o non raggiungibili in quel momento. All'accensione un modulo deve scaricarsi la lista aggiornata dal primo modulo che trova, in modo da avere una lista "fresca".
Questo metodo ha il vantaggio di essere molto efficace nell'evitare conflitti, ma consuma un po' di ram (almeno 32 byte solo per la lista).
Ovviamente bisogna anche prevedere il comando "disconnetti dalla rete" per informare il bus che l'indirizzo XYZ è nuovamente libero.

L'opzione di avere un coordinatore unico per tutto il bus è simile alla precedente ma c'è un solo microcontrollore che ha la copia della lista. Questo modulo risponderà sempre ad un indirizzo predefinito (es 0x01). In questo modo gli altri nodi sono più semplici, ma se il primo nodo non è raggiungibile, allora tutta la rete non accetta più aggiunte o rimozioni di moduli dalla rete.

gioscarab

Ciao Nabia. Grazie dei consigli.

Io rimarrei su modalita' multimaster, (quindi senza un unico device in grado di fare l'addressing).
Quello a cui pensavo e' che quando un device si connette a una rete, estrae un numero casuale e pinga a quell'ID, finche' non ne trova uno libero. Se riceve un messaggio spedito con come mittente il proprio ID (anche dopo ore che si e' trovato il suo ID), dovrebbe abbandonare l'ID attuale e ricercarne uno nuovo.

Cosi' alla connessione di un device potresti avere una leggera perdita di banda, ma avresti dalla tua la cosa positiva di non doverti ricordare tutti gli id e non dover avvisare di esserti disconnesso.

_nabla_

Eh sì ma il termometro che è "abituato" a sevegliarsi ogni 10 minuti e mandare la lettura della temperatura all'id 0x45  come fa a sapere che mentre dormiva è cambiato id ed ora deve mandare a 0x98?

Sopratutto se entri nel mondo della comunicazione radio è la norma (e non l'eccezione) che un dispositivo vada in sleep per contenere l'uso delle batterie quando non è strettamente necessario che sia acceso.

Guarda che la gestione dinamica dell'indirizzamento non è così semplice quanto sembri. Proprio per questo ti suggerivo di non mettere troppa roba nella libreria e affidarti ad un esempio, così si può personalizzare in base alle peculiarità della propria rete.

chetto983

Ciao a tutti,

Questa libreria è ottima  ma secondo me ha un problema di fondo:

void loop() {
  network.receive(1000);
}

non si ha mai la certezza che il dato sia ricevuto nel reciver.

Non si potrebbe usare un interrupt e mettere un  su buffer circolare i pacchetti ricevuti da leggere con comodo quando si ha tempo per farlo?

gioscarab

La certezza ce l'hai. Come spiegato nel readme, devi usare il setter "set_receiver" passando come parametro la funzione che verra' chiamata ogni volta che un messaggio sara' correttamente ricevuto.
Il bello e il cuore di questa libreria e' proprio che non usa interrupt o timer hardware  :)

gioscarab

Ciao nabia, ho analizzato la cosa, secondo me  la strada migliore e' avere una lista di id e il loro stato di utilizzo su ogni nodo, Ovviamente il nodo che si connette dovrebbe compilarla (partendo da un punto a caso della lista) finche' non ne trova uno libero. Se succede che il nodo sente un pacchetto inviato dal suo stesso ID dovrebbe rilanciare il processo di auto addressing...

cosa ne dici?

_nabla_

Se devo avere una copia locale degli indirizzi occupati, tanto vale la si possa scaricare da altri nodi e non compilarla interamente da sé.
Così ti eviti i problemi di nodi temporaneamente non raggiungibili.
Poi ovviamente è da pensare quali strategie siano le migliori per tenerla aggiornata. Ad esempio messaggi broadcast per segnalare l'attivazione di un nuovo nodo + monitoraggio del bus: quando passano dati da un id (verso un qualunque altro dispositivo) verifico di averlo nella lista degli id attivi e se non lo è, lo aggiungo

A parte, si può pensare a come rimuovere dalle liste di ID. A parte uno specifico evento broadcast, potrebbe essere uno dei dispositivi che abbia qualche riga di codice in più. Quindi nessun intervento in libreria ma l'utente che a seconda della rete che installa, metterà nel nodo più "intelligente" qualche riga di codice.
Ad esempio se il tutto nelle intenzioni dello sviluppatore della rete è supervisionato da un pannellino con display per monitorare lo stato della domotica di casa, allora in quel pannellino ci sarà l'opzione avanzata di "ripulire" gli id che da settimane non si fanno sentire.

Bisogna ricordarsi che gli ID cambiano ogni morte di papa in una rete reale, e gli id disponibili sono molti di più dei dispositivi realmente connessi: va bene gli automatismi per gestire gli id, ma senza esagerare...

gioscarab

Ciao ragazzi, non so se avete visto questo nuovo progetto di home automation di Michael Teuww che utilizza PJON come protocollo di comunicazione!!! :)

http://michaelteeuw.nl/post/130558526217/pjon-my-son
http://michaelteeuw.nl/post/131017701882/prototyping-complete

gioscarab

Ciao ragazzi! In questi giorni ho reso compatibile anche l'Arduino Mega e ora sto lavorando a compatibilizzare l ATtiny!!

gioscarab

Ciao! Buon Natale in ritardo.

Finalmente sono riuscito a rendere compatibile con PJON i seguenti processori:

- ATmega88/168/328/1280/2560
- ATtiny25/45/85

Sto lavorando a rendere compatibile anche l'architettura ESP8266 con Arduino IDE (boards molto interessanti).


Come potete vedere dal commit:
https://github.com/gioblu/PJON/commit/bdc0b570e2d017b56cd633dc48972805f9fc9e41
E' bastato cambiare leggermente i valori di timing dei device precedentemente non supportati.

In piu' ho portato avanti la mia versione di digitalWriteFast che ora supporta Arduino Mega e ATtiny45/85!! https://github.com/gioblu/PJON/blob/master/includes/digitalWriteFast.h
(Avrei fatto una pull request a chi mantiene la libreria ma non esiste un repository ufficiale)

Questa ricerca mi ha portato a scoprire che, nessuna delle schede che usiamo genera con delay() e delayMicroseconds() dei delay precisi. Infatti per fare in modo di ottenere una timing accettabilmente simile su tutti i device ho dovuto per esempio sull'Arduino Mega settare la durata di un bit di 2 microsecondi piu' ridotta per matchare la timing del Duemilanove/Uno/Nano. Stessa cosa vale per ATtiny a 8Mhz (oscillatore interno) che produce dei delay decisamente piu lunghi delle altre due architetture, infatti ho dovuto settare la durata di un bit 4 microsecondi piu' corta per matchare quella di Mega e Uno.

La cosa impressionante ora e' vedere la semplicita' d'uso con reti con 3 diverse architetture!!  :)  :)
Accuratezza del segnale 99.9%

Questo e' il mio regalo di Natale per il forum  :)

Franchelli

Sto sperimentando le librerie Pjon per un progetto che ho in testa che richiede il collegamento di tre Arduino che dovrebbero scambiarsi dati ricevuti da altrettanti sensori.
Sono partito con l'esempio di base illustrato su Github che ho leggermente modificato:

TX
Code: [Select]
#include <PJON.h>
PJON network(7, 0); // network(Arduino pin used, selected device id)

void setup() {
network.send(1, "B", 1, 1000000);// invia carattere B a Id 1 ogni 1 secondo
network.send(2, "B", 1, 500000);// invia carattere B a Id 2 ogni 0,5 secondo
}

void loop() {
network.update();
}


RX1
Code: [Select]
#include <PJON.h>
PJON network(7, 1); // network(Arduino pin used, selected device id)

void setup() {
pinModeFast(13, OUTPUT);
digitalWrite(13, LOW);
Serial.begin(9600);
network.set_receiver(receiver_function);
}
void receiver_function(uint8_t length, uint8_t *payload) {
if(payload[0] == 'B') {
Serial.println("BLINK");
digitalWrite(13, HIGH);
delay(30);
digitalWrite(13, LOW);
}
}

void loop() {
network.receive(1000);
}


RX2
Code: [Select]
#include <PJON.h>
PJON network(7, 2);// network(Arduino pin used, selected device id)
long  tempo = 0;
long  oldTempo = 0;

void setup() {
pinModeFast(13, OUTPUT);
digitalWrite(13, LOW);
Serial.begin(9600);
network.set_receiver(receiver_function);
}

void loop() {
network.receive(1000);
}
void receiver_function(uint8_t length, uint8_t *payload) {
if(payload[0] == 'B') {
Serial.print("BLINK   ");
tempo= millis();
Serial.println(tempo - oldTempo);
oldTempo= tempo;
digitalWrite(13, HIGH);
delay(30);
digitalWrite(13, LOW);
}
}


Il TX invia ogni 1 secondo un comando di blink ad il RX ed ogni 0,5 secondi uno al RX1.
Ho notato le seguenti stranezze:

alimentando sia da USB che da fonte esterna prima di eseguire correttamente le funzioni per cui sono stati programmati,  il pin 13 di RX1 fa tre lampeggi in rapida successione e poi parte con il ciclo,
RX2 invece fa 5 serie di tre lampeggi per poi partire correttamente.  Come mai?

Vedo che nello sketch originario nel setup dei RX trovo dei PinModeFast, DigitalWriteFast: non li avevo mai trovati prima quindi a cosa servono?  Possono essere sostituiti nello specifico con i normali PinMode e DigitalWrite? Negli sketch allegati li ho sostituiti con le versioni "normali" e sembra funzionare tutto correttamente...

a circuiti collegati e funzionanti da ore, accendendo una lampada a braccio (con trasformatore) posizionata nelle vicinanze noto che alle volte i ricevitori si fermano per qualche secondo per poi ripartire con i lampeggi: ovviamente si tratta dell'interferenza provocata dalla lampada che si propaga in qualche modo. Preciso che il TX è alimantato tramite USB , Il TX1 tramite USB con la sua uscita a 5V che alimanta TX2.

Per ora questo poi avrò altre domande da fare sul codice ma voglio affrontare un problema per volta.
Grazie



Go Up