---
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
}
]
}
```