- How to use the SDK
- How are HR apps structured
- Code structure
- Callbacks
- Events
- Api functions
- State machine
- Layout
- Example
To use the SDK a few conditions have to be met
- JerryScript v2.1.0 needs to be built with:
python3 tools/build.py --jerry-cmdline-snapshot ON
-
jerry-snapshot
needs to be in$PATH
-
pip3 install crc32c
-
jq
needs to be installed and executable -
for pushing and installing via Makefile:
- adb needs to be installed
- if phone supports adb over wifi,
make connect
can be used after adjusting the Makefile - Gadgetbridge needs to be running and connected to the watch. Authentication is not needed.
- Gadgetbridge needs to have read access to the sd card. Android 11 makes that a bit harder
-
If the project is an example or derived from one, build/app.json needs to be edited to contain the desired appo id
-
then, in the folder where the Makefile lies,
make
can be run. That shouldmake compile
make pack
make push
make install
all at once.
-
Gadgetbridge should then push the app to the watch. It should be automatically installed.
each app package is an archive of files. Those files consist of:
- app executable
- compressed images
- layout files
- single file with app name
The image files are compressed using RLE compression. Also, each byte in the image file describes one pixel on the screen. The first two bits describe the blackness, and the third and fourth bit describe alpha.
The executable consists of Jerryscript (Javascript framework) compiled to a function-snapshot. This is done vie the command-line
jerry-snapshot generate -f '' code.js -o code
code
is then executed on the watch and returns an app object.
This object defines the app's meta information and callbacks.
It is mandatory to use JerryScript v2.1.0 as of firmware DN1.0.2.19r.v7
.
the compiled snapshot need to return an object containing a few fields:
- node_name
- assigned by master (runtime), equals the apps identifier
- manifest->timers
- declares the identifiers of all used timers
- init
- init callback, see callback
- deinit
- see callbacks
- handler
- see callbacks
- persist
- no clue, prolly some persistance manifest
- config
- puhhh
- additional fields
- can contain app-scope fields
- called after applicaiton start
- initializes state machine (in most cases, does not seem necessary)
- may be called when app is killed, did not encounter
- callback receiving system events
- forwards events to state machine (in most cases)
- parameters:
- event object (further described in events section)
- response object, used to trigger following actions
every event is passed to the handler-callback as an object. The object's type-field contains the event name:
-
time_telling_update
- probably indicates hand movement to show the current time every few seconds
-
common_update contains common data such as heart rate info, time info, wear state info. For a list of possible fields check the
common
section -
timer_expired
- indicates an expired time, check each timer with
is_this_timer_expired
- indicates an expired time, check each timer with
-
user input ([pos] can be replaces with top/middle/bottom)
- flick_away
- double_tap
- [pos]_press
- [pos]_short_press_release
- [pos]_hold
- [pos]_long_press_release
-
screen_update_finished
-
pending_config_verify
- indicates the upcoming verification of e.g. config for the backgrounds
-
node_config_update
- indicates finished verification
-
screen_sleep_command_completed
- what
-
system_state_update
- gets broadcastet whenever an app is opened or closed from the launcher
- contains the following fields:
- de: boolean indicating if this events has anything to do with receiving app
- ze: old, exited state
- le: new, entered state
- states can have the following values:
- background: app is running, but in background, e.g. not shown
- hidden: app is being overlaid by notification or similar
- visible: app is in foreground
sends a request to the phone with an argument string.
parameters:
- node name (can be acquired by this.node_name)
- query string
- timeout (probably)
- some boolean parameter (no clue)
stop timer which triggers timeout for requested info using req_data
probably check data request is expired
starts a timer with the given parameters:
- node_name (again, this.node_name)
- id (one of the strings declared in the manifest)
- timeout (timer duration in milliseconds)
parameters:
- node_name
- timer id (as defined in manifest)
dunno, might not even be a function
Each application is an Object.
For persistence to work, the object needs to have a persist field.
Here is an example.
When calling save_node_persist(node_name)
, the OS reads the objects persist.data contents, that contain the fields to be persisted.
Let's say our application looks like this:
return {
node_name: '',
persist: {
version: 1,
data: ['distance', 'names']
},
distance: 0.7,
names: [
'Daniel',
'James'
],
...
}
When we then call save_node_persist(self.node_name)
, the OS reads the value of distance and names, and stores them.
Next time our application is started and the OS finds those values stored it will assign them in out application.
My timer app is a working example of this.
translates the input according to the installed translation file. Very similar to snprintf in c.
parameters:
- format string
- parameters
dunno what this enables, but returns an object containing the hand angles for displaying the time
disables something
get common data, content unknown yet also, seems undefined in my test cases.
To access common data, just use the global common
-object.
common['hour']
Should contain these fields:
- R
- settings
- unit_setting
- daily_goal
- firmware_version
- serial_number
- default_theme
- hand_hour
- hand_minute
- battery_soc
- battery_voltage
- charge_status
- date
- day
- month
- year
- offset_unix_time_change
- active_minutes
- total_sleep
- step_increase
- time_zone_local
- hour
- minute
- bluetooth_status
- app_status
- chance_of_rain
- hr_bpm
- step_count
- calories
- distance
- weatherInfo
- music_playback_state
- device_offwrist
- ringMyPhone
- pending_config_verify_result
- notification_reply_mask
- hr_bpm_resting
- hr_bpm_peak
returns the current time in integer milliseconds since boot.
returns integer seconds since the Unix epoch (1970-01-01T00:00)
check if a timer is expired based on a timer_expired
-event.
the response abstraction layer can be used for this.
Just call response = wrap_response(response)
, on the response object, then you can use
response.move_hands(angle_hour, angle_minute, relative)
.
Also, to move the watch hands the reponse object passed to handler
needs to be used.
response.move = {
'h': angle,
'm': angle,
'is_relative': false
}
the response abstraction layer can be used for this.
Just call response = wrap_response(response)
, on the response object, then you can use
response.draw_screen(node_name, full_refresh, layout_info)
.
Also, to draw to the screen a layout system is used. I am unsure whether there is raw access to the screen.
Once again, the response object is used.
response.draw = {}
response.draw[this.node_name] = {
'layout_function': 'layout_parser_json',
'layout_info': {
'json_file': 'layout_file',
'var1': 'bla'
}
}
the keys in layout_info
(besides json_file) correspond to placeholders set in the layout.
the response abstraction layer can be used for this.
Just call response = wrap_response(response)
, on the response object, then you can use
response.draw_screen(node_name, full_refresh, layout_info)
.
For instance,
response.send_user_class_event('double_tap')
or
response.send_generic_event({
type: 'double_tap',
class: 'user'
})
would light up the screen for a few seconds.
To send a generic event without the layer, set the response.i to an array containing the events
response.i = [
{
// this will trigger screen illumination for a few secs
'type': 'double_tap',
'class': 'user'
},
{
// this will move the hands 360° when showing the time
'type': 'flick_away',
'class': 'user'
}
]
to play a notification, the proper event needs to be fired
response.i = [
{
'type': 'urgent_notification',
'info': {
'title': localization_snprintf('%s', 'Brr Brr'),
'app_name': this.package_name,
'icon_name': 'icTimer',
'Re': 60, // timeout in secs for notification
'vibe': {
'type': 'timer',
'Te': 1500,
'Ie': 6000
},
'exit_event': {
'type': 'timer_dismiss'
},
'actions': [
{
'Ke': localization_snprintf('%s', 'Dismiss'),
'event': {
'type': 'timer_dismiss',
'param1': 'value1'
}
},
{
'Ke': localization_snprintf('%s', 'Repeat'),
'event': {
'type': 'timer_restart',
'param1': 'value1',
'param2': 'value2'
}
}
]
}
}
]
If a menu option within the notification with an event defined is selected, that event and the parameters will be fired.
the state machine (state_machine class) provides an api to switch to different states.
params:
-
self object
- object passed to all callbacks, mostly
this
is used
- object passed to all callbacks, mostly
-
global event handler
- AFAIK gets called when function
_
of the state machine gets called, which mostly only happens in thehandle
-function - is responsible for toggling the state_machine states by calling
state_machine_object.d('new_state')
- AFAIK gets called when function
-
state specific event handler
-
on every state change, this function is first called lie this
callback(old_state, 'exit')
callback(new_state, 'entry')
-
also each state can define an event handler thats gets called like this, but only when this state is active
handler(self_object, state_machine_object, event_object, response_object)
-
layouts are build hirachically using a JSON definition in a file. Each node has an id a type, a position, dimensions and a parent container id (besides node 0).
These are some (probably) common fields:
- id
- parent_id
- type
- dimension
- type
- no clue, mostly set to "rigid"
- width
- height
- type
- placement
- type
- whatever, set to "absolute"
- left
- space to lef
- top
- space to top
- type
- visible
- inversion
- no clue, someone help me out pls
Here are the so far known types:
-
wapp_template
- no clue that that means, results in a banner. Mostly used as the root node
- special fields
- header_icon
- icon on top of the screen (mostly used as home icon)
- filename of the file containing the image
- is_header_selected
- defines if the header icon is selected
- title_string
- dunno what to say about that
- header_icon
-
option_menu
- options
- array of options, set in code in layout_info
- option_selected
- number of the selected option
- is_translate
- wtf
- options
-
container
- direction
- 0 = horizontal
- 1 = vertical
- main_alignment
- vertical alignment
- 0 = align top
- 1 = align center
- 2 = align bottom
- cross_alignment
- horizontal alignment
- 0 = align left
- 1 = align center
- 2 = align right
- direction
-
text
- text
- this field contains the text displayed
- ppem
- the text size in points per m, whatever that means
- ascent
- what
- descent
- what
- color
- a value between 0 and 3, white and black
- text
-
text_page
- text
- the text displayed
- line_width
- an array with three values, probably the width of each text line
- page_index
- what
- ppem
- text size
- ascent
- what
- descent
- what
- justify
- what
- text
-
image
- image_name
- the filename of the image to display
- draw_mode
- no idea what that means
- image_name
-
solid
- color
- well, the color of the solid block, between 0 and 3
- color
-
content_table
- contents
- contains object with the entries, in this form:
- icon
- the icon of the entry
- text_low
- the label of the entry
- icon
- contains object with the entries, in this form:
- contents
Examples can be found in the examples folder. Too learn about running examples theck the How to use the SDK
section.
this example show a basic menu with a few options and handles button events to scroll through the menu.