mirror of
https://github.com/google/pebble.git
synced 2025-03-20 19:01:21 +00:00
334 lines
12 KiB
Markdown
334 lines
12 KiB
Markdown
|
---
|
|||
|
# Copyright 2025 Google LLC
|
|||
|
#
|
|||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|||
|
# you may not use this file except in compliance with the License.
|
|||
|
# You may obtain a copy of the License at
|
|||
|
#
|
|||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|||
|
#
|
|||
|
# Unless required by applicable law or agreed to in writing, software
|
|||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
|
# See the License for the specific language governing permissions and
|
|||
|
# limitations under the License.
|
|||
|
|
|||
|
title: Pebble SDK Tutorial (Part 1)
|
|||
|
author: Chris Lewis
|
|||
|
excerpt: |
|
|||
|
This is the first in a series of articles that will hopefully help newcomers
|
|||
|
and seasoned programmers alike begin to flex their creative muscles and make
|
|||
|
their own Pebble watchapps. If you prefer a more visual approach, Thomas has
|
|||
|
made videos you can watch to help you get started.
|
|||
|
---
|
|||
|
|
|||
|
##Your First Watchapp
|
|||
|
|
|||
|
###Introduction
|
|||
|
|
|||
|
This is the first in a series of articles that will hopefully help newcomers and
|
|||
|
seasoned programmers alike begin to flex their creative muscles and make their
|
|||
|
own Pebble watchapps. If you prefer a more visual approach, Thomas has made
|
|||
|
videos you can watch
|
|||
|
[here](https://www.youtube.com/channel/UCFnawAsyEiux7oPWvGPJCJQ) to help you get
|
|||
|
started.
|
|||
|
|
|||
|
Firstly, make sure you are familiar with the following basic C language
|
|||
|
concepts. Pebble has [Developer Guides](/guides/) that may help:
|
|||
|
|
|||
|
- Variables and variable scope (local or global?)
|
|||
|
- Functions (definitions and calls)
|
|||
|
- Structures
|
|||
|
- Pointers
|
|||
|
- Program flow (if, else etc.)
|
|||
|
- Preprocessor statements (`#define`, `#include` etc.)
|
|||
|
|
|||
|
Up to speed? Good! Let's create our first watchapp.
|
|||
|
|
|||
|
|
|||
|
|
|||
|
###Development Environment
|
|||
|
The easiest way to get started is to use [CloudPebble]({{site.links.cloudpebble}})
|
|||
|
, a free online IDE for Pebble. This approach requires no installations to start
|
|||
|
making watchapps. Advanced users can install the SDK on Linux, OS X or Windows
|
|||
|
(via VM such as [VirtualBox](https://www.virtualbox.org/)) for a more hands-on
|
|||
|
approach. Instructions for installing the native SDK can be found on the
|
|||
|
[Pebble Developer site](/sdk/install/linux/)
|
|||
|
. For this tutorial, we will be using CloudPebble.
|
|||
|
|
|||
|
###First Steps
|
|||
|
To get started, log into [CloudPebble]({{site.links.cloudpebble}}) and choose
|
|||
|
'Create Project'.
|
|||
|
|
|||
|
1. Enter a suitable name for the project such as 'My First App'.
|
|||
|
2. Set the project type to 'Pebble C SDK'.
|
|||
|
3. Set the template as 'Empty project'.
|
|||
|
4. Confirm with 'Create'.
|
|||
|
|
|||
|
To start entering code, choose 'New C file' on the left hand pane. C language
|
|||
|
source files typically have the extension '.c'. A suggested standard name is
|
|||
|
`main.c`, although this can be anything you like.
|
|||
|
|
|||
|
###Setting Up the Basics
|
|||
|
The power behind the C language comes from its ability to be adapted for many
|
|||
|
different devices and platforms, such as the Pebble watch, through the use of
|
|||
|
SDKs such as this one. Therefore, to be able to use all the features the Pebble
|
|||
|
SDK has to offer us, we must include it at the start of the file by using the
|
|||
|
`#include` preprocessor statement (meaning it is processed before the rest of
|
|||
|
the code):
|
|||
|
|
|||
|
```c
|
|||
|
#include <pebble.h>
|
|||
|
```
|
|||
|
|
|||
|
All C applications begin with a call to a function called `main()` by the host
|
|||
|
operating system, also known as the ‘entry point’ of the program. This must
|
|||
|
contain ``app_event_loop()``, and by convention also contains two other
|
|||
|
functions to manage the start and end of the watchapp's life:
|
|||
|
|
|||
|
- `init()` - Used to create (or initialize) our app.
|
|||
|
- ``app_event_loop()`` - Handles all events that happen from the start of the
|
|||
|
app to the end of its life.
|
|||
|
- `deinit()` - Used to clean up after ourselves and make sure any memory we use
|
|||
|
is free for apps that are run after us.
|
|||
|
|
|||
|
Following these rules, our first function, `main()` looks like this:
|
|||
|
|
|||
|
```c
|
|||
|
int main(void)
|
|||
|
{
|
|||
|
init();
|
|||
|
app_event_loop();
|
|||
|
deinit();
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
All functions we call must be defined before they are used so that they are
|
|||
|
known to the compiler when the first call is encountered. To this end we will
|
|||
|
define our `init()` and `deinit()` functions before they are called in `main()`
|
|||
|
by placing them after the `#include` preprocessor statement and before the
|
|||
|
definition of `main()` itself. The resulting code file now looks like this:
|
|||
|
|
|||
|
```c
|
|||
|
#include <pebble.h>
|
|||
|
|
|||
|
void init()
|
|||
|
{
|
|||
|
//Create app elements here
|
|||
|
}
|
|||
|
|
|||
|
void deinit()
|
|||
|
{
|
|||
|
//Destroy app elements here
|
|||
|
}
|
|||
|
|
|||
|
int main(void)
|
|||
|
{
|
|||
|
init();
|
|||
|
app_event_loop();
|
|||
|
deinit();
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
###Using the SDK
|
|||
|
With the boilerplate app structure done, let's begin using the Pebble C SDK to
|
|||
|
create our first watchapp. The first element we will use is the ``Window``. We
|
|||
|
will declare our first instance of a ``Window`` outside the scope of any
|
|||
|
function in what is known as a 'global variable', before it is first used. The
|
|||
|
reason for this is for us to be able to use this reference from any location
|
|||
|
within the program. By convention, a global variable's name is prefixed with
|
|||
|
`g_` to make it easily identifiable as such. An ideal place for these
|
|||
|
declarations is below the preprocessor statements in the source file.
|
|||
|
|
|||
|
Declare a pointer to a ``Window`` structure to be created later like so:
|
|||
|
|
|||
|
```c
|
|||
|
Window *g_window;
|
|||
|
```
|
|||
|
|
|||
|
At the moment, this pointer does not point anywhere and is unusable. The next
|
|||
|
step is to use the ``window_create()`` function to construct a ``Window``
|
|||
|
element for the pointer to point to. Once this is done, we also register two
|
|||
|
references to the `window_load()` and `window_unload()` functions (known as
|
|||
|
handlers) which will manage the creation and destruction of the elements within
|
|||
|
that window. This is shown below:
|
|||
|
|
|||
|
```c
|
|||
|
void init()
|
|||
|
{
|
|||
|
//Create the app elements here!
|
|||
|
g_window = window_create();
|
|||
|
window_set_window_handlers(g_window, (WindowHandlers) {
|
|||
|
.load = window_load,
|
|||
|
.unload = window_unload,
|
|||
|
});
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
The next logical step is to define what we mean by `window_load()` and
|
|||
|
`window_unload()`. In a similar fashion to `init()` and `deinit()`, these must
|
|||
|
be defined before they are first used; above `init()`, but below the definition
|
|||
|
of `g_window` at the top of the file. Think of them as the `init()` and
|
|||
|
`deinit()` equivalent functions for the ``Window``:
|
|||
|
|
|||
|
```c
|
|||
|
void window_load(Window *window)
|
|||
|
{
|
|||
|
//We will add the creation of the Window's elements here soon!
|
|||
|
}
|
|||
|
|
|||
|
void window_unload(Window *window)
|
|||
|
{
|
|||
|
//We will safely destroy the Window's elements here!
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
This modular approach to app creation allows a developer to create apps with all
|
|||
|
relevant sub-elements managed within the life of that particular ``Window``.
|
|||
|
This process is shown visually in the diagram below:
|
|||
|
|
|||
|

|
|||
|
|
|||
|
As a responsible app developer, it is a good practice to free up any memory we
|
|||
|
use to the system when our app is closed. Pebble SDK elements use functions
|
|||
|
suffixed with `_destroy` for this purpose, which can be done for our ``Window``
|
|||
|
element in `deinit()`:
|
|||
|
|
|||
|
```c
|
|||
|
void deinit()
|
|||
|
{
|
|||
|
//We will safely destroy the Window's elements here!
|
|||
|
window_destroy(g_window);
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
The final step in this process is to make the app actually appear when it is
|
|||
|
chosen from the Pebble menu. To do this we push our window to the top of the
|
|||
|
window stack using ``window_stack_push()``, placing it in the foreground in
|
|||
|
front of the user. This is done after the ``Window`` is created, and so we will
|
|||
|
place this call at the end of `init()`:
|
|||
|
|
|||
|
```c
|
|||
|
window_stack_push(g_window, true);
|
|||
|
```
|
|||
|
|
|||
|
The second argument denotes whether we want the push to be animated. This looks
|
|||
|
cool, so we use `true`! The app will now launch, but will be completely blank.
|
|||
|
Let's rectify that.
|
|||
|
|
|||
|
###Displaying Content
|
|||
|
So far we have created and pushed an empty ``Window`` element. So far so good.
|
|||
|
Now let's add our first sub-element to make it show some text. Introducing the
|
|||
|
``TextLayer``! This is an element used to show any standard string of characters
|
|||
|
in a pre-defined area, called a 'frame'. As with any element in the SDK, we
|
|||
|
begin with a global pointer to a structure-to-be of that type:
|
|||
|
|
|||
|
```c
|
|||
|
TextLayer *g_text_layer;
|
|||
|
```
|
|||
|
|
|||
|
The rest of the process of using the ``TextLayer`` is very similar to that of
|
|||
|
the ``Window``. This is by design, and means it is easy to tell which functions
|
|||
|
to use as they are named in a 'plain English' style. Creating the ``TextLayer``
|
|||
|
will be done within the `window_load()` function, as it will be shown within the
|
|||
|
parent ``Window`` (here called `g_window`).
|
|||
|
|
|||
|
The functions we call to perform this setup are almost self-explanatory, but I
|
|||
|
will go through them after the following segment. Can you spot the pattern in
|
|||
|
the SDK function names?
|
|||
|
|
|||
|
```c
|
|||
|
g_text_layer = text_layer_create(GRect(0, 0, 144, 168));
|
|||
|
text_layer_set_background_color(g_text_layer, GColorClear);
|
|||
|
text_layer_set_text_color(g_text_layer, GColorBlack);
|
|||
|
|
|||
|
layer_add_child(window_get_root_layer(window), text_layer_get_layer(g_text_layer));
|
|||
|
```
|
|||
|
|
|||
|
Now the explanation:
|
|||
|
|
|||
|
1. ``text_layer_create()`` - This creates the ``TextLayer`` and sets its frame
|
|||
|
to that in the ``GRect`` supplied as its only argument to x = 0, y = 0, width
|
|||
|
= 144 pixels and height = 168 pixels (this is the size of the entire screen).
|
|||
|
Just like ``window_create()``, this function returns a pointer to the newly
|
|||
|
created ``TextLayer``
|
|||
|
2. ``text_layer_set_background_color()`` - This also does what it says: sets the
|
|||
|
``TextLayer``'s background color to ``GColorClear``, which is transparent.
|
|||
|
3. ``text_layer_set_text_color()`` - Similar to the last function, but sets the
|
|||
|
text color to ``GColorBlack``.
|
|||
|
4. ``layer_add_child()`` - This is used to add the ``TextLayer``'s "root"
|
|||
|
``Layer`` (which is the part drawn to the screen) as a 'child' of the
|
|||
|
``Window``'s root layer. Simply put, all child ``Layer``s are drawn in front
|
|||
|
of their 'parents' and allows us to control layering of ``Layer``s and in
|
|||
|
which order they are drawn.
|
|||
|
|
|||
|
As should always be the case, we must add the required destruction function
|
|||
|
calls to free up the memory we used in creating the ``TextLayer``. This is done
|
|||
|
in the parent ``Window``'s `window_unload()` function, which now looks like
|
|||
|
this:
|
|||
|
|
|||
|
```c
|
|||
|
void window_unload(Window *window)
|
|||
|
{
|
|||
|
//We will safely destroy the Window's elements here!
|
|||
|
text_layer_destroy(g_text_layer);
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
Now for what we have been working towards all this time - making the app show
|
|||
|
any text we want! After creating the ``TextLayer``, add a call to
|
|||
|
``text_layer_set_text()`` to set the text it should display, such as the example
|
|||
|
below:
|
|||
|
|
|||
|
```c
|
|||
|
text_layer_set_text(g_text_layer, "Anything you want, as long as it is in quotes!");
|
|||
|
```
|
|||
|
|
|||
|
Now you should be ready to see the fruits of your labor on your wrist! To do
|
|||
|
this we must compile our C source code into a `.pbw` package file that the
|
|||
|
Pebble app will install for us.
|
|||
|
|
|||
|
###Compilation and Installation
|
|||
|
|
|||
|
To do this, make sure you save your C file. Then go to the 'Compilation' screen
|
|||
|
in the left pane and click 'Run build'. After the compiler returns 'Succeeded',
|
|||
|
you can use either of the following options to install the app on your Pebble:
|
|||
|
|
|||
|
- Ensure you have enabled the
|
|||
|
[Pebble Developer Connection](/guides/tools-and-resources/developer-connection/)
|
|||
|
and enter the IP address shown into the 'Phone IP' box. Click 'Install and
|
|||
|
Run' to install your app.
|
|||
|
- Use the 'Get PBW' button in your phone's browser to install via the Pebble app.
|
|||
|
|
|||
|
If your code does not compile, compare it to the
|
|||
|
[sample code](https://gist.github.com/C-D-Lewis/a911f0b053260f209390) to see
|
|||
|
where you might have made an error. Once this is successful, you should see
|
|||
|
something similar to the screenshot below:
|
|||
|
|
|||
|

|
|||
|
|
|||
|
###Conclusions
|
|||
|
There you have it, a *very* simple watchapp to show some text of your choosing.
|
|||
|
This was done by:
|
|||
|
|
|||
|
1. Setting up boilerplate app structure.
|
|||
|
2. Creating a ``Window`` with a child layer, in this case the ``TextLayer``.
|
|||
|
3. Creating a ``TextLayer`` to show text on the screen.
|
|||
|
|
|||
|
By adding more types of layers such as ``BitmapLayer`` and `InverterLayer` we
|
|||
|
create much more sophisticated apps. By extension with ``AppMessage``s and
|
|||
|
``Clicks`` we can begin to respond to user data and input. All this and more to
|
|||
|
come in future instalments!
|
|||
|
|
|||
|
###Source Code
|
|||
|
The full source code file we have built up over the course of this article can
|
|||
|
be found [here](https://gist.github.com/C-D-Lewis/a911f0b053260f209390) and
|
|||
|
directly imported into CloudPebble
|
|||
|
[as a new project](https://cloudpebble.net/ide/gist/a911f0b053260f209390). If
|
|||
|
your code doesn't compile, have a look at this and see where you may have gone
|
|||
|
wrong.
|
|||
|
|
|||
|
Best of luck, and let me know what you come up with! Send any feedback or
|
|||
|
questions to [@PebbleDev](http://twitter.com/PebbleDev) or
|
|||
|
[@Chris_DL](http://twitter.com/Chris_DL).
|