rpc — libreria rpc

Il modulo rpc sull’OpenMV Cam consente di collegare la propria OpenMV Cam a un altro microcontrollore o computer ed eseguire chiamate remote a funzioni (o procedure) python sulla propria OpenMV Cam. Il modulo rpc permette anche il contrario, ossia di far eseguire alla propria OpenMV Cam chiamate remote a procedure (o funzioni python) su un altro microcontrollore o computer.

Come usare la libreria

Uno slave minimale che espone un callback su UART:

import rpc
import csi

csi0 = csi.CSI()
csi0.reset()
csi0.pixformat(csi.RGB565)
csi0.framesize(csi.QVGA)

interface = rpc.rpc_uart_slave(baudrate=115200)

def snapshot(_):
    return csi0.snapshot().compress().bytearray()

interface.register_callback(snapshot)
interface.loop()  # Does not return.

Il master corrispondente che richiede allo slave un frame JPEG:

import rpc

interface = rpc.rpc_uart_master(baudrate=115200)

result = interface.call("snapshot")
if result is None:
    print("communication failed")
elif len(result) == 0:
    print("remote function not registered on the slave")
else:
    # result is a memoryview of the JPEG bytes returned by the slave.
    print("received", len(result), "bytes")

Sostituisci rpc_uart_master / rpc_uart_slave con la coppia can, i2c o spi corrispondente per utilizzare un trasporto diverso.

In generale, affinché il dispositivo controllore possa usare la libreria rpc occorre creare un oggetto interfaccia utilizzando la libreria rpc. Per esempio:

interface = rpc.rpc_uart_master(baudrate=115200)

Questo crea un’interfaccia UART per comunicare con uno slave rpc.

Una volta creata l’interfaccia basta eseguire:

memory_view_object_result = interface.call("remote_function_or_method_name", bytes_object_argument)

E la libreria rpc proverà a eseguire quella "remote_function_or_method_name" sullo slave. La funzione o il metodo remoto riceverà l’argomento bytes_object_argument, che può avere una dimensione fino a 2^32-1 byte. Una volta terminata l’esecuzione, il metodo remoto restituirà un memory_view_object_result che può anch’esso avere una dimensione fino a 2^32-1 byte. Poiché sia l’argomento che la risposta sono contenitori di byte generici, è possibile passare qualsiasi cosa attraverso la libreria rpc e ricevere qualsiasi tipo di risposta. Un modo semplice per passare gli argomenti è usare struct.pack() per creare l’argomento e struct.unpack() per riceverlo dall’altra parte. Per la risposta, l’altra parte può inviare come risultato un oggetto stringa o una stringa json che il master può poi interpretare.

Per quanto riguarda gli errori, se si tenta di eseguire un nome di funzione o metodo inesistente il metodo rpc_master.call() restituirà un oggetto bytes() vuoto. Se la libreria rpc non è riuscita a comunicare con lo slave, la libreria rpc restituirà None.

Per mantenere le cose semplici, la libreria rpc non mantiene una connessione tra i dispositivi master e slave. Il metodo rpc_master.call() incapsula il tentativo di connettersi allo slave, l’avvio dell’esecuzione della funzione o del metodo remoto e l’ottenimento del risultato.

Ora, dal lato slave, occorre creare un’interfaccia rpc per comunicare con il master. Questo si presenta così:

interface = rpc.rpc_uart_slave(baudrate=115200)

Questo creerà il livello di interfaccia UART per comunicare con un master rpc.

Una volta creata l’interfaccia slave, occorre registrare i callback che il master può chiamare con l’oggetto interfaccia:

def remote_function_or_method_name(memoryview_object_argument):
    <lots of code>
    return bytes_object_result

interface.register_callback(remote_function_or_method_name)

È possibile registrare tutti i callback che si desidera sullo slave. Infine, una volta terminata la registrazione dei callback, basta eseguire:

interface.loop()

Sullo slave per avviare la libreria rpc e iniziare l’ascolto del master. Si noti che il metodo rpc_slave.loop() non ritorna.

classe rpc – classe base rpc

La classe base rpc viene reimplementata dalle classi rpc_master e rpc_slave per creare le interfacce master e slave. Non è pensata per essere usata direttamente.

class rpc.rpc

Crea un oggetto rpc. Non è pensato per essere usato direttamente.

get_bytes(buff: bytearray | memoryview, timeout_ms: int) bytes | None

Reimplementato dalle sottoclassi specifiche del trasporto. Riempie buff con i byte provenienti dall’interfaccia sottostante entro timeout_ms millisecondi. Restituisce None in caso di timeout.

put_bytes(data: bytes | memoryview, timeout_ms: int) None

Reimplementato dalle sottoclassi specifiche del trasporto. Invia data attraverso l’interfaccia sottostante entro timeout_ms millisecondi.

stream_reader(call_back: Callable[[memoryview], None], queue_depth: int = 1, read_timeout_ms: int = 5000) None

Riceve un flusso di payload da un rpc.stream_writer remoto. Dovrebbe essere chiamato dall’interno di un callback rpc_slave (o direttamente dopo una rpc_master.call riuscita) una volta che entrambe le parti si sono sincronizzate.

  • call_back – callable invocato una volta per ogni payload ricevuto come call_back(data) dove data è un memoryview. Il valore di ritorno viene ignorato.

  • queue_depth – numero di frame in transito che il writer può inviare prima di attendere il reader. Valori più alti aumentano il throughput a scapito della memoria.

  • read_timeout_ms – millisecondi da attendere per ogni payload.

Ritorna in caso di qualsiasi errore. Per annullare, sollevare un’eccezione all’interno di call_back; la parte remota andrà in timeout.

stream_writer(call_back: Callable[[], bytes | memoryview], write_timeout_ms: int = 5000) None

Invia un flusso di payload a un rpc.stream_reader remoto. Dovrebbe essere chiamato dall’interno di un callback rpc_slave (o direttamente dopo una rpc_master.call riuscita) una volta che entrambe le parti si sono sincronizzate.

  • call_back – callable invocato senza argomenti che restituisce il prossimo payload bytes o memoryview da inviare.

  • write_timeout_ms – millisecondi da attendere durante l’invio di ogni payload.

Ritorna in caso di qualsiasi errore. Per annullare, sollevare un’eccezione all’interno di call_back; la parte remota andrà in timeout.

classe rpc_master – classe base rpc_master

rpc_master è una classe base. Usare una delle sottoclassi specifiche del trasporto (rpc_can_master, rpc_i2c_master, rpc_spi_master, rpc_uart_master).

class rpc.rpc_master

Crea un oggetto rpc_master. Non è pensato per essere usato direttamente.

call(name: str, data: bytes = bytes(), send_timeout: int = 1000, recv_timeout: int = 1000) memoryview | None

Esegue una chiamata remota sul dispositivo slave.

  • name – nome stringa della funzione o del metodo remoto da eseguire.

  • data – oggetto di tipo bytes passato come argomento alla funzione remota.

  • send_timeout – millisecondi da attendere durante la connessione allo slave e l’avvio dell’esecuzione della funzione remota. Una volta che il master inizia a inviare l’argomento questo non si applica più; la libreria consente fino a 5 secondi per il trasferimento dell’argomento.

  • recv_timeout – millisecondi da attendere affinché lo slave inizi a restituire una risposta. Una volta che il master inizia a ricevere la risposta questo non si applica più; la libreria consente fino a 5 secondi per il trasferimento della risposta.

Restituisce un memoryview della risposta in caso di successo, un bytes() vuoto se il nome remoto non esiste sullo slave, oppure None in caso di errore di comunicazione.

classe rpc_slave – classe base rpc_slave

rpc_slave è una classe base. Usare una delle sottoclassi specifiche del trasporto (rpc_can_slave, rpc_i2c_slave, rpc_spi_slave, rpc_uart_slave).

class rpc.rpc_slave

Crea un oggetto rpc_slave. Non è pensato per essere usato direttamente.

register_callback(cb: Callable[[memoryview], bytes | memoryview]) None

Registra un callback che il master può invocare per nome. cb è un callable che accetta un argomento memoryview e restituisce un oggetto di tipo bytes. Il __name__ del callback viene usato come chiave di ricerca.

schedule_callback(cb: Callable[[], None]) None

Pianifica l’esecuzione di cb (un callable senza argomenti) una sola volta, immediatamente dopo che il callback rpc attualmente in esecuzione ha restituito con successo la sua risposta al master. Deve essere chiamato dall’interno di un callback rpc. Consente l’esecuzione di lavori di lunga durata o di trasferimenti cut-through rpc.get_bytes/rpc.put_bytes tra le transazioni rpc. Registrare nuovamente a ogni invocazione se è richiesta l’esecuzione ripetuta.

setup_loop_callback(cb: Callable[[], None]) None

Registra cb (un callable senza argomenti) affinché venga invocato a ogni iterazione di rpc_slave.loop. A differenza di rpc_slave.schedule_callback, questo callback rimane registrato. Deve essere non bloccante; la frequenza di chiamata è variabile.

loop(recv_timeout: int = 1000, send_timeout: int = 1000) None

Esegue il ciclo di dispatch dello slave rpc. Non ritorna se non tramite un’eccezione sollevata da un callback.

  • recv_timeout – millisecondi da attendere per un comando dal master prima di riprovare.

  • send_timeout – millisecondi da attendere affinché il master confermi la risposta prima di tornare alla ricezione.

classe rpc_can_master – Interfaccia Master CAN

Controlla un altro dispositivo rpc tramite CAN.

class rpc.rpc_can_master(message_id: int = 0x7FF, bit_rate: int = 250000, sample_point: float = 75, can_bus: int = 2)
  • message_id – id del messaggio CAN a 11 bit usato per il trasporto dati.

  • bit_rate – bit rate CAN in bit al secondo.

  • sample_point – percentuale del sample-point Tseg1/Tseg2 (es. 50.0, 62.5, 75, 87.5).

  • can_bus – numero della periferica CAN.

I valori message_id e bit_rate del master e dello slave devono corrispondere. Il bus deve essere terminato con 120 ohm.

classe rpc_can_slave – Interfaccia Slave CAN

Viene controllato da un altro dispositivo rpc tramite CAN.

class rpc.rpc_can_slave(message_id: int = 0x7FF, bit_rate: int = 250000, sample_point: float = 75, can_bus: int = 2)

Vedere rpc_can_master per le descrizioni degli argomenti.

classe rpc_i2c_master – Interfaccia Master I2C

Controlla un altro dispositivo rpc tramite I2C.

class rpc.rpc_i2c_master(slave_addr: int = 0x12, rate: int = 100000, i2c_bus: int = 2)
  • slave_addr – indirizzo I2C a 7 bit del dispositivo slave.

  • rate – frequenza di clock del bus I2C in Hz.

  • i2c_bus – numero della periferica I2C.

Gli indirizzi del master e dello slave devono corrispondere. Sono richieste pull-up esterne su SCL e SDA, ed entrambi i dispositivi devono condividere una massa comune.

classe rpc_i2c_slave – Interfaccia Slave I2C

Viene controllato da un altro dispositivo rpc tramite I2C.

class rpc.rpc_i2c_slave(slave_addr: int = 0x12, i2c_bus: int = 2)
  • slave_addr – indirizzo I2C a 7 bit a cui questo slave risponde.

  • i2c_bus – numero della periferica I2C.

classe rpc_spi_master – Interfaccia Master SPI

Controlla un altro dispositivo rpc tramite SPI.

class rpc.rpc_spi_master(cs_pin: str = 'P3', freq: int = 1000000, clk_polarity: int = 1, clk_phase: int = 0, spi_bus: int = 2)
  • cs_pin – nome del pin di chip-select.

  • freq – frequenza di clock del bus SPI in Hz.

  • clk_polarity – livello del clock a riposo (0 o 1).

  • clk_phase – campiona i dati sul primo (0) o sul secondo (1) fronte di clock.

  • spi_bus – numero della periferica SPI.

Le impostazioni del master e dello slave devono corrispondere. Collegare CS, SCLK, MOSI, MISO direttamente. Entrambi i dispositivi devono condividere una massa comune.

classe rpc_spi_slave – Interfaccia Slave SPI

Viene controllato da un altro dispositivo rpc tramite SPI.

class rpc.rpc_spi_slave(cs_pin: str = 'P3', clk_polarity: int = 1, clk_phase: int = 0, spi_bus: int = 2)
  • cs_pin – nome del pin di ingresso chip-select.

  • clk_polarity – livello del clock a riposo (0 o 1).

  • clk_phase – campiona i dati sul primo (0) o sul secondo (1) fronte di clock.

  • spi_bus – numero della periferica SPI.

classe rpc_uart_master – Interfaccia Master UART

Controlla un altro dispositivo rpc tramite Seriale Asincrona (UART).

class rpc.rpc_uart_master(baudrate: int = 9600, uart_port: int = 3)
  • baudrate – baud rate seriale.

  • uart_port – numero della periferica UART.

I baud rate del master e dello slave devono corrispondere. Collegare il TX del master al RX dello slave e il RX del master al TX dello slave. Entrambi i dispositivi devono condividere una massa comune.

classe rpc_uart_slave – Interfaccia Slave UART

Viene controllato da un altro dispositivo rpc tramite Seriale Asincrona (UART).

class rpc.rpc_uart_slave(baudrate: int = 9600, uart_port: int = 3)
  • baudrate – baud rate seriale.

  • uart_port – numero della periferica UART.