Your first glasses plugin: build from scratch
Create just manifest.json and main.c. The glasses display one line of text; click the main button to change it and long-press to exit. Then change the text and verify the update.
Before you begin: prepare these items#
Prepare the complete toolkit, Python, source-packaging dependencies and desktop Studio. Device steps also require the matching App and glasses. Try an existing example first; use the links below if tools are not ready.
This tutorial creates an independent glasses C plugin. You do not need to complete Breakout or write a phone page first. Run all terminal commands from the toolkit root, save code to the specified files and copy only the commands for your OS.
Step 1: create your project directory#
Run the commands below in the terminal. my_glass is a new project created by this tutorial, not a built-in SDK example. If a project with that name already exists, preserve its files instead of overwriting them.
macOS / Linux
mkdir -p GlassSDK/examples/my_glassWindows PowerShell
New-Item -ItemType Directory -Force GlassSDK/examples/my_glassStep 2: save manifest.json#
Save as GlassSDK/examples/my_glass/manifest.json. JSON does not support comments; copy the entire block below.
{
"id": "com.example.my-glass",
"name": "My Glass",
"version": 1,
"abi_version": 256
}Step 3: save main.c#
Save as GlassSDK/examples/my_glass/main.c. This complete source can be built directly. Do not add private firmware headers or create CMakeLists.txt.
#include "gm_plugin_lvgl_api.h"
#define GREETING "Hello from C!"
#define PRESSED_TEXT "Button pressed!"
static const gm_plugin_host_api_t *s_host;
static const gm_plugin_lvgl_api_t *s_ui;
static gm_plugin_lvgl_obj_t *s_label;
static bool s_pressed;
static gm_plugin_result_t start(void *context)
{
(void)context;
gm_plugin_lvgl_obj_t *root = s_ui->root_get();
if (root == NULL) return GM_PLUGIN_ESTATE;
s_label = s_ui->label_create(root);
if (s_label == NULL) return GM_PLUGIN_ENOMEM;
s_pressed = false;
s_ui->label_set_text(s_label, GREETING);
s_ui->obj_align(s_label, GM_PLUGIN_LVGL_ALIGN_CENTER, 0, 0);
return GM_PLUGIN_OK;
}
static bool on_event(void *context, const gm_plugin_event_t *event)
{
(void)context;
if (event == NULL || event->struct_size < GM_PLUGIN_EVENT_MIN_SIZE ||
event->type != GM_PLUGIN_EVENT_BUTTON ||
event->data.button.button != GM_PLUGIN_BUTTON_PRIMARY)
return false;
if (event->data.button.action == GM_PLUGIN_BUTTON_ACTION_LONG ||
event->data.button.action == GM_PLUGIN_BUTTON_ACTION_VERY_LONG) {
s_host->app_exit();
return true;
}
if (event->data.button.action != GM_PLUGIN_BUTTON_ACTION_SINGLE ||
s_label == NULL)
return false;
s_pressed = !s_pressed;
s_ui->label_set_text(s_label, s_pressed ? PRESSED_TEXT : GREETING);
return true;
}
static void stop(void *context)
{
(void)context;
if (s_label != NULL) s_ui->obj_delete(s_label);
s_label = NULL;
}
gm_plugin_result_t gm_plugin_entry(const gm_plugin_host_api_t *host,
gm_plugin_descriptor_t *plugin)
{
if (host == NULL || plugin == NULL ||
host->struct_size < GM_PLUGIN_HOST_API_MIN_SIZE ||
plugin->struct_size < GM_PLUGIN_DESCRIPTOR_MIN_SIZE ||
!GM_PLUGIN_VERSION_COMPATIBLE(host->abi_version,
GM_PLUGIN_ABI_MIN_VERSION))
return GM_PLUGIN_EVERSION;
const gm_plugin_capabilities_t required =
GM_PLUGIN_CAP_DISPLAY_BITMAP | GM_PLUGIN_CAP_BUTTON;
if ((host->capabilities & required) != required || host->app_exit == NULL)
return GM_PLUGIN_ENOTSUP;
const gm_plugin_lvgl_api_t *ui = host->graphics.lvgl;
if (ui == NULL || ui->struct_size < GM_PLUGIN_LVGL_API_MIN_SIZE ||
!GM_PLUGIN_VERSION_COMPATIBLE(ui->api_version,
GM_PLUGIN_LVGL_API_MIN_VERSION))
return GM_PLUGIN_EVERSION;
if (ui->root_get == NULL || ui->label_create == NULL ||
ui->label_set_text == NULL || ui->obj_align == NULL ||
ui->obj_delete == NULL)
return GM_PLUGIN_ENOTSUP;
s_host = host;
s_ui = ui;
plugin->abi_version = GM_PLUGIN_ABI_MIN_VERSION;
plugin->on_start = start;
plugin->on_event = on_event;
plugin->on_stop = stop;
return GM_PLUGIN_OK;
}Where to make future changes in this source file#
| Location | What this example does | How to extend it |
|---|---|---|
| GREETING / PRESSED_TEXT | Defines two text states. | Change these two lines first to verify that your code changes take effect. |
| start | Creates text under the Host root object. | Create the UI needed for this run and handle creation failures. |
| on_event | A single click switches text; a long press requests exit. | Handle recognized button and application events, keeping callbacks short. |
| stop | Deletes the label you created. | Release resources for this run without deleting the Host root object. |
| gm_plugin_entry | Checks compatibility and registers callbacks. | Retain the entry checks and add checks for new capabilities as specified in the API documentation. |
Step 4: compile and check the three output files#
Keep the terminal in the complete toolkit root. Save both files, then run the build command. The script discovers my_glass automatically and rebuilds glasses plugins according to dependencies. The first run may download the toolchain.
macOS / Linux
./build.py glassWindows PowerShell
py build.py glassExpected directory layout after a successful build#
Keep all three outputs together. The GMP is executable; the other two are source-review sidecars from this build that Studio uses to create the complete ZIP. Do not decrypt, edit or manually archive them.
plugin-open-platform/
└── GlassSDK/
├── examples/my_glass/
│ ├── manifest.json
│ └── main.c
└── build-host/.build/my_glass/
├── my_glass.gmp
├── my_glass.review.json
└── my_glass.review-source.encStep 5: select My Glass in Studio#
- Open desktop Studio and import the complete toolkit root containing GlassSDK, PhoneSDK and Studio. On a Mac, import it after each fresh launch. Use the installation link below if Studio is not installed.
- Refresh the glasses-plugin list and select My Glass. Select No phone plugin (glasses debugging only) on the phone side.
- The virtual glasses should show Hello from C! Click the Studio main button to change it to Button pressed! Click again to restore the original text.
- Long-press the main button to verify exit. Refresh and select My Glass again to rerun it. Confirm it has reloaded before scanning.
Step 6: scan with your phone and run on real glasses#
- Wear the glasses, keep them powered on and confirm the connection in MemoMind App. Put the computer and phone on mutually reachable LAN connections.
- Keep Studio open and wait for the My Glass combination to finish packaging. Sign in to the App with the account that owns the application. Open Settings → Memo Lab → Developer Workspace → Scan to open app, scan the current QR code, grant the requested permissions and wait for installation and startup to finish.
- The glasses should show Hello from C! Click the main button to switch text and long-press to exit. Then stop the current entry in the App list and tap Start to confirm that it runs again.
Step 7: customize the content and run it again#
- Open main.c and change GREETING from Hello from C! to My plugin v2!; Change version in manifest.json from 1 to 2, then save both files.
- Rerun the same build command from the toolkit root and wait for success.
- Refresh Studio, select My Glass and check that the default text is My plugin v2! Confirm the new content on the computer first.
- Wait for the new ZIP. Stop the old App entry, scan the current QR code and start it. The real glasses should also show the new text.
- Finally, close Studio and restart from the saved App entry to confirm that the application is stored on the phone. This example needs no business server, but the phone must still connect to the glasses.
macOS / Linux
./build.py glassWindows PowerShell
py build.py glassTroubleshoot the step that did not produce the expected result#
| Symptom | What to check first |
|---|---|
| main.c or manifest.json not found | Check that the files are saved under GlassSDK/examples/my_glass with the correct extensions and valid JSON. Keep the terminal in the toolkit root. |
| Header or compiler not found | Build through SDK build.py after installing the Python dependencies. Do not run system gcc main.c directly. |
| My Glass is missing from the Studio list | Confirm all three output files exist, import the correct root and refresh. If needed, use Import package to select my_glass.gmp, keeping its build sidecars alongside it. |
| The virtual screen shows text, but scanning fails | Check that the QR code is ready, then check phone–computer LAN connectivity, firewall rules and whether Studio is still running. |
| The phone download finishes, but nothing appears on the glasses | Check App Bluetooth connectivity and installation/startup messages. Stop the old entry and retry. Obtain the matching App/firmware if Developer Workspace is missing or the ABI is incompatible. |
| The old text still appears | Check that the files were saved and the build succeeded, then check the Studio selection and the running App entry. Make sure the new scan downloads the new ZIP. |
Extend the functionality after completing the tutorial#
You now have an independent project, your own identity and a complete update cycle. Add one capability that meets a real need. Each time, repeat: save → build → verify in Studio → wait for ZIP → scan in App → verify on the device.
Reference files in the toolkit#
- GlassSDK/include/gm_plugin.h
- GlassSDK/include/gm_plugin_lvgl_api.h
- GlassSDK/examples/lvgl_ui/lvgl_ui.c
- GlassSDK/docs/ABI.md