 5b6ab7faf3
			
		
	
	
		5b6ab7faf3
		
			
		
	
	
	
	
		
			
			* add blank example * add ipc example code, need to change FURI API * add ipc example code, need to change FURI API * change core API, add context * check handler at take * fix important bugs in furi * drawing example * add posix mq * fix unsigned demo counter * create at open * working local demo * russian version of IPC example * english version * add gif
		
			
				
	
	
		
			146 lines
		
	
	
		
			4.1 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
			
		
		
	
	
			146 lines
		
	
	
		
			4.1 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
| In this example we show how to interact data between different applications.
 | ||
| As we already know, we can use FURI Record for this purpose: one application create named record and other application opens it by name.
 | ||
| 
 | ||
| We will simulate the display. The first application (called `application_ipc_display`) will be display driver, and the second app (called `application_ipc_widget`) will be draw such simple demo on the screen.
 | ||
| 
 | ||
| # Dsiplay definition
 | ||
| 
 | ||
| For work with the display we create simple framebuffer and write some "pixels" into it.
 | ||
| 
 | ||
| ```C
 | ||
| #define FB_WIDTH 10
 | ||
| #define FB_HEIGHT 3
 | ||
| #define FB_SIZE (FB_WIDTH * FB_HEIGHT)
 | ||
| 
 | ||
| char _framebuffer[FB_SIZE];
 | ||
| 
 | ||
| for(size_t i = 0; i < FB_SIZE; i++) {
 | ||
|     _framebuffer[i] = ' ';
 | ||
| }
 | ||
| ```
 | ||
| 
 | ||
| On local target we just draw framebuffer content like this:
 | ||
| ```
 | ||
|     +==========+
 | ||
|     |          |
 | ||
|     |          |
 | ||
|     |          |
 | ||
|     +==========+
 | ||
| ```
 | ||
| 
 | ||
| ```C
 | ||
| fuprintf(log, "+==========+\n");
 | ||
| for(uint8_t i = 0; i < FB_HEIGHT; i++) {
 | ||
|     strncpy(row_buffer, &fb[FB_WIDTH * i], FB_WIDTH);
 | ||
|     fuprintf(log, "|%s|\n", row_buffer);
 | ||
| }
 | ||
| fuprintf(log, "+==========+\n");
 | ||
| ```
 | ||
| 
 | ||
| _Notice: after creating display emulator this example should be changed to work with real 128×64 display using u8g2_
 | ||
| 
 | ||
| # Demo "widget" application
 | ||
| 
 | ||
| The application opens record with framebuffer:
 | ||
| 
 | ||
| ```C
 | ||
| FuriRecordSubscriber* fb_record = furi_open(
 | ||
|     "test_fb", false, false, NULL, NULL, NULL
 | ||
| );
 | ||
| ```
 | ||
| 
 | ||
| Then it clear display and draw "pixel" every 120 ms, and pixel move across the screen.
 | ||
| 
 | ||
| For do that we should tale framebuffer:
 | ||
| 
 | ||
| `char* fb = (char*)furi_take(fb_record);`
 | ||
| 
 | ||
| Write some data:
 | ||
| 
 | ||
| ```C
 | ||
| if(fb == NULL) furiac_exit(NULL);
 | ||
| 
 | ||
| for(size_t i = 0; i < FB_SIZE; i++) {
 | ||
|     fb[i] = ' ';
 | ||
| }
 | ||
| 
 | ||
| fb[counter % FB_SIZE] = '#';
 | ||
| ```
 | ||
| And give framebuffer (use `furi_commit` to notify another apps that record was changed):
 | ||
| 
 | ||
| `furi_commit(fb_record);`
 | ||
| 
 | ||
| `counter` is increasing on every iteration to make "pixel" moving.
 | ||
| 
 | ||
| # Display driver
 | ||
| 
 | ||
| The driver application creates framebuffer after start (see [Display definition](#Display-definition)) and creates new FURI record with framebuffer pointer:
 | ||
| 
 | ||
| `furi_create("test_fb", (void*)_framebuffer, FB_SIZE)`
 | ||
| 
 | ||
| Next it opens this record and subscribe to its changing:
 | ||
| 
 | ||
| ```
 | ||
| FuriRecordSubscriber* fb_record = furi_open(
 | ||
|     "test_fb", false, false, handle_fb_change, NULL, &ctx
 | ||
| );
 | ||
| ```
 | ||
| 
 | ||
| The handler is called any time when some app writes to framebuffer record (by calling `furi_commit`):
 | ||
| 
 | ||
| ```C
 | ||
| static void handle_fb_change(const void* fb, size_t fb_size, void* raw_ctx) {
 | ||
|     IpcCtx* ctx = (IpcCtx*)raw_ctx; // make right type
 | ||
| 
 | ||
|     fuprintf(ctx->log, "[cb] framebuffer updated\n");
 | ||
| 
 | ||
|     // send event to app thread
 | ||
|     xSemaphoreGive(ctx->events);
 | ||
| 
 | ||
|     // Attention! Please, do not make blocking operation like IO and waits inside callback
 | ||
|     // Remember that callback execute in calling thread/context
 | ||
| }
 | ||
| ```
 | ||
| 
 | ||
| That callback execute in calling thread/context, so handler pass control flow to app thread by semaphore. App thread wait for semaphore and then do the "rendering":
 | ||
| 
 | ||
| ```C
 | ||
| if(xSemaphoreTake(events, portMAX_DELAY) == pdTRUE) {
 | ||
|     fuprintf(log, "[display] get fb update\n\n");
 | ||
| 
 | ||
|     #ifdef HW_DISPLAY
 | ||
|     // on Flipper target draw the screen
 | ||
|     #else
 | ||
|     // on local target just print
 | ||
|     {
 | ||
|         void* fb = furi_take(fb_record);
 | ||
|         print_fb((char*)fb, log);
 | ||
|         furi_give(fb_record);
 | ||
|     }
 | ||
|     #endif
 | ||
| }
 | ||
| ```
 | ||
| 
 | ||
| A structure containing the context is used so that the callback can access the semaphore. This structure is passed as an argument to `furi_open` and goes to the handler:
 | ||
| 
 | ||
| ```C
 | ||
| typedef struct {
 | ||
|     SemaphoreHandle_t events; // queue to pass events from callback to app thread
 | ||
|     FuriRecordSubscriber* log; // app logger
 | ||
| } IpcCtx;
 | ||
| ```
 | ||
| 
 | ||
| We just have to create a semaphore and define a context:
 | ||
| 
 | ||
| ```C
 | ||
| StaticSemaphore_t event_descriptor;
 | ||
| // create stack-based counting semaphore
 | ||
| SemaphoreHandle_t events = xSemaphoreCreateCountingStatic(255, 0, &event_descriptor);
 | ||
| 
 | ||
| IpcCtx ctx = {.events = events, .log = log};
 | ||
| ```
 | ||
| 
 | ||
| You can find full example code in `applications/examples/ipc.c`, and run it by `docker-compose exec dev make -C target_lo example_ipc`.
 | ||
| 
 | ||
| 
 |