mirror of
https://github.com/espruino/Espruino.git
synced 2025-12-08 19:06:15 +00:00
5721 lines
198 KiB
C
5721 lines
198 KiB
C
/*
|
|
* This file is part of Espruino, a JavaScript interpreter for Microcontrollers
|
|
*
|
|
* Copyright (C) 2019 Gordon Williams <gw@pur3.co.uk>
|
|
*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
*
|
|
* ----------------------------------------------------------------------------
|
|
* This file is designed to be parsed during the build process
|
|
*
|
|
* Contains JavaScript interface for Bangle.js (http://www.espruino.com/Bangle.js)
|
|
* ----------------------------------------------------------------------------
|
|
*/
|
|
|
|
#include <jswrap_bangle.h>
|
|
#include "jsinteractive.h"
|
|
#include "jsdevices.h"
|
|
#include "jsnative.h"
|
|
#include "jshardware.h"
|
|
#include "jsdevices.h"
|
|
#include "jspin.h"
|
|
#include "jstimer.h"
|
|
#include "jswrap_promise.h"
|
|
#include "jswrap_date.h"
|
|
#include "jswrap_math.h"
|
|
#include "jswrap_storage.h"
|
|
#include "jswrap_array.h"
|
|
#include "jswrap_arraybuffer.h"
|
|
#include "jswrap_heatshrink.h"
|
|
#include "jswrap_espruino.h"
|
|
#include "jswrap_terminal.h"
|
|
#include "jsflash.h"
|
|
#include "graphics.h"
|
|
#include "bitmap_font_6x8.h"
|
|
#ifndef EMULATED
|
|
#include "jswrap_bluetooth.h"
|
|
#include "app_timer.h"
|
|
#include "nrf_gpio.h"
|
|
#include "nrf_delay.h"
|
|
#include "nrf_soc.h"
|
|
#include "nrf_saadc.h"
|
|
#include "nrf5x_utils.h"
|
|
|
|
#include "bluetooth.h" // for self-test
|
|
#include "jsi2c.h" // accelerometer/etc
|
|
#endif
|
|
|
|
#include "jswrap_graphics.h"
|
|
#ifdef LCD_CONTROLLER_LPM013M126
|
|
#include "lcd_memlcd.h"
|
|
#endif
|
|
#ifdef LCD_CONTROLLER_ST7789_8BIT
|
|
#include "lcd_st7789_8bit.h"
|
|
#endif
|
|
#if defined(LCD_CONTROLLER_ST7789V) || defined(LCD_CONTROLLER_ST7735) || defined(LCD_CONTROLLER_GC9A01)
|
|
#include "lcd_spilcd.h"
|
|
#endif
|
|
|
|
#ifdef ACCEL_DEVICE_KX126
|
|
#include "kx126_registers.h"
|
|
#endif
|
|
|
|
#include "stepcount.h"
|
|
|
|
#ifdef GPS_PIN_RX
|
|
#include "nmea.h"
|
|
#endif
|
|
#ifdef USE_TENSORFLOW
|
|
#include "jswrap_tensorflow.h"
|
|
#endif
|
|
#if ESPR_BANGLE_UNISTROKE
|
|
#include "unistroke.h"
|
|
#endif
|
|
|
|
/*TYPESCRIPT
|
|
declare const BTN1: Pin;
|
|
declare const BTN2: Pin;
|
|
declare const BTN3: Pin;
|
|
declare const BTN4: Pin;
|
|
declare const BTN5: Pin;
|
|
|
|
declare const g: Graphics<false>;
|
|
|
|
type WidgetArea = "tl" | "tr" | "bl" | "br";
|
|
type Widget = {
|
|
area: WidgetArea;
|
|
width: number;
|
|
draw: (this: { x: number; y: number }) => void;
|
|
};
|
|
declare const WIDGETS: { [key: string]: Widget };
|
|
*/
|
|
|
|
/*JSON{
|
|
"type": "class",
|
|
"class" : "Bangle",
|
|
"ifdef" : "BANGLEJS"
|
|
}
|
|
Class containing utility functions for the [Bangle.js Smart
|
|
Watch](http://www.espruino.com/Bangle.js)
|
|
*/
|
|
/*TYPESCRIPT{
|
|
"class" : "Bangle"
|
|
}
|
|
static CLOCK: boolean;
|
|
static strokes: undefined | { [key: string]: Unistroke };
|
|
*/
|
|
|
|
|
|
/*JSON{
|
|
"type" : "variable",
|
|
"name" : "VIBRATE",
|
|
"generate_full" : "VIBRATE_PIN",
|
|
"ifdef" : "BANGLEJS",
|
|
"return" : ["pin",""]
|
|
}
|
|
The Bangle.js's vibration motor.
|
|
*/
|
|
|
|
/*TYPESCRIPT
|
|
type AccelData = {
|
|
x: number;
|
|
y: number;
|
|
z: number;
|
|
diff: number;
|
|
mag: number;
|
|
};
|
|
*/
|
|
/*JSON{
|
|
"type" : "event",
|
|
"class" : "Bangle",
|
|
"name" : "accel",
|
|
"params" : [["xyz","JsVar",""]],
|
|
"ifdef" : "BANGLEJS",
|
|
"typescript": "on(event: \"accel\", callback: (xyz: AccelData) => void): void;"
|
|
}
|
|
Accelerometer data available with `{x,y,z,diff,mag}` object as a parameter.
|
|
|
|
* `x` is X axis (left-right) in `g`
|
|
* `y` is Y axis (up-down) in `g`
|
|
* `z` is Z axis (in-out) in `g`
|
|
* `diff` is difference between this and the last reading in `g`
|
|
* `mag` is the magnitude of the acceleration in `g`
|
|
|
|
You can also retrieve the most recent reading with `Bangle.getAccel()`.
|
|
*/
|
|
/*JSON{
|
|
"type" : "event",
|
|
"class" : "Bangle",
|
|
"name" : "step",
|
|
"params" : [["up","int","The number of steps since Bangle.js was last reset"]],
|
|
"ifdef" : "BANGLEJS"
|
|
}
|
|
Called whenever a step is detected by Bangle.js's pedometer.
|
|
*/
|
|
/*TYPESCRIPT
|
|
type HealthStatus = {
|
|
movement: number;
|
|
steps: number;
|
|
bpm: number;
|
|
bpmConfidence: number;
|
|
};
|
|
*/
|
|
/*JSON{
|
|
"type" : "event",
|
|
"class" : "Bangle",
|
|
"name" : "health",
|
|
"params" : [["info","JsVar","An object containing the last 10 minutes health data"]],
|
|
"ifdef" : "BANGLEJS",
|
|
"typescript" : "on(event: \"health\", callback: (info: HealthStatus) => void): void;"
|
|
}
|
|
See `Bangle.getHealthStatus()` for more information. This is used for health
|
|
tracking to allow Bangle.js to record historical exercise data.
|
|
*/
|
|
/*JSON{
|
|
"type" : "event",
|
|
"class" : "Bangle",
|
|
"name" : "faceUp",
|
|
"params" : [["up","bool","`true` if face-up"]],
|
|
"ifdef" : "BANGLEJS"
|
|
}
|
|
Has the watch been moved so that it is face-up, or not face up?
|
|
*/
|
|
/*JSON{
|
|
"type" : "event",
|
|
"class" : "Bangle",
|
|
"name" : "twist",
|
|
"ifdef" : "BANGLEJS"
|
|
}
|
|
This event happens when the watch has been twisted around it's axis - for
|
|
instance as if it was rotated so someone could look at the time.
|
|
|
|
To tweak when this happens, see the `twist*` options in `Bangle.setOptions()`
|
|
*/
|
|
/*JSON{
|
|
"type" : "event",
|
|
"class" : "Bangle",
|
|
"name" : "charging",
|
|
"params" : [["charging","bool","`true` if charging"]],
|
|
"ifdef" : "BANGLEJS"
|
|
}
|
|
Is the battery charging or not?
|
|
*/
|
|
/*TYPESCRIPT
|
|
type CompassData = {
|
|
x: number;
|
|
y: number;
|
|
z: number;
|
|
dx: number;
|
|
dy: number;
|
|
dz: number;
|
|
heading: number;
|
|
};
|
|
*/
|
|
/*JSON{
|
|
"type" : "event",
|
|
"class" : "Bangle",
|
|
"name" : "mag",
|
|
"params" : [["xyz","JsVar",""]],
|
|
"ifdef" : "BANGLEJS",
|
|
"typescript" : "on(event: \"mag\", callback: (xyz: CompassData) => void): void;"
|
|
}
|
|
Magnetometer/Compass data available with `{x,y,z,dx,dy,dz,heading}` object as a
|
|
parameter
|
|
|
|
* `x/y/z` raw x,y,z magnetometer readings
|
|
* `dx/dy/dz` readings based on calibration since magnetometer turned on
|
|
* `heading` in degrees based on calibrated readings (will be NaN if magnetometer
|
|
hasn't been rotated around 360 degrees).
|
|
|
|
**Note:** In 2v15 firmware and earlier the heading is inverted (360-heading). There's
|
|
a fix in the bootloader which will apply a fix for those headings, but old apps may
|
|
still expect an inverted value.
|
|
|
|
To get this event you must turn the compass on with `Bangle.setCompassPower(1)`.
|
|
|
|
You can also retrieve the most recent reading with `Bangle.getCompass()`.
|
|
*/
|
|
/*JSON{
|
|
"type" : "event",
|
|
"class" : "Bangle",
|
|
"name" : "GPS-raw",
|
|
"params" : [
|
|
["nmea","JsVar","A string containing the raw NMEA data from the GPS"],
|
|
["dataLoss","bool","This is set to true if some lines of GPS data have previously been lost (eg because system was too busy to queue up a GPS-raw event)"]
|
|
],
|
|
"ifdef" : "BANGLEJS",
|
|
"typescript" : "on(event: \"GPS-raw\", callback: (nmea: string, dataLoss: boolean) => void): void;"
|
|
}
|
|
Raw NMEA GPS / u-blox data messages received as a string
|
|
|
|
To get this event you must turn the GPS on with `Bangle.setGPSPower(1)`.
|
|
*/
|
|
/*TYPESCRIPT
|
|
type GPSFix = {
|
|
lat: number;
|
|
lon: number;
|
|
alt: number;
|
|
speed: number;
|
|
course: number;
|
|
time: Date;
|
|
satellites: number;
|
|
fix: number;
|
|
hdop: number
|
|
};
|
|
*/
|
|
/*JSON{
|
|
"type" : "event",
|
|
"class" : "Bangle",
|
|
"name" : "GPS",
|
|
"params" : [["fix","JsVar","An object with fix info (see below)"]],
|
|
"ifdef" : "BANGLEJS",
|
|
"typescript" : "on(event: \"GPS\", callback: (fix: GPSFix) => void): void;"
|
|
}
|
|
GPS data, as an object. Contains:
|
|
|
|
```
|
|
{ "lat": number, // Latitude in degrees
|
|
"lon": number, // Longitude in degrees
|
|
"alt": number, // altitude in M
|
|
"speed": number, // Speed in kph
|
|
"course": number, // Course in degrees
|
|
"time": Date, // Current Time (or undefined if not known)
|
|
"satellites": 7, // Number of satellites
|
|
"fix": 1 // NMEA Fix state - 0 is no fix
|
|
"hdop": number, // Horizontal Dilution of Precision
|
|
}
|
|
```
|
|
|
|
If a value such as `lat` is not known because there is no fix, it'll be `NaN`.
|
|
|
|
`hdop` is a value from the GPS receiver that gives a rough idea of accuracy of
|
|
lat/lon based on the geometry of the satellites in range. Multiply by 5 to get a
|
|
value in meters. This is just a ballpark estimation and should not be considered
|
|
remotely accurate.
|
|
|
|
To get this event you must turn the GPS on with `Bangle.setGPSPower(1)`.
|
|
*/
|
|
/*JSON{
|
|
"type" : "event",
|
|
"class" : "Bangle",
|
|
"name" : "HRM",
|
|
"params" : [["hrm","JsVar","An object with heart rate info (see below)"]],
|
|
"ifdef" : "BANGLEJS",
|
|
"typescript" : "on(event: \"HRM\", callback: (hrm: { bpm: number, confidence: number, raw: Uint8Array }) => void): void;"
|
|
}
|
|
Heat rate data, as an object. Contains:
|
|
|
|
```
|
|
{ "bpm": number, // Beats per minute
|
|
"confidence": number, // 0-100 percentage confidence in the heart rate
|
|
"raw": Uint8Array, // raw samples from heart rate monitor
|
|
}
|
|
```
|
|
|
|
To get this event you must turn the heart rate monitor on with
|
|
`Bangle.setHRMPower(1)`.
|
|
*/
|
|
/*JSON{
|
|
"type" : "event",
|
|
"class" : "Bangle",
|
|
"name" : "HRM-raw",
|
|
"params" : [["hrm","JsVar","A object containing instant readings from the heart rate sensor"]],
|
|
"ifdef" : "BANGLEJS",
|
|
"typescript" : "on(event: \"HRM-raw\", callback: (hrm: { raw: number, filt: number, bpm: number, confidence: number }) => void): void;"
|
|
}
|
|
Called when heart rate sensor data is available - see `Bangle.setHRMPower(1)`.
|
|
|
|
`hrm` is of the form:
|
|
|
|
```
|
|
{ "raw": -1, // raw value from sensor
|
|
"filt": -1, // bandpass-filtered raw value from sensor
|
|
"bpm": 88.9, // last BPM value measured
|
|
"confidence": 0 // confidence in the BPM value
|
|
}
|
|
```
|
|
*/
|
|
/*TYPESCRIPT
|
|
type PressureData = {
|
|
temperature: number;
|
|
pressure: number;
|
|
altitude: number;
|
|
}
|
|
*/
|
|
/*JSON{
|
|
"type" : "event",
|
|
"class" : "Bangle",
|
|
"name" : "pressure",
|
|
"params" : [["e","JsVar","An object containing `{temperature,pressure,altitude}`"]],
|
|
"ifdef" : "BANGLEJS",
|
|
"typescript" : "on(event: \"pressure\", callback: (e: PressureData) => void): void;"
|
|
}
|
|
When `Bangle.setBarometerPower(true)` is called, this event is fired containing
|
|
barometer readings.
|
|
|
|
Same format as `Bangle.getPressure()`
|
|
*/
|
|
/*JSON{
|
|
"type" : "event",
|
|
"class" : "Bangle",
|
|
"name" : "lcdPower",
|
|
"params" : [["on","bool","`true` if screen is on"]],
|
|
"ifdef" : "BANGLEJS"
|
|
}
|
|
Has the screen been turned on or off? Can be used to stop tasks that are no
|
|
longer useful if nothing is displayed. Also see `Bangle.isLCDOn()`
|
|
*/
|
|
/*JSON{
|
|
"type" : "event",
|
|
"class" : "Bangle",
|
|
"name" : "lock",
|
|
"params" : [["on","bool","`true` if screen is locked, `false` if it is unlocked and touchscreen/buttons will work"]],
|
|
"ifdef" : "BANGLEJS"
|
|
}
|
|
Has the screen been locked? Also see `Bangle.isLocked()`
|
|
*/
|
|
/*TYPESCRIPT
|
|
type TapAxis = -2 | -1 | 0 | 1 | 2;
|
|
*/
|
|
/*JSON{
|
|
"type" : "event",
|
|
"class" : "Bangle",
|
|
"name" : "tap",
|
|
"params" : [["data","JsVar","`{dir, double, x, y, z}`"]],
|
|
"ifdef" : "BANGLEJS",
|
|
"typescript" : "on(event: \"tap\", callback: (data: { dir: \"left\" | \"right\" | \"top\" | \"bottom\" | \"front\" | \"back\", double: boolean, x: TapAxis, y: TapAxis, z: TapAxis }) => void): void;"
|
|
}
|
|
If the watch is tapped, this event contains information on the way it was
|
|
tapped.
|
|
|
|
`dir` reports the side of the watch that was tapped (not the direction it was
|
|
tapped in).
|
|
|
|
```
|
|
{
|
|
dir : "left/right/top/bottom/front/back",
|
|
double : true/false // was this a double-tap?
|
|
x : -2 .. 2, // the axis of the tap
|
|
y : -2 .. 2, // the axis of the tap
|
|
z : -2 .. 2 // the axis of the tap
|
|
```
|
|
*/
|
|
/*JSON{
|
|
"type" : "event",
|
|
"class" : "Bangle",
|
|
"name" : "gesture",
|
|
"params" : [["xyz","JsVar","An Int8Array of XYZXYZXYZ data"]],
|
|
"ifdef" : "BANGLEJS",
|
|
"typescript" : "on(event: \"gesture\", callback: (xyz: Int8Array) => void): void;"
|
|
}
|
|
Emitted when a 'gesture' (fast movement) is detected
|
|
*/
|
|
/*JSON{
|
|
"type" : "event",
|
|
"class" : "Bangle",
|
|
"name" : "aiGesture",
|
|
"params" : [["gesture","JsVar","The name of the gesture (if '.tfnames' exists, or the index. 'undefined' if not matching"],
|
|
["weights","JsVar","An array of floating point values output by the model"]],
|
|
"ifdef" : "BANGLEJS",
|
|
"typescript" : "on(event: \"aiGesture\", callback: (gesture: string | undefined, weights: number[]) => void): void;"
|
|
}
|
|
Emitted when a 'gesture' (fast movement) is detected, and a Tensorflow model is
|
|
in storage in the `".tfmodel"` file.
|
|
|
|
If a `".tfnames"` file is specified as a comma-separated list of names, it will
|
|
be used to decode `gesture` from a number into a string.
|
|
*/
|
|
/*TYPESCRIPT
|
|
type SwipeCallback = (directionLR: -1 | 0 | 1, directionUD?: -1 | 0 | 1) => void;
|
|
*/
|
|
/*JSON{
|
|
"type" : "event",
|
|
"class" : "Bangle",
|
|
"name" : "swipe",
|
|
"params" : [["directionLR","int","`-1` for left, `1` for right, `0` for up/down"],
|
|
["directionUD","int","`-1` for up, `1` for down, `0` for left/right (Bangle.js 2 only)"]],
|
|
"ifdef" : "BANGLEJS",
|
|
"typescript" : "on(event: \"swipe\", callback: SwipeCallback): void;"
|
|
}
|
|
Emitted when a swipe on the touchscreen is detected (a movement from
|
|
left->right, right->left, down->up or up->down)
|
|
|
|
Bangle.js 1 is only capable of detecting left/right swipes as it only contains a
|
|
2 zone touchscreen.
|
|
*/
|
|
/*TYPESCRIPT
|
|
type TouchCallback = (button: number, xy?: { x: number, y: number }) => void;
|
|
*/
|
|
/*JSON{
|
|
"type" : "event",
|
|
"class" : "Bangle",
|
|
"name" : "touch",
|
|
"params" : [
|
|
["button","int","`1` for left, `2` for right"],
|
|
["xy","JsVar","Object of form `{x,y}` containing touch coordinates (if the device supports full touch). Clipped to 0..175 (LCD pixel coordinates) on firmware 2v13 and later."]
|
|
],
|
|
"ifdef" : "BANGLEJS",
|
|
"typescript" : "on(event: \"touch\", callback: TouchCallback): void;"
|
|
}
|
|
Emitted when the touchscreen is pressed
|
|
*/
|
|
/*TYPESCRIPT
|
|
type DragCallback = (event: {
|
|
x: number;
|
|
y: number;
|
|
dx: number;
|
|
dy: number;
|
|
b: 1 | 0;
|
|
}) => void;
|
|
*/
|
|
/*JSON{
|
|
"type" : "event",
|
|
"class" : "Bangle",
|
|
"name" : "drag",
|
|
"params" : [["event","JsVar","Object of form `{x,y,dx,dy,b}` containing touch coordinates, difference in touch coordinates, and an integer `b` containing number of touch points (currently 1 or 0)"]],
|
|
"ifdef" : "BANGLEJS",
|
|
"typescript" : "on(event: \"drag\", callback: DragCallback): void;"
|
|
}
|
|
Emitted when the touchscreen is dragged or released
|
|
|
|
The touchscreen extends past the edge of the screen and while `x` and `y`
|
|
coordinates are arranged such that they align with the LCD's pixels, if your
|
|
finger goes towards the edge of the screen, `x` and `y` could end up larger than
|
|
175 (the screen's maximum pixel coordinates) or smaller than 0. Coordinates from
|
|
the `touch` event are clipped.
|
|
*/
|
|
/*JSON{
|
|
"type" : "event",
|
|
"class" : "Bangle",
|
|
"name" : "stroke",
|
|
"params" : [["event","JsVar","Object of form `{xy:Uint8Array([x1,y1,x2,y2...])}` containing touch coordinates"]],
|
|
"ifdef" : "BANGLEJS_Q3",
|
|
"typescript" : "on(event: \"stroke\", callback: (event: { xy: Uint8Array, stroke?: string }) => void): void;"
|
|
}
|
|
Emitted when the touchscreen is dragged for a large enough distance to count as
|
|
a gesture.
|
|
|
|
If Bangle.strokes is defined and populated with data from `Unistroke.new`, the
|
|
`event` argument will also contain a `stroke` field containing the most closely
|
|
matching stroke name.
|
|
|
|
For example:
|
|
|
|
```
|
|
Bangle.strokes = {
|
|
up : Unistroke.new(new Uint8Array([57, 151, ... 158, 137])),
|
|
alpha : Unistroke.new(new Uint8Array([161, 55, ... 159, 161])),
|
|
};
|
|
Bangle.on('stroke',o=>{
|
|
print(o.stroke);
|
|
g.clear(1).drawPoly(o.xy);
|
|
});
|
|
// Might print something like
|
|
{
|
|
"xy": new Uint8Array([149, 50, ... 107, 136]),
|
|
"stroke": "alpha"
|
|
}
|
|
```
|
|
*/
|
|
/*JSON{
|
|
"type" : "event",
|
|
"class" : "Bangle",
|
|
"name" : "midnight",
|
|
"ifdef" : "BANGLEJS"
|
|
}
|
|
Emitted at midnight (at the point the `day` health info is reset to 0).
|
|
|
|
Can be used for housekeeping tasks that don't want to be run during the day.
|
|
*/
|
|
|
|
#define ACCEL_HISTORY_LEN 50 ///< Number of samples of accelerometer history
|
|
|
|
typedef struct {
|
|
short x,y,z;
|
|
} Vector3;
|
|
|
|
// =========================================================================
|
|
// DEVICE SPECIFIC CONFIG
|
|
|
|
#ifdef BANGLEJS_Q3
|
|
#ifndef EMULATED
|
|
JshI2CInfo i2cAccel;
|
|
JshI2CInfo i2cMag;
|
|
JshI2CInfo i2cTouch;
|
|
JshI2CInfo i2cPressure;
|
|
JshI2CInfo i2cHRM;
|
|
#define ACCEL_I2C &i2cAccel
|
|
#define MAG_I2C &i2cMag
|
|
#define TOUCH_I2C &i2cTouch
|
|
#define PRESSURE_I2C &i2cPressure
|
|
#define HRM_I2C &i2cHRM
|
|
#define GPS_UART EV_SERIAL1
|
|
#define HEARTRATE 1
|
|
|
|
bool pressureBMP280Enabled = false;
|
|
bool pressureSPL06Enabled = false;
|
|
#define PRESSURE_DEVICE_SPL06_007 1 // BMP280 already defined
|
|
#define PRESSURE_DEVICE_BMP280_EN pressureBMP280Enabled
|
|
#define PRESSURE_DEVICE_SPL06_007_EN pressureSPL06Enabled // hardware v2.1 is SPL06_001 - we need this as well
|
|
#endif // EMULATED
|
|
|
|
#define HOME_BTN 1
|
|
#define DEFAULT_LCD_POWER_TIMEOUT 0 // don't turn LCD off
|
|
#define DEFAULT_BACKLIGHT_TIMEOUT 3000
|
|
#define DEFAULT_LOCK_TIMEOUT 5000
|
|
|
|
#endif
|
|
|
|
#ifdef BANGLEJS_F18
|
|
#ifndef EMULATED
|
|
/// Internal I2C used for Accelerometer/Pressure
|
|
JshI2CInfo i2cInternal;
|
|
#define ACCEL_I2C &i2cInternal
|
|
#define MAG_I2C &i2cInternal
|
|
// Nordic app timer to handle backlight PWM
|
|
APP_TIMER_DEF(m_backlight_on_timer_id);
|
|
APP_TIMER_DEF(m_backlight_off_timer_id);
|
|
#define BACKLIGHT_PWM_INTERVAL 15 // in msec - 67Hz PWM
|
|
#define HEARTRATE 1
|
|
#define GPS_UART EV_SERIAL1
|
|
#define GPS_UBLOX 1 // handle decoding of 'UBX' packets from the GPS
|
|
#endif // !EMULATED
|
|
|
|
#define IOEXP_GPS 0x01
|
|
#define IOEXP_LCD_BACKLIGHT 0x20
|
|
#define IOEXP_LCD_RESET 0x40
|
|
#define IOEXP_HRM 0x80
|
|
|
|
|
|
#define HOME_BTN 3
|
|
#endif
|
|
|
|
#ifdef DTNO1_F5
|
|
/// Internal I2C used for Accelerometer/Pressure
|
|
JshI2CInfo i2cInternal;
|
|
#define ACCEL_I2C &i2cInternal
|
|
#define PRESSURE_I2C &i2cInternal
|
|
#define HOME_BTN 3
|
|
#endif
|
|
|
|
#ifdef DICKENS
|
|
JshI2CInfo i2cInternal;
|
|
#define ACCEL_I2C &i2cInternal
|
|
#define PRESSURE_I2C &i2cInternal
|
|
#define MAG_I2C &i2cInternal
|
|
#define HOME_BTN 2
|
|
#define DEFAULT_BTN_LOAD_TIMEOUT 4000
|
|
#define DEFAULT_LCD_POWER_TIMEOUT 20000
|
|
#endif
|
|
|
|
#ifdef ID205
|
|
#define HOME_BTN 1
|
|
#endif
|
|
// =========================================================================
|
|
|
|
#if HOME_BTN==1
|
|
#define HOME_BTN_PININDEX BTN1_PININDEX
|
|
#endif
|
|
#if HOME_BTN==2
|
|
#define HOME_BTN_PININDEX BTN2_PININDEX
|
|
#endif
|
|
#if HOME_BTN==3
|
|
#define HOME_BTN_PININDEX BTN3_PININDEX
|
|
#endif
|
|
#if HOME_BTN==4
|
|
#define HOME_BTN_PININDEX BTN4_PININDEX
|
|
#endif
|
|
#if HOME_BTN==5
|
|
#define HOME_BTN_PININDEX BTN5_PININDEX
|
|
#endif
|
|
// =========================================================================
|
|
|
|
#define DEFAULT_ACCEL_POLL_INTERVAL 80 // in msec - 12.5 hz to match accelerometer
|
|
#define POWER_SAVE_ACCEL_POLL_INTERVAL 800 // in msec
|
|
#define POWER_SAVE_MIN_ACCEL 1638 // min acceleration before we exit power save... (8192*0.2)
|
|
#define POWER_SAVE_TIMEOUT 60000 // 60 seconds of inactivity
|
|
#define ACCEL_POLL_INTERVAL_MAX 4000 // in msec - DEFAULT_ACCEL_POLL_INTERVAL_MAX+TIMER_MAX must be <65535
|
|
#ifndef DEFAULT_BTN_LOAD_TIMEOUT
|
|
#define DEFAULT_BTN_LOAD_TIMEOUT 1500 // in msec - how long does the button have to be pressed for before we restart
|
|
#endif
|
|
#define TIMER_MAX 60000 // 60 sec - enough to fit in uint16_t without overflow if we add ACCEL_POLL_INTERVAL
|
|
#ifndef DEFAULT_LCD_POWER_TIMEOUT
|
|
#define DEFAULT_LCD_POWER_TIMEOUT 30000 // in msec - default for lcdPowerTimeout
|
|
#endif
|
|
#ifndef DEFAULT_BACKLIGHT_TIMEOUT
|
|
#define DEFAULT_BACKLIGHT_TIMEOUT DEFAULT_LCD_POWER_TIMEOUT
|
|
#endif
|
|
#ifndef DEFAULT_LOCK_TIMEOUT
|
|
#define DEFAULT_LOCK_TIMEOUT 30000 // in msec - default for lockTimeout
|
|
#endif
|
|
|
|
#ifdef TOUCH_DEVICE
|
|
short touchX, touchY; ///< current touch event coordinates
|
|
short lastTouchX, lastTouchY; ///< last touch event coordinates - updated when JSBT_DRAG is fired
|
|
bool touchPts, lastTouchPts; ///< whether a fnger is currently touching or not
|
|
unsigned char touchType; ///< variable to differentiate press, long press, double press
|
|
short touchMinX = 0, touchMinY = 0, touchMaxX = 160, touchMaxY = 160; ///< touchscreen calibration values (what we expect from hardware, then we map this to LCD_WIDTH/HEIGHT)
|
|
#endif
|
|
|
|
#ifdef PRESSURE_DEVICE
|
|
#ifdef PRESSURE_DEVICE_SPL06_007
|
|
#ifndef PRESSURE_DEVICE_SPL06_007_EN
|
|
#define PRESSURE_DEVICE_SPL06_007_EN 1
|
|
#endif
|
|
#define SPL06_PRSB2 0x00 ///< Pressure/temp data start
|
|
#define SPL06_PRSCFG 0x06 ///< Pressure config
|
|
#define SPL06_TMPCFG 0x07 ///< Temperature config
|
|
#define SPL06_MEASCFG 0x08 ///< Sensor status and config
|
|
#define SPL06_CFGREG 0x09 ///< FIFO config
|
|
#define SPL06_RESET 0x0C ///< reset
|
|
#define SPL06_COEF_START 0x10 ///< Start of calibration coefficients
|
|
#define SPL06_COEF_NUM 18 ///< Number of calibration coefficient registers
|
|
#define SPL06_8SAMPLES 3
|
|
/// Calibration coefficients
|
|
short barometer_c0, barometer_c1, barometer_c01, barometer_c11, barometer_c20, barometer_c21, barometer_c30;
|
|
int barometer_c00, barometer_c10;
|
|
#endif
|
|
#ifdef PRESSURE_DEVICE_BMP280
|
|
#ifndef PRESSURE_DEVICE_BMP280_007_EN
|
|
#define PRESSURE_DEVICE_BMP280_007_EN 1
|
|
#endif
|
|
int barometerDT[3]; // temp calibration
|
|
int barometerDP[9]; // pressure calibration
|
|
#endif
|
|
#ifdef PRESSURE_DEVICE_HP203
|
|
#ifndef PRESSURE_DEVICE_HP203_EN
|
|
#define PRESSURE_DEVICE_HP203_EN 1
|
|
#endif
|
|
#endif
|
|
|
|
/// Promise when pressure is requested
|
|
JsVar *promisePressure;
|
|
double barometerPressure;
|
|
double barometerTemperature;
|
|
double barometerAltitude;
|
|
double barometerSeaLevelPressure = 1013.25; // Standard atmospheric pressure (millibars)
|
|
bool jswrap_banglejs_barometerPoll();
|
|
JsVar *jswrap_banglejs_getBarometerObject();
|
|
#endif // PRESSURE_DEVICE
|
|
|
|
#ifdef HEARTRATE
|
|
#include "hrm.h"
|
|
#include "heartrate.h"
|
|
#endif
|
|
|
|
#ifdef GPS_PIN_RX
|
|
#ifdef GPS_UBLOX
|
|
/// Handling data coming from UBlox GPS
|
|
typedef enum {
|
|
UBLOX_PROTOCOL_NOT_DETECTED = 0,
|
|
UBLOX_PROTOCOL_NMEA = 1,
|
|
UBLOX_PROTOCOL_UBX = 2
|
|
} UBloxProtocol;
|
|
/// What protocol is the current packet??
|
|
UBloxProtocol inComingUbloxProtocol = UBLOX_PROTOCOL_NOT_DETECTED;
|
|
|
|
/// UBlox UBX message expected length
|
|
uint16_t ubxMsgPayloadEnd = 0;
|
|
#endif // GPS_UBLOX
|
|
|
|
// ------------------------- Current data as it comes from GPS
|
|
/// how many characters of NMEA/UBX data do we have in gpsLine
|
|
uint16_t gpsLineLength = 0;
|
|
/// Data received from GPS UART via IRQ, 82 is the max for NMEA
|
|
uint8_t gpsLine[NMEA_MAX_SIZE];
|
|
// ------------------------- Last line of data from GPS
|
|
/// length of data to be handled in jswrap_banglejs_idle
|
|
uint8_t gpsLastLineLength = 0;
|
|
/// GPS data line to be handled in jswrap_banglejs_idle
|
|
char gpsLastLine[NMEA_MAX_SIZE];
|
|
|
|
/// GPS fix data converted from GPS
|
|
NMEAFixInfo gpsFix;
|
|
#endif // GPS_PIN_RX
|
|
|
|
#ifdef ESPR_BATTERY_FULL_VOLTAGE
|
|
float batteryFullVoltage = ESPR_BATTERY_FULL_VOLTAGE;
|
|
#endif // ESPR_BATTERY_FULL_VOLTAGE
|
|
|
|
#ifndef EMULATED
|
|
/// Nordic app timer to handle call of peripheralPollHandler
|
|
APP_TIMER_DEF(m_peripheral_poll_timer_id);
|
|
#endif
|
|
|
|
/// Is I2C busy? if so we'll skip one reading in our interrupt so we don't overlap
|
|
bool i2cBusy;
|
|
/// How often should be poll for accelerometer/compass data?
|
|
volatile uint16_t pollInterval; // in ms
|
|
/// Timer used for power save (lowering the poll interval)
|
|
volatile uint16_t powerSaveTimer;
|
|
|
|
/// counter that counts up if watch has stayed face up or down
|
|
volatile uint16_t faceUpTimer;
|
|
/// Was the watch face-up? we use this when firing events
|
|
volatile bool wasFaceUp, faceUp;
|
|
/// Was the FACE_UP event sent yet?
|
|
bool faceUpSent;
|
|
/// Was the watch charging? we use this when firing events
|
|
volatile bool wasCharging;
|
|
/// time since a button/touchscreen/etc was last pressed
|
|
volatile uint16_t inactivityTimer; // in ms
|
|
/// How long has BTN1 been held down for (or TIMER_MAX is a reset has already happened)
|
|
volatile uint16_t homeBtnTimer; // in ms
|
|
/// How long has BTN1 been held down and watch hasn't reset (used to queue an interrupt)
|
|
volatile uint16_t homeBtnInterruptTimer; // in ms
|
|
/// How long does the home button have to be pressed before the default app is reloaded?
|
|
int btnLoadTimeout; // in ms
|
|
/// Is LCD power automatic? If true this is the number of ms for the timeout, if false it's 0
|
|
int lcdPowerTimeout; // in ms
|
|
/// Is LCD backlight automatic? If true this is the number of ms for the timeout, if false it's 0
|
|
int backlightTimeout; // in ms
|
|
/// Is locking automatic? If true this is the number of ms for the timeout, if false it's 0
|
|
int lockTimeout; // in ms
|
|
/// If a button was pressed to wake the LCD up, which one was it?
|
|
char lcdWakeButton;
|
|
/// If a button was pressed to wake the LCD up, when should we start accepting events for it?
|
|
JsSysTime lcdWakeButtonTime;
|
|
/// LCD Brightness - 255=full
|
|
uint8_t lcdBrightness;
|
|
#ifdef ESPR_BACKLIGHT_FADE
|
|
/// Actual LCD brightness (if we fade to a new brightness level)
|
|
uint8_t realLcdBrightness;
|
|
bool lcdFadeHandlerActive;
|
|
#endif
|
|
#ifdef MAG_I2C
|
|
// compass data
|
|
Vector3 mag, magmin, magmax;
|
|
#endif
|
|
/// accelerometer data
|
|
Vector3 acc;
|
|
/// squared accelerometer magnitude
|
|
int accMagSquared;
|
|
/// magnitude of difference in accelerometer vectors since last reading
|
|
unsigned int accDiff;
|
|
|
|
/// History of accelerometer readings
|
|
int8_t accHistory[ACCEL_HISTORY_LEN*3];
|
|
/// Index in accelerometer history of the last sample
|
|
volatile uint8_t accHistoryIdx;
|
|
/// How many samples have we been recording a gesture for? If 0, we're not recoding a gesture
|
|
volatile uint8_t accGestureCount;
|
|
/// How many samples have been recorded? Used when putting data into an array
|
|
volatile uint8_t accGestureRecordedCount;
|
|
/// How many samples has the accelerometer movement been less than accelGestureEndThresh for?
|
|
volatile uint8_t accIdleCount;
|
|
/// data on how watch was tapped
|
|
unsigned char tapInfo;
|
|
/// time since watch was last twisted enough past twistThreshold
|
|
volatile uint16_t twistTimer; // in ms
|
|
// Gesture settings
|
|
/// how big a difference before we consider a gesture started?
|
|
unsigned short accelGestureStartThresh = 800;
|
|
/// how small a difference before we consider a gesture ended?
|
|
unsigned short accelGestureEndThresh = 2000;
|
|
/// how many samples do we keep after a gesture has ended
|
|
int accelGestureInactiveCount = 4;
|
|
/// how many samples must a gesture have before we notify about it?
|
|
int accelGestureMinLength = 10;
|
|
/// How much acceleration to register a twist of the watch strap?
|
|
int twistThreshold = 800;
|
|
/// Maximum acceleration in Y to trigger a twist (low Y means watch is facing the right way up)
|
|
int twistMaxY = -800;
|
|
/// How little time (in ms) must a twist take from low->high acceleration?
|
|
int twistTimeout = 1000;
|
|
|
|
/// Current steps since reset
|
|
uint32_t stepCounter;
|
|
/// What state was the touchscreen last in
|
|
typedef enum {
|
|
TS_NONE = 0,
|
|
TS_LEFT = 1,
|
|
TS_RIGHT = 2,
|
|
TS_BOTH = 3,
|
|
TS_SWIPED = 4
|
|
} TouchState;
|
|
TouchState touchLastState; /// What happened in the last event?
|
|
TouchState touchLastState2; /// What happened in the event before last?
|
|
TouchState touchStatus; ///< What has happened *while the current touch is in progress
|
|
typedef enum {
|
|
TG_SWIPE_NONE,
|
|
TG_SWIPE_LEFT,
|
|
TG_SWIPE_RIGHT,
|
|
TG_SWIPE_UP,
|
|
TG_SWIPE_DOWN,
|
|
} TouchGestureType;
|
|
TouchGestureType touchGesture; /// is JSBT_SWIPE is set, what happened?
|
|
|
|
/// How often should we fire 'health' events?
|
|
#define HEALTH_INTERVAL 600000 // 10 minutes (600 seconds)
|
|
/// Struct with currently tracked health info
|
|
typedef struct {
|
|
uint8_t index; ///< time_in_ms / HEALTH_INTERVAL - we fire a new Health event when this changes
|
|
uint32_t movement; ///< total accelerometer difference. Used for activity tracking.
|
|
uint16_t movementSamples; ///< Number of samples added to movement
|
|
uint16_t stepCount; ///< steps during current period
|
|
uint16_t bpm10; ///< beats per minute (x10)
|
|
uint8_t bpmConfidence; ///< confidence of current BPM figure
|
|
} HealthState;
|
|
/// Currently tracked health info during this period
|
|
HealthState healthCurrent;
|
|
/// Health info during the last period, used when firing a health event
|
|
HealthState healthLast;
|
|
/// Health data so far this day
|
|
HealthState healthDaily;
|
|
|
|
/// Promise when beep is finished
|
|
JsVar *promiseBeep;
|
|
/// Promise when buzz is finished
|
|
JsVar *promiseBuzz;
|
|
//
|
|
unsigned short beepFreq;
|
|
unsigned char buzzAmt;
|
|
|
|
typedef enum {
|
|
JSBF_NONE,
|
|
JSBF_WAKEON_FACEUP = 1<<0,
|
|
JSBF_WAKEON_BTN1 = 1<<1,
|
|
JSBF_WAKEON_BTN2 = 1<<2,
|
|
JSBF_WAKEON_BTN3 = 1<<3,
|
|
JSBF_WAKEON_TOUCH = 1<<4,
|
|
JSBF_WAKEON_TWIST = 1<<5,
|
|
JSBF_BEEP_VIBRATE = 1<<6, // use vibration motor for beep
|
|
JSBF_ENABLE_BEEP = 1<<7,
|
|
JSBF_ENABLE_BUZZ = 1<<8,
|
|
JSBF_ACCEL_LISTENER = 1<<9, ///< we have a listener for accelerometer data
|
|
JSBF_POWER_SAVE = 1<<10, ///< if no movement detected for a while, lower the accelerometer poll interval
|
|
JSBF_HRM_ON = 1<<11,
|
|
JSBF_GPS_ON = 1<<12,
|
|
JSBF_COMPASS_ON = 1<<13,
|
|
JSBF_BAROMETER_ON = 1<<14,
|
|
JSBF_LCD_ON = 1<<15,
|
|
JSBF_LCD_BL_ON = 1<<16,
|
|
JSBF_LOCKED = 1<<17,
|
|
JSBF_HRM_INSTANT_LISTENER = 1<<18,
|
|
|
|
JSBF_DEFAULT = ///< default at power-on
|
|
JSBF_WAKEON_TWIST|
|
|
JSBF_WAKEON_BTN1|JSBF_WAKEON_BTN2|JSBF_WAKEON_BTN3
|
|
} JsBangleFlags;
|
|
volatile JsBangleFlags bangleFlags = JSBF_NONE;
|
|
|
|
|
|
typedef enum {
|
|
JSBT_NONE,
|
|
JSBT_RESET = 1<<0, ///< reset the watch and reload code from flash
|
|
JSBT_LCD_ON = 1<<1, ///< LCD controller (can turn this on without the backlight)
|
|
JSBT_LCD_OFF = 1<<2,
|
|
JSBT_LCD_BL_ON = 1<<3, ///< LCD backlight
|
|
JSBT_LCD_BL_OFF = 1<<4,
|
|
JSBT_LOCK = 1<<5, ///< watch is locked
|
|
JSBT_UNLOCK = 1<<6, ///< watch is unlocked
|
|
JSBT_ACCEL_DATA = 1<<7, ///< need to push xyz data to JS
|
|
JSBT_ACCEL_TAPPED = 1<<8, ///< tap event detected
|
|
#ifdef GPS_PIN_RX
|
|
JSBT_GPS_DATA = 1<<9, ///< we got a complete set of GPS data in 'gpsFix'
|
|
JSBT_GPS_DATA_LINE = 1<<10, ///< we got a line of GPS data
|
|
JSBT_GPS_DATA_PARTIAL = 1<<11, ///< we got some GPS data but it needs storing for later because it was too big to go in our buffer
|
|
JSBT_GPS_DATA_OVERFLOW = 1<<12, ///< we got more GPS data than we could handle and had to drop some
|
|
#endif
|
|
#ifdef PRESSURE_DEVICE
|
|
JSBT_PRESSURE_DATA = 1<<13,
|
|
#endif
|
|
JSBT_MAG_DATA = 1<<14, ///< need to push magnetometer data to JS
|
|
JSBT_GESTURE_DATA = 1<<15, ///< we have data from a gesture
|
|
JSBT_HRM_DATA = 1<<16, ///< Heart rate data is ready for analysis
|
|
JSBT_CHARGE_EVENT = 1<<17, ///< we need to fire a charging event
|
|
JSBT_STEP_EVENT = 1<<18, ///< we've detected a step via the pedometer
|
|
JSBT_SWIPE = 1<<19, ///< swiped over touchscreen, info in touchGesture
|
|
JSBT_TOUCH_LEFT = 1<<20, ///< touch lhs of touchscreen
|
|
JSBT_TOUCH_RIGHT = 1<<21, ///< touch rhs of touchscreen
|
|
JSBT_TOUCH_MASK = JSBT_TOUCH_LEFT | JSBT_TOUCH_RIGHT,
|
|
#ifdef TOUCH_DEVICE
|
|
JSBT_DRAG = 1<<22,
|
|
#endif
|
|
#if ESPR_BANGLE_UNISTROKE
|
|
JSBT_STROKE = 1<<23, // a gesture has been made on the touchscreen
|
|
#endif
|
|
JSBT_TWIST_EVENT = 1<<24, ///< Watch was twisted
|
|
JSBT_FACE_UP = 1<<25, ///< Watch was turned face up/down (faceUp holds the actual state)
|
|
JSBT_ACCEL_INTERVAL_DEFAULT = 1<<26, ///< reschedule accelerometer poll handler to default speed
|
|
JSBT_ACCEL_INTERVAL_POWERSAVE = 1<<27, ///< reschedule accelerometer poll handler to powersave speed
|
|
JSBT_HRM_INSTANT_DATA = 1<<28, ///< Instant heart rate data
|
|
JSBT_HEALTH = 1<<29, ///< New 'health' event
|
|
JSBT_MIDNIGHT = 1<<30, ///< Fired at midnight each day - for housekeeping tasks
|
|
} JsBangleTasks;
|
|
JsBangleTasks bangleTasks;
|
|
|
|
static void jswrap_banglejs_setLCDPowerBacklight(bool isOn);
|
|
|
|
void jswrap_banglejs_pwrGPS(bool on) {
|
|
if (on) bangleFlags |= JSBF_GPS_ON;
|
|
else bangleFlags &= ~JSBF_GPS_ON;
|
|
#ifdef BANGLEJS_F18
|
|
jswrap_banglejs_ioWr(IOEXP_GPS, on);
|
|
#endif
|
|
#ifdef GPS_PIN_EN
|
|
jshPinOutput(GPS_PIN_EN, on);
|
|
#endif
|
|
}
|
|
|
|
void jswrap_banglejs_pwrHRM(bool on) {
|
|
#ifdef HEARTRATE
|
|
if (on) bangleFlags |= JSBF_HRM_ON;
|
|
else bangleFlags &= ~JSBF_HRM_ON;
|
|
#endif
|
|
#ifdef BANGLEJS_F18
|
|
jswrap_banglejs_ioWr(IOEXP_HRM, !on);
|
|
#endif
|
|
#ifdef BANGLEJS_Q3
|
|
#ifndef EMULATED
|
|
// On Q3 the HRM power is gated, so if we leave
|
|
// I2C set up it parasitically powers the HRM through
|
|
// the pullups!
|
|
if (on) jsi2cSetup(&i2cHRM);
|
|
else jsi2cUnsetup(&i2cHRM);
|
|
#endif
|
|
#endif
|
|
#ifdef HEARTRATE_PIN_EN
|
|
jshPinOutput(HEARTRATE_PIN_EN, on);
|
|
#endif
|
|
}
|
|
|
|
void jswrap_banglejs_pwrBacklight(bool on) {
|
|
#ifdef BANGLEJS_F18
|
|
jswrap_banglejs_ioWr(IOEXP_LCD_BACKLIGHT, !on);
|
|
#endif
|
|
#ifdef LCD_BL
|
|
jshPinOutput(LCD_BL, on);
|
|
#endif
|
|
#ifdef LCD_CONTROLLER_LPM013M126
|
|
lcdMemLCD_extcominBacklight(on);
|
|
#endif
|
|
}
|
|
|
|
|
|
|
|
void graphicsInternalFlip() {
|
|
#ifdef LCD_CONTROLLER_LPM013M126
|
|
lcdMemLCD_flip(&graphicsInternal);
|
|
#endif
|
|
#ifdef LCD_CONTROLLER_ST7789_8BIT
|
|
lcdST7789_flip(&graphicsInternal);
|
|
#endif
|
|
#if defined(LCD_CONTROLLER_ST7789V) || defined(LCD_CONTROLLER_ST7735) || defined(LCD_CONTROLLER_GC9A01)
|
|
lcdFlip_SPILCD(&graphicsInternal);
|
|
#endif
|
|
}
|
|
|
|
/// Flip buffer contents with the screen.
|
|
void lcd_flip(JsVar *parent, bool all) {
|
|
#ifdef LCD_WIDTH
|
|
if (all) {
|
|
graphicsInternal.data.modMinX = 0;
|
|
graphicsInternal.data.modMinY = 0;
|
|
graphicsInternal.data.modMaxX = LCD_WIDTH-1;
|
|
graphicsInternal.data.modMaxY = LCD_HEIGHT-1;
|
|
}
|
|
graphicsInternalFlip();
|
|
#endif
|
|
}
|
|
|
|
/// Clear the given health state back to defaults
|
|
static void healthStateClear(HealthState *health) {
|
|
memset(health, 0, sizeof(HealthState));
|
|
}
|
|
|
|
/** This is called to set whether an app requests a device to be on or off.
|
|
* The value returned is whether the device should be on.
|
|
* Devices: GPS/Compass/HRM/Barom
|
|
*/
|
|
#define SETDEVICEPOWER_FORCE (execInfo.root)
|
|
bool setDeviceRequested(const char *deviceName, JsVar *appID, bool powerOn) {
|
|
if (appID==SETDEVICEPOWER_FORCE) {
|
|
// force the device power to what we asked for
|
|
return powerOn;
|
|
}
|
|
|
|
JsVar *bangle = jsvObjectGetChild(execInfo.root, "Bangle", 0);
|
|
if (!bangle) return false;
|
|
JsVar *uses = jsvObjectGetChild(bangle, "_PWR", JSV_OBJECT);
|
|
if (!uses) {
|
|
jsvUnLock(bangle);
|
|
return false;
|
|
}
|
|
bool isOn = false;
|
|
JsVar *device = jsvObjectGetChild(uses, deviceName, JSV_ARRAY);
|
|
if (device) {
|
|
if (appID) appID = jsvAsString(appID);
|
|
else appID = jsvNewFromString("?");
|
|
|
|
JsVar *idx = jsvGetIndexOf(device, appID, false);
|
|
if (powerOn) {
|
|
if (!idx) jsvArrayPush(device, appID);
|
|
} else {
|
|
if (idx) jsvRemoveChild(device, idx);
|
|
}
|
|
jsvUnLock2(appID, idx);
|
|
isOn = jsvGetArrayLength(device)>0;
|
|
// free memory by remove the device from the list if not used
|
|
if (!isOn)
|
|
jsvObjectRemoveChild(uses, deviceName);
|
|
}
|
|
jsvUnLock3(device, uses, bangle);
|
|
return isOn;
|
|
}
|
|
// Check whether a specific device has been requested to be on or not
|
|
bool getDeviceRequested(const char *deviceName) {
|
|
JsVar *bangle = jsvObjectGetChild(execInfo.root, "Bangle", 0);
|
|
if (!bangle) return false;
|
|
JsVar *uses = jsvObjectGetChild(bangle, "_PWR", JSV_OBJECT);
|
|
if (!uses) {
|
|
jsvUnLock(bangle);
|
|
return false;
|
|
}
|
|
bool isOn = false;
|
|
JsVar *device = jsvObjectGetChild(uses, deviceName, JSV_ARRAY);
|
|
if (device)
|
|
isOn = jsvGetArrayLength(device)>0;
|
|
jsvUnLock3(device, uses, bangle);
|
|
return isOn;
|
|
}
|
|
|
|
void jswrap_banglejs_setPollInterval_internal(uint16_t msec) {
|
|
pollInterval = (uint16_t)msec;
|
|
#ifndef EMULATED
|
|
app_timer_stop(m_peripheral_poll_timer_id);
|
|
#if NRF_SD_BLE_API_VERSION<5
|
|
app_timer_start(m_peripheral_poll_timer_id, APP_TIMER_TICKS(pollInterval, APP_TIMER_PRESCALER), NULL);
|
|
#else
|
|
app_timer_start(m_peripheral_poll_timer_id, APP_TIMER_TICKS(pollInterval), NULL);
|
|
#endif
|
|
#endif
|
|
}
|
|
|
|
/* If we're busy and really don't want to be interrupted (eg clearing flash memory)
|
|
then we should *NOT* allow the home button to set EXEC_INTERRUPTED (which happens
|
|
if it was held, JSBT_RESET was set, and then 0.5s later it wasn't handled).
|
|
*/
|
|
void jswrap_banglejs_kickPollWatchdog() {
|
|
homeBtnInterruptTimer = 0;
|
|
}
|
|
|
|
#ifndef EMULATED
|
|
/* Scan peripherals for any data that's needed
|
|
* Also, holding down both buttons will reboot */
|
|
void peripheralPollHandler() {
|
|
JsSysTime time = jshGetSystemTime();
|
|
// Handle watchdog
|
|
if (!(jshPinGetValue(BTN1_PININDEX)
|
|
#ifdef BTN2_PININDEX
|
|
&& jshPinGetValue(BTN2_PININDEX)
|
|
#endif
|
|
))
|
|
jshKickWatchDog();
|
|
|
|
// power on display if a button is pressed
|
|
if (inactivityTimer < TIMER_MAX)
|
|
inactivityTimer += pollInterval;
|
|
// If button is held down, trigger a soft reset so we go back to the clock
|
|
if (jshPinGetValue(HOME_BTN_PININDEX)) {
|
|
if (homeBtnTimer < TIMER_MAX) {
|
|
homeBtnTimer += pollInterval;
|
|
if (btnLoadTimeout && (homeBtnTimer >= btnLoadTimeout)) {
|
|
bangleTasks |= JSBT_RESET;
|
|
jshHadEvent();
|
|
homeBtnTimer = TIMER_MAX;
|
|
// Allow home button to break out of debugger
|
|
if (jsiStatus & JSIS_IN_DEBUGGER) {
|
|
jsiStatus |= JSIS_EXIT_DEBUGGER;
|
|
execInfo.execute |= EXEC_INTERRUPTED;
|
|
}
|
|
}
|
|
}
|
|
if (bangleTasks & JSBT_RESET) {
|
|
homeBtnInterruptTimer += pollInterval;
|
|
if (homeBtnInterruptTimer >= 500) {
|
|
// We already wanted to reset but we didn't get back to idle loop in
|
|
// 0.5 sec - let's force a break out of JS execution
|
|
jsiConsolePrintf("Button held down - interrupting JS execution...\n");
|
|
execInfo.execute |= EXEC_INTERRUPTED;
|
|
}
|
|
} else
|
|
homeBtnInterruptTimer = 0;
|
|
} else {
|
|
homeBtnTimer = 0;
|
|
homeBtnInterruptTimer = 0;
|
|
}
|
|
|
|
#ifdef LCD_CONTROLLER_LPM013M126
|
|
// toggle EXTCOMIN to avoid burn-in on LCD
|
|
if (bangleFlags & JSBF_LCD_ON)
|
|
lcdMemLCD_extcominToggle();
|
|
#endif
|
|
|
|
if (lcdPowerTimeout && (bangleFlags&JSBF_LCD_ON) && inactivityTimer>=lcdPowerTimeout) {
|
|
// 10 seconds of inactivity, turn off display
|
|
bangleTasks |= JSBT_LCD_OFF;
|
|
jshHadEvent();
|
|
}
|
|
if (backlightTimeout && (bangleFlags&JSBF_LCD_BL_ON) && inactivityTimer>=backlightTimeout) {
|
|
// 10 seconds of inactivity, turn off display
|
|
bangleTasks |= JSBT_LCD_BL_OFF;
|
|
jshHadEvent();
|
|
}
|
|
if (lockTimeout && !(bangleFlags&JSBF_LOCKED) && inactivityTimer>=lockTimeout) {
|
|
// 10 seconds of inactivity, lock display
|
|
bangleTasks |= JSBT_LOCK;
|
|
jshHadEvent();
|
|
}
|
|
|
|
|
|
// check charge status
|
|
bool isCharging = jswrap_banglejs_isCharging();
|
|
if (isCharging != wasCharging) {
|
|
wasCharging = isCharging;
|
|
bangleTasks |= JSBT_CHARGE_EVENT;
|
|
jshHadEvent();
|
|
}
|
|
if (i2cBusy) return;
|
|
i2cBusy = true;
|
|
unsigned char buf[7];
|
|
// check the magnetometer if we had it on
|
|
if (bangleFlags & JSBF_COMPASS_ON) {
|
|
bool newReading = false;
|
|
#ifdef MAG_DEVICE_GMC303
|
|
buf[0]=0x10;
|
|
jsi2cWrite(MAG_I2C, MAG_ADDR, 1, buf, false);
|
|
jsi2cRead(MAG_I2C, MAG_ADDR, 7, buf, true);
|
|
if (buf[0]&1) { // then we have data
|
|
mag.y = buf[1] | (buf[2]<<8);
|
|
mag.x = buf[3] | (buf[4]<<8);
|
|
mag.z = buf[5] | (buf[6]<<8);
|
|
newReading = true;
|
|
}
|
|
#endif
|
|
#ifdef MAG_DEVICE_UNKNOWN_0C
|
|
buf[0]=0x4E;
|
|
jsi2cWrite(MAG_I2C, MAG_ADDR, 1, buf, false);
|
|
jsi2cRead(MAG_I2C, MAG_ADDR, 7, buf, true);
|
|
if (!(buf[0]&16)) { // then we have data that wasn't read before
|
|
// &2 seems always set
|
|
// &16 seems set if we read twice
|
|
// &32 might be reading in progress
|
|
mag.y = buf[2] | (buf[1]<<8);
|
|
mag.x = buf[4] | (buf[3]<<8);
|
|
mag.z = buf[5] | (buf[5]<<8);
|
|
// Now read 0x3E which should kick off a new reading
|
|
buf[0]=0x3E;
|
|
jsi2cWrite(MAG_I2C, MAG_ADDR, 1, buf, false);
|
|
jsi2cRead(MAG_I2C, MAG_ADDR, 1, buf, true);
|
|
newReading = true;
|
|
}
|
|
#endif
|
|
if (newReading) {
|
|
if (mag.x<magmin.x) magmin.x=mag.x;
|
|
if (mag.y<magmin.y) magmin.y=mag.y;
|
|
if (mag.z<magmin.z) magmin.z=mag.z;
|
|
if (mag.x>magmax.x) magmax.x=mag.x;
|
|
if (mag.y>magmax.y) magmax.y=mag.y;
|
|
if (mag.z>magmax.z) magmax.z=mag.z;
|
|
bangleTasks |= JSBT_MAG_DATA;
|
|
jshHadEvent();
|
|
}
|
|
}
|
|
#ifdef ACCEL_I2C
|
|
#ifdef ACCEL_DEVICE_KX023
|
|
// poll KX023 accelerometer (no other way as IRQ line seems disconnected!)
|
|
// read interrupt source data
|
|
buf[0]=0x12; // INS1
|
|
jsi2cWrite(ACCEL_I2C, ACCEL_ADDR, 1, buf, false);
|
|
jsi2cRead(ACCEL_I2C, ACCEL_ADDR, 2, buf, true);
|
|
// 0 -> 0x12 INS1 - tap event
|
|
// 1 -> 0x13 INS2 - what kind of event
|
|
bool hasAccelData = (buf[1]&16)!=0; // DRDY
|
|
int tapType = (buf[1]>>2)&3; // TDTS0/1
|
|
if (tapType) {
|
|
tapInfo = buf[0] | (tapType<<6);
|
|
}
|
|
if (tapType) {
|
|
bool handled = false;
|
|
// wake on tap, for front (for Bangle.js 2)
|
|
#ifdef BANGLEJS_Q3
|
|
if ((bangleFlags&JSBF_WAKEON_TOUCH) && (tapInfo&2)) {
|
|
if (!(bangleFlags&JSBF_LCD_ON)) {
|
|
bangleTasks |= JSBT_LCD_ON;
|
|
handled = true;
|
|
}
|
|
if (!(bangleFlags&JSBF_LCD_BL_ON)) {
|
|
bangleTasks |= JSBT_LCD_BL_ON;
|
|
handled = true;
|
|
}
|
|
if (bangleFlags&JSBF_LOCKED) {
|
|
bangleTasks |= JSBT_UNLOCK;
|
|
handled = true;
|
|
}
|
|
}
|
|
#endif
|
|
// report tap
|
|
if (!handled)
|
|
bangleTasks |= JSBT_ACCEL_TAPPED;
|
|
jshHadEvent();
|
|
// clear the IRQ flags
|
|
buf[0]=0x17;
|
|
jsi2cWrite(ACCEL_I2C, ACCEL_ADDR, 1, buf, false);
|
|
jsi2cRead(ACCEL_I2C, ACCEL_ADDR, 1, buf, true);
|
|
}
|
|
#endif
|
|
#ifdef ACCEL_DEVICE_KXTJ3_1057
|
|
// read interrupt source data
|
|
buf[0]=0x16; // INT_SOURCE1
|
|
jsi2cWrite(ACCEL_I2C, ACCEL_ADDR, 1, buf, false);
|
|
jsi2cRead(ACCEL_I2C, ACCEL_ADDR, 1, buf, true);
|
|
bool hasAccelData = (buf[0]&16)!=0; // DRDY
|
|
#endif
|
|
#ifdef ACCEL_DEVICE_KX126
|
|
// read interrupt source data (INS1 and INS2 registers)
|
|
buf[0]=KX126_INS1;
|
|
jsi2cWrite(ACCEL_I2C, ACCEL_ADDR, 1, buf, false);
|
|
jsi2cRead(ACCEL_I2C, ACCEL_ADDR, 2, buf, true);
|
|
// 0 -> INS1 - step counter & tap events
|
|
// 1 -> INS2 - what kind of event
|
|
bool hasAccelData = (buf[1] & KX126_INS2_DRDY)!=0; // Is new data ready?
|
|
int tapType = (buf[1]>>2)&3; // TDTS0/1
|
|
if (tapType) {
|
|
// report tap
|
|
tapInfo = buf[0] | (tapType<<6);
|
|
bangleTasks |= JSBT_ACCEL_TAPPED;
|
|
jshHadEvent();
|
|
}
|
|
// clear the IRQ flags
|
|
buf[0]=KX126_INT_REL;
|
|
jsi2cWrite(ACCEL_I2C, ACCEL_ADDR, 1, buf, false);
|
|
jsi2cRead(ACCEL_I2C, ACCEL_ADDR, 1, buf, true);
|
|
#endif
|
|
if (hasAccelData) {
|
|
#ifdef ACCEL_DEVICE_KX126
|
|
buf[0]=KX126_XOUT_L;
|
|
jsi2cWrite(ACCEL_I2C, ACCEL_ADDR, 1, buf, false);
|
|
#else
|
|
buf[0]=6;
|
|
jsi2cWrite(ACCEL_I2C, ACCEL_ADDR, 1, buf, false);
|
|
#endif
|
|
jsi2cRead(ACCEL_I2C, ACCEL_ADDR, 6, buf, true);
|
|
// work out current reading in 16 bit
|
|
short newx = (buf[1]<<8)|buf[0];
|
|
short newy = (buf[3]<<8)|buf[2];
|
|
short newz = (buf[5]<<8)|buf[4];
|
|
#ifdef BANGLEJS_Q3
|
|
newx = -newx; //consistent directions with Bangle
|
|
newz = -newz;
|
|
#endif
|
|
#ifdef ACCEL_DEVICE_KX126
|
|
newy = -newy;
|
|
#endif
|
|
int dx = newx-acc.x;
|
|
int dy = newy-acc.y;
|
|
int dz = newz-acc.z;
|
|
acc.x = newx;
|
|
acc.y = newy;
|
|
acc.z = newz;
|
|
accMagSquared = acc.x*acc.x + acc.y*acc.y + acc.z*acc.z;
|
|
accDiff = int_sqrt32(dx*dx + dy*dy + dz*dz);
|
|
// save history
|
|
accHistoryIdx = (accHistoryIdx+3) % sizeof(accHistory);
|
|
accHistory[accHistoryIdx ] = clipi8(newx>>7);
|
|
accHistory[accHistoryIdx+1] = clipi8(newy>>7);
|
|
accHistory[accHistoryIdx+2] = clipi8(newz>>7);
|
|
// Power saving
|
|
if (bangleFlags & JSBF_POWER_SAVE) {
|
|
if (accDiff > POWER_SAVE_MIN_ACCEL) {
|
|
powerSaveTimer = 0;
|
|
if (pollInterval == POWER_SAVE_ACCEL_POLL_INTERVAL) {
|
|
bangleTasks |= JSBT_ACCEL_INTERVAL_DEFAULT;
|
|
jshHadEvent();
|
|
}
|
|
} else {
|
|
if (powerSaveTimer < TIMER_MAX)
|
|
powerSaveTimer += pollInterval;
|
|
if (powerSaveTimer >= POWER_SAVE_TIMEOUT && // stationary for POWER_SAVE_TIMEOUT
|
|
pollInterval == DEFAULT_ACCEL_POLL_INTERVAL && // we are in high power mode
|
|
!(bangleFlags & JSBF_ACCEL_LISTENER) && // nothing was listening to accelerometer data
|
|
#ifdef PRESSURE_DEVICE
|
|
!(bangleFlags & JSBF_BAROMETER_ON) && // barometer isn't on (streaming uses peripheralPollHandler)
|
|
#endif
|
|
#ifdef MAG_I2C
|
|
!(bangleFlags & JSBF_COMPASS_ON) && // compass isn't on (streaming uses peripheralPollHandler)
|
|
#endif
|
|
true) {
|
|
bangleTasks |= JSBT_ACCEL_INTERVAL_POWERSAVE;
|
|
jshHadEvent();
|
|
}
|
|
}
|
|
}
|
|
// trigger accelerometer data task if needed
|
|
if (bangleFlags & JSBF_ACCEL_LISTENER) {
|
|
bangleTasks |= JSBT_ACCEL_DATA;
|
|
jshHadEvent();
|
|
}
|
|
// check for 'face up'
|
|
faceUp = (acc.z<-6700) && (acc.z>-9000) && abs(acc.x)<2048 && abs(acc.y)<2048;
|
|
if (faceUp!=wasFaceUp) {
|
|
faceUpTimer = 0;
|
|
faceUpSent = false;
|
|
wasFaceUp = faceUp;
|
|
}
|
|
if (faceUpTimer<TIMER_MAX) faceUpTimer += pollInterval;
|
|
if (faceUpTimer>=300 && !faceUpSent) {
|
|
faceUpSent = true;
|
|
bangleTasks |= JSBT_FACE_UP;
|
|
jshHadEvent();
|
|
}
|
|
// Step counter
|
|
if (bangleTasks & JSBT_ACCEL_INTERVAL_DEFAULT) {
|
|
// we've come out of powersave, reset the algorithm
|
|
stepcount_init();
|
|
}
|
|
if (powerSaveTimer < POWER_SAVE_TIMEOUT) {
|
|
// only do step counting if power save is off (otherwise accel interval is too low - also wastes power)
|
|
int newSteps = stepcount_new(accMagSquared);
|
|
if (newSteps>0) {
|
|
stepCounter += newSteps;
|
|
healthCurrent.stepCount += newSteps;
|
|
healthDaily.stepCount += newSteps;
|
|
bangleTasks |= JSBT_STEP_EVENT;
|
|
jshHadEvent();
|
|
}
|
|
}
|
|
// check for twist action
|
|
if (twistTimer < TIMER_MAX)
|
|
twistTimer += pollInterval;
|
|
int tdy = dy;
|
|
int tthresh = twistThreshold;
|
|
if (tthresh<0) {
|
|
tthresh = -tthresh;
|
|
tdy = -tdy;
|
|
}
|
|
if (tdy>tthresh) twistTimer=0;
|
|
if (tdy<-tthresh && twistTimer<twistTimeout && acc.y<twistMaxY) {
|
|
twistTimer = TIMER_MAX; // ensure we don't trigger again until tdy>tthresh
|
|
bangleTasks |= JSBT_TWIST_EVENT;
|
|
jshHadEvent();
|
|
if (bangleFlags&JSBF_WAKEON_TWIST) {
|
|
inactivityTimer = 0;
|
|
if (!(bangleFlags&JSBF_LCD_ON))
|
|
bangleTasks |= JSBT_LCD_ON;
|
|
if (!(bangleFlags&JSBF_LCD_BL_ON))
|
|
bangleTasks |= JSBT_LCD_BL_ON;
|
|
if (bangleFlags&JSBF_LOCKED)
|
|
bangleTasks |= JSBT_UNLOCK;
|
|
}
|
|
}
|
|
|
|
// checking for gestures
|
|
if (accGestureCount==0) { // no gesture yet
|
|
// if movement is eniugh, start one
|
|
if (accDiff > accelGestureStartThresh) {
|
|
accIdleCount = 0;
|
|
accGestureCount = 1;
|
|
}
|
|
} else { // we're recording a gesture
|
|
// keep incrementing gesture size
|
|
if (accGestureCount < 255)
|
|
accGestureCount++;
|
|
// if idle for long enough...
|
|
if (accDiff < accelGestureEndThresh) {
|
|
if (accIdleCount<255) accIdleCount++;
|
|
if (accIdleCount==accelGestureInactiveCount) {
|
|
// inactive for long enough for a gesture, but not too long
|
|
accGestureRecordedCount = accGestureCount;
|
|
if ((accGestureCount >= accelGestureMinLength) &&
|
|
(accGestureCount < ACCEL_HISTORY_LEN)) {
|
|
bangleTasks |= JSBT_GESTURE_DATA; // trigger a gesture task
|
|
jshHadEvent();
|
|
}
|
|
accGestureCount = 0; // stop the gesture
|
|
}
|
|
} else if (accIdleCount < accelGestureInactiveCount)
|
|
accIdleCount = 0; // it was inactive but not long enough to trigger a gesture
|
|
}
|
|
}
|
|
|
|
#endif
|
|
#ifdef PRESSURE_DEVICE
|
|
if (bangleFlags & JSBF_BAROMETER_ON) {
|
|
if (jswrap_banglejs_barometerPoll()) {
|
|
bangleTasks |= JSBT_PRESSURE_DATA;
|
|
jshHadEvent();
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// Health tracking + midnight event
|
|
// Did we enter a new 10 minute interval?
|
|
JsVarFloat msecs = jshGetMillisecondsFromTime(time);
|
|
uint8_t healthIndex = (uint8_t)(msecs/HEALTH_INTERVAL);
|
|
if (healthIndex != healthCurrent.index) {
|
|
// we did - fire 'Bangle.health' event
|
|
healthLast = healthCurrent;
|
|
healthStateClear(&healthCurrent);
|
|
healthCurrent.index = healthIndex;
|
|
bangleTasks |= JSBT_HEALTH;
|
|
jshHadEvent();
|
|
// What if we've changed day?
|
|
TimeInDay td = getTimeFromMilliSeconds(msecs, false/*forceGMT*/);
|
|
uint8_t dayIndex = (uint8_t)td.daysSinceEpoch;
|
|
if (dayIndex != healthDaily.index) {
|
|
bangleTasks |= JSBT_MIDNIGHT;
|
|
healthStateClear(&healthDaily);
|
|
healthDaily.index = dayIndex;
|
|
}
|
|
}
|
|
// Update latest health info
|
|
healthCurrent.movement += accDiff;
|
|
healthCurrent.movementSamples++;
|
|
healthDaily.movement += accDiff;
|
|
healthDaily.movementSamples++;
|
|
|
|
// we're done, ensure we clear I2C flag
|
|
i2cBusy = false;
|
|
}
|
|
|
|
#ifdef HEARTRATE
|
|
static void hrmHandler(int ppgValue) {
|
|
if (hrm_new(ppgValue)) {
|
|
bangleTasks |= JSBT_HRM_DATA;
|
|
// keep track of best HRM sample during this period
|
|
if (hrmInfo.confidence >= healthCurrent.bpmConfidence) {
|
|
healthCurrent.bpmConfidence = hrmInfo.confidence;
|
|
healthCurrent.bpm10 = hrmInfo.bpm10;
|
|
}
|
|
if (hrmInfo.confidence >= healthDaily.bpmConfidence) {
|
|
healthDaily.bpmConfidence = hrmInfo.confidence;
|
|
healthDaily.bpm10 = hrmInfo.bpm10;
|
|
}
|
|
jshHadEvent();
|
|
}
|
|
if (bangleFlags & JSBF_HRM_INSTANT_LISTENER) {
|
|
bangleTasks |= JSBT_HRM_INSTANT_DATA;
|
|
jshHadEvent();
|
|
}
|
|
}
|
|
#endif // HEARTRATE
|
|
|
|
#ifdef BANGLEJS_F18
|
|
void backlightOnHandler() {
|
|
if (i2cBusy) return;
|
|
jswrap_banglejs_pwrBacklight(true); // backlight on
|
|
app_timer_start(m_backlight_off_timer_id, APP_TIMER_TICKS(BACKLIGHT_PWM_INTERVAL, APP_TIMER_PRESCALER) * lcdBrightness >> 8, NULL);
|
|
}
|
|
void backlightOffHandler() {
|
|
if (i2cBusy) return;
|
|
jswrap_banglejs_pwrBacklight(false); // backlight off
|
|
}
|
|
#endif // BANGLEJS_F18
|
|
#endif // !EMULATED
|
|
|
|
void btnHandlerCommon(int button, bool state, IOEventFlags flags) {
|
|
// wake up IF LCD power or Lock has a timeout (so will turn off automatically)
|
|
if (lcdPowerTimeout || backlightTimeout || lockTimeout) {
|
|
if (((bangleFlags&JSBF_WAKEON_BTN1)&&(button==1)) ||
|
|
((bangleFlags&JSBF_WAKEON_BTN2)&&(button==2)) ||
|
|
((bangleFlags&JSBF_WAKEON_BTN3)&&(button==3)) ||
|
|
#ifdef DICKENS
|
|
((bangleFlags&JSBF_WAKEON_BTN3)&&(button==4)) ||
|
|
#endif
|
|
false){
|
|
// if a 'hard' button, turn LCD on
|
|
inactivityTimer = 0;
|
|
if (state) {
|
|
bool ignoreBtnUp = false;
|
|
if (lcdPowerTimeout && !(bangleFlags&JSBF_LCD_ON) && state) {
|
|
bangleTasks |= JSBT_LCD_ON;
|
|
ignoreBtnUp = true;
|
|
}
|
|
if (backlightTimeout && !(bangleFlags&JSBF_LCD_BL_ON) && state) {
|
|
bangleTasks |= JSBT_LCD_BL_ON;
|
|
ignoreBtnUp = true;
|
|
}
|
|
if (lockTimeout && (bangleFlags&JSBF_LOCKED) && state) {
|
|
bangleTasks |= JSBT_UNLOCK;
|
|
ignoreBtnUp = true;
|
|
}
|
|
if (ignoreBtnUp) {
|
|
// This allows us to ignore subsequent button
|
|
// rising or 'bounce' events
|
|
lcdWakeButton = button;
|
|
lcdWakeButtonTime = jshGetSystemTime() + jshGetTimeFromMilliseconds(100);
|
|
return; // don't push button event if the LCD is off
|
|
}
|
|
}
|
|
} else {
|
|
// on touchscreen, keep LCD on if it was in previously
|
|
if (bangleFlags&JSBF_LCD_ON)
|
|
inactivityTimer = 0;
|
|
else // else don't push the event
|
|
return;
|
|
}
|
|
}
|
|
// Handle case where pressing 'home' button repeatedly at just the wrong times
|
|
// could cause us to go home!
|
|
if (button == HOME_BTN) homeBtnTimer = 0;
|
|
/* This stops the button 'up' or bounces from being
|
|
propagated if the button was used to wake the LCD up */
|
|
JsSysTime t = jshGetSystemTime();
|
|
if (button == lcdWakeButton) {
|
|
if ((t < lcdWakeButtonTime) || !state) {
|
|
/* If it's a rising edge *or* it's within our debounce
|
|
* period, reset the debounce timer and ignore it */
|
|
lcdWakeButtonTime = t + jshGetTimeFromMilliseconds(100);
|
|
return;
|
|
} else {
|
|
/* if the next event is a 'down', > 100ms after the last event, we propogate it
|
|
and subsequent events */
|
|
lcdWakeButton = 0;
|
|
lcdWakeButtonTime = 0;
|
|
}
|
|
}
|
|
// if not locked, add to the event queue for normal processing for watches
|
|
if (!(bangleFlags&JSBF_LOCKED))
|
|
jshPushIOEvent(flags | (state?EV_EXTI_IS_HIGH:0), t);
|
|
}
|
|
|
|
#if defined(BANGLEJS_F18)
|
|
// returns true if handled and shouldn't create a normal watch event
|
|
bool btnTouchHandler() {
|
|
if (bangleFlags&JSBF_WAKEON_TOUCH) {
|
|
inactivityTimer = 0; // ensure LCD doesn't sleep if we're touching it
|
|
bool eventUsed = false;
|
|
if (!(bangleFlags&JSBF_LCD_ON)) {
|
|
bangleTasks |= JSBT_LCD_ON;
|
|
eventUsed = true; // eat the event
|
|
}
|
|
if (bangleFlags&JSBF_LOCKED) {
|
|
bangleTasks |= JSBT_UNLOCK;
|
|
eventUsed = true;
|
|
}
|
|
if (eventUsed) return true; // eat the event
|
|
}
|
|
// if locked, ignore touch/swipe
|
|
if (bangleFlags&JSBF_LOCKED) {
|
|
touchLastState = touchLastState2 = touchStatus = TS_NONE;
|
|
return false;
|
|
}
|
|
// Detect touch/swipe
|
|
TouchState state =
|
|
(jshPinGetValue(BTN4_PININDEX)?TS_LEFT:0) |
|
|
(jshPinGetValue(BTN5_PININDEX)?TS_RIGHT:0);
|
|
touchStatus |= state;
|
|
if ((touchLastState2==TS_RIGHT && touchLastState==TS_BOTH && state==TS_LEFT) ||
|
|
(touchLastState==TS_RIGHT && state==1)) {
|
|
touchStatus |= TS_SWIPED;
|
|
touchGesture = TG_SWIPE_LEFT;
|
|
bangleTasks |= JSBT_SWIPE;
|
|
}
|
|
if ((touchLastState2==TS_LEFT && touchLastState==TS_BOTH && state==TS_RIGHT) ||
|
|
(touchLastState==TS_LEFT && state==TS_RIGHT)) {
|
|
touchStatus |= TS_SWIPED;
|
|
touchGesture = TG_SWIPE_RIGHT;
|
|
bangleTasks |= JSBT_SWIPE;
|
|
}
|
|
if (!state) {
|
|
if (touchLastState && !(touchStatus&TS_SWIPED)) {
|
|
if (touchStatus&TS_LEFT) bangleTasks |= JSBT_TOUCH_LEFT;
|
|
if (touchStatus&TS_RIGHT) bangleTasks |= JSBT_TOUCH_RIGHT;
|
|
}
|
|
touchStatus = TS_NONE;
|
|
}
|
|
touchLastState2 = touchLastState;
|
|
touchLastState = state;
|
|
return false;
|
|
}
|
|
#endif
|
|
void btn1Handler(bool state, IOEventFlags flags) {
|
|
btnHandlerCommon(1,state,flags);
|
|
}
|
|
#ifdef BTN2_PININDEX
|
|
void btn2Handler(bool state, IOEventFlags flags) {
|
|
btnHandlerCommon(2,state,flags);
|
|
}
|
|
#endif
|
|
#ifdef BTN3_PININDEX
|
|
void btn3Handler(bool state, IOEventFlags flags) {
|
|
btnHandlerCommon(3,state,flags);
|
|
}
|
|
#endif
|
|
#if defined(BANGLEJS_F18)
|
|
void btn4Handler(bool state, IOEventFlags flags) {
|
|
if (btnTouchHandler()) return;
|
|
btnHandlerCommon(4,state,flags);
|
|
}
|
|
void btn5Handler(bool state, IOEventFlags flags) {
|
|
if (btnTouchHandler()) return;
|
|
btnHandlerCommon(5,state,flags);
|
|
}
|
|
#else
|
|
void btn4Handler(bool state, IOEventFlags flags) {
|
|
btnHandlerCommon(4,state,flags);
|
|
}
|
|
#endif
|
|
|
|
#ifdef TOUCH_DEVICE // so it's available even in emulator
|
|
|
|
// Convert Touchscreen gesture based on graphics orientation
|
|
TouchGestureType touchSwipeRotate(TouchGestureType g) {
|
|
// gesture is the value that comes straight from the touchscreen
|
|
if (graphicsInternal.data.flags & JSGRAPHICSFLAGS_INVERT_X) {
|
|
if (g==TG_SWIPE_LEFT) g=TG_SWIPE_RIGHT;
|
|
else if (g==TG_SWIPE_RIGHT) g=TG_SWIPE_LEFT;
|
|
}
|
|
if (graphicsInternal.data.flags & JSGRAPHICSFLAGS_INVERT_Y) {
|
|
if (g==TG_SWIPE_UP) g=TG_SWIPE_DOWN;
|
|
else if (g==TG_SWIPE_DOWN) g=TG_SWIPE_UP;
|
|
}
|
|
if (graphicsInternal.data.flags & JSGRAPHICSFLAGS_SWAP_XY) {
|
|
if (g==TG_SWIPE_LEFT) g=TG_SWIPE_UP;
|
|
else if (g==TG_SWIPE_RIGHT) g=TG_SWIPE_DOWN;
|
|
else if (g==TG_SWIPE_UP) g=TG_SWIPE_LEFT;
|
|
else if (g==TG_SWIPE_DOWN) g=TG_SWIPE_RIGHT;
|
|
}
|
|
return g;
|
|
}
|
|
|
|
void touchHandlerInternal(int tx, int ty, int pts, int gesture) {
|
|
// ignore if locked
|
|
if (bangleFlags & JSBF_LOCKED) return;
|
|
// deal with the case where we rotated the Bangle.js screen
|
|
deviceToGraphicsCoordinates(&graphicsInternal, &tx, &ty);
|
|
|
|
int dx = tx-touchX;
|
|
int dy = ty-touchY;
|
|
|
|
touchX = tx;
|
|
touchY = ty;
|
|
touchPts = pts;
|
|
static int lastGesture = 0;
|
|
if (gesture!=lastGesture) {
|
|
switch (gesture) { // gesture
|
|
case 0:break; // no gesture
|
|
case 1: // slide down
|
|
touchGesture = touchSwipeRotate(TG_SWIPE_DOWN);
|
|
bangleTasks |= JSBT_SWIPE;
|
|
break;
|
|
case 2: // slide up
|
|
touchGesture = touchSwipeRotate(TG_SWIPE_UP);
|
|
bangleTasks |= JSBT_SWIPE;
|
|
break;
|
|
case 3: // slide left
|
|
touchGesture = touchSwipeRotate(TG_SWIPE_LEFT);
|
|
bangleTasks |= JSBT_SWIPE;
|
|
break;
|
|
case 4: // slide right
|
|
touchGesture = touchSwipeRotate(TG_SWIPE_RIGHT);
|
|
bangleTasks |= JSBT_SWIPE;
|
|
break;
|
|
case 5: // single click
|
|
if (touchX<80) bangleTasks |= JSBT_TOUCH_LEFT;
|
|
else bangleTasks |= JSBT_TOUCH_RIGHT;
|
|
touchType = 0;
|
|
break;
|
|
case 0x0B: // double touch
|
|
if (touchX<80) bangleTasks |= JSBT_TOUCH_LEFT;
|
|
else bangleTasks |= JSBT_TOUCH_RIGHT;
|
|
touchType = 1;
|
|
break;
|
|
case 0x0C: // long touch
|
|
if (touchX<80) bangleTasks |= JSBT_TOUCH_LEFT;
|
|
else bangleTasks |= JSBT_TOUCH_RIGHT;
|
|
touchType = 2;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (touchPts!=lastTouchPts || lastTouchX!=touchX || lastTouchY!=touchY) {
|
|
bangleTasks |= JSBT_DRAG;
|
|
// ensure we don't sleep if touchscreen is being used
|
|
inactivityTimer = 0;
|
|
#if ESPR_BANGLE_UNISTROKE
|
|
if (unistroke_touch(touchX, touchY, dx, dy, touchPts)) {
|
|
bangleTasks |= JSBT_STROKE;
|
|
}
|
|
#endif
|
|
jshHadEvent();
|
|
}
|
|
|
|
lastGesture = gesture;
|
|
}
|
|
#endif
|
|
#ifdef TOUCH_I2C
|
|
void touchHandler(bool state, IOEventFlags flags) {
|
|
if (state) return; // only interested in when low
|
|
// Ok, now get touch info
|
|
unsigned char buf[6];
|
|
buf[0]=1;
|
|
jsi2cWrite(TOUCH_I2C, TOUCH_ADDR, 1, buf, false);
|
|
jsi2cRead(TOUCH_I2C, TOUCH_ADDR, 6, buf, true);
|
|
|
|
// 0: Gesture type
|
|
// 1: touch pts (0 or 1)
|
|
// 2: Status / X hi (0x00 first, 0x80 pressed, 0x40 released)
|
|
// 3: X lo (0..160)
|
|
// 4: Y hi
|
|
// 5: Y lo (0..160)
|
|
|
|
int tx = buf[3]/* | ((buf[2] & 0x0F)<<8)*/; // top bits are never used on our touchscreen
|
|
int ty = buf[5]/* | ((buf[4] & 0x0F)<<8)*/;
|
|
if (tx>=250) tx=0; // on some devices, 251-255 gets reported for touches right at the top of the screen
|
|
if (ty>=250) ty=0;
|
|
touchHandlerInternal(
|
|
(tx-touchMinX) * LCD_WIDTH / (touchMaxX-touchMinX), // touchX
|
|
(ty-touchMinY) * LCD_HEIGHT / (touchMaxY-touchMinY), // touchY
|
|
buf[1], // touchPts
|
|
buf[0]); // gesture
|
|
}
|
|
#endif
|
|
|
|
|
|
static void jswrap_banglejs_setLCDPowerController(bool isOn) {
|
|
#ifdef LCD_CONTROLLER_LPM013M126
|
|
jshPinOutput(LCD_EXTCOMIN, 0);
|
|
jshPinOutput(LCD_DISP, isOn); // enable
|
|
#endif
|
|
#ifdef LCD_CONTROLLER_ST7789_8BIT
|
|
if (isOn) { // wake
|
|
lcdST7789_cmd(0x11, 0, NULL); // SLPOUT
|
|
jshDelayMicroseconds(20);
|
|
lcdST7789_cmd(0x29, 0, NULL); // DISPON
|
|
} else { // sleep
|
|
lcdST7789_cmd(0x28, 0, NULL); // DISPOFF
|
|
jshDelayMicroseconds(20);
|
|
lcdST7789_cmd(0x10, 0, NULL); // SLPIN
|
|
}
|
|
#endif
|
|
#if defined(LCD_CONTROLLER_ST7789V) || defined(LCD_CONTROLLER_ST7735) || defined(LCD_CONTROLLER_GC9A01)
|
|
// TODO: LCD_CONTROLLER_GC9A01 - has an enable/power pin
|
|
if (isOn) { // wake
|
|
lcdCmd_SPILCD(0x11, 0, NULL); // SLPOUT
|
|
jshDelayMicroseconds(20);
|
|
lcdCmd_SPILCD(0x29, 0, NULL); // DISPON
|
|
} else { // sleep
|
|
lcdCmd_SPILCD(0x28, 0, NULL); // DISPOFF
|
|
jshDelayMicroseconds(20);
|
|
lcdCmd_SPILCD(0x10, 0, NULL); // SLPIN
|
|
}
|
|
#endif
|
|
#ifdef LCD_EN
|
|
jshPinOutput(LCD_EN,isOn); // enable off
|
|
#endif
|
|
}
|
|
|
|
#ifdef ESPR_BACKLIGHT_FADE
|
|
static void backlightFadeHandler() {
|
|
int target = (bangleFlags&JSBF_LCD_ON) ? lcdBrightness : 0;
|
|
int brightness = realLcdBrightness;
|
|
int step = brightness>>3; // to make this more linear
|
|
if (step<4) step=4;
|
|
if (target > brightness) {
|
|
brightness += step;
|
|
if (brightness > target)
|
|
brightness = target;
|
|
} else if (target < brightness) {
|
|
brightness -= step;
|
|
if (brightness < target)
|
|
brightness = target;
|
|
}
|
|
realLcdBrightness = brightness;
|
|
if (brightness==0) jswrap_banglejs_pwrBacklight(0);
|
|
else if (realLcdBrightness==255) jswrap_banglejs_pwrBacklight(1);
|
|
else {
|
|
jshPinAnalogOutput(LCD_BL, realLcdBrightness/256.0, 200, JSAOF_NONE);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/// Turn just the backlight on or off (or adjust brightness)
|
|
static void jswrap_banglejs_setLCDPowerBacklight(bool isOn) {
|
|
if (isOn) bangleFlags |= JSBF_LCD_BL_ON;
|
|
else bangleFlags &= ~JSBF_LCD_BL_ON;
|
|
#ifndef EMULATED
|
|
#ifdef BANGLEJS_F18
|
|
app_timer_stop(m_backlight_on_timer_id);
|
|
app_timer_stop(m_backlight_off_timer_id);
|
|
if (isOn) { // wake
|
|
if (lcdBrightness > 0) {
|
|
if (lcdBrightness < 255) { // only do PWM if brightness isn't full
|
|
app_timer_start(m_backlight_on_timer_id, APP_TIMER_TICKS(BACKLIGHT_PWM_INTERVAL, APP_TIMER_PRESCALER), NULL);
|
|
} else // full brightness
|
|
jswrap_banglejs_pwrBacklight(true); // backlight on
|
|
} else { // lcdBrightness == 0
|
|
jswrap_banglejs_pwrBacklight(false); // backlight off
|
|
}
|
|
} else { // sleep
|
|
jswrap_banglejs_pwrBacklight(false); // backlight off
|
|
}
|
|
#elif defined(ESPR_BACKLIGHT_FADE)
|
|
if (!lcdFadeHandlerActive) {
|
|
JsSysTime t = jshGetTimeFromMilliseconds(10);
|
|
jstExecuteFn(backlightFadeHandler, NULL, t, t, NULL);
|
|
lcdFadeHandlerActive = true;
|
|
backlightFadeHandler();
|
|
}
|
|
#else
|
|
jswrap_banglejs_pwrBacklight(isOn && (lcdBrightness>0));
|
|
#ifdef LCD_BL
|
|
if (isOn && lcdBrightness > 0 && lcdBrightness < 255) {
|
|
jshPinAnalogOutput(LCD_BL, lcdBrightness/256.0, 200, JSAOF_NONE);
|
|
}
|
|
#endif // LCD_BL
|
|
#endif
|
|
#endif // !EMULATED
|
|
}
|
|
|
|
|
|
|
|
/*JSON{
|
|
"type" : "staticmethod",
|
|
"class" : "Bangle",
|
|
"name" : "setLCDPower",
|
|
"generate" : "jswrap_banglejs_setLCDPower",
|
|
"params" : [
|
|
["isOn","bool","True if the LCD should be on, false if not"]
|
|
],
|
|
"ifdef" : "BANGLEJS"
|
|
}
|
|
This function can be used to turn Bangle.js's LCD off or on.
|
|
|
|
This function resets the Bangle's 'activity timer' (like pressing a button or
|
|
the screen would) so after a time period of inactivity set by
|
|
`Bangle.setLCDTimeout` the screen will turn off.
|
|
|
|
If you want to keep the screen on permanently (until apps are changed) you can
|
|
do:
|
|
|
|
```
|
|
Bangle.setLCDTimeout(0); // turn off the timeout
|
|
Bangle.setLCDPower(1); // keep screen on
|
|
```
|
|
|
|
|
|
**When on full, the LCD draws roughly 40mA.** You can adjust When brightness
|
|
using `Bangle.setLCDBrightness`.
|
|
*/
|
|
void jswrap_banglejs_setLCDPower(bool isOn) {
|
|
#ifdef ESPR_BACKLIGHT_FADE
|
|
if (isOn) jswrap_banglejs_setLCDPowerController(1);
|
|
else jswrap_banglejs_setLCDPowerBacklight(0); // RB: don't turn on the backlight here if fading is enabled
|
|
jswrap_banglejs_setLCDPowerBacklight(isOn);
|
|
#else
|
|
jswrap_banglejs_setLCDPowerController(isOn);
|
|
jswrap_banglejs_setLCDPowerBacklight(isOn);
|
|
#endif
|
|
if (((bangleFlags&JSBF_LCD_ON)!=0) != isOn) {
|
|
JsVar *bangle =jsvObjectGetChild(execInfo.root, "Bangle", 0);
|
|
if (bangle) {
|
|
JsVar *v = jsvNewFromBool(isOn);
|
|
jsiQueueObjectCallbacks(bangle, JS_EVENT_PREFIX"lcdPower", &v, 1);
|
|
jsvUnLock(v);
|
|
}
|
|
jsvUnLock(bangle);
|
|
}
|
|
inactivityTimer = 0;
|
|
if (isOn) bangleFlags |= JSBF_LCD_ON;
|
|
else bangleFlags &= ~JSBF_LCD_ON;
|
|
}
|
|
|
|
|
|
|
|
|
|
/*JSON{
|
|
"type" : "staticmethod",
|
|
"class" : "Bangle",
|
|
"name" : "setLCDBrightness",
|
|
"generate" : "jswrap_banglejs_setLCDBrightness",
|
|
"params" : [
|
|
["brightness","float","The brightness of Bangle.js's display - from 0(off) to 1(on full)"]
|
|
],
|
|
"ifdef" : "BANGLEJS"
|
|
}
|
|
This function can be used to adjust the brightness of Bangle.js's display, and
|
|
hence prolong its battery life.
|
|
|
|
Due to hardware design constraints, software PWM has to be used which means that
|
|
the display may flicker slightly when Bluetooth is active and the display is not
|
|
at full power.
|
|
|
|
**Power consumption**
|
|
|
|
* 0 = 7mA
|
|
* 0.1 = 12mA
|
|
* 0.2 = 18mA
|
|
* 0.5 = 28mA
|
|
* 0.9 = 40mA (switching overhead)
|
|
* 1 = 40mA
|
|
*/
|
|
void jswrap_banglejs_setLCDBrightness(JsVarFloat v) {
|
|
int b = (int)(v*256 + 0.5);
|
|
if (b<0) b=0;
|
|
if (b>255) b=255;
|
|
lcdBrightness = b;
|
|
if (bangleFlags&JSBF_LCD_ON) // need to re-run to adjust brightness
|
|
jswrap_banglejs_setLCDPowerBacklight(1);
|
|
}
|
|
|
|
/*TYPESCRIPT
|
|
type LCDMode =
|
|
| "direct"
|
|
| "doublebuffered"
|
|
| "120x120"
|
|
| "80x80"
|
|
*/
|
|
/*JSON{
|
|
"type" : "staticmethod",
|
|
"class" : "Bangle",
|
|
"name" : "setLCDMode",
|
|
"generate" : "jswrap_banglejs_setLCDMode",
|
|
"params" : [
|
|
["mode","JsVar","The LCD mode (See below)"]
|
|
],
|
|
"ifdef" : "BANGLEJS",
|
|
"typescript" : "setLCDMode(mode?: LCDMode): void;"
|
|
}
|
|
This function can be used to change the way graphics is handled on Bangle.js.
|
|
|
|
Available options for `Bangle.setLCDMode` are:
|
|
|
|
* `Bangle.setLCDMode()` or `Bangle.setLCDMode("direct")` (the default) - The
|
|
drawable area is 240x240 16 bit. Unbuffered, so draw calls take effect
|
|
immediately. Terminal and vertical scrolling work (horizontal scrolling
|
|
doesn't).
|
|
* `Bangle.setLCDMode("doublebuffered")` - The drawable area is 240x160 16 bit,
|
|
terminal and scrolling will not work. `g.flip()` must be called for draw
|
|
operations to take effect.
|
|
* `Bangle.setLCDMode("120x120")` - The drawable area is 120x120 8 bit,
|
|
`g.getPixel`, terminal, and full scrolling work. Uses an offscreen buffer
|
|
stored on Bangle.js, `g.flip()` must be called for draw operations to take
|
|
effect.
|
|
* `Bangle.setLCDMode("80x80")` - The drawable area is 80x80 8 bit, `g.getPixel`,
|
|
terminal, and full scrolling work. Uses an offscreen buffer stored on
|
|
Bangle.js, `g.flip()` must be called for draw operations to take effect.
|
|
|
|
You can also call `Bangle.setLCDMode()` to return to normal, unbuffered
|
|
`"direct"` mode.
|
|
*/
|
|
void jswrap_banglejs_setLCDMode(JsVar *mode) {
|
|
#ifdef LCD_CONTROLLER_ST7789_8BIT
|
|
LCDST7789Mode lcdMode = LCDST7789_MODE_UNBUFFERED;
|
|
if (jsvIsUndefined(mode) || jsvIsStringEqual(mode,"direct"))
|
|
lcdMode = LCDST7789_MODE_UNBUFFERED;
|
|
else if (jsvIsStringEqual(mode,"null"))
|
|
lcdMode = LCDST7789_MODE_NULL;
|
|
else if (jsvIsStringEqual(mode,"doublebuffered"))
|
|
lcdMode = LCDST7789_MODE_DOUBLEBUFFERED;
|
|
else if (jsvIsStringEqual(mode,"120x120"))
|
|
lcdMode = LCDST7789_MODE_BUFFER_120x120;
|
|
else if (jsvIsStringEqual(mode,"80x80"))
|
|
lcdMode = LCDST7789_MODE_BUFFER_80x80;
|
|
else
|
|
jsExceptionHere(JSET_ERROR,"Unknown LCD Mode %j",mode);
|
|
|
|
JsVar *graphics = jsvObjectGetChild(execInfo.hiddenRoot, JS_GRAPHICS_VAR, 0);
|
|
if (!graphics) return;
|
|
jswrap_graphics_setFont(graphics, NULL, 1); // reset fonts - this will free any memory associated with a custom font
|
|
// remove the buffer if it was defined
|
|
jsvObjectSetOrRemoveChild(graphics, "buffer", 0);
|
|
unsigned int bufferSize = 0;
|
|
switch (lcdMode) {
|
|
case LCDST7789_MODE_NULL:
|
|
case LCDST7789_MODE_UNBUFFERED:
|
|
graphicsInternal.data.width = LCD_WIDTH;
|
|
graphicsInternal.data.height = LCD_HEIGHT;
|
|
graphicsInternal.data.bpp = 16;
|
|
break;
|
|
case LCDST7789_MODE_DOUBLEBUFFERED:
|
|
graphicsInternal.data.width = LCD_WIDTH;
|
|
graphicsInternal.data.height = 160;
|
|
graphicsInternal.data.bpp = 16;
|
|
break;
|
|
case LCDST7789_MODE_BUFFER_120x120:
|
|
graphicsInternal.data.width = 120;
|
|
graphicsInternal.data.height = 120;
|
|
graphicsInternal.data.bpp = 8;
|
|
bufferSize = 120*120;
|
|
break;
|
|
case LCDST7789_MODE_BUFFER_80x80:
|
|
graphicsInternal.data.width = 80;
|
|
graphicsInternal.data.height = 80;
|
|
graphicsInternal.data.bpp = 8;
|
|
bufferSize = 80*80;
|
|
break;
|
|
}
|
|
if (bufferSize) {
|
|
jsvGarbageCollect();
|
|
jsvDefragment();
|
|
JsVar *arrData = jsvNewFlatStringOfLength(bufferSize);
|
|
if (arrData) {
|
|
jsvObjectSetChildAndUnLock(graphics, "buffer", jsvNewArrayBufferFromString(arrData, (unsigned int)bufferSize));
|
|
} else {
|
|
jsExceptionHere(JSET_ERROR, "Not enough memory to allocate offscreen buffer");
|
|
jswrap_banglejs_setLCDMode(0); // go back to default mode
|
|
return;
|
|
}
|
|
jsvUnLock(arrData);
|
|
}
|
|
graphicsStructResetState(&graphicsInternal); // reset colour, cliprect, etc
|
|
jsvUnLock(graphics);
|
|
lcdST7789_setMode( lcdMode );
|
|
graphicsSetCallbacks(&graphicsInternal); // set the callbacks up after the mode change
|
|
#else
|
|
jsExceptionHere(JSET_ERROR, "setLCDMode is unsupported on this device");
|
|
#endif
|
|
}
|
|
/*JSON{
|
|
"type" : "staticmethod",
|
|
"class" : "Bangle",
|
|
"name" : "getLCDMode",
|
|
"generate" : "jswrap_banglejs_getLCDMode",
|
|
"return" : ["JsVar","The LCD mode as a String"],
|
|
"ifdef" : "BANGLEJS",
|
|
"typescript" : "getLCDMode(): LCDMode;"
|
|
}
|
|
The current LCD mode.
|
|
|
|
See `Bangle.setLCDMode` for examples.
|
|
*/
|
|
JsVar *jswrap_banglejs_getLCDMode() {
|
|
const char *name=0;
|
|
#ifdef LCD_CONTROLLER_ST7789_8BIT
|
|
switch (lcdST7789_getMode()) {
|
|
case LCDST7789_MODE_NULL:
|
|
name = "null";
|
|
break;
|
|
case LCDST7789_MODE_UNBUFFERED:
|
|
name = "direct";
|
|
break;
|
|
case LCDST7789_MODE_DOUBLEBUFFERED:
|
|
name = "doublebuffered";
|
|
break;
|
|
case LCDST7789_MODE_BUFFER_120x120:
|
|
name = "120x120";
|
|
break;
|
|
case LCDST7789_MODE_BUFFER_80x80:
|
|
name = "80x80";
|
|
break;
|
|
}
|
|
#endif
|
|
if (!name) return 0;
|
|
return jsvNewFromString(name);
|
|
}
|
|
|
|
/*JSON{
|
|
"type" : "staticmethod",
|
|
"class" : "Bangle",
|
|
"name" : "setLCDOffset",
|
|
"generate" : "jswrap_banglejs_setLCDOffset",
|
|
"params" : [
|
|
["y","int","The amount of pixels to shift the LCD up or down"]
|
|
],
|
|
"ifdef" : "BANGLEJS"
|
|
}
|
|
This can be used to move the displayed memory area up or down temporarily. It's
|
|
used for displaying notifications while keeping the main display contents
|
|
intact.
|
|
*/
|
|
void jswrap_banglejs_setLCDOffset(int y) {
|
|
#ifdef LCD_CONTROLLER_ST7789_8BIT
|
|
lcdST7789_setYOffset(y);
|
|
#endif
|
|
}
|
|
|
|
/*JSON{
|
|
"type" : "staticmethod",
|
|
"class" : "Bangle",
|
|
"name" : "setLCDOverlay",
|
|
"generate" : "jswrap_banglejs_setLCDOverlay",
|
|
"params" : [
|
|
["img","JsVar","An image"],
|
|
["x","int","The X offset the graphics instance should be overlaid on the screen with"],
|
|
["y","int","The Y offset the graphics instance should be overlaid on the screen with"]
|
|
],
|
|
"ifdef" : "BANGLEJS_Q3"
|
|
}
|
|
Overlay an image or graphics instance on top of the contents of the graphics buffer.
|
|
|
|
This only works on Bangle.js 2 because Bangle.js 1 doesn't have an offscreen buffer accessible from the CPU.
|
|
|
|
```
|
|
// display an alarm clock icon on the screen
|
|
var img = require("heatshrink").decompress(atob(`lss4UBvvv///ovBlMyqoADv/VAwlV//1qtfAQX/BINXDoPVq/9DAP
|
|
/AYIKDrWq0oREAYPW1QAB1IWCBQXaBQWq04WCAQP6BQeqA4P1AQPq1WggEK1WrBAIkBBQJsCBYO///fBQOoPAcqCwP3BQnwgECCwP9
|
|
GwIKCngWC14sB7QKCh4CBCwN/64KDgfACwWn6vWGwYsBCwOputWJgYsCgGqytVBQYsCLYOlqtqwAsFEINVrR4BFgghBBQosDEINWIQ
|
|
YsDEIQ3DFgYhCG4msSYeVFgnrFhMvOAgsEkE/FhEggYWCFgIhDkEACwQKBEIYKBCwSGFBQJxCQwYhBBQTKDqohCBQhCCEIJlDXwrKE
|
|
BQoWHBQdaCwuqJoI4CCwgKECwJ9CJgIKDq+qBYUq1WtBQf+BYIAC3/VBQX/tQKDz/9BQY5BAAVV/4WCBQJcBKwVf+oHBv4wCAAYhB`));
|
|
Bangle.setLCDOverlay(img,66,66);
|
|
```
|
|
|
|
Or use a `Graphics` instance:
|
|
|
|
```
|
|
var ovr = Graphics.createArrayBuffer(100,100,1,{msb:true}); // 1bpp
|
|
ovr.drawLine(0,0,100,100);
|
|
ovr.drawRect(0,0,99,99);
|
|
Bangle.setLCDOverlay(ovr,38,38);
|
|
```
|
|
|
|
Although `Graphics` can be specified directly, it can often make more sense to
|
|
create an Image from the `Graphics` instance, as this gives you access
|
|
to color palettes and transparent colors. For instance this will draw a colored
|
|
overlay with rounded corners:
|
|
|
|
```
|
|
var ovr = Graphics.createArrayBuffer(100,100,2,{msb:true});
|
|
ovr.setColor(1).fillRect({x:0,y:0,w:99,h:99,r:8});
|
|
ovr.setColor(3).fillRect({x:2,y:2,w:95,h:95,r:7});
|
|
ovr.setColor(2).setFont("Vector:30").setFontAlign(0,0).drawString("Hi",50,50);
|
|
Bangle.setLCDOverlay({
|
|
width:ovr.getWidth(), height:ovr.getHeight(),
|
|
bpp:2, transparent:0,
|
|
palette:new Uint16Array([0,0,g.toColor("#F00"),g.toColor("#FFF")]),
|
|
buffer:ovr.buffer
|
|
},38,38);
|
|
```
|
|
*/
|
|
void jswrap_banglejs_setLCDOverlay(JsVar *imgVar, int x, int y) {
|
|
#ifdef LCD_CONTROLLER_LPM013M126
|
|
lcdMemLCD_setOverlay(imgVar, x, y);
|
|
// set all as modified
|
|
// TODO: Could look at old vs new overlay state and update only lines that had changed?
|
|
graphicsInternal.data.modMinX = 0;
|
|
graphicsInternal.data.modMinY = 0;
|
|
graphicsInternal.data.modMaxX = LCD_WIDTH-1;
|
|
graphicsInternal.data.modMaxY = LCD_HEIGHT-1;
|
|
#endif
|
|
}
|
|
|
|
/*JSON{
|
|
"type" : "staticmethod",
|
|
"class" : "Bangle",
|
|
"name" : "setLCDTimeout",
|
|
"generate" : "jswrap_banglejs_setLCDTimeout",
|
|
"params" : [
|
|
["isOn","float","The timeout of the display in seconds, or `0`/`undefined` to turn power saving off. Default is 10 seconds."]
|
|
],
|
|
"ifdef" : "BANGLEJS"
|
|
}
|
|
This function can be used to turn Bangle.js's LCD power saving on or off.
|
|
|
|
With power saving off, the display will remain in the state you set it with
|
|
`Bangle.setLCDPower`.
|
|
|
|
With power saving on, the display will turn on if a button is pressed, the watch
|
|
is turned face up, or the screen is updated (see `Bangle.setOptions` for
|
|
configuration). It'll turn off automatically after the given timeout.
|
|
|
|
**Note:** This function also sets the Backlight and Lock timeout (the time at
|
|
which the touchscreen/buttons start being ignored). To set both separately, use
|
|
`Bangle.setOptions`
|
|
*/
|
|
void jswrap_banglejs_setLCDTimeout(JsVarFloat timeout) {
|
|
if (!isfinite(timeout))
|
|
timeout=0;
|
|
else if (timeout<0) timeout=0;
|
|
#ifndef BANGLEJS_Q3 // for backwards compatibility, don't set LCD timeout as we don't want to turn the LCD off
|
|
lcdPowerTimeout = timeout*1000;
|
|
#endif
|
|
backlightTimeout = timeout*1000;
|
|
lockTimeout = timeout*1000;
|
|
}
|
|
|
|
/*JSON{
|
|
"type" : "staticmethod",
|
|
"class" : "Bangle",
|
|
"name" : "setPollInterval",
|
|
"generate" : "jswrap_banglejs_setPollInterval",
|
|
"params" : [
|
|
["interval","float","Polling interval in milliseconds (Default is 80ms - 12.5Hz to match accelerometer)"]
|
|
],
|
|
"ifdef" : "BANGLEJS"
|
|
}
|
|
Set how often the watch should poll for new acceleration/gyro data and kick the
|
|
Watchdog timer. It isn't recommended that you make this interval much larger
|
|
than 1000ms, but values up to 4000ms are allowed.
|
|
|
|
Calling this will set `Bangle.setOptions({powerSave: false})` - disabling the
|
|
dynamic adjustment of poll interval to save battery power when Bangle.js is
|
|
stationary.
|
|
*/
|
|
void jswrap_banglejs_setPollInterval(JsVarFloat interval) {
|
|
if (!isfinite(interval) || interval<10 || interval>ACCEL_POLL_INTERVAL_MAX) {
|
|
jsExceptionHere(JSET_ERROR, "Invalid interval");
|
|
return;
|
|
}
|
|
bangleFlags &= ~JSBF_POWER_SAVE; // turn off power save since it'll just overwrite the poll interval
|
|
jswrap_banglejs_setPollInterval_internal((uint16_t)interval);
|
|
}
|
|
|
|
/*TYPESCRIPT
|
|
type BangleOptions = {
|
|
wakeOnBTN1: boolean;
|
|
wakeOnBTN2: boolean;
|
|
wakeOnBTN3: boolean;
|
|
wakeOnFaceUp: boolean;
|
|
wakeOnTouch: boolean;
|
|
wakeOnTwist: boolean;
|
|
twistThreshold: number;
|
|
twistMaxY: number;
|
|
twistTimeout: number;
|
|
gestureStartThresh: number;
|
|
gestureEndThresh: number;
|
|
gestureInactiveCount: number;
|
|
gestureMinLength: number;
|
|
powerSave: boolean;
|
|
lockTimeout: number;
|
|
lcdPowerTimeout: number;
|
|
backlightTimeout: number;
|
|
btnLoadTimeout: number;
|
|
};
|
|
*/
|
|
/*JSON{
|
|
"type" : "staticmethod",
|
|
"class" : "Bangle",
|
|
"name" : "setOptions",
|
|
"generate" : "jswrap_banglejs_setOptions",
|
|
"params" : [
|
|
["options","JsVar",""]
|
|
],
|
|
"ifdef" : "BANGLEJS",
|
|
"typescript" : "setOptions(options: { [key in keyof BangleOptions]?: BangleOptions[key] }): void;"
|
|
}
|
|
Set internal options used for gestures, etc...
|
|
|
|
* `wakeOnBTN1` should the LCD turn on when BTN1 is pressed? default = `true`
|
|
* `wakeOnBTN2` (Bangle.js 1) should the LCD turn on when BTN2 is pressed?
|
|
default = `true`
|
|
* `wakeOnBTN3` (Bangle.js 1) should the LCD turn on when BTN3 is pressed?
|
|
default = `true`
|
|
* `wakeOnFaceUp` should the LCD turn on when the watch is turned face up?
|
|
default = `false`
|
|
* `wakeOnTouch` should the LCD turn on when the touchscreen is pressed? default
|
|
= `false`
|
|
* `wakeOnTwist` should the LCD turn on when the watch is twisted? default =
|
|
`true`
|
|
* `twistThreshold` How much acceleration to register a twist of the watch strap?
|
|
Can be negative for opposite direction. default = `800`
|
|
* `twistMaxY` Maximum acceleration in Y to trigger a twist (low Y means watch is
|
|
facing the right way up). default = `-800`
|
|
* `twistTimeout` How little time (in ms) must a twist take from low->high
|
|
acceleration? default = `1000`
|
|
* `gestureStartThresh` how big a difference before we consider a gesture
|
|
started? default = `sqr(800)`
|
|
* `gestureEndThresh` how small a difference before we consider a gesture ended?
|
|
default = `sqr(2000)`
|
|
* `gestureInactiveCount` how many samples do we keep after a gesture has ended?
|
|
default = `4`
|
|
* `gestureMinLength` how many samples must a gesture have before we notify about
|
|
it? default = `10`
|
|
* `powerSave` after a minute of not being moved, Bangle.js will change the
|
|
accelerometer poll interval down to 800ms (10x accelerometer samples). On
|
|
movement it'll be raised to the default 80ms. If `Bangle.setPollInterval` is
|
|
used this is disabled, and for it to work the poll interval must be either
|
|
80ms or 800ms. default = `true`. Setting `powerSave:false` will disable this
|
|
automatic power saving, but will **not** change the poll interval from its
|
|
current value. If you desire a specific interval (e.g. the default 80ms) you
|
|
must set it manually with `Bangle.setPollInterval(80)` after setting
|
|
`powerSave:false`.
|
|
* `lockTimeout` how many milliseconds before the screen locks
|
|
* `lcdPowerTimeout` how many milliseconds before the screen turns off
|
|
* `backlightTimeout` how many milliseconds before the screen's backlight turns
|
|
off
|
|
* `btnLoadTimeout` how many milliseconds does the home button have to be pressed
|
|
for before the clock is reloaded? 1500ms default, or 0 means never.
|
|
* `hrmPollInterval` set the requested poll interval (in milliseconds) for the
|
|
heart rate monitor. On Bangle.js 2 only 10,20,40,80,160,200 ms are supported,
|
|
and polling rate may not be exact. The algorithm's filtering is tuned for
|
|
20-40ms poll intervals, so higher/lower intervals may effect the reliability
|
|
of the BPM reading.
|
|
* `seaLevelPressure` (Bangle.js 2) Normally 1013.25 millibars - this is used for
|
|
calculating altitude with the pressure sensor
|
|
|
|
Where accelerations are used they are in internal units, where `8192 = 1g`
|
|
|
|
*/
|
|
JsVar * _jswrap_banglejs_setOptions(JsVar *options, bool createObject) {
|
|
bool wakeOnBTN1 = bangleFlags&JSBF_WAKEON_BTN1;
|
|
bool wakeOnBTN2 = bangleFlags&JSBF_WAKEON_BTN2;
|
|
bool wakeOnBTN3 = bangleFlags&JSBF_WAKEON_BTN3;
|
|
bool wakeOnFaceUp = bangleFlags&JSBF_WAKEON_FACEUP;
|
|
bool wakeOnTouch = bangleFlags&JSBF_WAKEON_TOUCH;
|
|
bool wakeOnTwist = bangleFlags&JSBF_WAKEON_TWIST;
|
|
bool powerSave = bangleFlags&JSBF_POWER_SAVE;
|
|
int stepCounterThresholdLow, stepCounterThresholdHigh; // ignore these with new step counter
|
|
int _accelGestureStartThresh = accelGestureStartThresh*accelGestureStartThresh;
|
|
int _accelGestureEndThresh = accelGestureEndThresh*accelGestureEndThresh;
|
|
#ifdef HEARTRATE
|
|
int _hrmPollInterval = hrmPollInterval;
|
|
#endif
|
|
#ifdef TOUCH_DEVICE
|
|
int touchX1 = touchMinX;
|
|
int touchY1 = touchMinY;
|
|
int touchX2 = touchMaxX;
|
|
int touchY2 = touchMaxY;
|
|
#endif
|
|
jsvConfigObject configs[] = {
|
|
#ifdef HEARTRATE
|
|
{"hrmPollInterval", JSV_INTEGER, &_hrmPollInterval},
|
|
#endif
|
|
#ifdef PRESSURE_DEVICE
|
|
{"seaLevelPressure", JSV_FLOAT, &barometerSeaLevelPressure},
|
|
#endif
|
|
{"gestureStartThresh", JSV_INTEGER, &_accelGestureStartThresh},
|
|
{"gestureEndThresh", JSV_INTEGER, &_accelGestureEndThresh},
|
|
{"gestureInactiveCount", JSV_INTEGER, &accelGestureInactiveCount},
|
|
{"gestureMinLength", JSV_INTEGER, &accelGestureMinLength},
|
|
{"stepCounterThresholdLow", JSV_INTEGER, &stepCounterThresholdLow},
|
|
{"stepCounterThresholdHigh", JSV_INTEGER, &stepCounterThresholdHigh},
|
|
{"twistThreshold", JSV_INTEGER, &twistThreshold},
|
|
{"twistTimeout", JSV_INTEGER, &twistTimeout},
|
|
{"twistMaxY", JSV_INTEGER, &twistMaxY},
|
|
{"wakeOnBTN1", JSV_BOOLEAN, &wakeOnBTN1},
|
|
{"wakeOnBTN2", JSV_BOOLEAN, &wakeOnBTN2},
|
|
{"wakeOnBTN3", JSV_BOOLEAN, &wakeOnBTN3},
|
|
{"wakeOnFaceUp", JSV_BOOLEAN, &wakeOnFaceUp},
|
|
{"wakeOnTouch", JSV_BOOLEAN, &wakeOnTouch},
|
|
{"wakeOnTwist", JSV_BOOLEAN, &wakeOnTwist},
|
|
{"powerSave", JSV_BOOLEAN, &powerSave},
|
|
{"lockTimeout", JSV_INTEGER, &lockTimeout},
|
|
{"lcdPowerTimeout", JSV_INTEGER, &lcdPowerTimeout},
|
|
{"backlightTimeout", JSV_INTEGER, &backlightTimeout},
|
|
{"btnLoadTimeout", JSV_INTEGER, &btnLoadTimeout},
|
|
#ifdef TOUCH_DEVICE
|
|
{"touchX1", JSV_INTEGER, &touchX1},
|
|
{"touchY1", JSV_INTEGER, &touchY1},
|
|
{"touchX2", JSV_INTEGER, &touchX2},
|
|
{"touchY2", JSV_INTEGER, &touchY2},
|
|
#endif
|
|
};
|
|
if (createObject) {
|
|
return jsvCreateConfigObject(configs, sizeof(configs) / sizeof(jsvConfigObject));
|
|
}
|
|
if (jsvReadConfigObject(options, configs, sizeof(configs) / sizeof(jsvConfigObject))) {
|
|
bangleFlags = (bangleFlags&~JSBF_WAKEON_BTN1) | (wakeOnBTN1?JSBF_WAKEON_BTN1:0);
|
|
bangleFlags = (bangleFlags&~JSBF_WAKEON_BTN2) | (wakeOnBTN2?JSBF_WAKEON_BTN2:0);
|
|
bangleFlags = (bangleFlags&~JSBF_WAKEON_BTN3) | (wakeOnBTN3?JSBF_WAKEON_BTN3:0);
|
|
bangleFlags = (bangleFlags&~JSBF_WAKEON_FACEUP) | (wakeOnFaceUp?JSBF_WAKEON_FACEUP:0);
|
|
bangleFlags = (bangleFlags&~JSBF_WAKEON_TOUCH) | (wakeOnTouch?JSBF_WAKEON_TOUCH:0);
|
|
bangleFlags = (bangleFlags&~JSBF_WAKEON_TWIST) | (wakeOnTwist?JSBF_WAKEON_TWIST:0);
|
|
bangleFlags = (bangleFlags&~JSBF_POWER_SAVE) | (powerSave?JSBF_POWER_SAVE:0);
|
|
if (lockTimeout<0) lockTimeout=0;
|
|
if (lcdPowerTimeout<0) lcdPowerTimeout=0;
|
|
if (backlightTimeout<0) backlightTimeout=0;
|
|
accelGestureStartThresh = int_sqrt32(_accelGestureStartThresh);
|
|
accelGestureEndThresh = int_sqrt32(_accelGestureEndThresh);
|
|
#ifdef HEARTRATE
|
|
hrmPollInterval = (uint16_t)_hrmPollInterval;
|
|
#endif
|
|
#ifdef TOUCH_DEVICE
|
|
touchMinX = touchX1;
|
|
touchMinY = touchY1;
|
|
touchMaxX = touchX2;
|
|
touchMaxY = touchY2;
|
|
#endif
|
|
}
|
|
return 0;
|
|
}
|
|
void jswrap_banglejs_setOptions(JsVar *options) {
|
|
_jswrap_banglejs_setOptions(options, false);
|
|
}
|
|
/*JSON{
|
|
"type" : "staticmethod",
|
|
"class" : "Bangle",
|
|
"name" : "getOptions",
|
|
"generate" : "jswrap_banglejs_getOptions",
|
|
"return" : ["JsVar","The current state of all options"],
|
|
"ifdef" : "BANGLEJS",
|
|
"typescript" : "getOptions(): BangleOptions;"
|
|
}
|
|
Return the current state of options as set by `Bangle.setOptions`
|
|
*/
|
|
JsVar *jswrap_banglejs_getOptions() {
|
|
return _jswrap_banglejs_setOptions(NULL, true);
|
|
}
|
|
|
|
/*JSON{
|
|
"type" : "staticmethod",
|
|
"class" : "Bangle",
|
|
"name" : "isLCDOn",
|
|
"generate" : "jswrap_banglejs_isLCDOn",
|
|
"return" : ["bool","Is the display on or not?"],
|
|
"ifdef" : "BANGLEJS"
|
|
}
|
|
Also see the `Bangle.lcdPower` event
|
|
*/
|
|
// emscripten bug means we can't use 'bool' as return value here!
|
|
int jswrap_banglejs_isLCDOn() {
|
|
return (bangleFlags&JSBF_LCD_ON)!=0;
|
|
}
|
|
|
|
/*JSON{
|
|
"type" : "staticmethod",
|
|
"class" : "Bangle",
|
|
"name" : "setLocked",
|
|
"generate" : "jswrap_banglejs_setLocked",
|
|
"params" : [
|
|
["isLocked","bool","`true` if the Bangle is locked (no user input allowed)"]
|
|
],
|
|
"ifdef" : "BANGLEJS"
|
|
}
|
|
This function can be used to lock or unlock Bangle.js (e.g. whether buttons and
|
|
touchscreen work or not)
|
|
*/
|
|
void jswrap_banglejs_setLocked(bool isLocked) {
|
|
#if defined(TOUCH_I2C)
|
|
if (isLocked) {
|
|
unsigned char buf[2];
|
|
buf[0]=0xE5;
|
|
buf[1]=0x03;
|
|
jsi2cWrite(TOUCH_I2C, TOUCH_ADDR, 2, buf, true);
|
|
} else { // best way to wake up is to reset
|
|
jshPinOutput(TOUCH_PIN_RST, 0);
|
|
jshDelayMicroseconds(1000);
|
|
jshPinOutput(TOUCH_PIN_RST, 1);
|
|
jshDelayMicroseconds(1000);
|
|
}
|
|
#endif
|
|
if ((bangleFlags&JSBF_LOCKED) != isLocked) {
|
|
JsVar *bangle =jsvObjectGetChild(execInfo.root, "Bangle", 0);
|
|
if (bangle) {
|
|
JsVar *v = jsvNewFromBool(isLocked);
|
|
jsiQueueObjectCallbacks(bangle, JS_EVENT_PREFIX"lock", &v, 1);
|
|
jsvUnLock(v);
|
|
}
|
|
jsvUnLock(bangle);
|
|
}
|
|
if (isLocked) bangleFlags |= JSBF_LOCKED;
|
|
else bangleFlags &= ~JSBF_LOCKED;
|
|
// Reset inactivity timer so we will lock ourselves after a delay
|
|
inactivityTimer = 0;
|
|
}
|
|
|
|
/*JSON{
|
|
"type" : "staticmethod",
|
|
"class" : "Bangle",
|
|
"name" : "isLocked",
|
|
"generate" : "jswrap_banglejs_isLocked",
|
|
"return" : ["bool","Is the screen locked or not?"],
|
|
"ifdef" : "BANGLEJS"
|
|
}
|
|
Also see the `Bangle.lock` event
|
|
*/
|
|
// emscripten bug means we can't use 'bool' as return value here!
|
|
int jswrap_banglejs_isLocked() {
|
|
return (bangleFlags&JSBF_LOCKED)!=0;
|
|
}
|
|
|
|
/*JSON{
|
|
"type" : "staticmethod",
|
|
"class" : "Bangle",
|
|
"name" : "isCharging",
|
|
"generate" : "jswrap_banglejs_isCharging",
|
|
"return" : ["bool","Is the battery charging or not?"],
|
|
"ifdef" : "BANGLEJS"
|
|
}
|
|
*/
|
|
// emscripten bug means we can't use 'bool' as return value here!
|
|
int jswrap_banglejs_isCharging() {
|
|
#ifdef BAT_PIN_CHARGING
|
|
return !jshPinGetValue(BAT_PIN_CHARGING);
|
|
#else
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
/// get battery percentage
|
|
JsVarInt jswrap_banglejs_getBattery() {
|
|
#if defined(BAT_PIN_VOLTAGE) && !defined(EMULATED)
|
|
JsVarFloat v = jshPinAnalog(BAT_PIN_VOLTAGE);
|
|
|
|
#ifdef ESPR_BATTERY_FULL_VOLTAGE
|
|
// a configurable 'battery full voltage' is available
|
|
int pc;
|
|
v = 4.2 * v / batteryFullVoltage; // now 'v' should be in actual volts
|
|
if (v>=3.95) pc = 80 + (v-3.95)*20/(4.2-3.95); // 80%+
|
|
else if (v>=3.7) pc = 10 + (v-3.7)*70/(3.95-3.7); // 10%+ is linear
|
|
else pc = (v-3.3)*10/(3.7-3.3); // 0%+
|
|
#else // otherwise normal linear battery scaling...
|
|
#ifdef BANGLEJS_Q3
|
|
const JsVarFloat vlo = 0.246;
|
|
const JsVarFloat vhi = 0.3144; // on some watches this is 100%, on others it's s a bit higher
|
|
#elif defined(BANGLEJS_F18)
|
|
const JsVarFloat vlo = 0.51;
|
|
const JsVarFloat vhi = 0.62;
|
|
#elif defined(DICKENS)
|
|
#ifdef LCD_TEARING // DICKENS2 hardware (with LCD tearing signal) has VDD=3.3V
|
|
const JsVarFloat vlo = 3.55 / (3.3*2); // Operates down to 3.05V, but battery starts dropping very rapidly from 3.55V, so treat this as the end-point.
|
|
const JsVarFloat vhi = 4.15 / (3.3*2); // Fully charged is 4.20V, but drops quickly to 4.15V
|
|
#else // Original DICKENS hardware has VDD=2.8V
|
|
const JsVarFloat vlo = 3.55 / (2.8*2);
|
|
const JsVarFloat vhi = 4.15 / (2.8*2);
|
|
#endif
|
|
#else
|
|
const JsVarFloat vlo = 0;
|
|
const JsVarFloat vhi = 1;
|
|
#endif
|
|
int pc = (v-vlo)*100/(vhi-vlo);
|
|
#endif // !ESPR_BATTERY_FULL_VOLTAGE
|
|
if (pc>100) pc=100;
|
|
if (pc<0) pc=0;
|
|
return pc;
|
|
#else //!BAT_PIN_VOLTAGE || EMULATED
|
|
return 50;
|
|
#endif
|
|
}
|
|
|
|
/*JSON{
|
|
"type" : "staticmethod",
|
|
"class" : "Bangle",
|
|
"name" : "lcdWr",
|
|
"generate" : "jswrap_banglejs_lcdWr",
|
|
"params" : [
|
|
["cmd","int",""],
|
|
["data","JsVar",""]
|
|
],
|
|
"ifdef" : "BANGLEJS"
|
|
}
|
|
Writes a command directly to the ST7735 LCD controller
|
|
*/
|
|
void jswrap_banglejs_lcdWr(JsVarInt cmd, JsVar *data) {
|
|
JSV_GET_AS_CHAR_ARRAY(dPtr, dLen, data);
|
|
#ifdef LCD_CONTROLLER_ST7789_8BIT
|
|
lcdST7789_cmd(cmd, dLen, (const uint8_t *)dPtr);
|
|
#endif
|
|
#if defined(LCD_CONTROLLER_ST7789V) || defined(LCD_CONTROLLER_ST7735) || defined(LCD_CONTROLLER_GC9A01)
|
|
lcdCmd_SPILCD(cmd, dLen, (const uint8_t *)dPtr);
|
|
#endif
|
|
}
|
|
|
|
/*JSON{
|
|
"type" : "staticmethod",
|
|
"class" : "Bangle",
|
|
"name" : "setHRMPower",
|
|
"generate" : "jswrap_banglejs_setHRMPower",
|
|
"params" : [
|
|
["isOn","bool","True if the heart rate monitor should be on, false if not"],
|
|
["appID","JsVar","A string with the app's name in, used to ensure one app can't turn off something another app is using"]
|
|
],
|
|
"return" : ["bool","Is HRM on?"],
|
|
"ifdef" : "BANGLEJS",
|
|
"typescript" : "setHRMPower(isOn: boolean, appID: string): boolean;"
|
|
}
|
|
Set the power to the Heart rate monitor
|
|
|
|
When on, data is output via the `HRM` event on `Bangle`:
|
|
|
|
```
|
|
Bangle.setHRMPower(true, "myapp");
|
|
Bangle.on('HRM',print);
|
|
```
|
|
|
|
*When on, the Heart rate monitor draws roughly 5mA*
|
|
*/
|
|
bool jswrap_banglejs_setHRMPower(bool isOn, JsVar *appId) {
|
|
#ifdef HEARTRATE
|
|
bool wasOn = bangleFlags & JSBF_HRM_ON;
|
|
isOn = setDeviceRequested("HRM", appId, isOn);
|
|
|
|
if (isOn != wasOn) {
|
|
if (isOn) {
|
|
hrm_init();
|
|
jswrap_banglejs_pwrHRM(true); // HRM on, set JSBF_HRM_ON
|
|
hrm_sensor_on(hrmHandler);
|
|
} else {
|
|
hrm_sensor_off();
|
|
jswrap_banglejs_pwrHRM(false); // HRM off
|
|
}
|
|
}
|
|
return isOn;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
/*JSON{
|
|
"type" : "staticmethod",
|
|
"class" : "Bangle",
|
|
"name" : "isHRMOn",
|
|
"generate" : "jswrap_banglejs_isHRMOn",
|
|
"return" : ["bool","Is HRM on?"],
|
|
"ifdef" : "BANGLEJS"
|
|
}
|
|
Is the Heart rate monitor powered?
|
|
|
|
Set power with `Bangle.setHRMPower(...);`
|
|
*/
|
|
// emscripten bug means we can't use 'bool' as return value here!
|
|
int jswrap_banglejs_isHRMOn() {
|
|
return bangleFlags & JSBF_HRM_ON;
|
|
}
|
|
|
|
#ifdef GPS_PIN_RX
|
|
/// Clear all data stored for the GPS input line
|
|
void gpsClearLine() {
|
|
gpsLineLength = 0;
|
|
#ifdef GPS_UBLOX
|
|
ubxMsgPayloadEnd = 0;
|
|
inComingUbloxProtocol = UBLOX_PROTOCOL_NOT_DETECTED;
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
/*JSON{
|
|
"type" : "staticmethod",
|
|
"class" : "Bangle",
|
|
"name" : "setGPSPower",
|
|
"generate" : "jswrap_banglejs_setGPSPower",
|
|
"params" : [
|
|
["isOn","bool","True if the GPS should be on, false if not"],
|
|
["appID","JsVar","A string with the app's name in, used to ensure one app can't turn off something another app is using"]
|
|
],
|
|
"return" : ["bool","Is the GPS on?"],
|
|
"ifdef" : "BANGLEJS",
|
|
"typescript" : "setGPSPower(isOn: boolean, appID: string): boolean;"
|
|
}
|
|
Set the power to the GPS.
|
|
|
|
When on, data is output via the `GPS` event on `Bangle`:
|
|
|
|
```
|
|
Bangle.setGPSPower(true, "myapp");
|
|
Bangle.on('GPS',print);
|
|
```
|
|
|
|
*When on, the GPS draws roughly 20mA*
|
|
*/
|
|
bool jswrap_banglejs_setGPSPower(bool isOn, JsVar *appId) {
|
|
#ifdef GPS_PIN_RX
|
|
bool wasOn = bangleFlags & JSBF_GPS_ON;
|
|
isOn = setDeviceRequested("GPS", appId, isOn);
|
|
if (isOn) {
|
|
if (!wasOn) {
|
|
JshUSARTInfo inf;
|
|
jshUSARTInitInfo(&inf);
|
|
inf.baudRate = 9600;
|
|
inf.pinRX = GPS_PIN_RX;
|
|
inf.pinTX = GPS_PIN_TX;
|
|
jshUSARTSetup(GPS_UART, &inf);
|
|
jswrap_banglejs_pwrGPS(true); // turn on, set JSBF_GPS_ON
|
|
gpsClearLine();
|
|
memset(&gpsFix,0,sizeof(gpsFix));
|
|
}
|
|
} else { // !isOn
|
|
jswrap_banglejs_pwrGPS(false); // turn off, clear JSBF_GPS_ON
|
|
// setting pins to pullup will cause jshardware.c to disable the UART, saving power
|
|
jshPinSetState(GPS_PIN_RX, JSHPINSTATE_GPIO_IN_PULLUP);
|
|
jshPinSetState(GPS_PIN_TX, JSHPINSTATE_GPIO_IN_PULLUP);
|
|
}
|
|
return isOn;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
/*JSON{
|
|
"type" : "staticmethod",
|
|
"class" : "Bangle",
|
|
"name" : "isGPSOn",
|
|
"generate" : "jswrap_banglejs_isGPSOn",
|
|
"return" : ["bool","Is the GPS on?"],
|
|
"ifdef" : "BANGLEJS"
|
|
}
|
|
Is the GPS powered?
|
|
|
|
Set power with `Bangle.setGPSPower(...);`
|
|
*/
|
|
// emscripten bug means we can't use 'bool' as return value here!
|
|
int jswrap_banglejs_isGPSOn() {
|
|
return bangleFlags & JSBF_GPS_ON;
|
|
}
|
|
|
|
/*JSON{
|
|
"type" : "staticmethod",
|
|
"class" : "Bangle",
|
|
"name" : "getGPSFix",
|
|
"generate" : "jswrap_banglejs_getGPSFix",
|
|
"return" : ["JsVar","A GPS fix object with `{lat,lon,...}`"],
|
|
"ifdef" : "BANGLEJS",
|
|
"typescript" : "getGPSFix(): GPSFix;"
|
|
}
|
|
Get the last available GPS fix info (or `undefined` if GPS is off).
|
|
|
|
The fix info received is the same as you'd get from the `Bangle.GPS` event.
|
|
*/
|
|
JsVar *jswrap_banglejs_getGPSFix() {
|
|
#ifdef GPS_PIN_RX
|
|
if (!jswrap_banglejs_isGPSOn()) return NULL;
|
|
return nmea_to_jsVar(&gpsFix);
|
|
#else
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
/*JSON{
|
|
"type" : "staticmethod",
|
|
"class" : "Bangle",
|
|
"name" : "setCompassPower",
|
|
"generate" : "jswrap_banglejs_setCompassPower",
|
|
"params" : [
|
|
["isOn","bool","True if the Compass should be on, false if not"],
|
|
["appID","JsVar","A string with the app's name in, used to ensure one app can't turn off something another app is using"]
|
|
],
|
|
"return" : ["bool","Is the Compass on?"],
|
|
"ifdef" : "BANGLEJS",
|
|
"typescript" : "setCompassPower(isOn: boolean, appID: string): boolean;"
|
|
}
|
|
Set the power to the Compass
|
|
|
|
When on, data is output via the `mag` event on `Bangle`:
|
|
|
|
```
|
|
Bangle.setCompassPower(true, "myapp");
|
|
Bangle.on('mag',print);
|
|
```
|
|
|
|
*When on, the compass draws roughly 2mA*
|
|
*/
|
|
bool jswrap_banglejs_setCompassPower(bool isOn, JsVar *appId) {
|
|
#ifdef MAG_I2C
|
|
bool wasOn = bangleFlags & JSBF_COMPASS_ON;
|
|
isOn = setDeviceRequested("Compass", appId, isOn);
|
|
//jsiConsolePrintf("setCompassPower %d %d\n",wasOn,isOn);
|
|
|
|
if (isOn) bangleFlags |= JSBF_COMPASS_ON;
|
|
else bangleFlags &= ~JSBF_COMPASS_ON;
|
|
|
|
if (isOn) {
|
|
if (!wasOn) { // If it wasn't on before, reset
|
|
#ifdef MAG_DEVICE_GMC303
|
|
jswrap_banglejs_compassWr(0x31,4); // continuous measurement mode, 20Hz
|
|
#endif
|
|
#ifdef MAG_DEVICE_UNKNOWN_0C
|
|
// Read 0x3E to enable compass readings
|
|
jsvUnLock(jswrap_banglejs_compassRd(0x3E,0));
|
|
#endif
|
|
}
|
|
} else { // !isOn -> turn off
|
|
#ifdef MAG_DEVICE_GMC303
|
|
jswrap_banglejs_compassWr(0x31,0); // off
|
|
#endif
|
|
}
|
|
return isOn;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
/*JSON{
|
|
"type" : "staticmethod",
|
|
"class" : "Bangle",
|
|
"name" : "isCompassOn",
|
|
"generate" : "jswrap_banglejs_isCompassOn",
|
|
"return" : ["bool","Is the Compass on?"],
|
|
"ifdef" : "BANGLEJS"
|
|
}
|
|
Is the compass powered?
|
|
|
|
Set power with `Bangle.setCompassPower(...);`
|
|
*/
|
|
// emscripten bug means we can't use 'bool' as return value here!
|
|
int jswrap_banglejs_isCompassOn() {
|
|
return bangleFlags & JSBF_COMPASS_ON;
|
|
}
|
|
|
|
/*JSON{
|
|
"type" : "staticmethod",
|
|
"class" : "Bangle",
|
|
"name" : "resetCompass",
|
|
"generate" : "jswrap_banglejs_resetCompass",
|
|
"params" : [],
|
|
"ifdef" : "BANGLEJS"
|
|
}
|
|
Resets the compass minimum/maximum values. Can be used if the compass isn't
|
|
providing a reliable heading any more.
|
|
*/
|
|
void jswrap_banglejs_resetCompass() {
|
|
#ifdef MAG_I2C
|
|
mag.x = 0;
|
|
mag.y = 0;
|
|
mag.z = 0;
|
|
magmin.x = 32767;
|
|
magmin.y = 32767;
|
|
magmin.z = 32767;
|
|
magmax.x = -32768;
|
|
magmax.y = -32768;
|
|
magmax.z = -32768;
|
|
#endif
|
|
}
|
|
|
|
/*JSON{
|
|
"type" : "staticmethod",
|
|
"class" : "Bangle",
|
|
"name" : "setBarometerPower",
|
|
"generate" : "jswrap_banglejs_setBarometerPower",
|
|
"params" : [
|
|
["isOn","bool","True if the barometer IC should be on, false if not"],
|
|
["appID","JsVar","A string with the app's name in, used to ensure one app can't turn off something another app is using"]
|
|
],
|
|
"return" : ["bool","Is the Barometer on?"],
|
|
"#if" : "defined(DTNO1_F5) || defined(BANGLEJS_Q3) || defined(DICKENS)",
|
|
"typescript" : "setBarometerPower(isOn: boolean, appID: string): boolean;"
|
|
}
|
|
Set the power to the barometer IC. Once enabled, `Bangle.pressure` events are
|
|
fired each time a new barometer reading is available.
|
|
|
|
When on, the barometer draws roughly 50uA
|
|
*/
|
|
bool jswrap_banglejs_setBarometerPower(bool isOn, JsVar *appId) {
|
|
#ifdef PRESSURE_DEVICE
|
|
bool wasOn = bangleFlags & JSBF_BAROMETER_ON;
|
|
isOn = setDeviceRequested("Barom", appId, isOn);
|
|
if (isOn) bangleFlags |= JSBF_BAROMETER_ON;
|
|
else bangleFlags &= ~JSBF_BAROMETER_ON;
|
|
int tries = 3;
|
|
while (tries-- > 0) {
|
|
if (isOn) {
|
|
if (!wasOn) {
|
|
#ifdef PRESSURE_DEVICE_SPL06_007_EN
|
|
if (PRESSURE_DEVICE_SPL06_007_EN) {
|
|
jswrap_banglejs_barometerWr(SPL06_CFGREG, 0); // No FIFO or IRQ (should be default but has been nonzero when read!
|
|
jswrap_banglejs_barometerWr(SPL06_PRSCFG, 0x33); // pressure oversample by 8x, 8 measurement per second
|
|
jswrap_banglejs_barometerWr(SPL06_TMPCFG, 0xB3); // temperature oversample by 8x, 8 measurements per second, external sensor
|
|
jswrap_banglejs_barometerWr(SPL06_MEASCFG, 7); // continuous temperature and pressure measurement
|
|
// read calibration data
|
|
unsigned char buf[SPL06_COEF_NUM];
|
|
buf[0] = SPL06_COEF_START; jsi2cWrite(PRESSURE_I2C, PRESSURE_ADDR, 1, buf, false);
|
|
jsi2cRead(PRESSURE_I2C, PRESSURE_ADDR, SPL06_COEF_NUM, buf, true);
|
|
barometer_c0 = twosComplement(((unsigned short)buf[0] << 4) | (((unsigned short)buf[1] >> 4) & 0x0F), 12);
|
|
barometer_c1 = twosComplement((((unsigned short)buf[1] & 0x0F) << 8) | buf[2], 12);
|
|
barometer_c00 = twosComplement(((unsigned int)buf[3] << 12) | ((unsigned int)buf[4] << 4) | (((unsigned int)buf[5] >> 4) & 0x0F), 20);
|
|
barometer_c10 = twosComplement((((unsigned int)buf[5] & 0x0F) << 16) | ((unsigned int)buf[6] << 8) | (unsigned int)buf[7], 20);
|
|
barometer_c01 = twosComplement(((unsigned short)buf[8] << 8) | (unsigned short)buf[9], 16);
|
|
barometer_c11 = twosComplement(((unsigned short)buf[10] << 8) | (unsigned short)buf[11], 16);
|
|
barometer_c20 = twosComplement(((unsigned short)buf[12] << 8) | (unsigned short)buf[13], 16);
|
|
barometer_c21 = twosComplement(((unsigned short)buf[14] << 8) | (unsigned short)buf[15], 16);
|
|
barometer_c30 = twosComplement(((unsigned short)buf[16] << 8) | (unsigned short)buf[17], 16);
|
|
}
|
|
#endif // PRESSURE_DEVICE_SPL06_007_EN
|
|
#ifdef PRESSURE_DEVICE_BMP280_EN
|
|
if (PRESSURE_DEVICE_BMP280_EN) {
|
|
jswrap_banglejs_barometerWr(0xF4, 0x27); // ctrl_meas_reg - normal mode, no pressure/temp oversample
|
|
jswrap_banglejs_barometerWr(0xF5, 0xA0); // config_reg - 1s standby, no filter, I2C
|
|
// read calibration data
|
|
unsigned char buf[24];
|
|
buf[0] = 0x88; jsi2cWrite(PRESSURE_I2C, PRESSURE_ADDR, 1, buf, false);
|
|
jsi2cRead(PRESSURE_I2C, PRESSURE_ADDR, 24, buf, true);
|
|
int i;
|
|
barometerDT[0] = ((int)buf[1] << 8) | (int)buf[0]; //first coeff is unsigned
|
|
for (i=1;i<3;i++)
|
|
barometerDT[i] = twosComplement(((int)buf[(i*2)+1] << 8) | (int)buf[i*2], 16);
|
|
barometerDP[0] = ((int)buf[7] << 8) | (int)buf[6]; //first coeff is unsigned
|
|
for (i=1;i<9;i++)
|
|
barometerDP[i] = twosComplement(((int)buf[(i*2)+7] << 8) | (int)buf[(i*2)+6], 16);
|
|
}
|
|
#endif // PRESSURE_DEVICE_BMP280_EN
|
|
} // wasOn
|
|
} else { // !isOn -> turn off
|
|
#ifdef PRESSURE_DEVICE_SPL06_007_EN
|
|
if (PRESSURE_DEVICE_SPL06_007_EN)
|
|
jswrap_banglejs_barometerWr(SPL06_MEASCFG, 0); // Barometer off
|
|
#endif
|
|
#ifdef PRESSURE_DEVICE_BMP280_EN
|
|
if (PRESSURE_DEVICE_BMP280_EN)
|
|
jswrap_banglejs_barometerWr(0xF4, 0); // Barometer off
|
|
#endif
|
|
}
|
|
if (!tries || !jspHasError()) return isOn; // return - all is going correctly (or we tried a few times and failed)
|
|
// we had an error (almost certainly I2C) - clear the error and try again hopefully
|
|
jsvUnLock(jspGetException());
|
|
}
|
|
return isOn;
|
|
#else // PRESSURE_DEVICE
|
|
return false;
|
|
#endif // PRESSURE_DEVICE
|
|
}
|
|
|
|
|
|
/*JSON{
|
|
"type" : "staticmethod",
|
|
"class" : "Bangle",
|
|
"name" : "isBarometerOn",
|
|
"generate" : "jswrap_banglejs_isBarometerOn",
|
|
"return" : ["bool","Is the Barometer on?"],
|
|
"#if" : "defined(DTNO1_F5) || defined(BANGLEJS_Q3) || defined(DICKENS)"
|
|
}
|
|
Is the Barometer powered?
|
|
|
|
Set power with `Bangle.setBarometerPower(...);`
|
|
*/
|
|
// emscripten bug means we can't use 'bool' as return value here!
|
|
int jswrap_banglejs_isBarometerOn() {
|
|
return bangleFlags & JSBF_BAROMETER_ON;
|
|
}
|
|
|
|
/*JSON{
|
|
"type" : "staticmethod",
|
|
"class" : "Bangle",
|
|
"name" : "getStepCount",
|
|
"generate" : "jswrap_banglejs_getStepCount",
|
|
"return" : ["int","The number of steps recorded by the step counter"],
|
|
"ifdef" : "BANGLEJS"
|
|
}
|
|
Returns the current amount of steps recorded by the step counter
|
|
*/
|
|
int jswrap_banglejs_getStepCount() {
|
|
return stepCounter;
|
|
}
|
|
|
|
/*JSON{
|
|
"type" : "staticmethod",
|
|
"class" : "Bangle",
|
|
"name" : "setStepCount",
|
|
"generate" : "jswrap_banglejs_setStepCount",
|
|
"params" : [
|
|
["count","int","The value with which to reload the step counter"]
|
|
],
|
|
"ifdef" : "BANGLEJS"
|
|
}
|
|
Sets the current value of the step counter
|
|
*/
|
|
void jswrap_banglejs_setStepCount(JsVarInt count) {
|
|
stepCounter = count;
|
|
}
|
|
|
|
/*JSON{
|
|
"type" : "staticmethod",
|
|
"class" : "Bangle",
|
|
"name" : "getCompass",
|
|
"generate" : "jswrap_banglejs_getCompass",
|
|
"return" : ["JsVar","An object containing magnetometer readings (as below)"],
|
|
"ifdef" : "BANGLEJS",
|
|
"typescript" : "getCompass(): CompassData;"
|
|
}
|
|
Get the most recent Magnetometer/Compass reading. Data is in the same format as
|
|
the `Bangle.on('mag',` event.
|
|
|
|
Returns an `{x,y,z,dx,dy,dz,heading}` object
|
|
|
|
* `x/y/z` raw x,y,z magnetometer readings
|
|
* `dx/dy/dz` readings based on calibration since magnetometer turned on
|
|
* `heading` in degrees based on calibrated readings (will be NaN if magnetometer
|
|
hasn't been rotated around 360 degrees).
|
|
|
|
**Note:** In 2v15 firmware and earlier the heading is inverted (360-heading). There's
|
|
a fix in the bootloader which will apply a fix for those headings, but old apps may
|
|
still expect an inverted value.
|
|
|
|
To get this event you must turn the compass on with `Bangle.setCompassPower(1)`.*/
|
|
JsVar *jswrap_banglejs_getCompass() {
|
|
#ifdef MAG_I2C
|
|
JsVar *o = jsvNewObject();
|
|
if (o) {
|
|
jsvObjectSetChildAndUnLock(o, "x", jsvNewFromInteger(mag.x));
|
|
jsvObjectSetChildAndUnLock(o, "y", jsvNewFromInteger(mag.y));
|
|
jsvObjectSetChildAndUnLock(o, "z", jsvNewFromInteger(mag.z));
|
|
int dx = mag.x - ((magmin.x+magmax.x)/2);
|
|
int dy = mag.y - ((magmin.y+magmax.y)/2);
|
|
int dz = mag.z - ((magmin.z+magmax.z)/2);
|
|
jsvObjectSetChildAndUnLock(o, "dx", jsvNewFromInteger(dx));
|
|
jsvObjectSetChildAndUnLock(o, "dy", jsvNewFromInteger(dy));
|
|
jsvObjectSetChildAndUnLock(o, "dz", jsvNewFromInteger(dz));
|
|
int cx = magmax.x-magmin.x;
|
|
int cy = magmax.y-magmin.y;
|
|
int c = cx*cx+cy*cy;
|
|
double h = NAN;
|
|
if (c>3000) { // only give a heading if we think we have valid data (eg enough magnetic field difference in min/max
|
|
h = jswrap_math_atan2(dx,dy)*180/PI;
|
|
if (h<0) h+=360;
|
|
h = 360-h; // ensure heading matches with what we'd expect from a compass
|
|
}
|
|
jsvObjectSetChildAndUnLock(o, "heading", jsvNewFromFloat(h));
|
|
}
|
|
return o;
|
|
#else
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
/*JSON{
|
|
"type" : "staticmethod",
|
|
"class" : "Bangle",
|
|
"name" : "getAccel",
|
|
"generate" : "jswrap_banglejs_getAccel",
|
|
"return" : ["JsVar","An object containing accelerometer readings (as below)"],
|
|
"ifdef" : "BANGLEJS",
|
|
"typescript" : "getAccel(): AccelData & { td: number };"
|
|
}
|
|
Get the most recent accelerometer reading. Data is in the same format as the
|
|
`Bangle.on('accel',` event.
|
|
|
|
* `x` is X axis (left-right) in `g`
|
|
* `y` is Y axis (up-down) in `g`
|
|
* `z` is Z axis (in-out) in `g`
|
|
* `diff` is difference between this and the last reading in `g` (calculated by
|
|
comparing vectors, not magnitudes)
|
|
* `td` is the elapsed
|
|
* `mag` is the magnitude of the acceleration in `g`
|
|
*/
|
|
JsVar *jswrap_banglejs_getAccel() {
|
|
JsVar *o = jsvNewObject();
|
|
if (o) {
|
|
jsvObjectSetChildAndUnLock(o, "x", jsvNewFromFloat(acc.x/8192.0));
|
|
jsvObjectSetChildAndUnLock(o, "y", jsvNewFromFloat(acc.y/8192.0));
|
|
jsvObjectSetChildAndUnLock(o, "z", jsvNewFromFloat(acc.z/8192.0));
|
|
jsvObjectSetChildAndUnLock(o, "mag", jsvNewFromFloat(sqrt(accMagSquared)/8192.0));
|
|
jsvObjectSetChildAndUnLock(o, "diff", jsvNewFromFloat(accDiff/8192.0));
|
|
}
|
|
return o;
|
|
}
|
|
|
|
/*JSON{
|
|
"type" : "staticmethod",
|
|
"class" : "Bangle",
|
|
"name" : "getHealthStatus",
|
|
"generate" : "jswrap_banglejs_getHealthStatus",
|
|
"return" : ["JsVar","Returns an object containing various health info"],
|
|
"params" : [
|
|
["range","JsVar","What time period to return data for, see below:"]
|
|
],
|
|
"ifdef" : "BANGLEJS",
|
|
"typescript" : "getHealthStatus(range?: \"current\" | \"last\" | \"day\"): HealthStatus;"
|
|
}
|
|
|
|
`range` is one of:
|
|
|
|
* `undefined` or `'10min'` - health data so far in this 10 minute block (eg. 9:00.00 - 9:09.59)
|
|
* `'last'` - health data during the last 10 minute block
|
|
* `'day'` - the health data so far for the day
|
|
|
|
|
|
`getHealthStatus` returns an object containing:
|
|
|
|
* `movement` is the 32 bit sum of all `acc.diff` readings since power on (and
|
|
rolls over). It is the difference in accelerometer values as `g*8192`
|
|
* `steps` is the number of steps during this period
|
|
* `bpm` the best BPM reading from HRM sensor during this period
|
|
* `bpmConfidence` best BPM confidence (0-100%) during this period
|
|
|
|
*/
|
|
static JsVar *_jswrap_banglejs_getHealthStatusObject(HealthState *health) {
|
|
JsVar *o = jsvNewObject();
|
|
if (o) {
|
|
//jsvObjectSetChildAndUnLock(o,"index", jsvNewFromInteger(health->index)); // DEBUG only
|
|
jsvObjectSetChildAndUnLock(o,"movement", jsvNewFromInteger(health->movement / health->movementSamples));
|
|
jsvObjectSetChildAndUnLock(o,"steps",jsvNewFromInteger(health->stepCount));
|
|
#ifdef HEARTRATE
|
|
jsvObjectSetChildAndUnLock(o,"bpm",jsvNewFromFloat(health->bpm10 / 10.0));
|
|
jsvObjectSetChildAndUnLock(o,"bpmConfidence",jsvNewFromInteger(health->bpmConfidence));
|
|
#endif
|
|
}
|
|
return o;
|
|
}
|
|
JsVar *jswrap_banglejs_getHealthStatus(JsVar *range) {
|
|
if (jsvIsUndefined(range) || jsvIsStringEqual(range,"10min"))
|
|
return _jswrap_banglejs_getHealthStatusObject(&healthCurrent);
|
|
if (jsvIsStringEqual(range,"last"))
|
|
return _jswrap_banglejs_getHealthStatusObject(&healthLast);
|
|
if (jsvIsStringEqual(range,"day"))
|
|
return _jswrap_banglejs_getHealthStatusObject(&healthDaily);
|
|
jsExceptionHere(JSET_ERROR, "Unknown range name %q", range);
|
|
return 0;
|
|
}
|
|
|
|
/* After init is called (a second time, NOT first time), we execute any JS that is due to be executed,
|
|
* then we call this afterwards to shut down anything that isn't required (compass/hrm/etc). */
|
|
void jswrap_banglejs_postInit() {
|
|
#ifdef HEARTRATE
|
|
if ((bangleFlags & JSBF_HRM_ON) && !getDeviceRequested("HRM")) {
|
|
jswrap_banglejs_setHRMPower(false, SETDEVICEPOWER_FORCE);
|
|
}
|
|
#endif
|
|
#ifdef PRESSURE_DEVICE
|
|
//jsiConsolePrintf("Barometer %d %d\n",bangleFlags & JSBF_BAROMETER_ON, getDeviceRequested("Barom"));
|
|
if ((bangleFlags & JSBF_BAROMETER_ON) && !getDeviceRequested("Barom")) {
|
|
jswrap_banglejs_setBarometerPower(false, SETDEVICEPOWER_FORCE);
|
|
}
|
|
#endif
|
|
#ifdef MAG_I2C
|
|
//jsiConsolePrintf("Magnetometer %d %d\n",bangleFlags & JSBF_COMPASS_ON, getDeviceRequested("Compass"));
|
|
if ((bangleFlags & JSBF_COMPASS_ON) && !getDeviceRequested("Compass")) {
|
|
jswrap_banglejs_setCompassPower(false, SETDEVICEPOWER_FORCE);
|
|
}
|
|
#endif
|
|
#ifdef GPS_PIN_RX
|
|
//jsiConsolePrintf("GPS %d %d\n",bangleFlags & JSBF_GPS_ON, getDeviceRequested("GPS"));
|
|
if ((bangleFlags & JSBF_GPS_ON) && !getDeviceRequested("GPS")) {
|
|
jswrap_banglejs_setGPSPower(false, SETDEVICEPOWER_FORCE);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
NO_INLINE void jswrap_banglejs_setTheme() {
|
|
#if LCD_BPP==16
|
|
graphicsTheme.fg = GRAPHICS_COL_RGB_TO_16(255,255,255);
|
|
graphicsTheme.bg = GRAPHICS_COL_RGB_TO_16(0,0,0);
|
|
graphicsTheme.fg2 = GRAPHICS_COL_RGB_TO_16(255,255,255);
|
|
graphicsTheme.bg2 = GRAPHICS_COL_RGB_TO_16(0,0,63);
|
|
graphicsTheme.fgH = GRAPHICS_COL_RGB_TO_16(255,255,255);
|
|
graphicsTheme.bgH = GRAPHICS_COL_RGB_TO_16(0,95,190);
|
|
graphicsTheme.dark = true;
|
|
#else // still 16 bit, we just want it inverted
|
|
graphicsTheme.fg = GRAPHICS_COL_RGB_TO_16(0,0,0);
|
|
graphicsTheme.bg = GRAPHICS_COL_RGB_TO_16(255,255,255);
|
|
graphicsTheme.fg2 = GRAPHICS_COL_RGB_TO_16(0,0,0);
|
|
graphicsTheme.bg2 = GRAPHICS_COL_RGB_TO_16(191,255,255);
|
|
graphicsTheme.fgH = GRAPHICS_COL_RGB_TO_16(0,0,0);
|
|
graphicsTheme.bgH = GRAPHICS_COL_RGB_TO_16(0,255,255);
|
|
graphicsTheme.dark = false;
|
|
#endif
|
|
}
|
|
|
|
/*JSON{
|
|
"type" : "hwinit",
|
|
"generate" : "jswrap_banglejs_hwinit"
|
|
}*/
|
|
NO_INLINE void jswrap_banglejs_hwinit() {
|
|
// Hardware init that we only do the very first time we start
|
|
#ifdef BANGLEJS_F18
|
|
jshPinOutput(18,0); // what's this?
|
|
#endif
|
|
#ifdef ID205
|
|
jshPinOutput(3,1); // general VDD power?
|
|
jshPinOutput(46,0); // What's this? Who knows! But it stops screen flicker and makes the touchscreen work nicely
|
|
jshPinOutput(LCD_BL,1); // Backlight
|
|
#endif
|
|
#ifndef EMULATED
|
|
#ifdef NRF52832
|
|
jswrap_ble_setTxPower(4);
|
|
#endif
|
|
|
|
// Set up I2C
|
|
i2cBusy = true;
|
|
#ifdef BANGLEJS_Q3
|
|
jshI2CInitInfo(&i2cAccel);
|
|
i2cAccel.bitrate = 0x7FFFFFFF; // make it as fast as we can go
|
|
i2cAccel.pinSDA = ACCEL_PIN_SDA;
|
|
i2cAccel.pinSCL = ACCEL_PIN_SCL;
|
|
jsi2cSetup(&i2cAccel);
|
|
|
|
jshI2CInitInfo(&i2cMag);
|
|
i2cMag.bitrate = 0x7FFFFFFF; // make it as fast as we can go
|
|
i2cMag.pinSDA = MAG_PIN_SDA;
|
|
i2cMag.pinSCL = MAG_PIN_SCL;
|
|
jsi2cSetup(&i2cMag);
|
|
|
|
jshI2CInitInfo(&i2cTouch);
|
|
i2cTouch.bitrate = 0x7FFFFFFF; // make it as fast as we can go
|
|
i2cTouch.pinSDA = TOUCH_PIN_SDA;
|
|
i2cTouch.pinSCL = TOUCH_PIN_SCL;
|
|
jsi2cSetup(&i2cTouch);
|
|
|
|
jshI2CInitInfo(&i2cPressure);
|
|
i2cPressure.bitrate = 0x7FFFFFFF; // make it as fast as we can go
|
|
i2cPressure.pinSDA = PRESSURE_PIN_SDA;
|
|
i2cPressure.pinSCL = PRESSURE_PIN_SCL;
|
|
jsi2cSetup(&i2cPressure);
|
|
|
|
jshI2CInitInfo(&i2cHRM);
|
|
i2cHRM.bitrate = 0x7FFFFFFF; // make it as fast as we can go
|
|
i2cHRM.pinSDA = HEARTRATE_PIN_SDA;
|
|
i2cHRM.pinSCL = HEARTRATE_PIN_SCL;
|
|
//jsi2cSetup(&i2cHRM); // we configure when needed in jswrap_banglejs_pwrHRM so we don't parasitically power
|
|
|
|
#elif defined(ACCEL_PIN_SDA) // assume all the rest just use a global I2C
|
|
jshI2CInitInfo(&i2cInternal);
|
|
i2cInternal.bitrate = 0x7FFFFFFF; // make it as fast as we can go
|
|
i2cInternal.pinSDA = ACCEL_PIN_SDA;
|
|
i2cInternal.pinSCL = ACCEL_PIN_SCL;
|
|
i2cInternal.clockStretch = false;
|
|
jsi2cSetup(&i2cInternal);
|
|
#endif // BANGLEJS_Q3/ACCEL_PIN_SDA
|
|
#ifdef BANGLEJS_Q3
|
|
// Touch init
|
|
jshPinOutput(TOUCH_PIN_RST, 0);
|
|
jshDelayMicroseconds(1000);
|
|
jshPinOutput(TOUCH_PIN_RST, 1);
|
|
|
|
// Check pressure sensor
|
|
unsigned char buf[2];
|
|
// Check BMP280 ID
|
|
buf[0] = 0xD0; jsi2cWrite(PRESSURE_I2C, PRESSURE_ADDR, 1, buf, false); // ID
|
|
jsi2cRead(PRESSURE_I2C, PRESSURE_ADDR, 1, buf, true);
|
|
pressureBMP280Enabled = buf[0]==0x58;
|
|
// jsiConsolePrintf("BMP280 %d %d\n", buf[0], pressureBMP280Enabled);
|
|
// Check SPL07_001 ID
|
|
buf[0] = 0x0D; jsi2cWrite(PRESSURE_I2C, PRESSURE_ADDR, 1, buf, false); // ID
|
|
jsi2cRead(PRESSURE_I2C, PRESSURE_ADDR, 1, buf, true);
|
|
pressureSPL06Enabled = buf[0]==0x10;
|
|
// jsiConsolePrintf("SPL06 %d %d\n", buf[0], pressureSPL06Enabled);
|
|
#endif
|
|
#ifdef BANGLEJS_F18
|
|
jshDelayMicroseconds(10000);
|
|
// LCD pin init
|
|
jshPinOutput(LCD_PIN_CS, 1);
|
|
jshPinOutput(LCD_PIN_DC, 1);
|
|
jshPinOutput(LCD_PIN_SCK, 1);
|
|
for (int i=0;i<8;i++) jshPinOutput(i, 0);
|
|
// IO expander reset
|
|
jshPinOutput(28,0);
|
|
jshDelayMicroseconds(10000);
|
|
jshPinOutput(28,1);
|
|
jshDelayMicroseconds(50000);
|
|
jswrap_banglejs_ioWr(0,0);
|
|
jswrap_banglejs_pwrHRM(false); // HRM off
|
|
jswrap_banglejs_pwrGPS(false); // GPS off
|
|
jswrap_banglejs_ioWr(IOEXP_LCD_RESET,0); // LCD reset on
|
|
jshDelayMicroseconds(100000);
|
|
jswrap_banglejs_ioWr(IOEXP_LCD_RESET,1); // LCD reset off
|
|
jswrap_banglejs_pwrBacklight(true); // backlight on
|
|
jshDelayMicroseconds(10000);
|
|
#endif
|
|
#endif //EMULATED
|
|
// we need ESPR_GRAPHICS_INTERNAL=1
|
|
|
|
graphicsStructInit(&graphicsInternal, LCD_WIDTH, LCD_HEIGHT, LCD_BPP);
|
|
#ifdef LCD_CONTROLLER_LPM013M126
|
|
graphicsInternal.data.type = JSGRAPHICSTYPE_MEMLCD;
|
|
graphicsInternal.data.bpp = 16; // hack - so we can dither we pretend we're 16 bit
|
|
#endif
|
|
#ifdef LCD_CONTROLLER_ST7789_8BIT
|
|
graphicsInternal.data.type = JSGRAPHICSTYPE_ST7789_8BIT;
|
|
#endif
|
|
#if defined(LCD_CONTROLLER_ST7789V) || defined(LCD_CONTROLLER_ST7735) || defined(LCD_CONTROLLER_GC9A01)
|
|
graphicsInternal.data.type = JSGRAPHICSTYPE_SPILCD;
|
|
#endif
|
|
graphicsInternal.data.flags = 0;
|
|
#ifdef DTNO1_F5
|
|
graphicsInternal.data.flags = JSGRAPHICSFLAGS_INVERT_X | JSGRAPHICSFLAGS_INVERT_Y;
|
|
#endif
|
|
graphicsInternal.data.fontSize = JSGRAPHICS_FONTSIZE_6X8+1; // 4x6 size is default
|
|
#ifdef LCD_CONTROLLER_LPM013M126
|
|
lcdMemLCD_init(&graphicsInternal);
|
|
jswrap_banglejs_pwrBacklight(true);
|
|
#endif
|
|
#ifdef LCD_CONTROLLER_ST7789_8BIT
|
|
lcdST7789_init(&graphicsInternal);
|
|
#endif
|
|
#if defined(LCD_CONTROLLER_ST7789V) || defined(LCD_CONTROLLER_ST7735) || defined(LCD_CONTROLLER_GC9A01)
|
|
lcdInit_SPILCD(&graphicsInternal);
|
|
#endif
|
|
graphicsSetCallbacks(&graphicsInternal);
|
|
// set default graphics themes - before we even start to load settings.json
|
|
jswrap_banglejs_setTheme();
|
|
graphicsFillRect(&graphicsInternal, 0,0,LCD_WIDTH-1,LCD_HEIGHT-1,graphicsTheme.bg);
|
|
}
|
|
|
|
/*JSON{
|
|
"type" : "init",
|
|
"generate" : "jswrap_banglejs_init"
|
|
}*/
|
|
NO_INLINE void jswrap_banglejs_init() {
|
|
IOEventFlags channel;
|
|
bool firstRun = jsiStatus & JSIS_FIRST_BOOT; // is this the first time jswrap_banglejs_init was called?
|
|
|
|
#ifndef EMULATED
|
|
// turn vibrate off every time Bangle is reset
|
|
jshPinOutput(VIBRATE_PIN,0);
|
|
#endif
|
|
|
|
#ifdef BANGLEJS_Q3
|
|
#ifndef EMULATED
|
|
jshSetPinShouldStayWatched(TOUCH_PIN_IRQ,true);
|
|
channel = jshPinWatch(TOUCH_PIN_IRQ, true, JSPW_NONE);
|
|
if (channel!=EV_NONE) jshSetEventCallback(channel, touchHandler);
|
|
#endif
|
|
#endif
|
|
|
|
//jsiConsolePrintf("bangleFlags %d\n",bangleFlags);
|
|
if (firstRun) {
|
|
bangleFlags = JSBF_DEFAULT | JSBF_LCD_ON | JSBF_LCD_BL_ON; // includes bangleFlags
|
|
lcdBrightness = 255;
|
|
accDiff = 0;
|
|
healthStateClear(&healthCurrent);
|
|
healthStateClear(&healthLast);
|
|
healthStateClear(&healthDaily);
|
|
}
|
|
bangleFlags |= JSBF_POWER_SAVE; // ensure we turn power-save on by default every restart
|
|
inactivityTimer = 0; // reset the LCD timeout timer
|
|
btnLoadTimeout = DEFAULT_BTN_LOAD_TIMEOUT;
|
|
lcdPowerTimeout = DEFAULT_LCD_POWER_TIMEOUT;
|
|
backlightTimeout = DEFAULT_BACKLIGHT_TIMEOUT;
|
|
lockTimeout = DEFAULT_LOCK_TIMEOUT;
|
|
lcdWakeButton = 0;
|
|
// If the home button is still pressed when we're restarting, set up
|
|
// lcdWakeButton so the event for button release is 'eaten'
|
|
if (jshPinGetValue(HOME_BTN_PININDEX))
|
|
lcdWakeButton = HOME_BTN;
|
|
#ifdef ESPR_BACKLIGHT_FADE
|
|
realLcdBrightness = firstRun ? 0 : lcdBrightness;
|
|
lcdFadeHandlerActive = false;
|
|
jswrap_banglejs_setLCDPowerBacklight(bangleFlags & JSBF_LCD_BL_ON);
|
|
#endif
|
|
|
|
buzzAmt = 0;
|
|
beepFreq = 0;
|
|
// Read settings and change beep/buzz behaviour...
|
|
JsVar *settingsFN = jsvNewFromString("setting.json");
|
|
JsVar *settings = jswrap_storage_readJSON(settingsFN,true);
|
|
jsvUnLock(settingsFN);
|
|
JsVar *v;
|
|
v = jsvIsObject(settings) ? jsvObjectGetChild(settings,"beep",0) : 0;
|
|
if (v && jsvGetBool(v)==false) {
|
|
bangleFlags &= ~JSBF_ENABLE_BEEP;
|
|
} else {
|
|
bangleFlags |= JSBF_ENABLE_BEEP;
|
|
#ifdef SPEAKER_PIN
|
|
if (!v || jsvIsStringEqual(v,"vib")) // default to use vibration for beep
|
|
bangleFlags |= JSBF_BEEP_VIBRATE;
|
|
else
|
|
bangleFlags &= ~JSBF_BEEP_VIBRATE;
|
|
#else
|
|
bangleFlags |= JSBF_BEEP_VIBRATE;
|
|
#endif
|
|
}
|
|
jsvUnLock(v);
|
|
v = jsvIsObject(settings) ? jsvObjectGetChild(settings,"vibrate",0) : 0;
|
|
if (v && jsvGetBool(v)==false) {
|
|
bangleFlags &= ~JSBF_ENABLE_BUZZ;
|
|
} else {
|
|
bangleFlags |= JSBF_ENABLE_BUZZ;
|
|
}
|
|
jsvUnLock(v);
|
|
|
|
// If enabled, load battery 'full' voltage
|
|
#ifdef ESPR_BATTERY_FULL_VOLTAGE
|
|
batteryFullVoltage = ESPR_BATTERY_FULL_VOLTAGE;
|
|
v = jsvIsObject(settings) ? jsvObjectGetChild(settings,"batFullVoltage",0) : 0;
|
|
if (jsvIsNumeric(v)) batteryFullVoltage = jsvGetFloatAndUnLock(v);
|
|
#endif // ESPR_BATTERY_FULL_VOLTAGE
|
|
|
|
// Load themes from the settings.json file
|
|
jswrap_banglejs_setTheme();
|
|
v = jsvIsObject(settings) ? jsvObjectGetChild(settings,"theme",0) : 0;
|
|
if (jsvIsObject(v)) {
|
|
graphicsTheme.fg = jsvGetIntegerAndUnLock(jsvObjectGetChild(v,"fg",0));
|
|
graphicsTheme.bg = jsvGetIntegerAndUnLock(jsvObjectGetChild(v,"bg",0));
|
|
graphicsTheme.fg2 = jsvGetIntegerAndUnLock(jsvObjectGetChild(v,"fg2",0));
|
|
graphicsTheme.bg2 = jsvGetIntegerAndUnLock(jsvObjectGetChild(v,"bg2",0));
|
|
graphicsTheme.fgH = jsvGetIntegerAndUnLock(jsvObjectGetChild(v,"fgH",0));
|
|
graphicsTheme.bgH = jsvGetIntegerAndUnLock(jsvObjectGetChild(v,"bgH",0));
|
|
graphicsTheme.dark = jsvGetBoolAndUnLock(jsvObjectGetChild(v,"dark",0));
|
|
}
|
|
jsvUnLock(v);
|
|
#ifdef TOUCH_DEVICE
|
|
// load touchscreen calibration
|
|
v = jsvIsObject(settings) ? jsvObjectGetChild(settings,"touch",0) : 0;
|
|
if (jsvIsObject(v)) {
|
|
touchMinX = jsvGetIntegerAndUnLock(jsvObjectGetChild(v,"x1",0));
|
|
touchMinY = jsvGetIntegerAndUnLock(jsvObjectGetChild(v,"y1",0));
|
|
touchMaxX = jsvGetIntegerAndUnLock(jsvObjectGetChild(v,"x2",0));
|
|
touchMaxY = jsvGetIntegerAndUnLock(jsvObjectGetChild(v,"y2",0));
|
|
}
|
|
jsvUnLock(v);
|
|
#endif
|
|
jsvUnLock(settings);
|
|
|
|
#ifdef LCD_WIDTH
|
|
// Just reset any graphics settings that may need updating
|
|
jswrap_banglejs_setLCDOffset(0);
|
|
#ifdef LCD_CONTROLLER_ST7789_8BIT
|
|
graphicsInternal.data.width = LCD_WIDTH;
|
|
graphicsInternal.data.height = LCD_HEIGHT;
|
|
graphicsInternal.data.bpp = 16;
|
|
#endif
|
|
// Reset global graphics instance
|
|
graphicsStructResetState(&graphicsInternal);
|
|
|
|
// Create backing graphics object for LCD
|
|
JsVar *graphics = jspNewObject(0, "Graphics");
|
|
// if there's nothing in the Graphics object, we assume it's for the built-in graphics
|
|
if (!graphics) return; // low memory
|
|
// add it as a global var
|
|
jsvObjectSetChild(execInfo.root, "g", graphics);
|
|
jsvObjectSetChild(execInfo.hiddenRoot, JS_GRAPHICS_VAR, graphics);
|
|
graphicsInternal.graphicsVar = graphics;
|
|
|
|
// Create 'flip' fn
|
|
JsVar *fn = jsvNewNativeFunction((void (*)(void))lcd_flip, JSWAT_VOID|JSWAT_THIS_ARG|(JSWAT_BOOL << (JSWAT_BITS*1)));
|
|
jsvObjectSetChildAndUnLock(graphics,"flip",fn);
|
|
|
|
if (!firstRun) {
|
|
// Not first run - reset the LCD mode if it was set
|
|
#ifdef LCD_CONTROLLER_ST7789_8BIT
|
|
if (lcdST7789_getMode()!=LCDST7789_MODE_UNBUFFERED) {
|
|
lcdST7789_setMode( LCDST7789_MODE_UNBUFFERED );
|
|
// screen will now be garbled - clear it
|
|
graphicsClear(&graphicsInternal);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
bool showSplashScreen = true;
|
|
/* If we're doing a flash load, don't show
|
|
the logo because it'll just get overwritten
|
|
in a fraction of a second anyway */
|
|
if (jsiStatus & JSIS_TODO_FLASH_LOAD) {
|
|
showSplashScreen = false;
|
|
#ifndef ESPR_NO_LOADING_SCREEN
|
|
if (!firstRun) {
|
|
// Display a loading screen
|
|
int x = LCD_WIDTH/2;
|
|
int y = LCD_HEIGHT/2;
|
|
graphicsFillRect(&graphicsInternal, x-49, y-19, x+49, y+19, graphicsTheme.bg);
|
|
graphicsInternal.data.fgColor = graphicsTheme.fg;
|
|
graphicsDrawRect(&graphicsInternal, x-50, y-20, x+50, y+20);
|
|
y -= 4;
|
|
x -= 4*6;
|
|
const char *s = "Loading...";
|
|
while (*s) {
|
|
graphicsDrawChar6x8(&graphicsInternal, x, y, *s, 1, 1, false);
|
|
x+=6;
|
|
s++;
|
|
}
|
|
graphicsInternalFlip();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#ifdef DICKENS
|
|
// don't show splash screen unless the watch has been totally reset - stops flicker on boot
|
|
if (!(jsiStatus & JSIS_COMPLETELY_RESET))
|
|
showSplashScreen = false;
|
|
#endif
|
|
if (showSplashScreen) {
|
|
graphicsInternal.data.fontSize = JSGRAPHICS_FONTSIZE_6X8+1; // 4x6 size is default
|
|
graphicsClear(&graphicsInternal);
|
|
bool drawInfo = false;
|
|
JsVar *img = jsfReadFile(jsfNameFromString(".splash"),0,0);
|
|
int w,h;
|
|
if (!jsvIsString(img) || !jsvGetStringLength(img)) {
|
|
jsvUnLock(img);
|
|
drawInfo = true;
|
|
img = jswrap_banglejs_getLogo();
|
|
}
|
|
w = (int)(unsigned char)jsvGetCharInString(img, 0);
|
|
h = (int)(unsigned char)jsvGetCharInString(img, 1);
|
|
int y=(LCD_HEIGHT-h)/2;
|
|
jsvUnLock2(jswrap_graphics_drawImage(graphics,img,(LCD_WIDTH-w)/2,y,NULL),img);
|
|
if (drawInfo) {
|
|
if (h > 56) y += h-28;
|
|
else y += h-15;
|
|
char addrStr[20];
|
|
#ifndef EMULATED
|
|
JsVar *addr = jswrap_ble_getAddress(); // Write MAC address in bottom right
|
|
#else
|
|
JsVar *addr = jsvNewFromString("Emulated");
|
|
#endif
|
|
jsvGetString(addr, addrStr, sizeof(addrStr));
|
|
jsvUnLock(addr);
|
|
jswrap_graphics_drawCString(&graphicsInternal,8,y,JS_VERSION);
|
|
jswrap_graphics_drawCString(&graphicsInternal,8,y+10,addrStr);
|
|
jswrap_graphics_drawCString(&graphicsInternal,8,y+20,"Copyright 2021 G.Williams");
|
|
}
|
|
}
|
|
graphicsInternalFlip();
|
|
graphicsStructResetState(&graphicsInternal);
|
|
// no need to unlock graphics as we stored it in 'graphicsVar'
|
|
#endif
|
|
|
|
if (firstRun) {
|
|
unsigned char buf[2];
|
|
#ifdef ACCEL_DEVICE_KX023
|
|
// KX023-1025 accelerometer init
|
|
jswrap_banglejs_accelWr(0x18,0x0a); // CNTL1 Off (top bit)
|
|
jswrap_banglejs_accelWr(0x19,0x80); // CNTL2 Software reset
|
|
buf[0] = 0x19; buf[1] = 0x80; // Second I2C address for software reset (issue #1972)
|
|
jsi2cWrite(ACCEL_I2C, ACCEL_ADDR-2, 2, buf, true);
|
|
jshDelayMicroseconds(2000);
|
|
jswrap_banglejs_accelWr(0x1a,0b10011000); // CNTL3 12.5Hz tilt, 400Hz tap, 0.781Hz motion detection
|
|
//jswrap_banglejs_accelWr(0x1b,0b00000001); // ODCNTL - 25Hz acceleration output data rate, filtering low-pass ODR/9
|
|
jswrap_banglejs_accelWr(0x1b,0b00000000); // ODCNTL - 12.5Hz acceleration output data rate, filtering low-pass ODR/9
|
|
|
|
jswrap_banglejs_accelWr(0x1c,0); // INC1 disabled
|
|
jswrap_banglejs_accelWr(0x1d,0); // INC2 disabled
|
|
jswrap_banglejs_accelWr(0x1e,0x3F); // INC3 enable tap detect in all 6 directions
|
|
jswrap_banglejs_accelWr(0x1f,0); // INC4 disabled
|
|
jswrap_banglejs_accelWr(0x20,0); // INC5 disabled
|
|
jswrap_banglejs_accelWr(0x21,0); // INC6 disabled
|
|
jswrap_banglejs_accelWr(0x23,3); // WUFC wakeupi detect counter
|
|
jswrap_banglejs_accelWr(0x24,3); // TDTRC Tap detect enable
|
|
jswrap_banglejs_accelWr(0x25, 0x78); // TDTC Tap detect double tap (0x78 default)
|
|
jswrap_banglejs_accelWr(0x26, 0xCB); // TTH Tap detect threshold high (0xCB default)
|
|
jswrap_banglejs_accelWr(0x27, 0x1A); // TTL Tap detect threshold low (0x1A default)
|
|
jswrap_banglejs_accelWr(0x30,1); // ATH low wakeup detect threshold
|
|
//jswrap_banglejs_accelWr(0x35,0 << 4); // LP_CNTL no averaging of samples
|
|
jswrap_banglejs_accelWr(0x35,2 << 4); // LP_CNTL 4x averaging of samples
|
|
jswrap_banglejs_accelWr(0x3e,0); // clear the buffer
|
|
jswrap_banglejs_accelWr(0x18,0b00101100); // CNTL1 Off, low power, DRDYE=1, 4g range, TDTE (tap enable)=1, Wakeup=0, Tilt=0
|
|
jswrap_banglejs_accelWr(0x18,0b10101100); // CNTL1 On, low power, DRDYE=1, 4g range, TDTE (tap enable)=1, Wakeup=0, Tilt=0
|
|
// high power vs low power uses an extra 150uA
|
|
#endif
|
|
#ifdef ACCEL_DEVICE_KXTJ3_1057
|
|
// KXTJ3-1057 accelerometer init
|
|
jswrap_banglejs_accelWr(0x1B,0b00101000); // CNTL1 Off (top bit)
|
|
jswrap_banglejs_accelWr(0x1D,0x80); // CNTL2 Software reset
|
|
jshDelayMicroseconds(2000);
|
|
jswrap_banglejs_accelWr(0x21,0); // DATA_CTRL_REG - 12.5Hz out
|
|
jswrap_banglejs_accelWr(0x1B,0b00101000); // CNTL1 Off (top bit), low power, DRDYE=1, 4g, Wakeup=0,
|
|
jswrap_banglejs_accelWr(0x1B,0b10101000); // CNTL1 On (top bit), low power, DRDYE=1, 4g, Wakeup=0,
|
|
#endif
|
|
#ifdef ACCEL_DEVICE_KX126
|
|
// KX126_1063 accelerometer init
|
|
jswrap_banglejs_accelWr(KX126_CNTL1,0x00); // CNTL1 standby mode (top bit)
|
|
jswrap_banglejs_accelWr(KX126_CNTL2,KX126_CNTL2_SRST); // CNTL2 Software reset (top bit)
|
|
jshDelayMicroseconds(2000);
|
|
jswrap_banglejs_accelWr(KX126_CNTL3,KX126_CNTL3_OTP_12P5|KX126_CNTL3_OTDT_400|KX126_CNTL3_OWUF_0P781); // CNTL3 12.5Hz tilt, 400Hz tap, 0.781Hz motion detection
|
|
jswrap_banglejs_accelWr(KX126_ODCNTL,KX126_ODCNTL_OSA_12P5); // ODCNTL - 12.5Hz output data rate (ODR), with low-pass filter set to ODR/9
|
|
jswrap_banglejs_accelWr(KX126_INC1,0); // INC1 - interrupt output pin INT1 disabled
|
|
jswrap_banglejs_accelWr(KX126_INC2,0); // INC2 - wake-up & back-to-sleep ignores all 3 axes
|
|
jswrap_banglejs_accelWr(KX126_INC3,0); // INC3 - tap detection ignores all 3 axes
|
|
jswrap_banglejs_accelWr(KX126_INC4,0); // INC4 - no routing of interrupt reporting to pin INT1
|
|
jswrap_banglejs_accelWr(KX126_INC5,0); // INC5 - interrupt output pin INT2 disabled
|
|
jswrap_banglejs_accelWr(KX126_INC6,0); // INC6 - no routing of interrupt reporting to pin INT2
|
|
jswrap_banglejs_accelWr(KX126_INC7,0); // INC7 - no step counter interrupts reported on INT1 or INT2
|
|
jswrap_banglejs_accelWr(KX126_BUF_CLEAR,0); // clear the buffer
|
|
|
|
jswrap_banglejs_accelWr(KX126_CNTL1,KX126_CNTL1_DRDYE|KX126_CNTL1_GSEL_4G); // CNTL1 - standby mode, low power, enable "data ready" interrupt, 4g, disable tap, tilt & pedometer (for now)
|
|
jswrap_banglejs_accelWr(KX126_CNTL1,KX126_CNTL1_DRDYE|KX126_CNTL1_GSEL_4G|KX126_CNTL1_PC1); // CNTL1 - same as above but change from standby to operating mode
|
|
#endif
|
|
|
|
#ifdef PRESSURE_DEVICE
|
|
#ifdef PRESSURE_DEVICE_HP203_EN
|
|
if (PRESSURE_DEVICE_HP203_EN) {
|
|
// pressure init
|
|
buf[0]=0x06;
|
|
jsi2cWrite(PRESSURE_I2C, PRESSURE_ADDR, 1, buf, true); // SOFT_RST
|
|
}
|
|
#endif
|
|
#ifdef PRESSURE_DEVICE_SPL06_007_EN
|
|
if (PRESSURE_DEVICE_SPL06_007_EN) {
|
|
// pressure init
|
|
buf[0]=SPL06_RESET; buf[1]=0x89;
|
|
jsi2cWrite(PRESSURE_I2C, PRESSURE_ADDR, 1, buf, true); // SOFT_RST
|
|
}
|
|
#endif
|
|
#ifdef PRESSURE_DEVICE_BMP280_EN
|
|
if (PRESSURE_DEVICE_BMP280_EN) {
|
|
buf[0]=0xE0; buf[1]=0xB6;
|
|
jsi2cWrite(PRESSURE_I2C, PRESSURE_ADDR, 1, buf, true); // reset
|
|
}
|
|
#endif
|
|
bangleFlags &= ~JSBF_BAROMETER_ON;
|
|
#endif // PRESSURE_DEVICE
|
|
|
|
// Accelerometer variables init
|
|
stepcount_init();
|
|
stepCounter = 0;
|
|
#ifdef MAG_I2C
|
|
#ifdef MAG_DEVICE_GMC303
|
|
// compass init
|
|
jswrap_banglejs_compassWr(0x32,1); // soft reset
|
|
jswrap_banglejs_compassWr(0x31,0); // power down mode
|
|
#endif
|
|
bangleFlags &= ~JSBF_COMPASS_ON;
|
|
// ensure compass readings are reset to power-on state
|
|
jswrap_banglejs_resetCompass();
|
|
#endif
|
|
// Touchscreen gesture detection
|
|
#if ESPR_BANGLE_UNISTROKE
|
|
unistroke_init();
|
|
#endif
|
|
} // firstRun
|
|
|
|
i2cBusy = false;
|
|
// Other IO
|
|
#ifdef BAT_PIN_CHARGING
|
|
jshPinSetState(BAT_PIN_CHARGING, JSHPINSTATE_GPIO_IN_PULLUP);
|
|
#endif
|
|
// touch
|
|
touchStatus = TS_NONE;
|
|
touchLastState = 0;
|
|
touchLastState2 = 0;
|
|
#ifdef HEARTRATE
|
|
if (firstRun)
|
|
hrm_init();
|
|
hrm_sensor_init();
|
|
#endif
|
|
|
|
#ifndef EMULATED
|
|
// Add watchdog timer to ensure watch always stays usable (hopefully!)
|
|
// This gets killed when _kill / _init happens
|
|
// - the bootloader probably already set this up so the
|
|
// enable will do nothing - but good to try anyway
|
|
jshEnableWatchDog(5); // 5 second watchdog
|
|
// This timer kicks the watchdog, and does some other stuff as well
|
|
pollInterval = DEFAULT_ACCEL_POLL_INTERVAL;
|
|
// requires APP_TIMER_OP_QUEUE_SIZE=5 in BOARD.py
|
|
uint32_t err_code = app_timer_create(&m_peripheral_poll_timer_id,
|
|
APP_TIMER_MODE_REPEATED,
|
|
peripheralPollHandler);
|
|
jsble_check_error(err_code);
|
|
#if NRF_SD_BLE_API_VERSION<5
|
|
app_timer_start(m_peripheral_poll_timer_id, APP_TIMER_TICKS(pollInterval, APP_TIMER_PRESCALER), NULL);
|
|
#else
|
|
app_timer_start(m_peripheral_poll_timer_id, APP_TIMER_TICKS(pollInterval), NULL);
|
|
#endif
|
|
#ifdef BANGLEJS_F18
|
|
// Backlight PWM
|
|
err_code = app_timer_create(&m_backlight_on_timer_id,
|
|
APP_TIMER_MODE_REPEATED,
|
|
backlightOnHandler);
|
|
jsble_check_error(err_code);
|
|
err_code = app_timer_create(&m_backlight_off_timer_id,
|
|
APP_TIMER_MODE_SINGLE_SHOT,
|
|
backlightOffHandler);
|
|
jsble_check_error(err_code);
|
|
#endif
|
|
#endif // EMULATED
|
|
|
|
#ifdef BANGLEJS_Q3
|
|
jshSetPinShouldStayWatched(BTN1_PININDEX,true);
|
|
channel = jshPinWatch(BTN1_PININDEX, true, JSPW_NONE);
|
|
if (channel!=EV_NONE) jshSetEventCallback(channel, btn1Handler);
|
|
#else
|
|
jshSetPinShouldStayWatched(BTN1_PININDEX,true);
|
|
jshSetPinShouldStayWatched(BTN2_PININDEX,true);
|
|
channel = jshPinWatch(BTN1_PININDEX, true, JSPW_NONE);
|
|
if (channel!=EV_NONE) jshSetEventCallback(channel, btn1Handler);
|
|
channel = jshPinWatch(BTN2_PININDEX, true, JSPW_NONE);
|
|
if (channel!=EV_NONE) jshSetEventCallback(channel, btn2Handler);
|
|
#ifdef BTN3_PININDEX
|
|
jshSetPinShouldStayWatched(BTN3_PININDEX,true);
|
|
channel = jshPinWatch(BTN3_PININDEX, true, JSPW_NONE);
|
|
if (channel!=EV_NONE) jshSetEventCallback(channel, btn3Handler);
|
|
#endif
|
|
#ifdef BTN4_PININDEX
|
|
jshSetPinShouldStayWatched(BTN4_PININDEX,true);
|
|
channel = jshPinWatch(BTN4_PININDEX, true, JSPW_NONE);
|
|
if (channel!=EV_NONE) jshSetEventCallback(channel, btn4Handler);
|
|
#endif
|
|
#ifdef BTN5_PININDEX
|
|
jshSetPinShouldStayWatched(BTN5_PININDEX,true);
|
|
channel = jshPinWatch(BTN5_PININDEX, true, JSPW_NONE);
|
|
if (channel!=EV_NONE) jshSetEventCallback(channel, btn5Handler);
|
|
#endif
|
|
#endif
|
|
|
|
/* If this isn't our first run, schedule this function 500ms
|
|
* after everything is loaded. It'll then check whether any
|
|
* peripherals got left on that should now be off, and will
|
|
* shut them down if needed. This allows things like the
|
|
* magnetometer to keep calibration, as well as stopping
|
|
* resets of GPS/etc when swapping between apps.
|
|
*/
|
|
if (!firstRun) {
|
|
jsvUnLock(jsiSetTimeout(jswrap_banglejs_postInit, 500));
|
|
}
|
|
//jsiConsolePrintf("bangleFlags2 %d\n",bangleFlags);
|
|
}
|
|
|
|
/*JSON{
|
|
"type" : "kill",
|
|
"generate" : "jswrap_banglejs_kill"
|
|
}*/
|
|
void jswrap_banglejs_kill() {
|
|
#ifndef EMULATED
|
|
#ifdef BANGLEJS_F18
|
|
app_timer_stop(m_backlight_on_timer_id);
|
|
app_timer_stop(m_backlight_off_timer_id);
|
|
#endif
|
|
app_timer_stop(m_peripheral_poll_timer_id);
|
|
#endif
|
|
#ifdef HEARTRATE
|
|
hrm_sensor_kill();
|
|
#endif
|
|
#ifdef ESPR_BACKLIGHT_FADE
|
|
if (lcdFadeHandlerActive) {
|
|
jstStopExecuteFn(backlightFadeHandler, NULL);
|
|
lcdFadeHandlerActive = false;
|
|
}
|
|
#endif
|
|
// stop and unlock beep & buzz
|
|
jsvUnLock(promiseBeep);
|
|
promiseBeep = 0;
|
|
jsvUnLock(promiseBuzz);
|
|
promiseBuzz = 0;
|
|
if (beepFreq) jswrap_banglejs_beep_callback();
|
|
if (buzzAmt) jswrap_banglejs_buzz_callback();
|
|
#ifdef PRESSURE_DEVICE
|
|
jsvUnLock(promisePressure);
|
|
promisePressure = 0;
|
|
#endif
|
|
|
|
jshPinWatch(BTN1_PININDEX, false, JSPW_NONE);
|
|
jshSetPinShouldStayWatched(BTN1_PININDEX,false);
|
|
#ifdef BTN2_PININDEX
|
|
jshPinWatch(BTN2_PININDEX, false, JSPW_NONE);
|
|
jshSetPinShouldStayWatched(BTN2_PININDEX,false);
|
|
#endif
|
|
#ifdef BTN3_PININDEX
|
|
jshPinWatch(BTN3_PININDEX, false, JSPW_NONE);
|
|
jshSetPinShouldStayWatched(BTN3_PININDEX,false);
|
|
#endif
|
|
#ifdef BTN4_PININDEX
|
|
jshSetPinShouldStayWatched(BTN4_PININDEX,false);
|
|
jshPinWatch(BTN4_PININDEX, false, JSPW_NONE);
|
|
#endif
|
|
#ifdef BTN5_PININDEX
|
|
jshPinWatch(BTN5_PININDEX, false, JSPW_NONE);
|
|
jshSetPinShouldStayWatched(BTN5_PININDEX,false);
|
|
#endif
|
|
#ifdef LCD_CONTROLLER_LPM013M126
|
|
// ensure we remove any overlay we might have set
|
|
lcdMemLCD_setOverlay(NULL, 0, 0);
|
|
#endif
|
|
// Graphics var is getting removed, so set this to null.
|
|
jsvUnLock(graphicsInternal.graphicsVar);
|
|
graphicsInternal.graphicsVar = NULL;
|
|
}
|
|
|
|
/*JSON{
|
|
"type" : "idle",
|
|
"generate" : "jswrap_banglejs_idle"
|
|
}*/
|
|
bool jswrap_banglejs_idle() {
|
|
JsVar *bangle =jsvObjectGetChild(execInfo.root, "Bangle", 0);
|
|
/* Check if we have an accelerometer listener, and set JSBF_ACCEL_LISTENER
|
|
* accordingly - so we don't get a wakeup if we have no listener. */
|
|
if (jsiObjectHasCallbacks(bangle, JS_EVENT_PREFIX"accel"))
|
|
bangleFlags |= JSBF_ACCEL_LISTENER;
|
|
else
|
|
bangleFlags &= ~JSBF_ACCEL_LISTENER;
|
|
#ifdef HEARTRATE
|
|
if (jsiObjectHasCallbacks(bangle, JS_EVENT_PREFIX"HRM-raw"))
|
|
bangleFlags |= JSBF_HRM_INSTANT_LISTENER;
|
|
else
|
|
bangleFlags &= ~JSBF_HRM_INSTANT_LISTENER;
|
|
#endif
|
|
|
|
if (!bangle) {
|
|
bangleTasks = JSBT_NONE;
|
|
}
|
|
if (bangleTasks != JSBT_NONE) {
|
|
if (bangleTasks & JSBT_LCD_OFF) jswrap_banglejs_setLCDPower(0);
|
|
if (bangleTasks & JSBT_LCD_ON) jswrap_banglejs_setLCDPower(1);
|
|
if (bangleTasks & JSBT_LCD_BL_OFF) jswrap_banglejs_setLCDPowerBacklight(0);
|
|
if (bangleTasks & JSBT_LCD_BL_ON) jswrap_banglejs_setLCDPowerBacklight(1);
|
|
if (bangleTasks & JSBT_LOCK) jswrap_banglejs_setLocked(1);
|
|
if (bangleTasks & JSBT_UNLOCK) jswrap_banglejs_setLocked(0);
|
|
if (bangleTasks & JSBT_RESET) jsiStatus |= JSIS_TODO_FLASH_LOAD;
|
|
if (bangleTasks & JSBT_ACCEL_INTERVAL_DEFAULT) jswrap_banglejs_setPollInterval_internal(DEFAULT_ACCEL_POLL_INTERVAL);
|
|
if (bangleTasks & JSBT_ACCEL_INTERVAL_POWERSAVE) jswrap_banglejs_setPollInterval_internal(POWER_SAVE_ACCEL_POLL_INTERVAL);
|
|
if (bangleTasks & JSBT_ACCEL_DATA) {
|
|
JsVar *o = jswrap_banglejs_getAccel();
|
|
if (o) {
|
|
jsiQueueObjectCallbacks(bangle, JS_EVENT_PREFIX"accel", &o, 1);
|
|
jsvUnLock(o);
|
|
}
|
|
}
|
|
if (bangleTasks & JSBT_ACCEL_TAPPED) {
|
|
JsVar *o = jsvNewObject();
|
|
if (o) {
|
|
const char *string="";
|
|
#ifdef BANGLEJS_Q3
|
|
if (tapInfo&2) string="front";
|
|
if (tapInfo&1) string="back";
|
|
if (tapInfo&8) string="bottom";
|
|
if (tapInfo&4) string="top";
|
|
#else
|
|
if (tapInfo&1) string="front";
|
|
if (tapInfo&2) string="back";
|
|
if (tapInfo&4) string="bottom";
|
|
if (tapInfo&8) string="top";
|
|
#endif
|
|
|
|
if (tapInfo&16) string="right";
|
|
if (tapInfo&32) string="left";
|
|
int n = (tapInfo&0x80)?2:1;
|
|
jsvObjectSetChildAndUnLock(o, "dir", jsvNewFromString(string));
|
|
jsvObjectSetChildAndUnLock(o, "double", jsvNewFromBool(tapInfo&0x80));
|
|
jsvObjectSetChildAndUnLock(o, "x", jsvNewFromInteger((tapInfo&16)?-n:(tapInfo&32)?n:0));
|
|
jsvObjectSetChildAndUnLock(o, "y", jsvNewFromInteger((tapInfo&4)?-n:(tapInfo&8)?n:0));
|
|
jsvObjectSetChildAndUnLock(o, "z", jsvNewFromInteger((tapInfo&1)?-n:(tapInfo&2)?n:0));
|
|
jsiQueueObjectCallbacks(bangle, JS_EVENT_PREFIX"tap", &o, 1);
|
|
jsvUnLock(o);
|
|
}
|
|
}
|
|
#ifdef PRESSURE_DEVICE
|
|
if (bangleTasks & JSBT_PRESSURE_DATA) {
|
|
JsVar *o = jswrap_banglejs_getBarometerObject();
|
|
if (o) {
|
|
jsiQueueObjectCallbacks(bangle, JS_EVENT_PREFIX"pressure", &o, 1);
|
|
jsvUnLock(o);
|
|
}
|
|
}
|
|
#endif
|
|
#ifdef GPS_PIN_RX
|
|
if (bangleTasks & JSBT_GPS_DATA) {
|
|
JsVar *o = nmea_to_jsVar(&gpsFix);
|
|
if (o) {
|
|
jsiQueueObjectCallbacks(bangle, JS_EVENT_PREFIX"GPS", &o, 1);
|
|
jsvUnLock(o);
|
|
}
|
|
}
|
|
if (bangleTasks & JSBT_GPS_DATA_PARTIAL) {
|
|
if (jsiObjectHasCallbacks(bangle, JS_EVENT_PREFIX"GPS-raw")) {
|
|
JsVar *data = jsvObjectGetChild(bangle,"_gpsdata",0);
|
|
if (!data) {
|
|
data = jsvNewFromEmptyString();
|
|
jsvObjectSetChild(bangle,"_gpsdata",data);
|
|
}
|
|
jsvAppendStringBuf(data, gpsLastLine, gpsLastLineLength);
|
|
jsvUnLock(data);
|
|
}
|
|
}
|
|
if (bangleTasks & JSBT_GPS_DATA_LINE) {
|
|
if (jsiObjectHasCallbacks(bangle, JS_EVENT_PREFIX"GPS-raw")) {
|
|
|
|
// Get any data previously added with JSBT_GPS_DATA_PARTIAL
|
|
JsVar *line = jsvObjectGetChild(bangle,"_gpsdata",0);
|
|
if (line) {
|
|
jsvObjectRemoveChild(bangle,"_gpsdata");
|
|
jsvAppendStringBuf(line, gpsLastLine, gpsLastLineLength);
|
|
} else line = jsvNewStringOfLength(gpsLastLineLength, gpsLastLine);
|
|
// if we have any data, queue it
|
|
if (line) {
|
|
// if GPS data has overflowed, second arg is true
|
|
JsVar *dataLoss = jsvNewFromBool(bangleTasks & JSBT_GPS_DATA_OVERFLOW);
|
|
JsVar *args[2] = { line, dataLoss };
|
|
jsiQueueObjectCallbacks(bangle, JS_EVENT_PREFIX"GPS-raw", args, 2);
|
|
jsvUnLock(dataLoss);
|
|
}
|
|
jsvUnLock(line);
|
|
} else {
|
|
jsvObjectRemoveChild(bangle,"_gpsdata");
|
|
}
|
|
}
|
|
#endif
|
|
if (bangleTasks & JSBT_MAG_DATA) {
|
|
if (bangle && jsiObjectHasCallbacks(bangle, JS_EVENT_PREFIX"mag")) {
|
|
JsVar *o = jswrap_banglejs_getCompass();
|
|
if (o) {
|
|
jsiQueueObjectCallbacks(bangle, JS_EVENT_PREFIX"mag", &o, 1);
|
|
jsvUnLock(o);
|
|
}
|
|
}
|
|
}
|
|
#ifdef HEARTRATE
|
|
if (bangleTasks & JSBT_HRM_INSTANT_DATA) {
|
|
JsVar *o = hrm_sensor_getJsVar();
|
|
if (o) {
|
|
jsvObjectSetChildAndUnLock(o,"raw",jsvNewFromInteger(hrmInfo.raw));
|
|
jsvObjectSetChildAndUnLock(o,"filt",jsvNewFromInteger(hrmInfo.filtered));
|
|
jsvObjectSetChildAndUnLock(o,"avg",jsvNewFromInteger(hrmInfo.avg));
|
|
jsvObjectSetChildAndUnLock(o,"isBeat",jsvNewFromBool(hrmInfo.isBeat));
|
|
jsvObjectSetChildAndUnLock(o,"bpm",jsvNewFromFloat(hrmInfo.bpm10 / 10.0));
|
|
jsvObjectSetChildAndUnLock(o,"confidence",jsvNewFromInteger(hrmInfo.confidence));
|
|
jsiQueueObjectCallbacks(bangle, JS_EVENT_PREFIX"HRM-raw", &o, 1);
|
|
jsvUnLock(o);
|
|
}
|
|
}
|
|
if (bangleTasks & JSBT_HRM_DATA) {
|
|
JsVar *o = jsvNewObject();
|
|
if (o) {
|
|
jsvObjectSetChildAndUnLock(o,"bpm",jsvNewFromInteger(hrmInfo.bpm10 / 10.0));
|
|
jsvObjectSetChildAndUnLock(o,"confidence",jsvNewFromInteger(hrmInfo.confidence));
|
|
JsVar *a = jsvNewEmptyArray();
|
|
if (a) {
|
|
int n = hrmInfo.timeIdx;
|
|
for (int i=0;i<HRM_HIST_LEN;i++) {
|
|
jsvArrayPushAndUnLock(a, jsvNewFromFloat(hrm_time_to_bpm10(hrmInfo.times[n]) / 10.0));
|
|
n++;
|
|
if (n==HRM_HIST_LEN) n=0;
|
|
}
|
|
jsvObjectSetChildAndUnLock(o,"history",a);
|
|
}
|
|
jsiQueueObjectCallbacks(bangle, JS_EVENT_PREFIX"HRM", &o, 1);
|
|
jsvUnLock(o);
|
|
}
|
|
}
|
|
#endif
|
|
if (bangleTasks & JSBT_HEALTH) {
|
|
JsVar *o = _jswrap_banglejs_getHealthStatusObject(&healthLast);
|
|
if (o) {
|
|
jsiQueueObjectCallbacks(bangle, JS_EVENT_PREFIX"health", &o, 1);
|
|
jsvUnLock(o);
|
|
}
|
|
}
|
|
if (bangleTasks & JSBT_MIDNIGHT) {
|
|
jsiQueueObjectCallbacks(bangle, JS_EVENT_PREFIX"midnight", NULL, 0);
|
|
}
|
|
if (bangleTasks & JSBT_GESTURE_DATA) {
|
|
if (jsiObjectHasCallbacks(bangle, JS_EVENT_PREFIX"gesture")) {
|
|
JsVar *arr = jsvNewTypedArray(ARRAYBUFFERVIEW_INT8, accGestureRecordedCount*3);
|
|
if (arr) {
|
|
int idx = accHistoryIdx - (accGestureRecordedCount*3);
|
|
while (idx<0) idx+=sizeof(accHistory);
|
|
JsvArrayBufferIterator it;
|
|
jsvArrayBufferIteratorNew(&it, arr, 0);
|
|
for (int i=0;i<accGestureRecordedCount*3;i++) {
|
|
jsvArrayBufferIteratorSetByteValue(&it, accHistory[idx++]);
|
|
jsvArrayBufferIteratorNext(&it);
|
|
if (idx>=(int)sizeof(accHistory)) idx-=sizeof(accHistory);
|
|
}
|
|
jsvArrayBufferIteratorFree(&it);
|
|
jsiQueueObjectCallbacks(bangle, JS_EVENT_PREFIX"gesture", &arr, 1);
|
|
jsvUnLock(arr);
|
|
}
|
|
}
|
|
#ifdef USE_TENSORFLOW
|
|
if (jsiObjectHasCallbacks(bangle, JS_EVENT_PREFIX"aiGesture")) {
|
|
//JsVar *model = jsfReadFile(jsfNameFromString(".tfmodel"),0,0);
|
|
JsfFileHeader header;
|
|
uint32_t modelAddr = jsfFindFile(jsfNameFromString(".tfmodel"), &header);
|
|
|
|
if (!modelAddr) {
|
|
jsiConsolePrintf("TF error - no model\n");
|
|
} else {
|
|
// allocate the model on the stack rather than using ReadFile
|
|
// as that will save us a few JsVars
|
|
size_t modelSize = jsfGetFileSize(&header);
|
|
char *modelBuf = alloca(modelSize);
|
|
jshFlashRead(modelBuf, modelAddr, modelSize);
|
|
JsVar *model = jsvNewNativeString(modelBuf, modelSize);
|
|
|
|
// delete command history and run a GC pass to try and free up some space
|
|
while (jsiFreeMoreMemory());
|
|
jsvGarbageCollect();
|
|
JsVar *tf = jswrap_tensorflow_create(4000, model);
|
|
jsvUnLock(model);
|
|
if (!tf) {
|
|
//jsiConsolePrintf("TF error - no memory\n");
|
|
// we get an exception anyway
|
|
} else {
|
|
//jsiConsolePrintf("TF in\n");
|
|
JsVar *v = jswrap_tfmicrointerpreter_getInput(tf);
|
|
JsvArrayBufferIterator it;
|
|
jsvArrayBufferIteratorNew(&it, v, 0);
|
|
int idx = accHistoryIdx - (accGestureRecordedCount*3);
|
|
while (idx<0) idx+=sizeof(accHistory);
|
|
for (int i=0;i<accGestureRecordedCount*3;i++) {
|
|
jsvArrayBufferIteratorSetIntegerValue(&it, accHistory[idx++]);
|
|
jsvArrayBufferIteratorNext(&it);
|
|
if (idx>=(int)sizeof(accHistory)) idx-=sizeof(accHistory);
|
|
}
|
|
jsvArrayBufferIteratorFree(&it);
|
|
jsvUnLock(v);
|
|
//jsiConsolePrintf("TF invoke\n");
|
|
jswrap_tfmicrointerpreter_invoke(tf);
|
|
//jsiConsolePrintf("TF out\n");
|
|
v = jswrap_tfmicrointerpreter_getOutput(tf);
|
|
JsVar *arr = jswrap_array_slice(v,0,0); // clone, so it's not referencing all of Tensorflow!
|
|
jsvUnLock2(v,tf);
|
|
//jsiConsolePrintf("TF queue\n");
|
|
JsVar *gesture = jspExecuteJSFunction("(function(a) {"
|
|
"var m=0,g;"
|
|
"for (var i in a) if (a[i]>m) { m=a[i];g=i; }"
|
|
"if (g!==undefined) {"
|
|
"var n=require('Storage').read('.tfnames');"
|
|
"if (n) g=n.split(',')[g];"
|
|
"}"
|
|
"return g;})",NULL,1,&arr);
|
|
JsVar *args[2] = {gesture,arr};
|
|
jsiQueueObjectCallbacks(bangle, JS_EVENT_PREFIX"aiGesture", args, 2);
|
|
jsvUnLock2(gesture,arr);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
if (bangleTasks & JSBT_CHARGE_EVENT) {
|
|
JsVar *charging = jsvNewFromBool(wasCharging);
|
|
jsiQueueObjectCallbacks(bangle, JS_EVENT_PREFIX"charging", &charging, 1);
|
|
jsvUnLock(charging);
|
|
}
|
|
if (bangleTasks & JSBT_STEP_EVENT) {
|
|
JsVar *steps = jsvNewFromInteger(stepCounter);
|
|
jsiQueueObjectCallbacks(bangle, JS_EVENT_PREFIX"step", &steps, 1);
|
|
jsvUnLock(steps);
|
|
}
|
|
if (bangleTasks & JSBT_TWIST_EVENT) {
|
|
jsiQueueObjectCallbacks(bangle, JS_EVENT_PREFIX"twist", NULL, 0);
|
|
}
|
|
if (bangleTasks & JSBT_FACE_UP) {
|
|
JsVar *v = jsvNewFromBool(faceUp);
|
|
jsiQueueObjectCallbacks(bangle, JS_EVENT_PREFIX"faceUp", &v, 1);
|
|
jsvUnLock(v);
|
|
if (faceUp && (bangleFlags&JSBF_WAKEON_FACEUP)) {
|
|
// LCD was turned off, turn it back on
|
|
if (lcdPowerTimeout && !(bangleFlags&JSBF_LCD_ON))
|
|
jswrap_banglejs_setLCDPower(1);
|
|
if (backlightTimeout && !(bangleFlags&JSBF_LCD_BL_ON))
|
|
jswrap_banglejs_setLCDPowerBacklight(1);
|
|
if (lockTimeout && (bangleFlags&JSBF_LOCKED))
|
|
jswrap_banglejs_setLocked(false);
|
|
inactivityTimer = 0;
|
|
}
|
|
}
|
|
if (bangleTasks & JSBT_SWIPE) {
|
|
JsVar *o[2] = {
|
|
jsvNewFromInteger((touchGesture==TG_SWIPE_LEFT)?-1:((touchGesture==TG_SWIPE_RIGHT)?1:0)),
|
|
jsvNewFromInteger((touchGesture==TG_SWIPE_UP)?-1:((touchGesture==TG_SWIPE_DOWN)?1:0)),
|
|
};
|
|
touchGesture = TG_SWIPE_NONE;
|
|
jsiQueueObjectCallbacks(bangle, JS_EVENT_PREFIX"swipe", o, 2);
|
|
jsvUnLockMany(2,o);
|
|
}
|
|
if (bangleTasks & JSBT_TOUCH_MASK) {
|
|
#ifdef TOUCH_DEVICE
|
|
JsVar *o[2] = {
|
|
jsvNewFromInteger(((bangleTasks & JSBT_TOUCH_LEFT)?1:0) |
|
|
((bangleTasks & JSBT_TOUCH_RIGHT)?2:0)),
|
|
jsvNewObject()
|
|
};
|
|
int x = lastTouchX;
|
|
int y = lastTouchY;
|
|
if (x<0) x=0;
|
|
if (y<0) y=0;
|
|
if (x>=LCD_WIDTH) x=LCD_WIDTH-1;
|
|
if (y>=LCD_HEIGHT) y=LCD_HEIGHT-1;
|
|
jsvObjectSetChildAndUnLock(o[1], "x", jsvNewFromInteger(x));
|
|
jsvObjectSetChildAndUnLock(o[1], "y", jsvNewFromInteger(y));
|
|
jsvObjectSetChildAndUnLock(o[1], "type", jsvNewFromInteger(touchType));
|
|
jsiQueueObjectCallbacks(bangle, JS_EVENT_PREFIX"touch", o, 2);
|
|
jsvUnLockMany(2,o);
|
|
#else
|
|
JsVar *o = jsvNewFromInteger(((bangleTasks & JSBT_TOUCH_LEFT)?1:0) |
|
|
((bangleTasks & JSBT_TOUCH_RIGHT)?2:0));
|
|
jsiQueueObjectCallbacks(bangle, JS_EVENT_PREFIX"touch", &o, 1);
|
|
jsvUnLock(o);
|
|
#endif
|
|
}
|
|
}
|
|
#ifdef TOUCH_DEVICE
|
|
if (bangleTasks & JSBT_DRAG) {
|
|
JsVar *o = jsvNewObject();
|
|
jsvObjectSetChildAndUnLock(o, "x", jsvNewFromInteger(touchX));
|
|
jsvObjectSetChildAndUnLock(o, "y", jsvNewFromInteger(touchY));
|
|
jsvObjectSetChildAndUnLock(o, "b", jsvNewFromInteger(touchPts));
|
|
jsvObjectSetChildAndUnLock(o, "dx", jsvNewFromInteger(lastTouchPts ? touchX-lastTouchX : 0));
|
|
jsvObjectSetChildAndUnLock(o, "dy", jsvNewFromInteger(lastTouchPts ? touchY-lastTouchY : 0));
|
|
jsiQueueObjectCallbacks(bangle, JS_EVENT_PREFIX"drag", &o, 1);
|
|
jsvUnLock(o);
|
|
lastTouchX = touchX;
|
|
lastTouchY = touchY;
|
|
lastTouchPts = touchPts;
|
|
}
|
|
#endif
|
|
#if ESPR_BANGLE_UNISTROKE
|
|
if (bangleTasks & JSBT_STROKE) {
|
|
JsVar *o = unistroke_getEventVar();
|
|
if (o) {
|
|
jsiQueueObjectCallbacks(bangle, JS_EVENT_PREFIX"stroke", &o, 1);
|
|
jsvUnLock(o);
|
|
}
|
|
}
|
|
#endif
|
|
jsvUnLock(bangle);
|
|
bangleTasks = JSBT_NONE;
|
|
#if defined(LCD_CONTROLLER_LPM013M126) || defined(LCD_CONTROLLER_ST7789V) || defined(LCD_CONTROLLER_ST7735) || defined(LCD_CONTROLLER_GC9A01)
|
|
// Automatically flip!
|
|
if (graphicsInternal.data.modMaxX >= graphicsInternal.data.modMinX) {
|
|
graphicsInternalFlip();
|
|
}
|
|
#endif
|
|
#ifdef ESPR_BACKLIGHT_FADE
|
|
if (lcdFadeHandlerActive && realLcdBrightness == ((bangleFlags&JSBF_LCD_ON)?lcdBrightness:0)) {
|
|
jstStopExecuteFn(backlightFadeHandler, NULL);
|
|
lcdFadeHandlerActive = false;
|
|
if (!(bangleFlags&JSBF_LCD_ON)) jswrap_banglejs_setLCDPowerController(0);
|
|
}
|
|
#endif
|
|
// resolve any beep/buzz promises
|
|
if (promiseBuzz && !buzzAmt) {
|
|
jspromise_resolve(promiseBuzz, 0);
|
|
jsvUnLock(promiseBuzz);
|
|
promiseBuzz = 0;
|
|
}
|
|
if (promiseBeep && !beepFreq) {
|
|
jspromise_resolve(promiseBeep, 0);
|
|
jsvUnLock(promiseBeep);
|
|
promiseBeep = 0;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*JSON{
|
|
"type" : "EV_SERIAL1",
|
|
"generate" : "jswrap_banglejs_gps_character",
|
|
"#if" : "defined(BANGLEJS_F18) || defined(DTNO1_F5) || defined(BANGLEJS_Q3)"
|
|
}*/
|
|
bool jswrap_banglejs_gps_character(char ch) {
|
|
#ifdef GPS_PIN_RX
|
|
// if too many chars, roll over since it's probably because we skipped a newline
|
|
// or messed the message length
|
|
if (gpsLineLength >= sizeof(gpsLine)) {
|
|
#ifdef GPS_UBLOX
|
|
if (inComingUbloxProtocol == UBLOX_PROTOCOL_UBX &&
|
|
ubxMsgPayloadEnd > gpsLineLength) {
|
|
if (bangleTasks & (JSBT_GPS_DATA_PARTIAL|JSBT_GPS_DATA_LINE)) {
|
|
// we were already waiting to post data, so lets not overwrite it
|
|
bangleTasks |= JSBT_GPS_DATA_OVERFLOW;
|
|
} else {
|
|
memcpy(gpsLastLine, gpsLine, gpsLineLength);
|
|
gpsLastLineLength = gpsLineLength;
|
|
bangleTasks |= JSBT_GPS_DATA_PARTIAL;
|
|
}
|
|
ubxMsgPayloadEnd -= gpsLineLength;
|
|
gpsLineLength = 0;
|
|
} else
|
|
#endif // GPS_UBLOX
|
|
gpsClearLine();
|
|
}
|
|
#ifdef GPS_UBLOX
|
|
if (inComingUbloxProtocol == UBLOX_PROTOCOL_NOT_DETECTED) {
|
|
gpsLineLength = 0;
|
|
if (ch == '$') {
|
|
inComingUbloxProtocol = UBLOX_PROTOCOL_NMEA;
|
|
} else if (ch == 0xB5) {
|
|
inComingUbloxProtocol = UBLOX_PROTOCOL_UBX;
|
|
ubxMsgPayloadEnd = 0;
|
|
}
|
|
}
|
|
#endif // GPS_UBLOX
|
|
gpsLine[gpsLineLength++] = ch;
|
|
if (
|
|
#ifdef GPS_UBLOX
|
|
inComingUbloxProtocol == UBLOX_PROTOCOL_NMEA &&
|
|
#endif // GPS_UBLOX
|
|
ch == '\n') {
|
|
// Now we have a line of GPS data...
|
|
/*$GNRMC,161945.00,A,5139.11397,N,00116.07202,W,1.530,,190919,,,A*7E
|
|
$GNVTG,,T,,M,1.530,N,2.834,K,A*37
|
|
$GNGGA,161945.00,5139.11397,N,00116.07202,W,1,06,1.29,71.1,M,47.0,M,,*64
|
|
$GNGSA,A,3,09,06,23,07,03,29,,,,,,,1.96,1.29,1.48*14
|
|
$GPGSV,3,1,12,02,45,293,13,03,10,109,16,05,13,291,,06,56,213,25*73
|
|
$GPGSV,3,2,12,07,39,155,18,09,76,074,33,16,08,059,,19,02,218,18*7E
|
|
$GPGSV,3,3,12,23,40,066,23,26,08,033,18,29,07,342,20,30,14,180,*7F
|
|
$GNGLL,5139.11397,N,00116.07202,W,161945.00,A,A*69 */
|
|
// Let's just chuck it over into JS-land for now
|
|
if (gpsLineLength > 2 && gpsLineLength <= NMEA_MAX_SIZE && gpsLine[gpsLineLength - 2] =='\r') {
|
|
gpsLine[gpsLineLength - 2] = 0; // just overwriting \r\n
|
|
gpsLine[gpsLineLength - 1] = 0;
|
|
if (nmea_decode(&gpsFix, (char *)gpsLine))
|
|
bangleTasks |= JSBT_GPS_DATA;
|
|
if (bangleTasks & (JSBT_GPS_DATA_PARTIAL|JSBT_GPS_DATA_LINE)) {
|
|
// we were already waiting to post data, so lets not overwrite it
|
|
bangleTasks |= JSBT_GPS_DATA_OVERFLOW;
|
|
} else {
|
|
memcpy(gpsLastLine, gpsLine, gpsLineLength);
|
|
gpsLastLineLength = gpsLineLength - 2;
|
|
bangleTasks |= JSBT_GPS_DATA_LINE;
|
|
}
|
|
}
|
|
gpsClearLine();
|
|
}
|
|
#ifdef GPS_UBLOX
|
|
else if (inComingUbloxProtocol == UBLOX_PROTOCOL_UBX) {
|
|
if (!ubxMsgPayloadEnd) {
|
|
if (gpsLineLength == 2 && ch != 0x62) { // Invalid u-blox protocol message, missing header second byte
|
|
gpsClearLine();
|
|
} else if (gpsLineLength == 6) {
|
|
// Header: 0xB5 0x62, Class: 1 byte, ID: 1 byte, Length: 2 bytes, data..., CRC: 2 bytes
|
|
ubxMsgPayloadEnd = 6 + ((gpsLine[5] << 8) | gpsLine[4]) + 2;
|
|
if (ubxMsgPayloadEnd < gpsLineLength) { // Length is some odd way horribly wrong
|
|
gpsClearLine();
|
|
}
|
|
}
|
|
} else if (gpsLineLength >= ubxMsgPayloadEnd) {
|
|
if (bangleTasks & (JSBT_GPS_DATA_PARTIAL|JSBT_GPS_DATA_LINE)) {
|
|
// we were already waiting to post data, so lets not overwrite it
|
|
bangleTasks |= JSBT_GPS_DATA_OVERFLOW;
|
|
} else {
|
|
memcpy(gpsLastLine, gpsLine, gpsLineLength);
|
|
gpsLastLineLength = gpsLineLength;
|
|
bangleTasks |= JSBT_GPS_DATA_LINE;
|
|
}
|
|
gpsClearLine();
|
|
}
|
|
}
|
|
#endif // GPS_UBLOX
|
|
#endif // GPS_PIN_RX
|
|
return true; // handled
|
|
}
|
|
|
|
// TODO Improve TypeScript declaration
|
|
/*JSON{
|
|
"type" : "staticmethod",
|
|
"class" : "Bangle",
|
|
"name" : "dbg",
|
|
"generate" : "jswrap_banglejs_dbg",
|
|
"return" : ["JsVar",""],
|
|
"ifdef" : "BANGLEJS"
|
|
}
|
|
Reads debug info
|
|
*/
|
|
JsVar *jswrap_banglejs_dbg() {
|
|
JsVar *o = jsvNewObject();
|
|
if (!o) return 0;
|
|
jsvObjectSetChildAndUnLock(o,"accHistoryIdx",jsvNewFromInteger(accHistoryIdx));
|
|
jsvObjectSetChildAndUnLock(o,"accGestureCount",jsvNewFromInteger(accGestureCount));
|
|
jsvObjectSetChildAndUnLock(o,"accIdleCount",jsvNewFromInteger(accIdleCount));
|
|
return o;
|
|
}
|
|
|
|
#ifndef EMULATED
|
|
void _jswrap_banglejs_i2cWr(JshI2CInfo *i2c, int i2cAddr, JsVarInt reg, JsVarInt data) {
|
|
unsigned char buf[2];
|
|
buf[0] = (unsigned char)reg;
|
|
buf[1] = (unsigned char)data;
|
|
i2cBusy = true;
|
|
jsi2cWrite(i2c, i2cAddr, 2, buf, true);
|
|
i2cBusy = false;
|
|
}
|
|
|
|
JsVar *_jswrap_banglejs_i2cRd(JshI2CInfo *i2c, int i2cAddr, JsVarInt reg, JsVarInt cnt) {
|
|
if (cnt<0) cnt=0;
|
|
unsigned char buf[128];
|
|
if (cnt>(int)sizeof(buf)) cnt=sizeof(buf);
|
|
buf[0] = (unsigned char)reg;
|
|
i2cBusy = true;
|
|
jsi2cWrite(i2c, i2cAddr, 1, buf, false);
|
|
jsi2cRead(i2c, i2cAddr, (cnt==0)?1:cnt, buf, true);
|
|
i2cBusy = false;
|
|
if (cnt) {
|
|
JsVar *ab = jsvNewArrayBufferWithData(cnt, buf);
|
|
JsVar *d = jswrap_typedarray_constructor(ARRAYBUFFERVIEW_UINT8, ab,0,0);
|
|
jsvUnLock(ab);
|
|
return d;
|
|
} else return jsvNewFromInteger(buf[0]);
|
|
}
|
|
#endif
|
|
|
|
/*JSON{
|
|
"type" : "staticmethod",
|
|
"class" : "Bangle",
|
|
"name" : "accelWr",
|
|
"generate" : "jswrap_banglejs_accelWr",
|
|
"params" : [
|
|
["reg","int",""],
|
|
["data","int",""]
|
|
],
|
|
"ifdef" : "BANGLEJS"
|
|
}
|
|
Writes a register on the accelerometer
|
|
*/
|
|
void jswrap_banglejs_accelWr(JsVarInt reg, JsVarInt data) {
|
|
#ifdef ACCEL_I2C
|
|
_jswrap_banglejs_i2cWr(ACCEL_I2C, ACCEL_ADDR, reg, data);
|
|
#endif
|
|
}
|
|
|
|
/*JSON{
|
|
"type" : "staticmethod",
|
|
"class" : "Bangle",
|
|
"name" : "accelRd",
|
|
"generate" : "jswrap_banglejs_accelRd",
|
|
"params" : [
|
|
["reg","int",""],
|
|
["cnt","int","If specified, returns an array of the given length (max 128). If not (or 0) it returns a number"]
|
|
],
|
|
"return" : ["JsVar",""],
|
|
"ifdef" : "BANGLEJS",
|
|
"typescript" : [
|
|
"accelRd(reg: number, cnt?: 0): number;",
|
|
"accelRd(reg: number, cnt: number): number[];"
|
|
]
|
|
}
|
|
Reads a register from the accelerometer
|
|
|
|
**Note:** On Espruino 2v06 and before this function only returns a number (`cnt`
|
|
is ignored).
|
|
*/
|
|
|
|
|
|
JsVar *jswrap_banglejs_accelRd(JsVarInt reg, JsVarInt cnt) {
|
|
#ifdef ACCEL_I2C
|
|
return _jswrap_banglejs_i2cRd(ACCEL_I2C, ACCEL_ADDR, reg, cnt);
|
|
#else
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
|
|
/*JSON{
|
|
"type" : "staticmethod",
|
|
"class" : "Bangle",
|
|
"name" : "barometerWr",
|
|
"generate" : "jswrap_banglejs_barometerWr",
|
|
"params" : [
|
|
["reg","int",""],
|
|
["data","int",""]
|
|
],
|
|
"#if" : "defined(DTNO1_F5) || defined(BANGLEJS_Q3) || defined(DICKENS)"
|
|
}
|
|
Writes a register on the barometer IC
|
|
*/
|
|
void jswrap_banglejs_barometerWr(JsVarInt reg, JsVarInt data) {
|
|
#ifdef PRESSURE_I2C
|
|
_jswrap_banglejs_i2cWr(PRESSURE_I2C, PRESSURE_ADDR, reg, data);
|
|
#endif
|
|
}
|
|
|
|
/*JSON{
|
|
"type" : "staticmethod",
|
|
"class" : "Bangle",
|
|
"name" : "barometerRd",
|
|
"generate" : "jswrap_banglejs_barometerRd",
|
|
"params" : [
|
|
["reg","int",""],
|
|
["cnt","int","If specified, returns an array of the given length (max 128). If not (or 0) it returns a number"]
|
|
],
|
|
"return" : ["JsVar",""],
|
|
"#if" : "defined(DTNO1_F5) || defined(BANGLEJS_Q3) || defined(DICKENS)",
|
|
"typescript" : [
|
|
"barometerRd(reg: number, cnt?: 0): number;",
|
|
"barometerRd(reg: number, cnt: number): number[];"
|
|
]
|
|
}
|
|
Reads a register from the barometer IC
|
|
*/
|
|
JsVar *jswrap_banglejs_barometerRd(JsVarInt reg, JsVarInt cnt) {
|
|
#ifdef PRESSURE_I2C
|
|
return _jswrap_banglejs_i2cRd(PRESSURE_I2C, PRESSURE_ADDR, reg, cnt);
|
|
#else
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
|
|
/*JSON{
|
|
"type" : "staticmethod",
|
|
"class" : "Bangle",
|
|
"name" : "compassWr",
|
|
"generate" : "jswrap_banglejs_compassWr",
|
|
"params" : [
|
|
["reg","int",""],
|
|
["data","int",""]
|
|
],
|
|
"ifdef" : "BANGLEJS"
|
|
}
|
|
Writes a register on the Magnetometer/Compass
|
|
*/
|
|
void jswrap_banglejs_compassWr(JsVarInt reg, JsVarInt data) {
|
|
#ifdef MAG_I2C
|
|
_jswrap_banglejs_i2cWr(MAG_I2C, MAG_ADDR, reg, data);
|
|
#endif
|
|
}
|
|
|
|
/*JSON{
|
|
"type" : "staticmethod",
|
|
"class" : "Bangle",
|
|
"name" : "compassRd",
|
|
"generate" : "jswrap_banglejs_compassRd",
|
|
"params" : [
|
|
["reg","int",""],
|
|
["cnt","int","If specified, returns an array of the given length (max 128). If not (or 0) it returns a number"]
|
|
],
|
|
"return" : ["JsVar",""],
|
|
"ifdef" : "BANGLEJS",
|
|
"typescript" : [
|
|
"compassRd(reg: number, cnt?: 0): number;",
|
|
"compassRd(reg: number, cnt: number): number[];"
|
|
]
|
|
}
|
|
Read a register on the Magnetometer/Compass
|
|
*/
|
|
JsVar *jswrap_banglejs_compassRd(JsVarInt reg, JsVarInt cnt) {
|
|
#ifdef MAG_I2C
|
|
return _jswrap_banglejs_i2cRd(MAG_I2C, MAG_ADDR, reg, cnt);
|
|
#else
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
/*JSON{
|
|
"type" : "staticmethod",
|
|
"class" : "Bangle",
|
|
"name" : "hrmWr",
|
|
"generate" : "jswrap_banglejs_hrmWr",
|
|
"params" : [
|
|
["reg","int",""],
|
|
["data","int",""]
|
|
],
|
|
"ifdef" : "BANGLEJS_Q3"
|
|
}
|
|
Writes a register on the Heart rate monitor
|
|
*/
|
|
void jswrap_banglejs_hrmWr(JsVarInt reg, JsVarInt data) {
|
|
#ifdef HRM_I2C
|
|
_jswrap_banglejs_i2cWr(HRM_I2C, HEARTRATE_ADDR, reg, data);
|
|
#endif
|
|
}
|
|
|
|
/*JSON{
|
|
"type" : "staticmethod",
|
|
"class" : "Bangle",
|
|
"name" : "hrmRd",
|
|
"generate" : "jswrap_banglejs_hrmRd",
|
|
"params" : [
|
|
["reg","int",""],
|
|
["cnt","int","If specified, returns an array of the given length (max 128). If not (or 0) it returns a number"]
|
|
],
|
|
"return" : ["JsVar",""],
|
|
"ifdef" : "BANGLEJS",
|
|
"typescript" : [
|
|
"hrmRd(reg: number, cnt?: 0): number;",
|
|
"hrmRd(reg: number, cnt: number): number[];"
|
|
]
|
|
}
|
|
Read a register on the Heart rate monitor
|
|
*/
|
|
JsVar *jswrap_banglejs_hrmRd(JsVarInt reg, JsVarInt cnt) {
|
|
#ifdef HRM_I2C
|
|
return _jswrap_banglejs_i2cRd(HRM_I2C, HEARTRATE_ADDR, reg, cnt);
|
|
#else
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
/*JSON{
|
|
"type" : "staticmethod",
|
|
"class" : "Bangle",
|
|
"name" : "ioWr",
|
|
"generate" : "jswrap_banglejs_ioWr",
|
|
"params" : [
|
|
["mask","int",""],
|
|
["isOn","int",""]
|
|
],
|
|
"ifdef" : "BANGLEJS_F18"
|
|
}
|
|
Changes a pin state on the IO expander
|
|
*/
|
|
#ifdef BANGLEJS_F18
|
|
void jswrap_banglejs_ioWr(JsVarInt mask, bool on) {
|
|
#ifndef EMULATED
|
|
static unsigned char state;
|
|
if (on) state |= mask;
|
|
else state &= ~mask;
|
|
i2cBusy = true;
|
|
jsi2cWrite(&i2cInternal, 0x20, 1, &state, true);
|
|
i2cBusy = false;
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
/*JSON{
|
|
"type" : "staticmethod",
|
|
"class" : "Bangle",
|
|
"name" : "getPressure",
|
|
"generate" : "jswrap_banglejs_getPressure",
|
|
"return" : ["JsVar","A promise that will be resolved with `{temperature, pressure, altitude}`"],
|
|
"#if" : "defined(DTNO1_F5) || defined(BANGLEJS_Q3) || defined(DICKENS)",
|
|
"typescript" : "getPressure(): PressureData;"
|
|
}
|
|
Read temperature, pressure and altitude data. A promise is returned which will
|
|
be resolved with `{temperature, pressure, altitude}`.
|
|
|
|
If the Barometer has been turned on with `Bangle.setBarometerPower` then this
|
|
will return almost immediately with the reading. If the Barometer is off,
|
|
conversions take between 500-750ms.
|
|
|
|
Altitude assumes a sea-level pressure of 1013.25 hPa
|
|
|
|
```
|
|
Bangle.getPressure().then(d=>{
|
|
console.log(d);
|
|
// {temperature, pressure, altitude}
|
|
});
|
|
```
|
|
*/
|
|
|
|
#ifdef PRESSURE_DEVICE
|
|
bool jswrap_banglejs_barometerPoll() {
|
|
#ifdef PRESSURE_DEVICE_HP203_EN
|
|
if (PRESSURE_DEVICE_HP203_EN) {
|
|
unsigned char buf[6];
|
|
// ADC_CVT - 0b010 01 000 - pressure and temperature channel, OSR = 4096
|
|
buf[0] = 0x48; jsi2cWrite(PRESSURE_I2C, PRESSURE_ADDR, 1, buf, true);
|
|
// wait 100ms
|
|
jshDelayMicroseconds(100*1000); // we should really have a callback
|
|
// READ_PT
|
|
buf[0] = 0x10; jsi2cWrite(PRESSURE_I2C, PRESSURE_ADDR, 1, buf, false);
|
|
jsi2cRead(PRESSURE_I2C, PRESSURE_ADDR, 6, buf, true);
|
|
int temperature = (buf[0]<<16)|(buf[1]<<8)|buf[2];
|
|
if (temperature&0x800000) temperature-=0x1000000;
|
|
int pressure = (buf[3]<<16)|(buf[4]<<8)|buf[5];
|
|
barometerTemperature = temperature/100.0;
|
|
barometerPressure = pressure/100.0;
|
|
|
|
buf[0] = 0x31; jsi2cWrite(PRESSURE_I2C, PRESSURE_ADDR, 1, buf, false); // READ_A
|
|
jsi2cRead(PRESSURE_I2C, PRESSURE_ADDR, 3, buf, true);
|
|
int altitude = (buf[0]<<16)|(buf[1]<<8)|buf[2];
|
|
if (altitude&0x800000) altitude-=0x1000000;
|
|
barometerAltitude = altitude/100.0;
|
|
return true;
|
|
}
|
|
#endif
|
|
#ifdef PRESSURE_DEVICE_SPL06_007_EN
|
|
if (PRESSURE_DEVICE_SPL06_007_EN) {
|
|
static int oversample_scalefactor[] = {524288, 1572864, 3670016, 7864320, 253952, 516096, 1040384, 2088960};
|
|
unsigned char buf[6];
|
|
|
|
// status values
|
|
buf[0] = SPL06_MEASCFG; jsi2cWrite(PRESSURE_I2C, PRESSURE_ADDR, 1, buf, false);
|
|
jsi2cRead(PRESSURE_I2C, PRESSURE_ADDR, 1, buf, true);
|
|
int status = buf[0];
|
|
if ((status & 0b00110000) != 0b00110000) {
|
|
// data hasn't arrived yet
|
|
return false;
|
|
}
|
|
|
|
// raw values
|
|
buf[0] = SPL06_PRSB2; jsi2cWrite(PRESSURE_I2C, PRESSURE_ADDR, 1, buf, false);
|
|
jsi2cRead(PRESSURE_I2C, PRESSURE_ADDR, 6, buf, true);
|
|
int praw = (buf[0]<<16)|(buf[1]<<8)|buf[2];
|
|
praw = twosComplement(praw, 24);
|
|
int traw = (buf[3]<<16)|(buf[4]<<8)|buf[5];
|
|
traw = twosComplement(traw, 24);
|
|
|
|
double traw_scaled = (double)traw / oversample_scalefactor[SPL06_8SAMPLES]; // temperature oversample by 8x
|
|
double praw_scaled = (double)praw / oversample_scalefactor[SPL06_8SAMPLES]; // pressure oversample by 8x
|
|
barometerTemperature = (barometer_c0/2) + (barometer_c1*traw_scaled);
|
|
double pressurePa = (barometer_c00 + praw_scaled * (barometer_c10 + praw_scaled * (barometer_c20 + praw_scaled * barometer_c30)) +
|
|
traw_scaled * barometer_c01 +
|
|
traw_scaled * praw_scaled * ( barometer_c11 + praw_scaled * barometer_c21));
|
|
barometerPressure = pressurePa / 100; // convert Pa to hPa/millibar
|
|
barometerAltitude = 44330 * (1.0 - jswrap_math_pow(barometerPressure / barometerSeaLevelPressure, 0.1903));
|
|
// TODO: temperature corrected altitude?
|
|
return true;
|
|
}
|
|
#endif // PRESSURE_DEVICE_SPL06_007_EN
|
|
#ifdef PRESSURE_DEVICE_BMP280_EN
|
|
if (PRESSURE_DEVICE_BMP280_EN) {
|
|
unsigned char buf[8];
|
|
buf[0] = 0xF7; jsi2cWrite(PRESSURE_I2C, PRESSURE_ADDR, 1, buf, false); // READ_A
|
|
jsi2cRead(PRESSURE_I2C, PRESSURE_ADDR, 6, buf, true);
|
|
int uncomp_pres = (buf[0] << 12) | (buf[1] << 4) | (buf[2] >> 4);
|
|
int uncomp_temp = (buf[3] << 12) | (buf[4] << 4) | (buf[5] >> 4);
|
|
double var1, var2;
|
|
// temperature
|
|
var1 = (((double) uncomp_temp) / 16384.0 - ((double) barometerDT[0]) / 1024.0) *
|
|
((double) barometerDT[1]);
|
|
var2 =
|
|
((((double) uncomp_temp) / 131072.0 - ((double) barometerDT[0]) / 8192.0) *
|
|
(((double) uncomp_temp) / 131072.0 - ((double) barometerDT[0]) / 8192.0)) *
|
|
((double) barometerDT[2]);
|
|
int32_t t_fine = (int32_t) (var1 + var2);
|
|
barometerTemperature = ((var1 + var2) / 5120.0);
|
|
// pressure
|
|
var1 = ((double) t_fine / 2.0) - 64000.0;
|
|
var2 = var1 * var1 * ((double) barometerDP[5]) / 32768.0;
|
|
var2 = var2 + var1 * ((double) barometerDP[4]) * 2.0;
|
|
var2 = (var2 / 4.0) + (((double) barometerDP[3]) * 65536.0);
|
|
var1 = (((double)barometerDP[2]) * var1 * var1 / 524288.0 + ((double)barometerDP[1]) * var1) /
|
|
524288.0;
|
|
var1 = (1.0 + var1 / 32768.0) * ((double) barometerDP[0]);
|
|
|
|
barometerPressure = 1048576.0 - (double)uncomp_pres;
|
|
if (var1 < 0 || var1 > 0) {
|
|
barometerPressure = (barometerPressure - (var2 / 4096.0)) * 6250.0 / var1;
|
|
var1 = ((double)barometerDP[8]) * (barometerPressure) * (barometerPressure) / 2147483648.0;
|
|
var2 = (barometerPressure) * ((double)barometerDP[7]) / 32768.0;
|
|
barometerPressure = barometerPressure + (var1 + var2 + ((double)barometerDP[6])) / 16.0;
|
|
barometerPressure = barometerPressure/100.0;
|
|
} else {
|
|
barometerPressure = 0;
|
|
}
|
|
|
|
barometerAltitude = 44330 * (1.0 - jswrap_math_pow(barometerPressure / barometerSeaLevelPressure, 0.1903));
|
|
// TODO: temperature corrected altitude?
|
|
return true;
|
|
}
|
|
#endif //PRESSURE_DEVICE_BMP280_EN
|
|
return false;
|
|
}
|
|
|
|
JsVar *jswrap_banglejs_getBarometerObject() {
|
|
JsVar *o = jsvNewObject();
|
|
if (o) {
|
|
jsvObjectSetChildAndUnLock(o,"temperature", jsvNewFromFloat(barometerTemperature));
|
|
jsvObjectSetChildAndUnLock(o,"pressure", jsvNewFromFloat(barometerPressure));
|
|
jsvObjectSetChildAndUnLock(o,"altitude", jsvNewFromFloat(barometerAltitude));
|
|
// jsvObjectSetChildAndUnLock(o,"SPL06", jsvNewFromBool(pressureSPL06Enabled));
|
|
// jsvObjectSetChildAndUnLock(o,"BMP280", jsvNewFromBool(pressureBMP280Enabled));
|
|
}
|
|
return o;
|
|
}
|
|
|
|
void jswrap_banglejs_getPressure_callback() {
|
|
JsVar *o = 0;
|
|
if (jswrap_banglejs_barometerPoll()) {
|
|
o = jswrap_banglejs_getBarometerObject();
|
|
}
|
|
// disable sensor now we have a result
|
|
JsVar *id = jsvNewFromString("getPressure");
|
|
jswrap_banglejs_setBarometerPower(0, id);
|
|
jsvUnLock(id);
|
|
// resolve the promise
|
|
jspromise_resolve(promisePressure, o);
|
|
jsvUnLock2(promisePressure,o);
|
|
promisePressure = 0;
|
|
}
|
|
#endif // PRESSURE_DEVICE
|
|
|
|
|
|
JsVar *jswrap_banglejs_getPressure() {
|
|
#ifdef PRESSURE_DEVICE
|
|
if (promisePressure) {
|
|
jsExceptionHere(JSET_ERROR, "Conversion in progress");
|
|
return 0;
|
|
}
|
|
promisePressure = jspromise_create();
|
|
if (!promisePressure) return 0;
|
|
|
|
// If barometer is already on, just resolve promise with the current result
|
|
if (bangleFlags & JSBF_BAROMETER_ON) {
|
|
JsVar *o = jswrap_banglejs_getBarometerObject();
|
|
jspromise_resolve(promisePressure, o);
|
|
jsvUnLock(o);
|
|
JsVar *r = promisePressure;
|
|
promisePressure = 0;
|
|
return r;
|
|
}
|
|
|
|
JsVar *id = jsvNewFromString("getPressure");
|
|
jswrap_banglejs_setBarometerPower(1, id);
|
|
jsvUnLock(id);
|
|
/* Occasionally on some devices (https://github.com/espruino/Espruino/issues/2137)
|
|
you can get an I2C error. This stops the error from being fired when getPressure
|
|
is called and instead rejects the promise. */
|
|
bool hadError = jspHasError();
|
|
if (hadError) {
|
|
JsVar *exception = jspGetException();
|
|
jspromise_reject(promisePressure, exception);
|
|
jsvUnLock2(promisePressure,exception);
|
|
promisePressure = 0;
|
|
} else {
|
|
int powerOnTimeout = 500;
|
|
#ifdef PRESSURE_DEVICE_BMP280_EN
|
|
if (PRESSURE_DEVICE_BMP280_EN)
|
|
powerOnTimeout = 750; // some devices seem to need this long to boot reliably
|
|
#endif
|
|
#ifdef PRESSURE_DEVICE_SPL06_007_EN
|
|
if (PRESSURE_DEVICE_SPL06_007_EN)
|
|
powerOnTimeout = 400; // on SPL06 we may actually be leaving it *too long* before requesting data, and it starts to do another reading
|
|
#endif
|
|
jsvUnLock(jsiSetTimeout(jswrap_banglejs_getPressure_callback, powerOnTimeout));
|
|
return jsvLockAgain(promisePressure);
|
|
}
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
/*JSON{
|
|
"type" : "staticmethod",
|
|
"class" : "Bangle",
|
|
"name" : "project",
|
|
"generate" : "jswrap_banglejs_project",
|
|
"params" : [
|
|
["latlong","JsVar","`{lat:..., lon:...}`"]
|
|
],
|
|
"return" : ["JsVar","{x:..., y:...}"],
|
|
"ifdef" : "BANGLEJS",
|
|
"typescript" : "project(latlong: { lat: number, lon: number }): { x: number, y: number };"
|
|
}
|
|
Perform a Spherical [Web Mercator
|
|
projection](https://en.wikipedia.org/wiki/Web_Mercator_projection) of latitude
|
|
and longitude into `x` and `y` coordinates, which are roughly equivalent to
|
|
meters from `{lat:0,lon:0}`.
|
|
|
|
This is the formula used for most online mapping and is a good way to compare
|
|
GPS coordinates to work out the distance between them.
|
|
*/
|
|
JsVar *jswrap_banglejs_project(JsVar *latlong) {
|
|
const double degToRad = PI / 180; // degree to radian conversion
|
|
const double latMax = 85.0511287798; // clip latitude to sane values
|
|
const double R = 6378137; // earth radius in m
|
|
double lat = jsvGetFloatAndUnLock(jsvObjectGetChild(latlong,"lat",0));
|
|
double lon = jsvGetFloatAndUnLock(jsvObjectGetChild(latlong,"lon",0));
|
|
if (lat > latMax) lat=latMax;
|
|
if (lat < -latMax) lat=-latMax;
|
|
double s = sin(lat * degToRad);
|
|
JsVar *o = jsvNewObject();
|
|
if (o) {
|
|
jsvObjectSetChildAndUnLock(o,"x", jsvNewFromFloat(R * lon * degToRad));
|
|
jsvObjectSetChildAndUnLock(o,"y", jsvNewFromFloat(R * log((1 + s) / (1 - s)) / 2));
|
|
}
|
|
return o;
|
|
}
|
|
|
|
|
|
static NO_INLINE void _jswrap_banglejs_setVibration() {
|
|
int beep = 0;
|
|
if (bangleFlags & JSBF_BEEP_VIBRATE)
|
|
beep = beepFreq;
|
|
|
|
if (buzzAmt==0 && beep==0)
|
|
jshPinOutput(VIBRATE_PIN,0); // vibrate off
|
|
else if (beep==0) { // vibrate only
|
|
jshPinAnalogOutput(VIBRATE_PIN, 0.4 + buzzAmt*0.6/255, 1000, JSAOF_NONE);
|
|
} else { // beep and vibrate
|
|
jshPinAnalogOutput(VIBRATE_PIN, 0.2 + buzzAmt*0.6/255, beep, JSAOF_NONE);
|
|
}
|
|
}
|
|
|
|
/*JSON{
|
|
"type" : "staticmethod",
|
|
"class" : "Bangle",
|
|
"name" : "beep",
|
|
"generate" : "jswrap_banglejs_beep",
|
|
"params" : [
|
|
["time","int","[optional] Time in ms (default 200)"],
|
|
["freq","int","[optional] Frequency in hz (default 4000)"]
|
|
],
|
|
"return" : ["JsVar","A promise, completed when beep is finished"],
|
|
"return_object":"Promise",
|
|
"ifdef" : "BANGLEJS"
|
|
}
|
|
Use the piezo speaker to Beep for a certain time period and frequency
|
|
*/
|
|
void jswrap_banglejs_beep_callback() {
|
|
beepFreq = 0;
|
|
if (bangleFlags & JSBF_BEEP_VIBRATE) {
|
|
_jswrap_banglejs_setVibration();
|
|
} else {
|
|
#ifdef SPEAKER_PIN
|
|
jshPinSetState(SPEAKER_PIN, JSHPINSTATE_GPIO_IN);
|
|
#endif
|
|
}
|
|
jshHadEvent();
|
|
}
|
|
|
|
JsVar *jswrap_banglejs_beep(int time, int freq) {
|
|
if (freq<=0) freq=4000;
|
|
if (freq>60000) freq=60000;
|
|
if (time<=0) time=200;
|
|
if (time>5000) time=5000;
|
|
if (promiseBeep) {
|
|
JsVar *fn = jsvNewNativeFunction((void (*)(void))jswrap_banglejs_beep, JSWAT_JSVAR|(JSWAT_INT32<<JSWAT_BITS)|(JSWAT_INT32<<(JSWAT_BITS*2)));
|
|
JsVar *v;
|
|
v=jsvNewFromInteger(time);jsvAddFunctionParameter(fn, 0, v);jsvUnLock(v); // bind param 1
|
|
v=jsvNewFromInteger(freq);jsvAddFunctionParameter(fn, 0, v);jsvUnLock(v); // bind param 2
|
|
JsVar *promise = jswrap_promise_then(promiseBeep, fn, NULL);
|
|
jsvUnLock(fn);
|
|
return promise;
|
|
}
|
|
promiseBeep = jspromise_create();
|
|
if (!promiseBeep) return 0;
|
|
|
|
if (bangleFlags & JSBF_ENABLE_BEEP) {
|
|
beepFreq = freq;
|
|
if (bangleFlags & JSBF_BEEP_VIBRATE) {
|
|
_jswrap_banglejs_setVibration();
|
|
} else {
|
|
#ifdef SPEAKER_PIN
|
|
jshPinAnalogOutput(SPEAKER_PIN, 0.5, freq, JSAOF_NONE);
|
|
#endif
|
|
}
|
|
}
|
|
jstExecuteFn(jswrap_banglejs_beep_callback, NULL, jshGetTimeFromMilliseconds(time), 0, NULL);
|
|
return jsvLockAgain(promiseBeep);
|
|
}
|
|
|
|
/*JSON{
|
|
"type" : "staticmethod",
|
|
"class" : "Bangle",
|
|
"name" : "buzz",
|
|
"generate" : "jswrap_banglejs_buzz",
|
|
"params" : [
|
|
["time","int","[optional] Time in ms (default 200)"],
|
|
["strength","float","[optional] Power of vibration from 0 to 1 (Default 1)"]
|
|
],
|
|
"return" : ["JsVar","A promise, completed when vibration is finished"],
|
|
"return_object":"Promise",
|
|
"ifdef" : "BANGLEJS"
|
|
}
|
|
Use the vibration motor to buzz for a certain time period
|
|
*/
|
|
void jswrap_banglejs_buzz_callback() {
|
|
buzzAmt = 0;
|
|
_jswrap_banglejs_setVibration();
|
|
jshHadEvent();
|
|
}
|
|
|
|
JsVar *jswrap_banglejs_buzz(int time, JsVarFloat amt) {
|
|
if (!isfinite(amt)|| amt>1) amt=1;
|
|
if (amt<0) amt=0;
|
|
if (time<=0) time=200;
|
|
if (time>5000) time=5000;
|
|
if (promiseBuzz) {
|
|
JsVar *fn = jsvNewNativeFunction((void (*)(void))jswrap_banglejs_buzz, JSWAT_JSVAR|(JSWAT_INT32<<JSWAT_BITS)|(JSWAT_JSVARFLOAT<<(JSWAT_BITS*2)));
|
|
JsVar *v;
|
|
v=jsvNewFromInteger(time);jsvAddFunctionParameter(fn, 0, v);jsvUnLock(v); // bind param 1
|
|
v=jsvNewFromFloat(amt);jsvAddFunctionParameter(fn, 0, v);jsvUnLock(v); // bind param 2
|
|
JsVar *promise = jswrap_promise_then(promiseBuzz, fn, NULL);
|
|
jsvUnLock(fn);
|
|
return promise;
|
|
}
|
|
promiseBuzz = jspromise_create();
|
|
if (!promiseBuzz) return 0;
|
|
|
|
buzzAmt = (unsigned char)(amt*255);
|
|
if (jstExecuteFn(jswrap_banglejs_buzz_callback, NULL, jshGetTimeFromMilliseconds(time), 0, NULL)) {
|
|
// task schedule succeeded - start buzz
|
|
if (bangleFlags & JSBF_ENABLE_BUZZ) {
|
|
_jswrap_banglejs_setVibration();
|
|
}
|
|
} else
|
|
buzzAmt = 0;
|
|
|
|
return jsvLockAgain(promiseBuzz);
|
|
}
|
|
|
|
static void jswrap_banglejs_periph_off() {
|
|
#ifndef EMULATED
|
|
#ifdef HEARTRATE
|
|
jswrap_banglejs_pwrHRM(false); // HRM off
|
|
#endif
|
|
#ifdef GPS_PIN_RX
|
|
jswrap_banglejs_pwrGPS(false); // GPS off
|
|
#endif
|
|
jshPinOutput(VIBRATE_PIN,0); // vibrate off
|
|
//jswrap_banglejs_setLCDPower calls JS events (and sometimes timers), so avoid it and manually turn controller + backlight off:
|
|
jswrap_banglejs_setLocked(1); // disable touchscreen if we have one
|
|
jswrap_banglejs_setLCDPowerController(0);
|
|
jswrap_banglejs_pwrBacklight(0);
|
|
#ifdef ACCEL_DEVICE_KX023
|
|
jswrap_banglejs_accelWr(0x18,0x0a); // accelerometer off
|
|
#endif
|
|
#ifdef ACCEL_DEVICE_KXTJ3_1057
|
|
jswrap_banglejs_accelWr(0x1B,0); // accelerometer off
|
|
#endif
|
|
#ifdef ACCEL_DEVICE_KX126
|
|
jswrap_banglejs_accelWr(KX126_CNTL1,0); // CNTL1 Off (top bit)
|
|
#endif
|
|
#ifdef MAG_DEVICE_GMC303
|
|
jswrap_banglejs_compassWr(0x31,0); // compass off
|
|
#endif
|
|
#ifdef PRESSURE_DEVICE
|
|
#ifdef PRESSURE_DEVICE_SPL06_007_EN
|
|
if (PRESSURE_DEVICE_SPL06_007_EN)
|
|
jswrap_banglejs_barometerWr(SPL06_MEASCFG, 0); // Barometer off
|
|
#endif
|
|
#ifdef PRESSURE_DEVICE_BMP280_EN
|
|
if (PRESSURE_DEVICE_BMP280_EN)
|
|
jswrap_banglejs_barometerWr(0xF4, 0); // Barometer off
|
|
#endif
|
|
#endif // PRESSURE_DEVICE
|
|
|
|
|
|
#ifdef BTN2_PININDEX
|
|
nrf_gpio_cfg_sense_set(pinInfo[BTN2_PININDEX].pin, NRF_GPIO_PIN_NOSENSE);
|
|
#endif
|
|
#ifdef BTN3_PININDEX
|
|
nrf_gpio_cfg_sense_set(pinInfo[BTN3_PININDEX].pin, NRF_GPIO_PIN_NOSENSE);
|
|
#endif
|
|
#ifdef BTN4_PININDEX
|
|
nrf_gpio_cfg_sense_set(pinInfo[BTN4_PININDEX].pin, NRF_GPIO_PIN_NOSENSE);
|
|
#endif
|
|
|
|
jsiKill();
|
|
jsvKill();
|
|
jshKill();
|
|
|
|
/* The low power pin watch code (nrf_drv_gpiote_in_init) somehow causes
|
|
the sensing to be disabled such that nrf_gpio_cfg_sense_set(pin, NRF_GPIO_PIN_SENSE_LOW)
|
|
no longer works. To work around this we just call our standard pin watch function
|
|
to re-enable everything. */
|
|
jshPinWatch(BTN1_PININDEX, true, JSPW_NONE);
|
|
nrf_gpio_cfg_sense_set(pinInfo[BTN1_PININDEX].pin, NRF_GPIO_PIN_SENSE_LOW);
|
|
#else
|
|
jsExceptionHere(JSET_ERROR, ".off not implemented on emulator");
|
|
#endif
|
|
|
|
}
|
|
|
|
/*JSON{
|
|
"type" : "staticmethod",
|
|
"class" : "Bangle",
|
|
"name" : "off",
|
|
"generate" : "jswrap_banglejs_off",
|
|
"ifdef" : "BANGLEJS"
|
|
}
|
|
Turn Bangle.js off. It can only be woken by pressing BTN1.
|
|
*/
|
|
void jswrap_banglejs_off() {
|
|
#ifndef EMULATED
|
|
// If BTN1 is pressed wait until it is released
|
|
while (jshPinGetValue(BTN1_PININDEX));
|
|
// turn peripherals off
|
|
jswrap_banglejs_periph_off();
|
|
// system off
|
|
sd_power_system_off();
|
|
while(1);
|
|
#else
|
|
jsExceptionHere(JSET_ERROR, ".off not implemented on emulator");
|
|
#endif
|
|
}
|
|
|
|
/*JSON{
|
|
"type" : "staticmethod",
|
|
"class" : "Bangle",
|
|
"name" : "softOff",
|
|
"generate" : "jswrap_banglejs_softOff",
|
|
"ifdef" : "BANGLEJS"
|
|
}
|
|
Turn Bangle.js (mostly) off, but keep the CPU in sleep mode until BTN1 is
|
|
pressed to preserve the RTC (current time).
|
|
*/
|
|
void jswrap_banglejs_softOff() {
|
|
#ifndef EMULATED
|
|
// If BTN1 is pressed wait until it is released
|
|
while (jshPinGetValue(BTN1_PININDEX));
|
|
// turn BLE and peripherals off
|
|
jswrap_ble_sleep();
|
|
jswrap_banglejs_periph_off();
|
|
jshDelayMicroseconds(100000); // wait 100ms for any button bounce to disappear
|
|
IOEventFlags channel = jshPinWatch(BTN1_PININDEX, true, JSPW_NONE);
|
|
if (channel!=EV_NONE) jshSetEventCallback(channel, (JshEventCallbackCallback)jshHadEvent);
|
|
// keep sleeping until a button is pressed
|
|
jshKickWatchDog();
|
|
do {
|
|
// sleep until BTN1 pressed
|
|
while (!jshPinGetValue(BTN1_PININDEX)) {
|
|
jshKickWatchDog();
|
|
jshSleep(jshGetTimeFromMilliseconds(4*1000));
|
|
}
|
|
// wait for button to be pressed for at least 200ms
|
|
int timeout = 200;
|
|
while (jshPinGetValue(BTN1_PININDEX) && timeout--)
|
|
nrf_delay_ms(1);
|
|
// if button not pressed, keep sleeping
|
|
} while (!jshPinGetValue(BTN1_PININDEX));
|
|
// restart
|
|
jshReboot();
|
|
|
|
#else
|
|
jsExceptionHere(JSET_ERROR, ".off not implemented on emulator");
|
|
#endif
|
|
}
|
|
|
|
/*JSON{
|
|
"type" : "staticmethod",
|
|
"class" : "Bangle",
|
|
"name" : "getLogo",
|
|
"generate" : "jswrap_banglejs_getLogo",
|
|
"return" : ["JsVar", "An image to be used with `g.drawImage` (as a String)" ],
|
|
"ifdef" : "BANGLEJS",
|
|
"typescript" : "getLogo(): string;"
|
|
}
|
|
|
|
* On platforms with an LCD of >=8bpp this is 222 x 104 x 2 bits
|
|
* Otherwise it's 119 x 56 x 1 bits
|
|
*/
|
|
JsVar *jswrap_banglejs_getLogo() {
|
|
#if LCD_BPP>=8
|
|
const unsigned char img_compressed[1419] = { // 222 x 104 x 2 bits
|
|
239, 90, 32, 66, 218, 160, 95, 240, 0, 127, 211, 46, 19, 241, 184, 159, 1,
|
|
181, 240, 177, 176, 159, 243, 118, 3, 97, 126, 131, 107, 225, 227,
|
|
113, 112, 6, 237, 127, 6, 239, 205, 214, 255, 13, 219, 112, 6, 215,
|
|
64, 77, 219, 71, 205, 218, 192, 141, 219, 75, 198, 226, 244, 6, 215,
|
|
64, 198, 194, 255, 77, 215, 207, 198, 226, 248, 13, 218, 255, 141,
|
|
219, 124, 13, 218, 255, 205, 215, 223, 205, 218, 194, 205, 219, 13,
|
|
134, 252, 13, 174, 143, 141, 198, 224, 13, 173, 129, 13, 134, 254,
|
|
138, 31, 131, 96, 1, 198, 75, 198, 227, 116, 6, 239, 239, 1, 166, 65,
|
|
198, 32, 255, 178, 182, 160, 32, 67, 103, 225, 108, 115, 20, 161, 120,
|
|
0, 193, 91, 212, 208, 155, 141, 27, 2, 181, 18, 55, 255, 252, 228, 0,
|
|
160, 195, 213, 193, 80, 13, 210, 95, 198, 194, 133, 65, 24, 8, 230,
|
|
12, 110, 185, 184, 200, 248, 216, 96, 64, 74, 112, 161, 254, 173, 86,
|
|
253, 204, 185, 184, 200, 63, 255, 160, 9, 26, 95, 255, 250, 1, 1, 252,
|
|
15, 194, 1, 132, 110, 130, 3, 255, 248, 2, 5, 136, 43, 194, 39, 131,
|
|
15, 61, 133, 47, 36, 14, 110, 110, 255, 252, 3, 139, 15, 32, 132, 10,
|
|
8, 212, 30, 252, 221, 20, 61, 150, 52, 7, 193, 0, 129, 185, 194, 55,
|
|
7, 3, 64, 12, 110, 110, 4, 154, 8, 0, 36, 241, 172, 23, 244, 220, 44,
|
|
190, 128, 110, 134, 254, 91, 26, 30, 43, 8, 22, 17, 184, 56, 8, 236,
|
|
51, 115, 208, 243, 16, 74, 81, 143, 65, 13, 1, 25, 6, 59, 12, 220,
|
|
240, 168, 20, 144, 202, 80, 136, 97, 155, 167, 159, 169, 70, 133, 143,
|
|
131, 6, 4, 221, 154, 22, 165, 40, 246, 25, 184, 56, 25, 40, 99, 115,
|
|
109, 0, 148, 164, 40, 193, 254, 27, 132, 191, 141, 149, 55, 23, 47,
|
|
250, 41, 74, 128, 111, 248, 6, 224, 225, 253, 3, 115, 85, 172, 128,
|
|
40, 56, 31, 253, 74, 80, 52, 31, 241, 184, 56, 57, 200, 51, 114, 208,
|
|
31, 255, 252, 58, 36, 252, 180, 50, 148, 67, 224, 63, 3, 112, 80, 177,
|
|
224, 32, 0, 226, 213, 0, 48, 145, 84, 56, 144, 27, 73, 184, 109, 248,
|
|
220, 21, 0, 112, 182, 120, 42, 82, 137, 128, 187, 65, 0, 4, 74, 16,
|
|
64, 15, 210, 22, 8, 7, 240, 6, 5, 16, 28, 28, 40, 14, 2, 124, 110, 14,
|
|
255, 249, 131, 4, 4, 255, 172, 67, 82, 142, 215, 7, 233, 15, 27, 134,
|
|
12, 14, 254, 87, 11, 122, 16, 4, 188, 17, 20, 33, 176, 95, 252, 31,
|
|
128, 220, 16, 128, 18, 128, 73, 16, 64, 224, 178, 70, 82, 138, 69, 7,
|
|
1, 254, 213, 106, 133, 64, 138, 66, 151, 255, 168, 223, 254, 216, 28,
|
|
31, 255, 250, 173, 255, 68, 130, 134, 7, 203, 91, 255, 27, 131, 129,
|
|
139, 130, 128, 177, 67, 75, 130, 165, 41, 176, 22, 124, 150, 20, 8,
|
|
82, 20, 44, 64, 8, 176, 19, 112, 76, 80, 96, 108, 128, 175, 228, 0,
|
|
97, 104, 64, 77, 193, 111, 214, 65, 252, 0, 96, 199, 192, 97, 0, 3,
|
|
176, 1, 133, 77, 130, 126, 14, 7, 253, 29, 2, 2, 4, 124, 17, 184, 36,
|
|
136, 64, 80, 99, 235, 112, 163, 230, 80, 77, 192, 195, 218, 129, 203,
|
|
203, 64, 134, 129, 23, 138, 0, 26, 6, 252, 8, 234, 25, 48, 27, 249,
|
|
252, 40, 88, 236, 17, 152, 64, 160, 83, 130, 1, 129, 55, 5, 1, 106, 8,
|
|
127, 10, 94, 110, 22, 30, 55, 26, 126, 110, 16, 12, 13, 2, 30, 96, 8,
|
|
56, 8, 208, 17, 16, 80, 50, 32, 64, 192, 154, 129, 27, 129, 159, 139,
|
|
4, 57, 130, 110, 29, 88, 49, 184, 104, 8, 180, 17, 120, 64, 0, 77,
|
|
193, 37, 130, 21, 130, 116, 12, 8, 9, 184, 32, 248, 64, 0, 152, 33,
|
|
27, 134, 0, 14, 110, 24, 90, 12, 125, 90, 32, 184, 16, 112, 80, 33,
|
|
16, 36, 65, 47, 198, 224, 198, 226, 34, 129, 110, 147, 80, 55, 21, 0,
|
|
222, 138, 194, 0, 5, 125, 32, 2, 14, 10, 95, 0, 34, 9, 3, 55, 7, 250,
|
|
213, 0, 2, 212, 183, 74, 223, 179, 131, 55, 14, 34, 5, 140, 36, 12,
|
|
220, 52, 7, 232, 13, 14, 124, 221, 28, 0, 19, 116, 137, 116, 124, 220,
|
|
92, 250, 6, 78, 18, 122, 110, 26, 95, 64, 55, 14, 212, 28, 220, 60,
|
|
13, 250, 17, 185, 136, 17, 185, 8, 9, 184, 121, 224, 24, 72, 57, 184,
|
|
107, 241, 184, 216, 249, 184, 114, 0, 166, 112, 205, 195, 64, 76, 194,
|
|
56, 193, 244, 129, 196, 133, 244, 19, 2, 1, 129, 55, 10, 5, 12, 220,
|
|
44, 125, 42, 28, 80, 6, 17, 120, 83, 112, 48, 112, 208, 144, 255, 193,
|
|
124, 48, 0, 63, 194, 48, 191, 211, 112, 208, 241, 104, 112, 115, 112,
|
|
65, 16, 142, 225, 26, 130, 135, 255, 13, 194, 192, 32, 200, 1, 63,
|
|
244, 1, 120, 146, 252, 0, 164, 72, 249, 184, 125, 240, 80, 63, 241,
|
|
184, 40, 58, 116, 61, 248, 12, 48, 248, 102, 224, 130, 32, 153, 195,
|
|
143, 252, 13, 194, 7, 66, 13, 135, 3, 55, 15, 3, 41, 135, 47, 55, 7,
|
|
47, 36, 130, 3, 4, 100, 16, 104, 35, 112, 176, 9, 224, 24, 40, 23,
|
|
253, 144, 36, 4, 178, 12, 12, 14, 31, 252, 220, 56, 180, 56, 89, 184,
|
|
50, 112, 36, 112, 99, 228, 64, 231, 231, 112, 205, 193, 93, 32, 103,
|
|
112, 231, 254, 3, 112, 129, 192, 194, 225, 79, 205, 195, 175, 66, 41,
|
|
4, 110, 12, 10, 15, 235, 254, 105, 12, 14, 8, 68, 19, 112, 112, 63,
|
|
41, 16, 56, 91, 172, 18, 248, 98, 16, 64, 128, 223, 192, 96, 203, 255,
|
|
102, 225, 224, 98, 80, 96, 63, 251, 241, 184, 56, 63, 255, 254, 118,
|
|
4, 96, 26, 56, 51, 112, 81, 245, 112, 159, 254, 4, 60, 46, 8, 0, 18,
|
|
16, 66, 96, 64, 0, 255, 55, 166, 225, 225, 96, 208, 186, 126, 8, 56,
|
|
48, 40, 48, 216, 84, 136, 43, 128, 155, 161, 193, 0, 193, 203, 198, 0,
|
|
141, 131, 37, 6, 115, 5, 28, 12, 44, 220, 58, 208, 16, 104, 62, 9,
|
|
240, 188, 64, 0, 200, 0, 77, 194, 131, 7, 44, 5, 254, 200, 2, 195, 27,
|
|
130, 128, 6, 250, 79, 5, 136, 20, 124, 26, 20, 108, 108, 12, 13, 118,
|
|
16, 0, 147, 8, 70, 225, 0, 2, 127, 207, 98, 0, 22, 55, 24, 42, 24,
|
|
220, 44, 189, 212, 40, 1, 88, 89, 184, 200, 58, 152, 67, 112, 160,
|
|
177, 64, 9, 85, 0, 129, 239, 2, 70, 75, 255, 128, 162, 32, 42, 85,
|
|
225, 97, 112, 225, 252, 5, 49, 167, 210, 103, 234, 85, 231, 224, 48,
|
|
160, 65, 195, 83, 64, 145, 136, 143, 130, 137, 0, 26, 50, 19, 126, 19,
|
|
52, 88, 9, 4, 149, 137, 32, 3, 97, 255, 70, 112, 76, 35, 175, 255,
|
|
210, 160, 255, 255, 253, 0, 110, 181, 255, 243, 64, 215, 142, 130, 0,
|
|
23, 1, 255, 173, 134, 4, 3, 255, 40, 156, 35, 40, 0, 9, 116, 116, 225,
|
|
35, 113, 183, 255, 242, 137, 195, 115, 6, 199, 174, 129, 27, 139, 1,
|
|
27, 3, 153, 27, 46, 0, 6, 213, 164, 9, 33, 47, 255, 255, 128, 161, 65,
|
|
148, 140, 188, 151, 24, 131, 233, 171, 107, 192, 128, 248, 6, 214,
|
|
192, 158, 65, 0, 3, 95, 160, 0, 121, 76, 8, 0, 85, 45, 208, 17, 176,
|
|
223, 195, 117, 208, 241, 184, 216, 1, 189, 219, 241, 176, 191, 1, 181,
|
|
208, 115, 112, 195, 107, 160, 19, 243, 118, 176, 51, 119, 190, 3, 118,
|
|
191, 227, 118, 221, 1, 181, 208, 33, 176, 191, 211, 117, 242, 241,
|
|
184, 188, 1, 181, 208, 17, 176, 191, 195, 117, 241, 243, 118, 176, 3,
|
|
118, 208, 243, 119, 159, 131, 118, 216, 3, 119, 230, 235, 104, 3, 123,
|
|
247, 227, 97, 62, 3, 107, 224, 102, 225, 70, 215, 192, 35, 227, 97,
|
|
58, 0, 177, 0
|
|
};
|
|
#else
|
|
const unsigned char img_compressed[467] = { // 119 x 56 x 1 bits
|
|
187, 206, 32, 32, 210, 160, 63, 255, 128, 112, 184, 63, 255, 192, 14, 46,
|
|
30, 14, 54, 63, 255, 224, 3, 139, 159, 255, 240, 0, 226, 239, 255, 243,
|
|
57, 159, 255, 240, 52, 179, 56, 63, 195, 57, 191, 131, 57, 168, 83, 12,
|
|
224, 157, 140, 51, 130, 14, 48, 206, 10, 20, 184, 15, 226, 62, 14, 33,
|
|
28, 20, 63, 1, 7, 240, 95, 194, 2, 185, 131, 158, 64, 32, 126, 12, 5, 0,
|
|
28, 36, 8, 6, 17, 148, 24, 15, 132, 74, 24, 0, 40, 32, 128, 33, 56, 48,
|
|
30, 16, 56, 104, 66, 76, 32, 16, 52, 30, 32, 208, 48, 0, 81, 0, 16, 49,
|
|
192, 224, 56, 28, 64, 208, 48, 0, 82, 0, 16, 51, 2, 0, 48, 60, 128, 148,
|
|
32, 0, 101, 0, 146, 32, 194, 32, 114, 1, 40, 71, 97, 96, 32, 32, 102,
|
|
16, 122, 1, 156, 82, 8, 80, 49, 16, 80, 229, 40, 49, 254, 69, 254, 1,
|
|
126, 161, 64, 255, 16, 224, 152, 80, 208, 136, 52, 10, 135, 6, 7, 2,
|
|
194, 66, 35, 5, 128, 96, 208, 8, 241, 168, 24, 5, 134, 3, 12, 129, 194,
|
|
36, 17, 130, 192, 37, 80, 84, 192, 209, 32, 20, 40, 20, 64, 2, 14, 2,
|
|
33, 96, 54, 5, 56, 128, 98, 32, 24, 72, 36, 130, 9, 33, 5, 130, 162, 4,
|
|
170, 13, 194, 3, 16, 128, 194, 33, 24, 8, 69, 15, 255, 193, 51, 131, 8,
|
|
128, 248, 32, 82, 1, 64, 49, 21, 16, 56, 67, 56, 56, 8, 12, 162, 7, 192,
|
|
130, 80, 5, 64, 138, 65, 144, 145, 65, 96, 3, 2, 192, 65, 132, 192, 128,
|
|
65, 160, 80, 137, 160, 166, 193, 50, 131, 4, 240, 71, 72, 32, 224, 38,
|
|
24, 12, 52, 7, 9, 16, 134, 67, 7, 64, 216, 39, 74, 176, 112, 26, 24, 12,
|
|
26, 11, 8, 136, 76, 16, 160, 197, 40, 22, 127, 244, 3, 252, 118, 6, 15,
|
|
242, 136, 71, 248, 17, 136, 4, 158, 2, 107, 4, 0, 36, 16, 6, 18, 4, 26,
|
|
3, 16, 32, 0, 143, 48, 193, 192, 224, 24, 128, 186, 197, 78, 130, 14, 9,
|
|
158, 8, 68, 23, 88, 175, 48, 239, 0, 32, 126, 0, 225, 112, 32, 80, 193,
|
|
192, 193, 201, 35, 127, 0, 129, 223, 32, 19, 242, 72, 192, 1, 39, 240,
|
|
63, 192, 56, 207, 255, 128, 28, 93, 255, 254, 58, 24, 0, 47, 255, 254,
|
|
13, 46, 3, 255, 87, 130, 0, 42, 7, 255, 47, 136, 14, 36, 176, 100, 31,
|
|
254, 147, 20, 0, 52, 60, 206, 108, 124, 206, 106, 20, 28, 2, 20, 208,
|
|
105, 104, 80, 76, 230, 3, 129, 51, 130 };
|
|
#endif
|
|
JsVar *v = jsvNewNativeString((char*)&img_compressed[0], sizeof(img_compressed));
|
|
JsVar *ab = jswrap_heatshrink_decompress(v);
|
|
JsVar *img = jsvGetArrayBufferBackingString(ab, NULL);
|
|
jsvUnLock2(v,ab);
|
|
return img;
|
|
}
|
|
|
|
/*JSON{
|
|
"type" : "staticmethod",
|
|
"class" : "Bangle",
|
|
"name" : "loadWidgets",
|
|
"generate_js" : "libs/js/banglejs/Bangle_loadWidgets.min.js",
|
|
"ifdef" : "BANGLEJS"
|
|
}
|
|
Load all widgets from flash Storage. Call this once at the beginning of your
|
|
application if you want any on-screen widgets to be loaded.
|
|
|
|
They will be loaded into a global `WIDGETS` array, and can be rendered with
|
|
`Bangle.drawWidgets`.
|
|
*/
|
|
/*JSON{
|
|
"type" : "staticmethod",
|
|
"class" : "Bangle",
|
|
"name" : "drawWidgets",
|
|
"generate_js" : "libs/js/banglejs/Bangle_drawWidgets.min.js",
|
|
"ifdef" : "BANGLEJS"
|
|
}
|
|
Draw any onscreen widgets that were loaded with `Bangle.loadWidgets()`.
|
|
|
|
Widgets should redraw themselves when something changes - you'll only need to
|
|
call drawWidgets if you decide to clear the entire screen with `g.clear()`.
|
|
*/
|
|
/*JSON{
|
|
"type" : "staticmethod", "class" : "Bangle", "name" : "drawWidgets", "patch":true,
|
|
"generate_js" : "libs/js/banglejs/Bangle_drawWidgets_Q3.min.js",
|
|
"#if" : "defined(BANGLEJS) && defined(BANGLEJS_Q3)"
|
|
}
|
|
*/
|
|
|
|
/*JSON{
|
|
"type" : "staticmethod",
|
|
"class" : "Bangle",
|
|
"name" : "showLauncher",
|
|
"generate_js" : "libs/js/banglejs/Bangle_showLauncher.min.js",
|
|
"ifdef" : "BANGLEJS"
|
|
}
|
|
Load the Bangle.js app launcher, which will allow the user to select an
|
|
application to launch.
|
|
*/
|
|
|
|
/*JSON{
|
|
"type" : "staticmethod",
|
|
"class" : "Bangle",
|
|
"name" : "showClock",
|
|
"generate_js" : "libs/js/banglejs/Bangle_showClock.min.js",
|
|
"ifdef" : "BANGLEJS"
|
|
}
|
|
Load the Bangle.js clock - this has the same effect as calling `Bangle.load()`.
|
|
*/
|
|
|
|
/*JSON{
|
|
"type" : "staticmethod",
|
|
"class" : "Bangle",
|
|
"name" : "load",
|
|
"generate_js" : "libs/js/banglejs/Bangle_load.min.js",
|
|
"params" : [
|
|
["file","JsVar","(optional) A string containing the file name for the app to be loaded"]
|
|
],
|
|
"ifdef" : "BANGLEJS",
|
|
"typescript": [
|
|
"load(file: string): void;",
|
|
"load(): void;"
|
|
]
|
|
}
|
|
This behaves the same as the global `load()` function, but if fast
|
|
loading is possible (`Bangle.setUI` was called with a `remove` handler)
|
|
then instead of a complete reload, the `remove` handler will be
|
|
called and the new app will be loaded straight after with `eval`.
|
|
|
|
**This should only be used if the app being loaded also uses widgets**
|
|
(eg it contains a `Bangle.loadWidgets()` call).
|
|
|
|
`load()` is slower, but safer. As such, care should be taken
|
|
when using `Bangle.load()` with `Bangle.setUI({..., remove:...})`
|
|
as if your remove handler doesn't completely clean up after your app,
|
|
memory leaks or other issues could occur - see `Bangle.setUI` for more
|
|
information.
|
|
*/
|
|
|
|
/*JSON{
|
|
"type" : "staticmethod",
|
|
"class" : "E",
|
|
"name" : "showMenu",
|
|
"generate_js" : "libs/js/banglejs/E_showMenu_F18.min.js",
|
|
"params" : [
|
|
["menu","JsVar","An object containing name->function mappings to to be used in a menu"]
|
|
],
|
|
"return" : ["JsVar", "A menu object with `draw`, `move` and `select` functions" ],
|
|
"ifdef" : "BANGLEJS",
|
|
"typescript": [
|
|
"showMenu(menu: Menu): MenuInstance;",
|
|
"showMenu(): void;"
|
|
]
|
|
}
|
|
Display a menu on the screen, and set up the buttons to navigate through it.
|
|
|
|
Supply an object containing menu items. When an item is selected, the function
|
|
it references will be executed. For example:
|
|
|
|
```
|
|
var boolean = false;
|
|
var number = 50;
|
|
// First menu
|
|
var mainmenu = {
|
|
"" : { title : "-- Main Menu --" }, // options
|
|
"LED On" : function() { LED1.set(); },
|
|
"LED Off" : function() { LED1.reset(); },
|
|
"Submenu" : function() { E.showMenu(submenu); },
|
|
"A Boolean" : {
|
|
value : boolean,
|
|
format : v => v?"On":"Off",
|
|
onchange : v => { boolean=v; }
|
|
},
|
|
"A Number" : {
|
|
value : number,
|
|
min:0,max:100,step:10,
|
|
onchange : v => { number=v; }
|
|
},
|
|
"Exit" : function() { E.showMenu(); }, // remove the menu
|
|
};
|
|
// Submenu
|
|
var submenu = {
|
|
"" : { title : "-- SubMenu --",
|
|
back : function() { E.showMenu(mainmenu); } },
|
|
"One" : undefined, // do nothing
|
|
"Two" : undefined // do nothing
|
|
};
|
|
// Actually display the menu
|
|
E.showMenu(mainmenu);
|
|
```
|
|
|
|
The menu will stay onscreen and active until explicitly removed, which you can
|
|
do by calling `E.showMenu()` without arguments.
|
|
|
|
See http://www.espruino.com/graphical_menu for more detailed information.
|
|
|
|
On Bangle.js there are a few additions over the standard `graphical_menu`:
|
|
|
|
* The options object can contain:
|
|
* `back : function() { }` - add a 'back' button, with the function called when
|
|
it is pressed
|
|
* `remove : function() { }` - add a handler function to be called when the
|
|
menu is removed
|
|
* (Bangle.js 2) `scroll : int` - an integer specifying how much the initial
|
|
menu should be scrolled by
|
|
* The object returned by `E.showMenu` contains:
|
|
* (Bangle.js 2) `scroller` - the object returned by `E.showScroller` -
|
|
`scroller.scroll` returns the amount the menu is currently scrolled by
|
|
* In the object specified for editable numbers:
|
|
* (Bangle.js 2) the `format` function is called with `format(value)` in the
|
|
main menu, `format(value,1)` when in a scrollable list, or `format(value,2)`
|
|
when in a popup window.
|
|
|
|
You can also specify menu items as an array (rather than an Object). This can be
|
|
useful if you have menu items with the same title, or you want to `push` menu
|
|
items onto an array:
|
|
|
|
```
|
|
var menu = [
|
|
{ title:"Something", onchange:function() { print("selected"); } },
|
|
{ title:"On or Off", value:false, onchange: v => print(v) },
|
|
{ title:"A Value", value:3, min:0, max:10, onchange: v => print(v) },
|
|
];
|
|
menu[""] = { title:"Hello" };
|
|
E.showMenu(menu);
|
|
```
|
|
*/
|
|
|
|
/*JSON{
|
|
"type" : "staticmethod",
|
|
"class" : "E",
|
|
"name" : "showMessage",
|
|
"generate_js" : "libs/js/banglejs/E_showMessage.min.js",
|
|
"params" : [
|
|
["message","JsVar","A message to display. Can include newlines"],
|
|
["options","JsVar","(optional) a title for the message, or an object of options `{title:string, img:image_string}`"]
|
|
],
|
|
"ifdef" : "BANGLEJS",
|
|
"typescript" : "showMessage(message: string, title?: string | { title?: string, img?: string }): void;"
|
|
}
|
|
|
|
A utility function for displaying a full screen message on the screen.
|
|
|
|
Draws to the screen and returns immediately.
|
|
|
|
```
|
|
E.showMessage("These are\nLots of\nLines","My Title")
|
|
```
|
|
|
|
or to display an image as well as text:
|
|
|
|
```
|
|
E.showMessage("Lots of text will wrap automatically",{
|
|
title:"Warning",
|
|
img:atob("FBQBAfgAf+Af/4P//D+fx/n+f5/v+f//n//5//+f//n////3//5/n+P//D//wf/4B/4AH4A=")
|
|
})
|
|
```
|
|
|
|
|
|
|
|
*/
|
|
/*JSON{
|
|
"type" : "staticmethod",
|
|
"class" : "E",
|
|
"name" : "showPrompt",
|
|
"generate_js" : "libs/js/banglejs/E_showPrompt.min.js",
|
|
"params" : [
|
|
["message","JsVar","A message to display. Can include newlines"],
|
|
["options","JsVar","(optional) an object of options (see below)"]
|
|
],
|
|
"return" : ["JsVar","A promise that is resolved when 'Ok' is pressed"],
|
|
"ifdef" : "BANGLEJS",
|
|
"typescript" : [
|
|
"showPrompt<T = boolean>(message: string, options?: { title?: string, buttons?: { [key: string]: T }, remove?: () => void }): Promise<T>;",
|
|
"showPrompt(): void;"
|
|
]
|
|
}
|
|
|
|
Displays a full screen prompt on the screen, with the buttons requested (or
|
|
`Yes` and `No` for defaults).
|
|
|
|
When the button is pressed the promise is resolved with the requested values
|
|
(for the `Yes` and `No` defaults, `true` and `false` are returned).
|
|
|
|
```
|
|
E.showPrompt("Do you like fish?").then(function(v) {
|
|
if (v) print("'Yes' chosen");
|
|
else print("'No' chosen");
|
|
});
|
|
// Or
|
|
E.showPrompt("How many fish\ndo you like?",{
|
|
title:"Fish",
|
|
buttons : {"One":1,"Two":2,"Three":3}
|
|
}).then(function(v) {
|
|
print("You like "+v+" fish");
|
|
});
|
|
// Or
|
|
E.showPrompt("Continue?", {
|
|
title:"Alert",
|
|
img:atob("FBQBAfgAf+Af/4P//D+fx/n+f5/v+f//n//5//+f//n////3//5/n+P//D//wf/4B/4AH4A=")}).then(function(v) {
|
|
if (v) print("'Yes' chosen");
|
|
else print("'No' chosen");
|
|
});
|
|
```
|
|
|
|
To remove the prompt, call `E.showPrompt()` with no arguments.
|
|
|
|
The second `options` argument can contain:
|
|
|
|
```
|
|
{
|
|
title: "Hello", // optional Title
|
|
buttons : {"Ok":true,"Cancel":false}, // optional list of button text & return value
|
|
img: "image_string" // optional image string to draw
|
|
remove: function() { } // Bangle.js: optional function to be called when the prompt is removed
|
|
}
|
|
```
|
|
*/
|
|
|
|
/*JSON{
|
|
"type" : "staticmethod",
|
|
"class" : "E",
|
|
"name" : "showScroller",
|
|
"generate_js" : "libs/js/banglejs/E_showScroller.min.js",
|
|
"params" : [
|
|
["options","JsVar","An object containing `{ h, c, draw, select, back, remove }` (see below) "]
|
|
],
|
|
"return" : ["JsVar", "A menu object with `draw()` and `drawItem(itemNo)` functions" ],
|
|
"ifdef" : "BANGLEJS",
|
|
"typescript" : [
|
|
"showScroller(options?: { h: number, c: number, draw: (idx: number, rect: { x: number, y: number, w: number, h: number }) => void, select: (idx: number, touch?: {x: number, y: number}) => void, back?: () => void, remove?: () => void }): { draw: () => void, drawItem: (itemNo: number) => void };",
|
|
"showScroller(): void;"
|
|
]
|
|
}
|
|
Display a scrollable menu on the screen, and set up the buttons/touchscreen to
|
|
navigate through it and select items.
|
|
|
|
Supply an object containing:
|
|
|
|
```
|
|
{
|
|
h : 24, // height of each menu item in pixels
|
|
c : 10, // number of menu items
|
|
// a function to draw a menu item
|
|
draw : function(idx, rect) { ... }
|
|
// a function to call when the item is selected, touch parameter is only relevant
|
|
// for Bangle.js 2 and contains the coordinates touched inside the selected item
|
|
select : function(idx, touch) { ... }
|
|
// optional function to be called when 'back' is tapped
|
|
back : function() { ...}
|
|
// Bangle.js: optional function to be called when the scroller should be removed
|
|
remove : function() {}
|
|
}
|
|
```
|
|
|
|
For example to display a list of numbers:
|
|
|
|
```
|
|
E.showScroller({
|
|
h : 40, c : 8,
|
|
draw : (idx, r) => {
|
|
g.setBgColor((idx&1)?"#666":"#999").clearRect(r.x,r.y,r.x+r.w-1,r.y+r.h-1);
|
|
g.setFont("6x8:2").drawString("Item Number\n"+idx,r.x+10,r.y+4);
|
|
},
|
|
select : (idx) => console.log("You selected ", idx)
|
|
});
|
|
```
|
|
|
|
To remove the scroller, just call `E.showScroller()`
|
|
*/
|
|
|
|
/*JSON{
|
|
"type" : "staticmethod", "class" : "E", "name" : "showMenu", "patch":true,
|
|
"generate_js" : "libs/js/banglejs/E_showMenu_Q3.min.js",
|
|
"#if" : "defined(BANGLEJS) && defined(BANGLEJS_Q3)"
|
|
}
|
|
*/
|
|
/*JSON{
|
|
"type" : "staticmethod", "class" : "E", "name" : "showMenu", "patch":true,
|
|
"generate_js" : "libs/js/banglejs/E_showMenu_F5.js",
|
|
"#if" : "defined(BANGLEJS) && defined(DTNO1_F5)"
|
|
}
|
|
*/
|
|
/*JSON{
|
|
"type" : "staticmethod", "class" : "E", "name" : "showPrompt", "patch":true,
|
|
"generate_js" : "libs/js/banglejs/E_showPrompt_Q3.min.js",
|
|
"#if" : "defined(BANGLEJS) && defined(BANGLEJS_Q3)"
|
|
}
|
|
*/
|
|
/*JSON{
|
|
"type" : "staticmethod", "class" : "E", "name" : "showScroller", "patch":true,
|
|
"generate_js" : "libs/js/banglejs/E_showScroller_Q3.min.js",
|
|
"#if" : "defined(BANGLEJS) && defined(BANGLEJS_Q3)"
|
|
}
|
|
*/
|
|
|
|
/*JSON{
|
|
"type" : "staticmethod",
|
|
"class" : "E",
|
|
"name" : "showAlert",
|
|
"generate_js" : "libs/js/banglejs/E_showAlert.min.js",
|
|
"params" : [
|
|
["message","JsVar","A message to display. Can include newlines"],
|
|
["options","JsVar","(optional) a title for the message or an object containing options"]
|
|
],
|
|
"return" : ["JsVar","A promise that is resolved when 'Ok' is pressed"],
|
|
"ifdef" : "BANGLEJS",
|
|
"typescript" : [
|
|
"showAlert(message?: string, options?: string): Promise<void>;",
|
|
"showAlert(message?: string, options?: { title?: string, remove?: () => void }): Promise<void>;"
|
|
]
|
|
}
|
|
|
|
Displays a full screen prompt on the screen, with a single 'Ok' button.
|
|
|
|
When the button is pressed the promise is resolved.
|
|
|
|
```
|
|
E.showAlert("Hello").then(function() {
|
|
print("Ok pressed");
|
|
});
|
|
// or
|
|
E.showAlert("These are\nLots of\nLines","My Title").then(function() {
|
|
print("Ok pressed");
|
|
});
|
|
```
|
|
|
|
To remove the window, call `E.showAlert()` with no arguments.
|
|
*/
|
|
|
|
/*JSON{
|
|
"type" : "variable",
|
|
"name" : "LED",
|
|
"generate" : "gen_jswrap_LED1",
|
|
"return" : ["JsVar","A `Pin` object for a fake LED which appears on "],
|
|
"ifdef" : "BANGLEJS", "no_docs":1
|
|
}
|
|
|
|
On most Espruino board there are LEDs, in which case `LED` will be an actual
|
|
Pin.
|
|
|
|
On Bangle.js there are no LEDs, so to remain compatible with example code that
|
|
might expect an LED, this is an object that behaves like a pin, but which just
|
|
displays a circle on the display
|
|
*/
|
|
/*JSON{
|
|
"type" : "variable",
|
|
"name" : "LED1",
|
|
"generate_js" : "libs/js/banglejs/LED1.min.js",
|
|
"return" : ["JsVar","A `Pin` object for a fake LED which appears on "],
|
|
"ifdef" : "BANGLEJS", "no_docs":1
|
|
}
|
|
|
|
On most Espruino board there are LEDs, in which case `LED1` will be an actual
|
|
Pin.
|
|
|
|
On Bangle.js there are no LEDs, so to remain compatible with example code that
|
|
might expect an LED, this is an object that behaves like a pin, but which just
|
|
displays a circle on the display
|
|
*/
|
|
/*JSON{
|
|
"type" : "variable",
|
|
"name" : "LED2",
|
|
"generate_js" : "libs/js/banglejs/LED2.min.js",
|
|
"return" : ["JsVar","A `Pin` object for a fake LED which appears on "],
|
|
"ifdef" : "BANGLEJS", "no_docs":1
|
|
}
|
|
|
|
On most Espruino board there are LEDs, in which case `LED2` will be an actual
|
|
Pin.
|
|
|
|
On Bangle.js there are no LEDs, so to remain compatible with example code that
|
|
might expect an LED, this is an object that behaves like a pin, but which just
|
|
displays a circle on the display
|
|
*/
|
|
|
|
/*JSON{
|
|
"type" : "staticmethod",
|
|
"class" : "Bangle",
|
|
"name" : "setUI",
|
|
"generate_js" : "libs/js/banglejs/Bangle_setUI_F18.min.js",
|
|
"params" : [
|
|
["type","JsVar","The type of UI input: 'updown', 'leftright', 'clock', 'clockupdown' or undefined to cancel. Can also be an object (see below)"],
|
|
["callback","JsVar","A function with one argument which is the direction"]
|
|
],
|
|
"ifdef" : "BANGLEJS",
|
|
"typescript" : "setUI(type?: \"updown\" | \"leftright\" | \"clock\" | \"clockupdown\" | { mode: \"custom\"; back?: () => void; touch?: TouchCallback; swipe?: SwipeCallback; drag?: DragCallback; btn?: (n: number) => void, clock?: boolean }, callback?: (direction?: -1 | 1) => void): void;"
|
|
}
|
|
This puts Bangle.js into the specified UI input mode, and calls the callback
|
|
provided when there is user input.
|
|
|
|
Currently supported interface types are:
|
|
|
|
* 'updown' - UI input with upwards motion `cb(-1)`, downwards motion `cb(1)`,
|
|
and select `cb()`
|
|
* Bangle.js 1 uses BTN1/3 for up/down and BTN2 for select
|
|
* Bangle.js 2 uses touchscreen swipe up/down and tap
|
|
* 'leftright' - UI input with left motion `cb(-1)`, right motion `cb(1)`, and
|
|
select `cb()`
|
|
* Bangle.js 1 uses BTN1/3 for left/right and BTN2 for select
|
|
* Bangle.js 2 uses touchscreen swipe left/right and tap/BTN1 for select
|
|
* 'clock' - called for clocks. Sets `Bangle.CLOCK=1` and allows a button to
|
|
start the launcher
|
|
* Bangle.js 1 BTN2 starts the launcher
|
|
* Bangle.js 2 BTN1 starts the launcher
|
|
* 'clockupdown' - called for clocks. Sets `Bangle.CLOCK=1`, allows a button to
|
|
start the launcher, but also provides up/down functionality
|
|
* Bangle.js 1 BTN2 starts the launcher, BTN1/BTN3 call `cb(-1)` and `cb(1)`
|
|
* Bangle.js 2 BTN1 starts the launcher, touchscreen tap in top/bottom right
|
|
hand side calls `cb(-1)` and `cb(1)`
|
|
* `{mode:"custom", ...}` allows you to specify custom handlers for different
|
|
interactions. See below.
|
|
* `undefined` removes all user interaction code
|
|
|
|
While you could use setWatch/etc manually, the benefit here is that you don't
|
|
end up with multiple `setWatch` instances, and the actual input method (touch,
|
|
or buttons) is implemented dependent on the watch (Bangle.js 1 or 2)
|
|
|
|
**Note:** You can override this function in boot code to change the interaction
|
|
mode with the watch. For instance you could make all clocks start the launcher
|
|
with a swipe by using:
|
|
|
|
```
|
|
(function() {
|
|
var sui = Bangle.setUI;
|
|
Bangle.setUI = function(mode, cb) {
|
|
if (mode!="clock") return sui(mode,cb);
|
|
sui(); // clear
|
|
Bangle.CLOCK=1;
|
|
Bangle.swipeHandler = Bangle.showLauncher;
|
|
Bangle.on("swipe", Bangle.swipeHandler);
|
|
};
|
|
})();
|
|
```
|
|
|
|
The first argument can also be an object, in which case more options can be
|
|
specified:
|
|
|
|
```
|
|
Bangle.setUI({
|
|
mode : "custom",
|
|
back : function() {}, // optional - add a 'back' icon in top-left widget area and call this function when it is pressed , also call it when the hardware button is clicked (does not override btn if defined)
|
|
remove : function() {}, // optional - add a handler for when the UI should be removed (eg stop any intervals/timers here)
|
|
touch : function(n,e) {}, // optional - handler for 'touch' events
|
|
swipe : function(dir) {}, // optional - handler for 'swipe' events
|
|
drag : function(e) {}, // optional - handler for 'drag' events (Bangle.js 2 only)
|
|
btn : function(n) {}, // optional - handler for 'button' events (n==1 on Bangle.js 2, n==1/2/3 depending on button for Bangle.js 1)
|
|
clock : 0 // optional - if set the behavior of 'clock' mode is added (does not override btn if defined)
|
|
});
|
|
```
|
|
|
|
If `remove` is specified, `Bangle.showLauncher`, `Bangle.showClock`, `Bangle.load` and some apps
|
|
may choose to just call the `remove` function and then load a new app without resetting Bangle.js.
|
|
As a result, **if you specify 'remove' you should make sure you test that after calling `Bangle.setUI()`
|
|
without arguments your app is completely unloaded**, otherwise you may end up with memory leaks or
|
|
other issues when switching apps.
|
|
*/
|
|
/*JSON{
|
|
"type" : "staticmethod", "class" : "Bangle", "name" : "setUI", "patch":true,
|
|
"generate_js" : "libs/js/banglejs/Bangle_setUI_Q3.min.js",
|
|
"#if" : "defined(BANGLEJS) && defined(BANGLEJS_Q3)"
|
|
}
|
|
*/
|
|
|
|
/*JSON{
|
|
"type" : "staticmethod",
|
|
"class" : "Bangle",
|
|
"name" : "factoryReset",
|
|
"generate" : "jswrap_banglejs_factoryReset",
|
|
"#if" : "defined(BANGLEJS_Q3) || defined(EMULATED)"
|
|
}
|
|
|
|
Erase all storage and reload it with the default contents.
|
|
|
|
This is only available on Bangle.js 2.0. On Bangle.js 1.0 you need to use
|
|
`Install Default Apps` under the `More...` tab of http://banglejs.com/apps
|
|
*/
|
|
extern void ble_app_error_handler(uint32_t error_code, uint32_t line_num, const uint8_t * p_file_name);
|
|
void jswrap_banglejs_factoryReset() {
|
|
jsfResetStorage();
|
|
jsiStatus |= JSIS_TODO_FLASH_LOAD;
|
|
}
|
|
|
|
/*JSON{
|
|
"type" : "staticproperty",
|
|
"class" : "Bangle",
|
|
"name" : "appRect",
|
|
"generate" : "jswrap_banglejs_appRect",
|
|
"return" : ["JsVar","An object of the form `{x,y,w,h,x2,y2}`"],
|
|
"ifdef" : "BANGLEJS",
|
|
"typescript" : "appRect: { x: number, y: number, w: number, h: number, x2: number, y2: number };"
|
|
}
|
|
Returns the rectangle on the screen that is currently reserved for the app.
|
|
*/
|
|
JsVar *jswrap_banglejs_appRect() {
|
|
JsVar *o = jsvNewObject();
|
|
if (!o) return 0;
|
|
JsVar *widgetsVar = jsvObjectGetChild(execInfo.root,"WIDGETS",0);
|
|
int top = 0, btm = 0; // size of various widget areas
|
|
// check all widgets and see if any are in the top or bottom areas,
|
|
// set top/btm accordingly
|
|
if (jsvIsObject(widgetsVar)) {
|
|
JsvObjectIterator it;
|
|
jsvObjectIteratorNew(&it, widgetsVar);
|
|
while (jsvObjectIteratorHasValue(&it)) {
|
|
JsVar *widget = jsvObjectIteratorGetValue(&it);
|
|
JsVar *area = jsvObjectGetChild(widget, "area", 0);
|
|
JsVar *width = jsvObjectGetChild(widget, "width", 0);
|
|
if (jsvIsString(area) && jsvIsNumeric(width)) {
|
|
char a = jsvGetCharInString(area, 0);
|
|
int w = jsvGetIntegerAndUnLock(width);
|
|
if (a=='t' && w > 0) top=24;
|
|
if (a=='b' && w > 0) btm=24;
|
|
}
|
|
jsvUnLock2(area,widget);
|
|
jsvObjectIteratorNext(&it);
|
|
}
|
|
jsvObjectIteratorFree(&it);
|
|
}
|
|
jsvUnLock(widgetsVar);
|
|
jsvObjectSetChildAndUnLock(o,"x",jsvNewFromInteger(0));
|
|
jsvObjectSetChildAndUnLock(o,"y",jsvNewFromInteger(top));
|
|
jsvObjectSetChildAndUnLock(o,"w",jsvNewFromInteger(graphicsInternal.data.width));
|
|
jsvObjectSetChildAndUnLock(o,"h",jsvNewFromInteger(graphicsInternal.data.height-(top+btm)));
|
|
jsvObjectSetChildAndUnLock(o,"x2",jsvNewFromInteger(graphicsInternal.data.width-1));
|
|
jsvObjectSetChildAndUnLock(o,"y2",jsvNewFromInteger(graphicsInternal.data.height-(1+btm)));
|
|
|
|
|
|
|
|
return o;
|
|
}
|
|
|