Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dictation API #109

Merged
merged 28 commits into from
Nov 14, 2015
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
f06b0c1
Dictation API proof of concept
cat-haines Nov 13, 2015
bcb58f5
Added aplite 'support'
cat-haines Nov 13, 2015
1da117c
Reverted example back to standard example
cat-haines Nov 13, 2015
8fe5cf9
Moved Examples and Acknowledgments to bottom
cat-haines Nov 13, 2015
da59fb6
Removed Accel.init() as it's called internally
cat-haines Nov 13, 2015
57a6c82
Docs for voice
cat-haines Nov 13, 2015
884f39e
Updated packet names and callback signature
cat-haines Nov 13, 2015
5dcb30d
Starting dictation session with am app_timer
cat-haines Nov 13, 2015
c1a88c0
Whitespace
cat-haines Nov 13, 2015
ff21db3
removed stray require in app.js
cat-haines Nov 13, 2015
3fbf578
Added optional parameter for startDictationSession to enable/disable …
cat-haines Nov 13, 2015
1fc5c11
Updated docs with new param for startDictationSession
cat-haines Nov 13, 2015
e439bf7
whitespace
cat-haines Nov 13, 2015
947348d
Removed CommandNumPackets
cat-haines Nov 13, 2015
0f9eeff
inProgress -> in_progress
cat-haines Nov 13, 2015
fe1c771
Moved compatibility defines to util/compat.h
cat-haines Nov 13, 2015
da2afba
whitespace + line length
cat-haines Nov 13, 2015
fa169a0
more whitespace
cat-haines Nov 13, 2015
b6a487a
Variable length result
cat-haines Nov 13, 2015
533eeb0
Storing dictation window/callback in state now
cat-haines Nov 14, 2015
0fad8e4
Refactored dictation API and added stop command
cat-haines Nov 14, 2015
a2ece17
Updated docs with new voice interface
cat-haines Nov 14, 2015
93ce3b6
Updated Voice API to return string errors
cat-haines Nov 14, 2015
9d191fe
Updated docs for new dictation callback
cat-haines Nov 14, 2015
ffbf49c
🐍case
cat-haines Nov 14, 2015
33faf29
Added failed key to Voice.dictate event
cat-haines Nov 14, 2015
0bc9e80
Reverted README
cat-haines Nov 14, 2015
f97bea8
reverted app.js
cat-haines Nov 14, 2015
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 39 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Pebble.js applications run on your phone. They have access to all the resources

## Getting Started

* In CloudPebble
* In CloudPebble
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extra whitespace


The easiest way to use Pebble.js is in [CloudPebble](https://cloudpebble.net). Select the 'Pebble.js' project type when creating a new project.

Expand Down Expand Up @@ -146,16 +146,6 @@ wind.add(textfield);
wind.show();
````

## Examples

Coming Soon!

## Acknowledgements

Pebble.js started as [Simply.JS](http://simplyjs.io), a project by [Meiguro](http://github.com/meiguro). It is now part of the Pebble SDK and supported by Pebble. Contact [[email protected]](mailto:[email protected]) with any questions!

This documentation uses [Flatdoc](http://ricostacruz.com/flatdoc/#flatdoc).

# API Reference

## Global namespace
Expand Down Expand Up @@ -407,14 +397,6 @@ You can use the accelerometer in two different ways:
var Accel = require('ui/accel');
````

#### Accel.init()

Before you can use the accelerometer, you must call `Accel.init()`.

````js
Accel.init();
````

#### Accel.config(accelConfig)

This function configures the accelerometer `data` events to your liking. The `tap` event requires no configuration for use. Configuring the accelerometer is a very error prone process, so it is recommended to not configure the accelerometer and use `data` events with the default configuration without calling `Accel.config`.
Expand Down Expand Up @@ -494,6 +476,34 @@ wind.on('accelData', function(e) {
});
````

### Voice

The `Voice` module allows you to interact with Pebble's dictation API on supported platforms (Basalt and Chalk).

````js
var Voice = require('ui/voice');
````

#### Voice.startDictationSession(callback)

This function starts the dictation UI, and invokes the callback upon completion. The callback is invoked with an event with the following fields:

* `status`: The [DictationSessionStatus](https://developer.getpebble.com/docs/c/Foundation/Dictation/#DictationSessionStatus) (or -1 if the platform is not supported).
* `transcription`: The transcribed string

```js
Voice.startDictationSession(function(e) {
if (e.status != 0) {
// if there was an error
console.log('Error: ' + e.status);
return;
}

// Log the result
console.log('Success: ' + e.transcription);
});
```

### Window

`Window` is the basic building block in your Pebble.js application. All windows share some common properties and methods.
Expand Down Expand Up @@ -1338,3 +1348,13 @@ For more information, see [Vector2 in the three.js reference documentation][thre
[Text]: #text
[TimeText]: #timetext
[three.js Vector2]: http://threejs.org/docs/#Reference/Math/Vector2

## Examples

Coming Soon!

## Acknowledgements

Pebble.js started as [Simply.JS](http://simplyjs.io), a project by [Meiguro](http://github.com/meiguro). It is now part of the Pebble SDK and supported by Pebble. Contact [[email protected]](mailto:[email protected]) with any questions!

This documentation uses [Flatdoc](http://ricostacruz.com/flatdoc/#flatdoc).
1 change: 1 addition & 0 deletions src/js/lib/struct.js
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ struct.prototype._prevField = function(field) {
struct.prototype._makeAccessor = function(field) {
this[field.name] = function(value) {
var type = field.type;

if (field.dynamic) {
var prevField = this._prevField(field);
if (prevField === undefined) {
Expand Down
52 changes: 52 additions & 0 deletions src/js/ui/simply-pebble.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ var Wakeup = require('wakeup');
var Timeline = require('timeline');
var Resource = require('ui/resource');
var Accel = require('ui/accel');
var Voice = require('ui/voice');
var ImageService = require('ui/imageservice');
var WindowStack = require('ui/windowstack');
var Window = require('ui/window');
Expand Down Expand Up @@ -764,6 +765,21 @@ var ElementAnimateDonePacket = new struct([
['uint32', 'id'],
]);

var NumCommandsPacket = new struct([
[Packet, 'packet'],
['uint32', 'id'],
]);

var VoiceDictationStartPacket = new struct([
[Packet, 'packet'],
]);

var VoiceDictationDataPacket = new struct([
[Packet, 'packet'],
['int8', 'status'],
['cstring', 'transcription'],
]);

var CommandPackets = [
Packet,
SegmentPacket,
Expand Down Expand Up @@ -815,6 +831,9 @@ var CommandPackets = [
ElementImagePacket,
ElementAnimatePacket,
ElementAnimateDonePacket,
NumCommandsPacket,
VoiceDictationStartPacket,
VoiceDictationDataPacket,
];

var accelAxes = [
Expand Down Expand Up @@ -1113,6 +1132,35 @@ SimplyPebble.accelConfig = function(def) {
SimplyPebble.sendPacket(AccelConfigPacket.prop(def));
};

SimplyPebble.voiceDictationSession = function(callback) {
// If there's a transcription in progress
if (SimplyPebble.dictationCallback) {
callback( { 'status': -1, 'transcription': null } );
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No spaces for round parens, but the spaces for curly is good. I have to admit I'm not sure what you'd call this style. No quotes necessary, but you did use the right kind of quotes!

    callback({ status: -1, transcription: null });

return;
}

// Grab the current window to re-show once we're done
SimplyPebble.window = WindowStack.top();

// Set the callback and send the packet
SimplyPebble.dictationCallback = callback;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use an array of callbacks similar to SimplyPebble.accelPeek. Packets are guaranteed order, so you know the first callback is always the one to call when a Packet comes in from the watch.

You'll probably want to start more information than just the callback, such as the top window, so you would have something like:

dictationListenerInfos.push({ callback: callback, window: WindowStack.top() });

SimplyPebble.sendPacket(VoiceDictationStartPacket);
}

SimplyPebble.onVoiceData = function(packet) {
if (!SimplyPebble.dictationCallback) {
// Something bad happened
console.log("No callback specified for dictation session");
} else {
// invoke and clear the callback
SimplyPebble.dictationCallback( { 'status': packet.status(), 'transcription': packet.transcription() } );
SimplyPebble.dictationCallback = null;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

accelPeek is a little weird in how it handles listeners since it tries to do a best effort of throwing the most recent accel data it was given to all listeners.

For here, you would just get the callback info with var callbackInfo = dictionationListenerInfos.shift() and then resume your normal handling as you have here.


// show the top window to re-register handlers, etc.
SimplyPebble.window.show();
}

SimplyPebble.menuClear = function() {
SimplyPebble.sendPacket(MenuClearPacket);
};
Expand Down Expand Up @@ -1381,11 +1429,15 @@ SimplyPebble.onPacket = function(buffer, offset) {
case ElementAnimateDonePacket:
StageElement.emitAnimateDone(packet.id());
break;
case VoiceDictationDataPacket:
SimplyPebble.onVoiceData(packet);
break;
}
};

SimplyPebble.onAppMessage = function(e) {
var data = e.payload[0];

Packet._view = toArrayBuffer(data);

var offset = 0;
Expand Down
9 changes: 9 additions & 0 deletions src/js/ui/voice.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
var simply = require('ui/simply');

var Voice = {};

Voice.startDictationSession = function(e) {
simply.impl.voiceDictationSession(e);
};

module.exports = Voice;
3 changes: 3 additions & 0 deletions src/simply/simply.c
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@
#include "simply_ui.h"
#include "simply_window_stack.h"
#include "simply_wakeup.h"
#include "simply_voice.h"

#include <pebble.h>

Simply *simply_init(void) {
Simply *simply = malloc(sizeof(*simply));
simply->accel = simply_accel_create(simply);
simply->voice = simply_voice_create(simply);
simply->res = simply_res_create(simply);
simply->splash = simply_splash_create(simply);
simply->stage = simply_stage_create(simply);
Expand All @@ -39,5 +41,6 @@ void simply_deinit(Simply *simply) {
simply_stage_destroy(simply->stage);
simply_res_destroy(simply->res);
simply_accel_destroy(simply->accel);
simply_voice_destroy(simply->voice);
free(simply);
}
1 change: 1 addition & 0 deletions src/simply/simply.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ typedef struct Simply Simply;

struct Simply {
struct SimplyAccel *accel;
struct SimplyVoice *voice;
struct SimplyRes *res;
struct SimplyMsg *msg;
struct SimplyWindowStack *window_stack;
Expand Down
4 changes: 3 additions & 1 deletion src/simply/simply_msg.c
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include "simply_msg.h"

#include "simply_accel.h"
#include "simply_voice.h"
#include "simply_res.h"
#include "simply_stage.h"
#include "simply_menu.h"
Expand All @@ -22,7 +23,7 @@

static const size_t APP_MSG_SIZE_INBOUND = 2044;

static const size_t APP_MSG_SIZE_OUTBOUND = 512;
static const size_t APP_MSG_SIZE_OUTBOUND = 1024;

typedef enum VibeType VibeType;

Expand Down Expand Up @@ -201,6 +202,7 @@ static void handle_packet(Simply *simply, Packet *packet) {
if (simply_window_handle_packet(simply, packet)) { return; }
if (simply_ui_handle_packet(simply, packet)) { return; }
if (simply_accel_handle_packet(simply, packet)) { return; }
if (simply_voice_handle_packet(simply, packet)) { return; }
if (simply_menu_handle_packet(simply, packet)) { return; }
if (simply_stage_handle_packet(simply, packet)) { return; }
}
Expand Down
2 changes: 2 additions & 0 deletions src/simply/simply_msg_commands.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,6 @@ enum Command {
CommandElementAnimate,
CommandElementAnimateDone,
NumCommands,
CommandVoiceStart,
CommandVoiceData,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add these before NumCommands and remove the NumCommands packet type in simply-pebble.js -- it isn't a real packet type, it's just to get the number of commands.

};
100 changes: 100 additions & 0 deletions src/simply/simply_voice.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
#include "simply_voice.h"

#include "simply_msg.h"

#include "simply.h"

#include <pebble.h>

typedef struct VoiceDataPacket VoiceDataPacket;

struct __attribute__((__packed__)) VoiceDataPacket {
Packet packet;
int8_t status;
char result[SIMPLY_VOICE_BUFFER_LENGTH];
};

static SimplyVoice *s_voice;

static bool send_voice_data(int status, char *transcription) {
VoiceDataPacket packet = {
.packet.type = CommandVoiceData,
.packet.length = sizeof(packet),
.status = (uint8_t) status,
};
snprintf(packet.result, sizeof(packet.result), "%s", transcription);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, it would be better to allocate a packet that is the exact size of the string, such as

size_t packet_length = sizeof(VoiceDataPacket) + transcription_length + 1;
uint8_t buffer[packet_length];
VoiceDataPacket *packet = (VoiceDataPacket *)buffer;
*packet = (VoiceDataPacket) {
  .packet.type = CommandVoiceData,
  .packet.length = packet_length,
};
strcpy(packet->result, transcription);

simply_msg_send_packet will copy the packet over with a malloc, which is we use the stack as a buffer.

You would also change char result[SIMPLY_VOICE_BUFFER_LENGTH]; to char result[];` in the struct definition since now it is variable size.

Bonus points if you use strncpy, but I'll accept it without it (too much to type here).


return simply_msg_send_packet(&packet.packet);
}

#ifndef PBL_SDK_2
// Define a callback for the dictation session
static void dictation_session_callback(DictationSession *session, DictationSessionStatus status, char *transcription, void *context) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This codebase has max line of 120 (although I'm starting to think about whether it should be 100) following Google C++ Style.

Long story short, you can format this like this.

static void dictation_session_callback(DictationSession *session, DictationSessionStatus status, char *transcription,
                                       void *context) {

s_voice->inProgress = false;

// Send the result
send_voice_data(status, transcription);
}
#endif

static void timer_callback_start_dictation(void *data) {
dictation_session_start(s_voice->session);
}


static void handle_voice_start_packet(Simply *simply, Packet *data) {
#ifdef PBL_SDK_2
// send an immediate reply if we don't support voice
send_voice_data(-1, "");
#else

// Send an immediate response if there's already a dictation session in progress
if (s_voice->inProgress) {
send_voice_data(-1, "");
return;
}

// Otherwise, start the timer as soon as possible
// (we start a timer so we can return true as quickly as possible)
s_voice->inProgress = true;
s_voice->timer = app_timer_register(0, timer_callback_start_dictation, NULL);
#endif
}

bool simply_voice_handle_packet(Simply *simply, Packet *packet) {
switch (packet->type) {
case CommandVoiceStart:
handle_voice_start_packet(simply, packet);
return true;
}

return false;
}

SimplyVoice *simply_voice_create(Simply *simply) {
if(s_voice) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Space between if and ( (I hope I didn't mess that up somewhere).

return s_voice;
}

SimplyVoice *self = malloc(sizeof(*self));
*self = (SimplyVoice) {
.simply = simply,
.inProgress = false,
};

#ifndef PBL_SDK_2
self->session = dictation_session_create(SIMPLY_VOICE_BUFFER_LENGTH, dictation_session_callback, NULL),
#endif

s_voice = self;
return self;
}

void simply_voice_destroy(SimplyVoice *self) {
if (!self) {
return;
}

free(self);
s_voice = NULL;
}
29 changes: 29 additions & 0 deletions src/simply/simply_voice.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#pragma once

#include "simply_msg.h"
#include "simply.h"

#include <pebble.h>

#define SIMPLY_VOICE_BUFFER_LENGTH 512

#ifdef PBL_SDK_2
typedef struct DictationSession DictationSession;
typedef struct DictationSessionStatus DictationSessionStatus;
void dictation_session_start(DictationSession *session);
#endif
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can move this into util/compat.h, there is a section for aplite on all versions. And then you can include "util/compat.h" in simply_voice.c


typedef struct SimplyVoice SimplyVoice;

struct SimplyVoice {
Simply *simply;
DictationSession *session;
AppTimer *timer;

bool inProgress;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We use snake_case in C, so this would be in_progress.

};

SimplyVoice *simply_voice_create(Simply *simply);
void simply_voice_destroy(SimplyVoice *self);

bool simply_voice_handle_packet(Simply *simply, Packet *packet);