--- tags: Openwrt --- # ubus communication example --- ## Three implementation methods --- - Invoke / reply - Subscribe / notify - Broadcast event ## 1. Invoke / reply --- 此範例包含兩支程式,其中 invoke_server 負責管理學生清單,而 invoke_client 用來通知 server 修改清單或查看清單 ### invoke server --- 使用 uloop 來做事件驅動管理,並將 student object 註冊到 ubusd 上 Student object 提供了三種 method ( add、del and list ) 用來操作 student list ```c enum { STUDENT_ATTR_NAME, STUDENT_ATTR_ID, __STUDENT_ATTR_MAX }; static const struct blobmsg_policy student_attrs[__STUDENT_ATTR_MAX] = { [STUDENT_ATTR_NAME] = { .name = "name", .type = BLOBMSG_TYPE_STRING }, [STUDENT_ATTR_ID] = { .name = "id", .type = BLOBMSG_TYPE_INT32 } }; static struct ubus_method student_object_methods[] = { UBUS_METHOD("add", student_handle_add, student_attrs), UBUS_METHOD("del", student_handle_del, student_attrs), UBUS_METHOD_NOARG("list", student_handle_list) }; static struct ubus_object_type student_object_type = UBUS_OBJECT_TYPE("student", student_object_methods); static struct ubus_object student_object = { .name = "student", .type = &student_object_type, .methods = student_object_methods, .n_methods = ARRAY_SIZE(student_object_methods), }; ``` Student list 的資料結構為 vlist_tree,libubox 把 avl_tree 封裝成 vlist,而每一個宣告出來的學生其結構為 vlist_node,可以透過 vlist_add 加到 tree 之中 ```c struct vlist_tree { struct avl_tree avl; vlist_update_cb update; bool keep_old; bool no_delete; int version; }; struct vlist_node { struct avl_node avl; int version; }; struct vlist_tree students; struct student { const char *name; int id; struct vlist_node node; }; ``` 如下是其中幾種 vlist API ```c void vlist_init(struct vlist_tree *tree, avl_tree_comp cmp, vlist_update_cb update); void vlist_add(struct vlist_tree *tree, struct vlist_node *node, const void *key); void vlist_delete(struct vlist_tree *tree, struct vlist_node *node); #define vlist_find(tree, name, element, node_member) \ avl_find_element(&(tree)->avl, name, element, node_member.avl) #define vlist_for_each_element(tree, element, node_member) \ avl_for_each_element(&(tree)->avl, element, node_member.avl) ``` 當執行 `vlist_add` or `vlist_delete` 之後,會呼叫 `vlist_init` 中所註冊的 update callback :::spoiler invoke_server.c ```c #include <stdio.h> #include <libubus.h> #include <libubox/uloop.h> #include <libubox/blobmsg_json.h> #include <libubox/vlist.h> #include <libubox/avl-cmp.h> static struct ubus_context *ctx; static struct blob_buf b; struct vlist_tree students; struct student { const char *name; int id; struct vlist_node node; }; static struct student * student_alloc(const char *name, int id) { struct student *s; char *new_name; s = calloc_a(sizeof(*s), &new_name, strlen(name) + 1); s->name = strcpy(new_name, name); s->id = id; return s; } static void student_update(struct vlist_tree *tree, struct vlist_node *node_new, struct vlist_node *node_old) { struct student *s_old = NULL, *s_new = NULL; if(node_new) s_new = container_of(node_new, struct student, node); if(node_old) s_old = container_of(node_old, struct student, node); if (s_old && s_new) { printf("Update student: name: %s id: %d\n", s_old->name, s_new->id); s_old->id = s_new->id; free(s_new); } else if (s_old) { printf("Remove student: %s\n", s_old->name); free(s_old); } else if (s_new) { printf("Add student: %s\n", s_new->name); } } static void student_list_init(void) { vlist_init(&students, avl_strcmp, student_update); students.keep_old = true; students.no_delete = false; } enum { STUDENT_ATTR_NAME, STUDENT_ATTR_ID, __STUDENT_ATTR_MAX }; static const struct blobmsg_policy student_attrs[__STUDENT_ATTR_MAX] = { [STUDENT_ATTR_NAME] = { .name = "name", .type = BLOBMSG_TYPE_STRING }, [STUDENT_ATTR_ID] = { .name = "id", .type = BLOBMSG_TYPE_INT32 } }; static int student_handle_add(struct ubus_context *ctx, struct ubus_object *obj, struct ubus_request_data *req, const char *method, struct blob_attr *msg) { struct blob_attr *tb[__STUDENT_ATTR_MAX]; int id; const char *name; struct student *s; blobmsg_parse(student_attrs, __STUDENT_ATTR_MAX, tb, blobmsg_data(msg), blobmsg_data_len(msg)); if (!tb[STUDENT_ATTR_NAME] || !tb[STUDENT_ATTR_ID]) { printf("Invalid argument\n"); return UBUS_STATUS_INVALID_ARGUMENT; } name = blobmsg_get_string(tb[STUDENT_ATTR_NAME]); id = blobmsg_get_u32(tb[STUDENT_ATTR_ID]); s = student_alloc(name, id); vlist_add(&students, &s->node, s->name); return 0; } static int student_handle_del(struct ubus_context *ctx, struct ubus_object *obj, struct ubus_request_data *req, const char *method, struct blob_attr *msg) { struct blob_attr *tb[__STUDENT_ATTR_MAX]; const char *name; struct student *s; blobmsg_parse(student_attrs, __STUDENT_ATTR_MAX, tb, blobmsg_data(msg), blobmsg_data_len(msg)); if (!tb[STUDENT_ATTR_NAME]) { printf("Invalid argument\n"); return UBUS_STATUS_INVALID_ARGUMENT; } name = blobmsg_get_string(tb[STUDENT_ATTR_NAME]); s = vlist_find(&students, name, s, node); if (!s) { printf("node %s not found\n", name); return UBUS_STATUS_NOT_FOUND; } vlist_delete(&students, &s->node); return 0; } static int student_handle_list(struct ubus_context *ctx, struct ubus_object *obj, struct ubus_request_data *req, const char *method, struct blob_attr *msg) { void *arr, *tb; struct student *student; if(students.avl.count == 0) return UBUS_STATUS_NO_DATA; blob_buf_init(&b, 0); arr = blobmsg_open_array(&b, NULL); vlist_for_each_element(&students, student, node) { tb = blobmsg_open_table(&b, NULL); blobmsg_add_string(&b, "name", student->name); blobmsg_add_u32(&b, "id", student->id); blobmsg_close_table(&b, tb); } blobmsg_close_array(&b, arr); ubus_send_reply(ctx, req, b.head); return 0; } static struct ubus_method student_object_methods[] = { UBUS_METHOD("add", student_handle_add, student_attrs), UBUS_METHOD("del", student_handle_del, student_attrs), UBUS_METHOD_NOARG("list", student_handle_list) }; static struct ubus_object_type student_object_type = UBUS_OBJECT_TYPE("student", student_object_methods); static struct ubus_object student_object = { .name = "student", .type = &student_object_type, .methods = student_object_methods, .n_methods = ARRAY_SIZE(student_object_methods), }; int main(int argc, char **argv) { int ret; uloop_init(); if (!(ctx = ubus_connect(NULL))) { printf("Unable to connect to ubus\n"); return -1; } printf("connected as %08x\n", ctx->local_id); ubus_add_uloop(ctx); student_list_init(); ret = ubus_add_object(ctx, &student_object); if (ret) { fprintf(stderr, "Failed to add object: %s\n", ubus_strerror(ret)); return -1; } uloop_run(); ubus_free(ctx); uloop_done(); return 0; } ``` ::: ### invoke_client --- 透過 blobmsg 將需傳送的資料封裝起來,並透過 `ubus_invoke` 調用 object 所提供的 method ```c const char *obj_name = "student"; blob_buf_init(&b, 0); switch (method) { case STUDENT_METHOD_ADD: obj_method = "add"; blobmsg_add_string(&b, "name", name); blobmsg_add_u32(&b, "id", id); break; case STUDENT_METHOD_DEL: obj_method = "del"; blobmsg_add_string(&b, "name", name); break; case STUDENT_METHOD_LIST: obj_method = "list"; break; default: break; ubus_invoke(ctx, obj_id, obj_method, b.head, dump_cb, NULL, timeout * 1000); ``` 再收到 response 後,如果有資料則會執行 `ubus_invoke` 中帶入的 data callback,並也使用 blobmsg 提供的 API 將資料做 parsing ```c static void dump_cb(struct ubus_request *req, int type, struct blob_attr *msg) { char *str; str = blobmsg_format_json_indent(msg, true, 0); printf("Received data:\n%s\n", str); free(str); } ``` 如下是其中幾種 blobmsg API ```c static inline int blobmsg_add_string(struct blob_buf *buf, const char *name, const char *string) static inline int blobmsg_add_u32(struct blob_buf *buf, const char *name, uint32_t val) static inline char *blobmsg_get_string(struct blob_attr *attr) static inline uint32_t blobmsg_get_u32(struct blob_attr *attr) ``` :::spoiler invoke_client.c ```c #include <stdio.h> #include <libubus.h> #include <libubox/uloop.h> #include <libubox/blobmsg_json.h> #include <libubox/vlist.h> static struct ubus_context *ctx; static struct blob_buf b; enum { STUDENT_METHOD_ADD, STUDENT_METHOD_DEL, STUDENT_METHOD_LIST, __STUDENT_METHOD_MAX, }; static void dump_cb(struct ubus_request *req, int type, struct blob_attr *msg) { char *str; str = blobmsg_format_json_indent(msg, true, 0); printf("Received data:\n%s\n", str); free(str); } static void student_method(int method, char *name, int id) { unsigned int obj_id; int ret; int timeout = 5; const char *obj_name = "student"; const char *obj_method; ret = ubus_lookup_id(ctx, obj_name, &obj_id); if (ret != UBUS_STATUS_OK) { fprintf(stderr, "ubus_lookup_id() failed: %s\n", ubus_strerror(ret)); return; } blob_buf_init(&b, 0); switch (method) { case STUDENT_METHOD_ADD: obj_method = "add"; blobmsg_add_string(&b, "name", name); blobmsg_add_u32(&b, "id", id); break; case STUDENT_METHOD_DEL: obj_method = "del"; blobmsg_add_string(&b, "name", name); break; case STUDENT_METHOD_LIST: obj_method = "list"; break; default: break; } ret = ubus_invoke(ctx, obj_id, obj_method, b.head, dump_cb, NULL, timeout * 1000); if (ret != UBUS_STATUS_OK) { fprintf(stderr, "ubus_invoke() failed: %s\n", ubus_strerror(ret)); return; } } int main(int argc, char **argv) { if (!(ctx = ubus_connect(NULL))) { fprintf(stderr, "Failed to connect to ubus\n"); return -1; } printf("connected as %08x\n", ctx->local_id); printf("Add two students\n"); student_method(STUDENT_METHOD_ADD, "Musk", 1001); student_method(STUDENT_METHOD_ADD, "Cook", 1002); printf("List students\n"); student_method(STUDENT_METHOD_LIST, NULL, 0); printf("Delete a student\n"); student_method(STUDENT_METHOD_DEL, "Cook", 0); printf("List students\n"); student_method(STUDENT_METHOD_LIST, NULL, 0); ubus_free(ctx); return 0; } ``` ::: ### Result --- invoke_server ```bash root@OpenWrt:/tmp# ./invoke_server connected as afacbded Add student: Musk Add student: Cook Remove student: Cook Update student: name: Musk id: 1101 ``` invoke_client ```bash root@OpenWrt:/tmp# ./invoke_client connected as 809001d8 Add two students List students Received data: { [ { "name": "Cook", "id": 1002 }, { "name": "Musk", "id": 1001 } ] } Delete a student List students Received data: { [ { "name": "Musk", "id": 1001 } ] } root@OpenWrt:/tmp# ubus call student add '{"name": "Musk", "id": 1101}' root@OpenWrt:/tmp# ubus call student list { [ { "name": "Musk", "id": 1101 } ] } ``` Invoke / reply 範例可由以下概念圖表示之. ![markdown](https://i.ibb.co/V9MvwJn/Capture-079.png "markdown") ## 2. Subscribe / notify --- 此範例改寫 invoke_server 並重新命名為 publisher (此溝通方式類似軟體設計中的 publish-subscribe pattern),而另一支程式為 subscriber,每當學生清單被修改時會收到通知 ### publisher --- 修改原先的 student_update(),當 vlist 發生新增或刪除事件時呼叫 handle_notify() 來通知已註冊上的 subscribers ```c static void student_update(struct vlist_tree *tree, struct vlist_node *node_new, struct vlist_node *node_old) { struct student *s_old = NULL, *s_new = NULL; if (node_new) s_new = container_of(node_new, struct student, node); if (node_old) s_old = container_of(node_old, struct student, node); if (s_old && s_new) { printf("Update student: name: %s id: %d\n", s_old->name, s_new->id); s_old->id = s_new->id; free(s_new); } else if (s_old) { printf("Remove student: %s\n", s_old->name); handle_notify("student.remove", s_old); free(s_old); } else if (s_new) { printf("Add student: %s\n", s_new->name); handle_notify("student.add", s_new); } } ``` 在這裡只將學生的名稱放進 blobmsg,並用指定的 type 送出通知 ```c static void handle_notify(const char *type, const struct student *student) { if (!ctx || !student_object.has_subscribers) return; printf("Send ubus notify '%s': %s\n", type, student->name); blob_buf_init(&b, 0); blobmsg_add_string(&b, "name", student->name); ubus_notify(ctx, &student_object, type, b.head, -1); } ``` :::spoiler publisher.c ```c #include <stdio.h> #include <libubus.h> #include <libubox/uloop.h> #include <libubox/blobmsg_json.h> #include <libubox/vlist.h> #include <libubox/avl-cmp.h> static struct ubus_context *ctx; static struct blob_buf b; struct vlist_tree students; struct student { const char *name; int id; struct vlist_node node; }; static struct student * student_alloc(const char *name, int id) { struct student *s; char *new_name; s = calloc_a(sizeof(*s), &new_name, strlen(name) + 1); s->name = strcpy(new_name, name); s->id = id; return s; } enum { STUDENT_ATTR_NAME, STUDENT_ATTR_ID, __STUDENT_ATTR_MAX }; static const struct blobmsg_policy student_attrs[__STUDENT_ATTR_MAX] = { [STUDENT_ATTR_NAME] = { .name = "name", .type = BLOBMSG_TYPE_STRING }, [STUDENT_ATTR_ID] = { .name = "id", .type = BLOBMSG_TYPE_INT32 } }; static int student_handle_add(struct ubus_context *ctx, struct ubus_object *obj, struct ubus_request_data *req, const char *method, struct blob_attr *msg) { struct blob_attr *tb[__STUDENT_ATTR_MAX]; int id; const char *name; struct student *s; blobmsg_parse(student_attrs, __STUDENT_ATTR_MAX, tb, blobmsg_data(msg), blobmsg_data_len(msg)); if (!tb[STUDENT_ATTR_NAME] || !tb[STUDENT_ATTR_ID]) { printf("Invalid argument\n"); return UBUS_STATUS_INVALID_ARGUMENT; } name = blobmsg_get_string(tb[STUDENT_ATTR_NAME]); id = blobmsg_get_u32(tb[STUDENT_ATTR_ID]); s = student_alloc(name, id); vlist_add(&students, &s->node, s->name); return 0; } static int student_handle_del(struct ubus_context *ctx, struct ubus_object *obj, struct ubus_request_data *req, const char *method, struct blob_attr *msg) { struct blob_attr *tb[__STUDENT_ATTR_MAX]; const char *name; struct student *s; blobmsg_parse(student_attrs, __STUDENT_ATTR_MAX, tb, blobmsg_data(msg), blobmsg_data_len(msg)); if (!tb[STUDENT_ATTR_NAME]) { printf("Invalid argument\n"); return UBUS_STATUS_INVALID_ARGUMENT; } name = blobmsg_get_string(tb[STUDENT_ATTR_NAME]); s = vlist_find(&students, name, s, node); if (!s) { printf("node %s not found\n", name); return UBUS_STATUS_NOT_FOUND; } vlist_delete(&students, &s->node); return 0; } static int student_handle_list(struct ubus_context *ctx, struct ubus_object *obj, struct ubus_request_data *req, const char *method, struct blob_attr *msg) { void *arr, *tb; struct student *student; if (students.avl.count == 0) return UBUS_STATUS_NO_DATA; blob_buf_init(&b, 0); arr = blobmsg_open_array(&b, NULL); vlist_for_each_element(&students, student, node) { tb = blobmsg_open_table(&b, NULL); blobmsg_add_string(&b, "name", student->name); blobmsg_add_u32(&b, "id", student->id); blobmsg_close_table(&b, tb); } blobmsg_close_array(&b, arr); ubus_send_reply(ctx, req, b.head); return 0; } static struct ubus_method student_object_methods[] = { UBUS_METHOD("add", student_handle_add, student_attrs), UBUS_METHOD("del", student_handle_del, student_attrs), UBUS_METHOD_NOARG("list", student_handle_list) }; static struct ubus_object_type student_object_type = UBUS_OBJECT_TYPE("student", student_object_methods); static struct ubus_object student_object = { .name = "student", .type = &student_object_type, .methods = student_object_methods, .n_methods = ARRAY_SIZE(student_object_methods), }; static void handle_notify(const char *type, const struct student *student) { if (!ctx || !student_object.has_subscribers) return; printf("Send ubus notify '%s': %s\n", type, student->name); blob_buf_init(&b, 0); blobmsg_add_string(&b, "name", student->name); ubus_notify(ctx, &student_object, type, b.head, -1); } static void student_update(struct vlist_tree *tree, struct vlist_node *node_new, struct vlist_node *node_old) { struct student *s_old = NULL, *s_new = NULL; if (node_new) s_new = container_of(node_new, struct student, node); if (node_old) s_old = container_of(node_old, struct student, node); if (s_old && s_new) { printf("Update student: name: %s id: %d\n", s_old->name, s_new->id); s_old->id = s_new->id; free(s_new); } else if (s_old) { printf("Remove student: %s\n", s_old->name); handle_notify("student.remove", s_old); free(s_old); } else if (s_new) { printf("Add student: %s\n", s_new->name); handle_notify("student.add", s_new); } } static void student_list_init(void) { vlist_init(&students, avl_strcmp, student_update); students.keep_old = true; students.no_delete = false; } int main(int argc, char **argv) { int ret; uloop_init(); if (!(ctx = ubus_connect(NULL))) { printf("Unable to connect to ubus\n"); return -1; } printf("connected as %08x\n", ctx->local_id); ubus_add_uloop(ctx); student_list_init(); ret = ubus_add_object(ctx, &student_object); if (ret) { fprintf(stderr, "Failed to add object: %s\n", ubus_strerror(ret)); return -1; } uloop_run(); ubus_free(ctx); uloop_done(); return 0; } ``` ::: ### subscriber --- 訂閱 student object 後等待通知 ```c static struct ubus_subscriber student_subscribe; student_subscribe.cb = handle_subscribe; ubus_register_subscriber(ctx, &student_subscribe); ubus_lookup_id(ctx, obj_name, &obj_id); ubus_subscribe(ctx, &student_subscribe, obj_id); ``` 將通知的內容 parse 後顯示 ```c static int handle_subscribe(struct ubus_context *ctx, struct ubus_object *obj, struct ubus_request_data *req, const char *method, struct blob_attr *msg) { const struct blobmsg_policy student_attrs = { .name = "name", .type = BLOBMSG_TYPE_STRING }; struct blob_attr *attr; const char *name; blobmsg_parse(&student_attrs, 1, &attr, blobmsg_data(msg), blobmsg_len(msg)); if (!attr) { printf("Invalid argument\n"); return UBUS_STATUS_INVALID_ARGUMENT; } name = blobmsg_get_string(attr); printf("Received ubus notify '%s': %s\n", method, name); return 0; } ``` :::spoiler subscriber.c ```c #include <stdio.h> #include <libubus.h> #include <libubox/uloop.h> #include <libubox/blobmsg_json.h> static struct ubus_context *ctx; static struct ubus_subscriber student_subscribe; static int handle_subscribe(struct ubus_context *ctx, struct ubus_object *obj, struct ubus_request_data *req, const char *method, struct blob_attr *msg) { const struct blobmsg_policy student_attrs = { .name = "name", .type = BLOBMSG_TYPE_STRING }; struct blob_attr *attr; const char *name; blobmsg_parse(&student_attrs, 1, &attr, blobmsg_data(msg), blobmsg_len(msg)); if (!attr) { printf("Invalid argument\n"); return UBUS_STATUS_INVALID_ARGUMENT; } name = blobmsg_get_string(attr); printf("Received ubus notify '%s': %s\n", method, name); return 0; } int main(int argc, char **argv) { int ret; unsigned int obj_id; const char *obj_name = "student"; uloop_init(); if (!(ctx = ubus_connect(NULL))) { fprintf(stderr, "Failed to connect to ubus\n"); return -1; } printf("connected as %08x\n", ctx->local_id); ubus_add_uloop(ctx); student_subscribe.cb = handle_subscribe; ret = ubus_register_subscriber(ctx, &student_subscribe); if (ret != UBUS_STATUS_OK) { fprintf(stderr, "ubus_register_subscriber() failed: %s\n", ubus_strerror(ret)); return ret; } ret = ubus_lookup_id(ctx, obj_name, &obj_id); if (ret != UBUS_STATUS_OK) { fprintf(stderr, "ubus_lookup_id() failed: %s\n", ubus_strerror(ret)); return ret; } ret = ubus_subscribe(ctx, &student_subscribe, obj_id); if (ret != UBUS_STATUS_OK) { fprintf(stderr, "ubus_subscribe() failed: %s\n", ubus_strerror(ret)); return ret; } uloop_run(); ubus_free(ctx); uloop_done(); return 0; } ``` ::: ### Result --- publisher ```bash root@OpenWrt:/# publisher connected as be3fdadb Add student: Musk Send ubus notify 'student.add': Musk Add student: Cook Send ubus notify 'student.add': Cook Remove student: Cook Send ubus notify 'student.remove': Cook ``` subscriber ```bash root@OpenWrt:~# subscriber connected as 2803c202 Received ubus notify 'student.add': Musk Received ubus notify 'student.add': Cook Received ubus notify 'student.remove': Cook ``` invoke_client ```bash root@OpenWrt:~# invoke_client connected as 994e4915 Add two students List students Received data: { [ { "name": "Cook", "id": 1002 }, { "name": "Musk", "id": 1001 } ] } Delete a student List students Received data: { [ { "name": "Musk", "id": 1001 } ] } ``` ## 3. Broadcast event --- 不同於 `Invoke / reply` 和 `Subscribe / notify` 需要等待 object 註冊到 ubus 後才能進行溝通 sender 只負責將訊息送出去而不知道有哪些 listeners 存在,並且所有 listeners 都會收到訊息,類似 broadcast 的溝通方式 此範例改寫 subscriber.c 並命名為 listener.c ,不會直接去做訂閱 student obj 的動作,而是等待收到 student object 新增的事件才去對他做訂閱 ### sender --- sender 為 ubus daemon,每當有 object 被註冊時,就會發送 `ubus.object.add` 的 event,以下為幾個重要的 call flow `ubus/ubusd_proto.c` ```c static int ubusd_handle_add_object(struct ubus_client *cl, struct ubus_msg_buf *ub, struct blob_attr **attr) { ... obj = ubusd_create_object(cl, attr); ... return 0; } ``` `ubus/ubusd_obj.c` ```c struct ubus_object *ubusd_create_object(struct ubus_client *cl, struct blob_attr **attr) { ... obj = ubusd_create_object_internal(type, 0); ... if (attr[UBUS_ATTR_OBJPATH]) { ... ubusd_send_obj_event(obj, true); } } ``` `ubus/ubusd_event.c` ```c void ubusd_send_obj_event(struct ubus_object *obj, bool add) { const char *id = add ? "ubus.object.add" : "ubus.object.remove"; ubusd_send_event(NULL, id, ubusd_create_object_event_msg, obj); } static struct ubus_msg_buf * ubusd_create_object_event_msg(void *priv, const char *id) { struct ubus_object *obj = priv; void *s; blob_buf_init(&b, 0); blob_put_int32(&b, UBUS_ATTR_OBJID, 0); blob_put_string(&b, UBUS_ATTR_METHOD, id); s = blob_nest_start(&b, UBUS_ATTR_DATA); blobmsg_add_u32(&b, "id", obj->id.id); blobmsg_add_string(&b, "path", obj->path.key); blob_nest_end(&b, s); return ubus_msg_new(b.head, blob_raw_len(b.head), true); } ``` 如果要透過 ubus 提供的 API 發送事件的話,使用下面這個 function ```c int ubus_send_event(struct ubus_context *ctx, const char *id, struct blob_attr *data); For example: blob_buf_init(&b, 0); blobmsg_add_string(&b, "name", "foo"); blobmsg_add_string(&b, "action", add ? "add" : "del"); ubus_send_event(ubus_ctx, "student", b.head); ``` ### listener --- 註冊 event handler 後等待 callback ```c static struct ubus_event_handler event_handler; event_handler.cb = handle_event; ubus_register_event_handler(ctx, &event_handler, "ubus.object.add"); ``` 收到 event 後 parse 出內容,判斷 path 是否為 student 這個 object,如果是則直接使用內容中的 id 來做訂閱 ```c static void handle_event(struct ubus_context *ctx, struct ubus_event_handler *ev, const char *type, struct blob_attr *msg) { int ret; struct blob_attr *tb[OBJ_ATTR_MAX]; uint32_t obj_id; const char *path; blobmsg_parse(obj_attrs, OBJ_ATTR_MAX, tb, blobmsg_data(msg), blobmsg_len(msg)); if (!tb[OBJ_ATTR_ID] || !tb[OBJ_ATTR_PATH]) return; path = blobmsg_get_string(tb[OBJ_ATTR_PATH]); if (strcmp(path, "student")) return; obj_id = blobmsg_get_u32(tb[OBJ_ATTR_ID]); printf("Received ubus event '%s': %s, %d\n", type, path, obj_id); ret = ubus_subscribe(ctx, &student_subscribe, obj_id); if (ret != UBUS_STATUS_OK) { fprintf(stderr, "ubus_subscribe() failed: %s\n", ubus_strerror(ret)); return; } } ``` :::spoiler listener.c ```c #include <stdio.h> #include <libubus.h> #include <libubox/uloop.h> #include <libubox/blobmsg_json.h> static struct ubus_context *ctx; static struct ubus_subscriber student_subscribe; static struct ubus_event_handler event_handler; static int handle_subscribe(struct ubus_context *ctx, struct ubus_object *obj, struct ubus_request_data *req, const char *method, struct blob_attr *msg) { const struct blobmsg_policy student_attrs = { .name = "name", .type = BLOBMSG_TYPE_STRING }; struct blob_attr *attr; const char *name; blobmsg_parse(&student_attrs, 1, &attr, blobmsg_data(msg), blobmsg_len(msg)); if (!attr) { printf("Invalid argument\n"); return UBUS_STATUS_INVALID_ARGUMENT; } name = blobmsg_get_string(attr); printf("Received ubus notify '%s': %s\n", method, name); return 0; } enum { OBJ_ATTR_ID, OBJ_ATTR_PATH, OBJ_ATTR_MAX }; static const struct blobmsg_policy obj_attrs[OBJ_ATTR_MAX] = { [OBJ_ATTR_ID] = { .name = "id", .type = BLOBMSG_TYPE_INT32 }, [OBJ_ATTR_PATH] = { .name = "path", .type = BLOBMSG_TYPE_STRING }, }; static void handle_event(struct ubus_context *ctx, struct ubus_event_handler *ev, const char *type, struct blob_attr *msg) { int ret; struct blob_attr *tb[OBJ_ATTR_MAX]; uint32_t obj_id; const char *path; blobmsg_parse(obj_attrs, OBJ_ATTR_MAX, tb, blobmsg_data(msg), blobmsg_len(msg)); if (!tb[OBJ_ATTR_ID] || !tb[OBJ_ATTR_PATH]) return; path = blobmsg_get_string(tb[OBJ_ATTR_PATH]); if (strcmp(path, "student")) return; obj_id = blobmsg_get_u32(tb[OBJ_ATTR_ID]); printf("Received ubus event '%s': %s, %d\n", type, path, obj_id); ret = ubus_subscribe(ctx, &student_subscribe, obj_id); if (ret != UBUS_STATUS_OK) { fprintf(stderr, "ubus_subscribe() failed: %s\n", ubus_strerror(ret)); return; } } int main(int argc, char **argv) { int ret; uloop_init(); if (!(ctx = ubus_connect(NULL))) { fprintf(stderr, "Failed to connect to ubus\n"); return -1; } printf("connected as %08x\n", ctx->local_id); ubus_add_uloop(ctx); event_handler.cb = handle_event; ubus_register_event_handler(ctx, &event_handler, "ubus.object.add"); student_subscribe.cb = handle_subscribe; ret = ubus_register_subscriber(ctx, &student_subscribe); if (ret != UBUS_STATUS_OK) { fprintf(stderr, "ubus_register_subscriber() failed: %s\n", ubus_strerror(ret)); return ret; } uloop_run(); ubus_free(ctx); uloop_done(); return 0; } ``` ::: ### Result --- listener ```bash root@OpenWrt:/# listener connected as 648db3cd Received ubus event 'ubus.object.add': student, 1224216331 Received ubus notify 'student.add': Musk Received ubus notify 'student.add': Cook Received ubus notify 'student.remove': Cook ``` publisher ```bash root@OpenWrt:~# publisher connected as 33eefc06 Add student: Musk Send ubus notify 'student.add': Musk Add student: Cook Send ubus notify 'student.add': Cook Remove student: Cook Send ubus notify 'student.remove': Cook ``` invoke_client ```bash root@OpenWrt:~# invoke_client connected as a3a5f652 Add two students List students Received data: { [ { "name": "Cook", "id": 1002 }, { "name": "Musk", "id": 1001 } ] } Delete a student List students Received data: { [ { "name": "Musk", "id": 1001 } ] } ```