OronBox

AppSide 伴生服务

ZeppOS 伴生服务:ZML 运行时与调试

管理 ZeppOS 设备的伴生服务(AppSide)

伴生服务是运行在手表小程序旁边的远程 JS 执行通道,通过 messaging.peerSocket 与小程序实时通信

appId 是 32 位无符号整数(Zepp OS 应用 ID)

ZML

用法参考:ZML

OronBox 内置了自包含的 ZML AppSide 通信运行时,插件不需要自行打包 @zeppos/zml;用 attach() 把运行时绑定到小程序,即可在插件钩子里使用官方 BaseSideService 风格的 this.request()this.call()onRequestonCall API

插件的 manifest.json 必须声明 appside 权限:

{
  "permissions": ["appside"]
}

attach(options)

appId 创建或获取 ZML 上下文

onInitonRunonDestroyonRequestonCall 钩子都是可选的;在每个钩子内部,this 就是 attach() 返回的同一个 ZML 上下文

globalThis.activate = async () => {
  const zml = await OronBox.appside.attach({
    appId: 0x0010ee3b,

    onInit() {
      console.log('ZML App-side initialized');
    },

    async onRun() {
      const result = await this.request({
        method: 'device.getData',
        params: { type: 'summary' },
      });
      console.log('Watch response:', JSON.stringify(result));
    },

    onRequest(req, res) {
      if (req.method === 'plugin.getState') {
        res(null, { ready: true });
        return;
      }
      res({
        code: 'METHOD_NOT_FOUND',
        message: `Unknown method: ${req.method}`,
      });
    },

    onCall(message) {
      console.log('Watch notification:', JSON.stringify(message));
    },

    onDestroy() {
      console.log('ZML App-side stopped');
    },
  });

  const result = await zml.request({
    method: 'device.getInfo',
    params: {},
  });

  await zml.call({
    method: 'plugin.ready',
    params: { success: true },
  });
};

request(message, options?)

发送一个期待响应的 ZML 请求并返回 Promise

默认超时遵循内置 ZML 运行时的配置

const result = await zml.request({
  method: 'weather.get',
  params: { city: 'Shanghai' },
});

call(message)

发送单向通知,不等待响应

await zml.call({
  method: 'settings.changed',
  params: { theme: 'dark' },
});

detach()

移除本插件的钩子并释放 ZML 上下文

插件关闭时 OronBox 也会自动清理

await zml.detach();

每个 appId 只创建一个伴生服务会话,钩子不能另开蓝牙连接

request() / call() 只能在手表已连接、设备就绪且小程序已打开伴生服务后发送数据

方法

list()

const ids = await OronBox.appside.list();
// [0x0010ee3b, 0x00001234, ...]

start(appId)

启动本地 AppSide 脚本运行时(需要有缓存的脚本)

await OronBox.appside.start(0x0010ee3b);

stop(appId)

await OronBox.appside.stop(0x0010ee3b);

send(appId, hexData)

向手表发送十六进制编码的二进制数据(需要活动的手表会话)

await OronBox.appside.send(0x0010ee3b, '0100ff');

inject(appId, hexData)

模拟一条手表发往宿主机的消息,注入本地运行时(调试用——不需要手表会话)

await OronBox.appside.inject(0x0010ee3b, '48656c6c6f');
// "Hello" 的十六进制 → 运行时的 peerSocket.onmessage 会收到它

sessions()

列出所有活动会话

const sessions = await OronBox.appside.sessions();
// [{ appId: 0x0010ee3b, watchSessionOpen: true }, ...]

events(appId)

读取某个 appId 的调试事件日志

const events = await OronBox.appside.events(0x0010ee3b);
// [{ timestamp: '…', type: 'start', message: 'Script loaded' }, ...]

clearEvents(appId)

await OronBox.appside.clearEvents(0x0010ee3b);

完整示例:AppSide 管理器

globalThis.activate = async (plugin) => {
  let ids = [];
  let result = '';
  const { Column, Text, Button } = OronBox.ui;

  const render = () => {
    const nodes = [
      Button('Refresh', {
        onClick: OronBox.ui.action(async () => {
          ids = await OronBox.appside.list();
          result = `${ids.length} scripts cached: ${ids.map(i => '0x'+i.toString(16)).join(', ')}`;
        }, render),
      }),
      Text(result),
    ];

    for (const id of ids) {
      const hex = '0x' + id.toString(16);
      nodes.push(Button(`Start ${hex}`, {
        onClick: OronBox.ui.action(async () => {
          try { await OronBox.appside.start(id); result = `${hex} started`; }
          catch (e) { result = `Error: ${e.message}`; }
        }, render),
      }));
    }
    return OronBox.ui.render(Column({ gap: 8 }, nodes));
  };

  render();
};

权限

在 manifest 中声明 appside 权限;只读操作(listsessionsevents)是中风险,控制操作(startstopsendinjectclearEvents)是高风险

On this page