Permalink
Cannot retrieve contributors at this time
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
5241 lines (4889 sloc)
181 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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 | |
/*JSON{ | |
"type": "class", | |
"class" : "Bangle", | |
"ifdef" : "BANGLEJS" | |
} | |
Class containing utility functions for the [Bangle.js Smart Watch](http://www.espruino.com/Bangle.js) | |
*/ | |
/*JSON{ | |
"type" : "variable", | |
"name" : "VIBRATE", | |
"generate_full" : "VIBRATE_PIN", | |
"ifdef" : "BANGLEJS", | |
"return" : ["pin",""] | |
} | |
The Bangle.js's vibration motor. | |
*/ | |
/*JSON{ | |
"type" : "event", | |
"class" : "Bangle", | |
"name" : "accel", | |
"params" : [["xyz","JsVar",""]], | |
"ifdef" : "BANGLEJS" | |
} | |
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. | |
*/ | |
/*JSON{ | |
"type" : "event", | |
"class" : "Bangle", | |
"name" : "health", | |
"params" : [["info","JsVar","An object containing the last 10 minutes health data"]], | |
"ifdef" : "BANGLEJS" | |
} | |
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? | |
*/ | |
/*JSON{ | |
"type" : "event", | |
"class" : "Bangle", | |
"name" : "mag", | |
"params" : [["xyz","JsVar",""]], | |
"ifdef" : "BANGLEJS" | |
} | |
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) | |
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" | |
} | |
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)`. | |
*/ | |
/*JSON{ | |
"type" : "event", | |
"class" : "Bangle", | |
"name" : "GPS", | |
"params" : [["fix","JsVar","An object with fix info (see below)"]], | |
"ifdef" : "BANGLEJS" | |
} | |
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" | |
} | |
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" | |
} | |
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 | |
} | |
``` | |
*/ | |
/*JSON{ | |
"type" : "event", | |
"class" : "Bangle", | |
"name" : "pressure", | |
"params" : [["e","JsVar","An object containing `{temperature,pressure,altitude}`"]], | |
"ifdef" : "BANGLEJS" | |
} | |
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()` | |
*/ | |
/*JSON{ | |
"type" : "event", | |
"class" : "Bangle", | |
"name" : "tap", | |
"params" : [["data","JsVar","`{dir, double, x, y, z}`"]], | |
"ifdef" : "BANGLEJS" | |
} | |
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" | |
} | |
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" | |
} | |
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. | |
*/ | |
/*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" | |
} | |
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. | |
*/ | |
/*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" | |
} | |
Emitted when the touchscreen is pressed | |
*/ | |
/*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" | |
} | |
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" : "BANGLEJS2" | |
} | |
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 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 BTN_LOAD_TIMEOUT | |
#define 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 | |
unsigned char touchX, touchY; ///< current touch event coordinates | |
unsigned char 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 | |
#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; | |
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 | |
volatile uint16_t homeBtnTimer; // 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 | |
} | |
#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 (bangleTasks & JSBT_RESET) { | |
// We already wanted to reset but we didn't get back to idle loop in | |
// 1/10th sec - let's force a break out of JS execution | |
jsiConsolePrintf("Button held down - interrupt JS execution...\n"); | |
execInfo.execute |= EXEC_INTERRUPTED; | |
} else if (homeBtnTimer < TIMER_MAX) { | |
homeBtnTimer += pollInterval; | |
if (homeBtnTimer >= BTN_LOAD_TIMEOUT) { | |
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; | |
} | |
} | |
} | |
} else { | |
homeBtnTimer = 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 | |
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 = TG_SWIPE_DOWN; | |
bangleTasks |= JSBT_SWIPE; | |
break; | |
case 2: // slide up | |
touchGesture = TG_SWIPE_UP; | |
bangleTasks |= JSBT_SWIPE; | |
break; | |
case 3: // slide left | |
touchGesture = TG_SWIPE_LEFT; | |
bangleTasks |= JSBT_SWIPE; | |
break; | |
case 4: // slide right | |
touchGesture = 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) | |
touchHandlerInternal( | |
buf[3] * LCD_WIDTH / 160, // touchX | |
buf[5] * LCD_HEIGHT / 160, // 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 `Bange.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); | |
} | |
/*JSON{ | |
"type" : "staticmethod", | |
"class" : "Bangle", | |
"name" : "setLCDMode", | |
"generate" : "jswrap_banglejs_setLCDMode", | |
"params" : [ | |
["mode","JsVar","The LCD mode (See below)"] | |
], | |
"ifdef" : "BANGLEJS" | |
} | |
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" | |
} | |
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" : "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); | |
} | |
/*JSON{ | |
"type" : "staticmethod", | |
"class" : "Bangle", | |
"name" : "setOptions", | |
"generate" : "jswrap_banglejs_setOptions", | |
"params" : [ | |
["options","JsVar",""] | |
], | |
"ifdef" : "BANGLEJS" | |
} | |
Set internal options used for gestures, etc... | |
* `wakeOnBTN1` should the LCD turn on when BTN1 is pressed? default = `true` | |
* `wakeOnBTN2` should the LCD turn on when BTN2 is pressed? default = `true` | |
* `wakeOnBTN3` 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 oppsite 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` | |
* `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 | |
* `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. | |
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 | |
jsvConfigObject configs[] = { | |
#ifdef HEARTRATE | |
{"hrmPollInterval", JSV_INTEGER, &_hrmPollInterval}, | |
#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} | |
}; | |
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 | |
} | |
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" | |
} | |
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 | |
(eg 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" | |
} | |
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" | |
} | |
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" | |
} | |
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" | |
} | |
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)" | |
} | |
Set the power to the barometer IC. Once enbled, `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; | |
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 | |
} | |
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" | |
} | |
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) | |
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; | |
} | |
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" | |
} | |
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" | |
} | |
`range` is one of: | |
* `undefined` or `'current'` - health data so far in the last 10 minutes is returned, | |
* `'last'` - health data during the last 10 minutes | |
* `'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 | |
#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 | |
// 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 | |
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 | |