ITADN
espruino/BangleApps
README.md
以下内容由 AI 翻译,如有问题请点此提交 issue 反馈

Bangle.js 应用加载器(及应用)

Build Status

release version 会定期手动刷新,而 development version 会随着新代码提交到本仓库而持续更新。

本仓库中的所有软件(包括应用)均采用 MIT 许可证 - 参见 LICENSE 通过 向本仓库提交代码,您确认同意其采用 MIT 许可证, 并且不存在其他可能使此情况不可能的许可方式。

工作原理?

  • 应用列表位于 apps.json(这是使用 Jekyll 或 bin/create_apps_json.sh 从所有 apps/yourapp/metadata.json 自动生成的)
  • 每个元素引用 apps/<id> 中的一个应用,该应用会被上传
  • 启动时,BangleAppLoader 会检查 JSON 并将其 与手表存储中看到的文件进行比较。
  • 要上传应用,BangleAppLoader 会检查 apps.json 中列出的文件,加载它们,并通过 Web Bluetooth 发送。

入门指南

查看:

使用的文件名

存储中的文件名限制为 28 个字符。为了 轻松区分文件类型,我们使用以下命名:

  • appid.info 是描述应用的 JSON - 这是由 App Loader 自动生成的
  • appid.img 是一张图片
  • appid.app.js 是应用程序的 JS 代码
  • appid.wid.jswidgets 的 JS 代码
  • appid.clkinfo.jsclock info 的 JS 代码
  • appid.settings.jsthe settings menu 的 JS 代码
  • appid.boot.js 是在启动时自动运行的 JS 代码
  • appid.json 用于应用的 JSON 设置

开发你自己的应用

  • 前往 the Web IDE 并确保设置中的 Save on Send 设置为 默认设置 To RAM
  • 我们建议你先使用 'Example Applications'(下方)中的代码开始...
  • app.jswidget.js 加载到 IDE 中并开始开发。
  • Upload 按钮会将你的应用临时加载到 Bangle.js

将你的应用添加到菜单

  • 想一个唯一的(全小写,无空格)名称,我们假设是 myappid。Bangle.js 文件名限制为 28 个字符,并会添加文件扩展名(例如 .js),因此请 尽量保持文件名简短,以避免缓冲区溢出。
  • 创建一个名为 apps/<id> 的文件夹,假设是 apps/myappid
  • 我们建议你从 apps/_example_* 中的一个示例(见下文)复制文件,或者...
  • apps/myappid/app.png 应该是一个 48px 的图标
  • 使用 http://www.espruino.com/Image+Converter 创建 apps/myappid/app-icon.js,使用 1 位、4 位或 8 位 Web 调色板 "Image String"
  • 创建/修改 apps/myappid/metadata.json 如下:
{ "id": "myappid",
  "name": "My app's human readable name",
  "shortName" : "Short Name",
  "icon": "app.png",
  "description": "A detailed description of my great app",
  "tags": "",
  "storage": [
    {"name":"myappid.app.js","url":"app.js"},
    {"name":"myappid.img","url":"app-icon.js","evaluate":true}
  ],
},

屏幕截图

在应用 metadata.json 文件中,你可以添加一个屏幕截图列表,使用类似如下的行:"screenshots" : [ { "url":"screenshot.png" } ],

要获取屏幕截图,你可以:

  • 在连接到 Bangle.js 2 时,在 Web IDE 的左侧输入 g.dump() - 然后你可以 右键点击并保存终端中显示的图像(这仅在 Bangle.js 2 上有效 - Bangle.js 1 无法 从 LCD 控制器读回数据)。
  • 在模拟器中运行你的代码,并使用窗口右下角的屏幕截图按钮。

你还可以截取多张屏幕截图,并使用像 this 这样的网站将其转换为动画 .gif 文件,并提供该文件以在应用中展示运动效果(闪烁的图像、动画等)。

测试

对于使用 Bangle.js 模拟器进行应用功能测试,请参阅 TESTING.md

在线

这是测试的最佳方式...

  • Fork https://github.com/espruino/BangleApps git 仓库
  • 添加你的文件
  • 前往 GitHub 设置并激活 GitHub Pages
  • 运行你的个人 Bangle App Loader 于 https://<your-github-username>.github.io/BangleApps/index.html 以将应用加载到你的设备上
  • 你的应用应该在其中 - 如果出现问题,请检查你的 Web 浏览器的“开发者控制台”中的错误

注意: 在你的 PC 上获取该仓库的本地副本是个好主意, 然后运行 bin/sanitycheck.js - 它会检查一系列可能存在的常见问题。要在本地运行该项目,你必须先初始化并更新 git 子模块:git submodule update --init

请注意提交与 github.io 上的更新之间的延迟——更改生效可能需要几分钟时间(并且需要对浏览器进行“强制刷新”)。

离线

使用 Web IDE (4 个磁盘)中的“存储”图标,将文件上传到 JSON 中描述的位置:

  • app-icon.js -> myappid.img

现在在编辑器中加载 app.js,并点击 Send to Espruino 图标右下角的下拉箭头。点击 Storage,然后选择 myappid.app.js(如果你之前已上传过你的应用),或者选择 New File 然后输入 myappid.app.js 作为名称。

现在,点击 Send to Espruino 图标将直接把应用加载到 Espruino 并且 会自动运行它。

当你以这种方式上传代码时,你的应用甚至会被上传到 Bangle.js 的菜单中, 而无需你使用 Bangle App Loader

注意: 小部件需要在时钟或应用内部运行,因此如果你正在 开发一个小部件,你需要前往 Settings -> Communications -> Load after saving 并将其设置为 Load default application

示例应用

为了简化流程,我们想出了几个示例应用,你可以在创建自己的应用时将其作为基础。只需想出一个独特的名称(理想情况下为小写,少于 20 个字符),将 apps/_example_appapps/_example_widget 复制到 apps/myappid,并相应地编辑 apps/myappid/metadata.json

注意: 文件名最大长度为 28 个字符,因此我们建议应用 ID 少于 20 个字符,这样当在末尾添加 .app.js/etc 时文件名不会被截断。

如果你正在制作一个小部件 请以 wid 开头命名,以便 轻松找到!

应用示例

应用示例可在 apps/_example_app 中找到

应用列在 Bangle.js 菜单中,可通过时钟应用中的中间按钮访问。

  • metadata.json - 向 bootloader 和 loader 描述该应用
  • app.png - 应用图标 - 48x48px
  • app-icon.js - 图标的 JS 版本(使用 http://www.espruino.com/Image+Converter 制作),用于 Bangle.js 的菜单
  • app.js - 应用代码
  • ChangeLog - 包含应用更改列表的文件,以便用户查看更改内容

app-icon.js

图标图像和简短描述用于 Bangle.js 的启动器。

使用 Espruino 图像转换器 并上传你的 app.png 文件。

按照以下步骤创建可读的图像字符串图标。

  1. 上传一个 48x48 的 png 文件 - 图像应为 48x48 或更小
  2. 设置 X 使用压缩
  3. 设置 X 透明度(可选)
  4. 设置扩散:flat
  5. 设置颜色:1 bit、任意优化选项或 8 bit Web Palette 最佳
  6. 设置输出为:Image String

用图像转换器的输出替换这一行:

require("heatshrink").decompress(atob("mEwwJC/AH4A/AH4AgA=="))

不要添加尾部分号

您还可以使用此转换器来创建您希望用 g.drawImage() 在您的应用中绘制的图像。

需要小部件的应用程序可以在启动时调用 Bangle.loadWidgets() 一次 来加载 它们,然后调用 Bangle.drawWidgets() 在应用程序需要完全清除屏幕时将它们绘制到屏幕上。小部件本身会在需要时自行更新。

小部件示例

小部件示例可在 apps/_example_widget 中找到

  • metadata.json - 向引导加载程序和加载器描述小部件
  • widget.js - 小部件代码

小部件只是运行在支持它们的应用程序调用 Bangle.loadWidgets() 时的一小段代码。如果它们想在屏幕顶部 24px 高 的小部件栏中显示某些内容,它们可以将自己添加到全局 WIDGETS 数组中:

WIDGETS["mywidget"]={
  area:"tl", // tl (top left), tr (top right), bl (bottom left), br (bottom right)
  sortorder:0, // (Optional) determines order of widgets in the same corner
  width: 24, // how wide is the widget? You can change this and call Bangle.drawWidgets() to re-layout
  draw:draw // called to draw the widget
};

当小部件需要绘制时,xy 的值会在 WIDGETS["mywidget"] 中设置, 然后 draw 可以使用 this.xthis.y 来确定需要绘制的位置。

ChangeLog

这是一个包含应用程序更改列表的文件,以便用户查看更改内容,例如:

0.01: New App!
0.02: Changed the colors
0.03: Made the app run quicker

条目应按最新条目置于末尾的顺序排列,且最后一个条目的版本号需与 metadata.json 中的版本一致。

请保持与示例相同的格式,因为该文件需要由 BangleApps 工具进行解析。

app.info 格式

此文件是从 metadata.json 自动生成 的,并由 App Loader 加载到 Bangle.js 上, 用于向 Launcher 提供有关该应用的信息。

{
  "name":"Short Name", // for Bangle.js menu
  "icon":"*myappid", // for Bangle.js menu
  "src":"-myappid", // source file
  "type":"widget/clock/app/bootloader/...", // optional, default "app"
     // see 'type' in 'metadata.json format' below for more options/info
  "version":"0.01",
     // added by BangleApps loader on upload based on metadata.json
  "files:"file1,file2,file3",
     // added by BangleApps loader on upload - lists all files
     // that belong to the app so it can be deleted
  "data":"appid.data.json,appid.data?.json;appidStorageFile,appidStorageFile*"
     // added by BangleApps loader on upload - lists files that
     // the app might write, so they can be deleted on uninstall
     // typically these files are not uploaded, but created by the app
     // these can include '*' or '?' wildcards
}

metadata.json 格式

{ "id": "appid",              // 7 character app id
  "name": "Readable name",    // readable name
  "shortName": "Short name",  // short name for launcher
  "version": "0.01",          // the version of this app
  "author": "github_username",// username of app author (GitHub username or 'Espruino' for anonymity)
  "description": "...",       // long description (can contain markdown)
  "icon": "icon.png",         // icon in apps/
  "screenshots" : [ { "url":"screenshot.png" } ], // optional screenshot for app
  "type":"...",               // optional(if app) -
                              //   'app' - an application
                              //   'clock' - a clock - required for clocks to automatically start
                              //   'widget' - a widget
                              //   'module' - this provides a module that can be used with 'require'.
                              //              'provides_modules' should be used if type:module is specified
                              //   'bootloader' - an app that at startup (app.boot.js) but doesn't have a launcher entry for 'app.js'
                              //   'settings' - apps that appear in Settings->Apps (with appname.settings.js) but that have no 'app.js'
                              //   'clkinfo' - Provides a 'myapp.clkinfo.js' file that can be used to display info in clocks - see modules/clock_info.js
                              //   'RAM' - code that runs and doesn't upload anything to storage
                              //   'launch' - replacement 'Launcher'
                              //   'textinput' - provides a 'textinput' library that allows text to be input on the Bangle
                              //   'scheduler' - provides 'sched' library and boot code for scheduling alarms/timers
                              //                 (currently only 'sched' app)
                              //   'notify' - provides 'notify' library for showing notifications
                              //   'locale' - provides 'locale' library for language-specific date/distance/etc
                              //              (a version of 'locale' is included in the firmware)
                              //   'defaultconfig' - a set of apps that will can be installed and will wipe out all previously installed apps
  "tags": "",                 // comma separated tag list for searching (don't include uppercase or spaces)
                              // common types are:
                              //   'clock' - it's a clock
                              //   'widget' - it is (or provides) a widget
                              //   'outdoors' - useful for outdoor activities
                              //   'tool' - a useful utility (timer, calculator, etc)
                              //   'game' - a game
                              //   'bluetooth' - uses Bluetooth LE
                              //   'system' - used by the system
                              //   'clkinfo' - provides or uses clock_info module for data on your clock face or clocks that support it (see apps/clock_info/README.md)
                              //   'health' - e.g. heart rate monitors or step counting
  "supports": ["BANGLEJS2"],  // List of device IDs supported, either BANGLEJS, BANGLEJS2, BANGLEJS3 or BANGLEJS3_COMPAT(faking the 176x176 LCD)
  "dependencies" : { "notify":"type" } // optional, app 'types' we depend on (see "type" above)
  "dependencies" : { "messages":"app" } // optional, depend on a specific app ID
                              // for instance this will use notify/notifyfs is they exist, or will pull in 'notify'
  "dependencies" : { "messageicons":"module" } // optional, depend on a specific library to be used with 'require' - see provides_modules
  "dependencies" : { "message":"widget" } // optional, depend on a specific type of widget - see provides_widgets
  "provides_modules" : ["messageicons"] // optional, this app provides a module that can be used with 'require'
  "provides_widgets" : ["battery"] // optional, this app provides a type of widget - 'alarm/battery/bluetooth/pedometer/message'
  "provides_features" : ["welcome"] // optional, this app provides some feature, used to ensure two aren't installed at once. Currently just 'welcome'
  "requires_firmware" : "2v20", // optional, specify a minimum firmware version your app needs to work properly
  "default" : true,           // set if an app is the default implementer of something (a widget/module/etc)
  "readme": "README.md",      // if supplied, a link to a markdown-style text file
                              // that contains more information about this app (usage, etc)
                              // A 'Read more...' link will be added under the app

  "custom": "custom.html",    // if supplied, apps/custom.html is loaded in an
                              // iframe, and it must post back an 'app' structure
                              // like this one with 'storage','name' and 'id' set up
                              // see below for more info

  "customConnect": true,      // if supplied, ensure we are connected to a device
                              // before the "custom.html" iframe is loaded. An
                              // onInit function in "custom.html" is then called
                              // with info on the currently connected device.

  "interface": "interface.html",   // if supplied, apps/interface.html is loaded in an
                              // iframe, and it may interact with the connected Bangle
                              // to retrieve information from it
                              // see below for more info

  "allow_emulator":true,      // if 'app.js' will run in the emulator, set to true to
                              // add an icon to allow your app to be tested

  "storage": [                // list of files to add to storage
    {"name":"appid.js",       // filename to use in storage.
                              // If name=='RAM', the code is sent directly to Bangle.js and is not saved to a file
     "url":"",                // URL of file to load (currently relative to apps/)
     "content":"...",         // if supplied, this content is loaded directly
     "evaluate":true,         // if supplied, data isn't quoted into a String before upload
                              // (eg it's evaluated as JS)
     "noOverwrite":true       // if supplied, this file will not be overwritten if it
                              // already exists
     "supports": ["BANGLEJS2"]// if supplied, this file will ONLY be uploaded to the device
                              // types named in the array. This allows different versions of
                              // the app to be uploaded for different platforms
    },
  ]
  "data": [                   // list of files the app writes to
    {"name":"appid.data.json",  // filename used in storage
     "storageFile":true       // if supplied, file is treated as storageFile
     "url":"",                // if supplied URL of file to load (currently relative to apps/)
     "content":"...",         // if supplied, this content is loaded directly
     "evaluate":true,         // if supplied, data isn't quoted into a String before upload
                              // (eg it's evaluated as JS)
    },
    {"wildcard":"appid.data.*" // wildcard of filenames used in storage
    },                         // this is mutually exclusive with using "name"
  ],
  "sortorder" : 0,            // optional - choose where in the list this goes.
                              // this should only really be used to put system
                              // stuff at the top
}
  • name、icon 和 description 在应用加载器中展示应用。
  • tags 用于在库中对应用进行分组,多个条目之间用逗号分隔。已知的标签为 toolsystemclockgamesoundgpswidgetlauncherbluetooth 或为空。
  • storage 用于识别应用文件及其处理方式
  • data 用于在应用卸载时清理文件

metadata.json: custom 元素

可自定义的应用需要在 metadata.json 中定义一个 custom 元素, 该元素指定该应用文件夹中的一个 HTML 文件。

当定义 custom 时,'upload' 按钮会被替换为 customize 按钮,点击后会在 iframe 中打开指定的 HTML 页面。

在该 HTML 文件中,你需要负责处理按钮 点击事件,并调用 sendCustomizedApp,传入你对 metadata.json 中内容的自定义版本:

<html>
  <head>
    <link rel="stylesheet" href="../../css/spectre.min.css">
  </head>
  <body>
    <p><button id="upload" class="btn btn-primary">Upload</button></p>
    <script src="../../lib/customize.js"></script>
    <script>
      document.getElementById("upload").addEventListener("click", function() {
        sendCustomizedApp({
          id : "myappid",
          storage:[
            {name:"myappid.app.js", url:"app.js", content:app_source_code},
            {name:"myappid.img", content:'require("heatshrink").decompress(atob("mEwg...4"))', evaluate:true},
          ]
        });
      });
    </script>
  </body>
</html>

然后这将被加载到手表中。参见 [apps/qrcode/grcode.html](the QR Code app) 以获取一个简洁的示例。

注意: 我们为 JS 文件指定了 url,即使它不必存在 且永远不会被加载。这样应用加载器可以根据扩展名判断它是否为 JavaScript 文件,如果是,则可以对其进行压缩和预标记化。

metadata.json: interface 元素

可以创建可回读数据的应用可以在 metadata.json 中定义一个 interface 元素, 该元素指定该应用文件夹中的一个 HTML 文件。

当定义了 interface 时,应用的描述中会添加一个 Download from App 按钮, 点击它会在 iframe 中打开指定的 HTML 页面。

<html>
  <head>
    <link rel="stylesheet" href="../../css/spectre.min.css">
  </head>
  <body>
    <script src="../../core/lib/interface.js"></script>
    <div id="t">Loading...</div>
    <script>
      function onInit() {
        Puck.eval("E.getTemperature()", temp=> {
          document.getElementById("t").innerHTML = temp;
        });
      }
    </script>
  </body>
</html>

当页面准备就绪时,会调用一个名为 onInit 的函数, 在该函数中,你可以调用 Puck.writePuck.eval 来从 Bangle.js 获取 所需的数据。

请参阅 [apps/gpsrec/interface.html](the GPS Recorder) 以查看完整示例。

在“设置”菜单中添加配置

应用程序(或小部件)可以在“设置”菜单下的“App/widget settings”中添加自己的设置。 为此,应用程序需要包含一个 settings.js 文件,其中包含一个用于 处理应用程序配置的函数。 当打开应用程序设置时,该函数会被调用,并带有一个 参数 back:一个用于返回设置菜单的回调函数。

通常,它会将信息保存在 myappid.json 中,其中 myappid 是 你的应用程序名称——因此你应该相应地修改示例。

示例 settings.js

// make sure to enclose the function in parentheses
(function(back) {
  let settings = require('Storage').readJSON('myappid.json',1)||{};
  if (typeof settings.monkeys !== "number") settings.monkeys = 12; // default value
  function save(key, value) {
    settings[key] = value;
    require('Storage').write('myappid.json', settings);
  }
  const appMenu = {
    '': {'title': 'App Settings'},
    '< Back': back,
    'Monkeys': {
      value: settings.monkeys,
      onchange: (m) => {save('monkeys', m)}
    }
  };
  E.showMenu(appMenu)
})

在此示例中,应用程序需要在 metadata.json 中将 myappid.settings.js 添加到 storage。 它还应该将 myappid.json 添加到 data,以确保在应用程序卸载时进行清理。

  { "id": "myappid",
    ...
    "storage": [
      ...
      {"name":"myappid.settings.js","url":"settings.js"}
    ],
    "data": [
      {"name":"myappid.json"}
    ]
  },

模块

你可以使用 require("modulename") 正常包含 Espruino 的模块。 为了在 Web IDE 中使用 Bangle 的模块,请 将模块上传到内部存储更改 IDE 的搜索路径。 如果你想为你的 应用程序开发自己的模块,也可以这样做。只需将模块添加到 modules 文件夹 中,然后你就可以像平常一样在应用程序中使用它。

你无法使用 IDE 通过自己的模块来开发应用, 因此我们建议你先将模块写入 Bangle.js 上名为 modulename 的存储文件。然后你可以像往常一样在 Bangle.js 上从 IDE 开发你的应用。

编码提示

  • 使用 g.setFont(.., size) 来乘以字体大小,例如 ("6x8",3) : "18x24"

  • 使用 g.drawString(text,x,y,true) 以背景色绘制,以覆盖现有文本

  • 使用 g.clearRect() 来清除屏幕的部分区域,而不是使用 g.clear()

  • 使用 g.fillPoly()g.drawImage() 处理复杂的图形元素

  • 使用 g.clear() 可能会导致屏幕闪烁

  • 使用 g.setLCDBrightness() 可以在 LCD 长时间开启期间节省电量

  • 链式调用图形方法,例如 g.setColor(0xFD20).setFontAlign(0,0).setfont("6x8",3)

其他说明

  • 需要保存状态?使用 E.on('kill',...) 事件将 JSON 保存到名为 myappid.json 的文件中,然后在启动时加载它。

  • “闹钟”应用定义了一个名为 alarm.js 的文件,用于处理实际的闹钟窗口。

  • 区域设置由 require("locale") 处理。应用可以在存储中创建一个 locale 文件,该文件 是一个覆盖 Bangle.js 默认区域设置的模块。

图形区域

在 LCD 模式下,屏幕被划分为小部件区域和应用区域 direct(默认)。

区域作为矩形或点
小部件(0,0,239,23)
应用(0,24,239,239)
BTN1(230, 55)
BTN2(230, 140)
BTN3(230, 210)
BTN4(0,0,119, 239)
BTN5(120,0,239,239)
  • 使用 g.setFontAlign(0, 0, 3) 配合 g.drawString() 在 BTN1-BTN3 上绘制旋转字符串。

  • 对于 BTN4-5,触摸区域命名为

可用颜色

你可以使用 g.setColor(r,g,b)g.setColor(16bitnumber) - 以下是一些常见的 16 位颜色:

color-namecolor-value
Black0x0000
Navy0x000F
DarkGreen0x03E0
DarkCyan0x03EF
Maroon0x7800
Purple0x780F
Olive0x7BE0
LightGray0xC618
DarkGrey0x7BEF
Blue0x001F
Green0x07E0
Cyan0x07FF
RED0xF800
Magenta0xF81F
Yellow0xFFE0
White0xFFFF
Orange0xFD20
GreenYellow0xAFE5
Pink0xF81F

Fonts

Bangle.js 最近新增的功能是支持使用包含更多字符的额外字体。 例如 所有区域扩展 字体。

安装后,这些应用会向 Graphics 添加一个新字体 Intl,只需使用 g.setFont("Intl") 即可调用。

还有一个 font 库 - 目前尚未实现,但有关计划实现的更多信息 可在 https://github.com/espruino/BangleApps/issues/3109 获取

目前,为了让您的应用支持国际字体,您可以检查 Graphics.prototype.setFontIntl 是否存在, 如果存在,您可以更改计划使用的字体:

myFont = "6x8:2";
if (Graphics.prototype.setFontIntl)
  myFont = "Intl";

任何新的 Font 库都必须包含元数据 "icon": "app.png", "tags": "font", "type": "module", "provides_modules" : ["fonts"], 并且应提供一个 font 库,以及一个添加 Graphics.prototype.setFontIntlboot.js。如果您计划 创建一个新的库,目前最好直接复制其中一个现有的库。

API Reference

Reference

Bangle Class

Graphics Class

'Testing' folder

testing 文件夹包含可能对您的应用有用的代码片段。

  • testing/colors.js - 作为名称值对的 16 位颜色
  • testing/gpstrack.js - 将 GPS 轨迹存储在 Bangle.js 存储中并将其输出到控制台的代码

Credits

这些应用中使用的大部分图标来自 Icons8 - 我们拥有商业许可,但图标也可免费用于开源项目。