Skip to content
Permalink
master
Switch branches/tags
Go to file
 
 
Cannot retrieve contributors at this time
/*
* 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 "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" : "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" : [["direction","int","`-1` for left, `1` for right"]],
"ifdef" : "BANGLEJS"
}
Emitted when a swipe on the touchscreen is detected (a movement from left->right, or right->left)
*/
/*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)"]
],
"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
*/
/*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"
}
```
*/
#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;
#undef PRESSURE_DEVICE_BMP280 // PRESSURE_DEVICE_BMP280 already defined (hardware v2.0)
#define PRESSURE_DEVICE_BMP280 pressureBMP280Enabled
#define PRESSURE_DEVICE_SPL06_007 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
#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;
#else
#define PRESSURE_DEVICE_SPL06_007 0
#endif
#ifdef PRESSURE_DEVICE_BMP280
int barometerDT[3]; // temp calibration
int barometerDP[9]; // pressure calibration
#else
#define PRESSURE_DEVICE_BMP280 0
#endif
#ifndef PRESSURE_DEVICE_HP203
#define PRESSURE_DEVICE_HP203 0
#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
#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
} 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
}
/// Flip buffer contents with the screen.
void lcd_flip(JsVar *parent, bool all) {
#ifdef LCD_WIDTH
JsVar *graphics = jsvObjectGetChild(execInfo.hiddenRoot, JS_GRAPHICS_VAR, 0);
if (!graphics) return;
JsGraphics gfx;
if (!graphicsGetFromVar(&gfx, graphics)) {
jsvUnLock(graphics);
return;
}
if (all) {
gfx.data.modMinX = 0;
gfx.data.modMinY = 0;
gfx.data.modMaxX = LCD_WIDTH-1;
gfx.data.modMaxY = LCD_HEIGHT-1;
}
#ifdef LCD_CONTROLLER_LPM013M126
lcdMemLCD_flip(&gfx);
#endif
#ifdef LCD_CONTROLLER_ST7789_8BIT
lcdST7789_flip(&gfx);
#endif
#if defined(LCD_CONTROLLER_ST7789V) || defined(LCD_CONTROLLER_ST7735) || defined(LCD_CONTROLLER_GC9A01)
lcdFlip_SPILCD(&gfx);
#endif
graphicsSetVar(&gfx);
jsvUnLock(graphics);
#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 (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;
}
// execInfo.execute |= EXEC_CTRL_C|EXEC_CTRL_C_WAIT; // set CTRLC
}
}
} 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
// 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;
// What if we've changed day?
TimeInDay td = getTimeFromMilliSeconds(msecs, false/*forceGMT*/);
uint8_t dayIndex = (uint8_t)td.daysSinceEpoch;
if (dayIndex != healthDaily.index) {
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;
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, jshGetSystemTime()+t, t);
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
JsGraphics gfx;
if (!graphicsGetFromVar(&gfx, graphics)) return;
// remove the buffer if it was defined
jsvObjectSetOrRemoveChild(gfx.graphicsVar, "buffer", 0);
unsigned int bufferSize = 0;
switch (lcdMode) {
case LCDST7789_MODE_NULL:
case LCDST7789_MODE_UNBUFFERED:
gfx.data.width = LCD_WIDTH;
gfx.data.height = LCD_HEIGHT;
gfx.data.bpp = 16;
break;
case LCDST7789_MODE_DOUBLEBUFFERED:
gfx.data.width = LCD_WIDTH;
gfx.data.height = 160;
gfx.data.bpp = 16;
break;
case LCDST7789_MODE_BUFFER_120x120:
gfx.data.width = 120;
gfx.data.height = 120;
gfx.data.bpp = 8;
bufferSize = 120*120;
break;
case LCDST7789_MODE_BUFFER_80x80:
gfx.data.width = 80;
gfx.data.height = 80;
gfx.data.bpp = 8;
bufferSize = 80*80;
break;
}
if (bufferSize) {
jsvGarbageCollect();
jsvDefragment();
JsVar *arrData = jsvNewFlatStringOfLength(bufferSize);
if (arrData) {
jsvObjectSetChildAndUnLock(gfx.graphicsVar, "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(&gfx); // reset colour, cliprect, etc
graphicsSetVar(&gfx);
jsvUnLock(graphics);
lcdST7789_setMode( lcdMode );
#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 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)
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 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);
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
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) {
if (PRESSURE_DEVICE_SPL06_007) {
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);
}
if (PRESSURE_DEVICE_BMP280) {
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);
}
} // wasOn
} else { // !isOn -> turn off
if (PRESSURE_DEVICE_SPL06_007)
jswrap_banglejs_barometerWr(SPL06_MEASCFG, 0); // Barometer off
if (PRESSURE_DEVICE_BMP280)
jswrap_banglejs_barometerWr(0xF4, 0); // Barometer off
}
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
}
/*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?
// Hardware init that we only do the very first time we start
if (firstRun) {
#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
// Check pressure sensor
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;
// 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;
#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);
#endif
#ifdef BANGLEJS_F18
// 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
}
#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);
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
bangleFlags |= JSBF_BEEP_VIBRATE;
#endif
}
jsvUnLock(v);
v = jsvIsObject(settings) ? jsvObjectGetChild(settings,"vibrate",0) : 0;
if (v && jsvGetBool(v)==false) {
bangleFlags &= ~JSBF_ENABLE_BUZZ;
} else {
bangleFlags |= JSBF_ENABLE_BUZZ;
}
jsvUnLock(v);
#if 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
//
v = jsvIsObject(settings) ? jsvObjectGetChild(settings,"theme",0) : 0;
if (jsvIsObject(v)) {
graphicsTheme.fg = jsvGetIntegerAndUnLock(jsvObjectGetChild(v,"fg",0));
graphicsTheme.bg = jsvGetIntegerAndUnLock(jsvObjectGetChild(v,"bg",0));
graphicsTheme.fg2 = jsvGetIntegerAndUnLock(jsvObjectGetChild(v,"fg2",0));
graphicsTheme.bg2 = jsvGetIntegerAndUnLock(jsvObjectGetChild(v,"bg2",0));
graphicsTheme.fgH = jsvGetIntegerAndUnLock(jsvObjectGetChild(v,"fgH",0));
graphicsTheme.bgH = jsvGetIntegerAndUnLock(jsvObjectGetChild(v,"bgH",0));
graphicsTheme.dark = jsvGetBoolAndUnLock(jsvObjectGetChild(v,"dark",0));
}
jsvUnLock2(v,settings);
#ifdef LCD_WIDTH
// Create backing graphics for LCD
JsVar *graphics = jspNewObject(0, "Graphics");
if (!graphics) return; // low memory
JsGraphics gfx;
graphicsStructInit(&gfx, LCD_WIDTH, LCD_HEIGHT, LCD_BPP);
#ifdef LCD_CONTROLLER_LPM013M126
gfx.data.type = JSGRAPHICSTYPE_MEMLCD;
gfx.data.bpp = 16; // hack - so we can dither we pretend we're 16 bit
#endif
#ifdef LCD_CONTROLLER_ST7789_8BIT
gfx.data.type = JSGRAPHICSTYPE_ST7789_8BIT;
#endif
#if defined(LCD_CONTROLLER_ST7789V) || defined(LCD_CONTROLLER_ST7735) || defined(LCD_CONTROLLER_GC9A01)
gfx.data.type = JSGRAPHICSTYPE_SPILCD;
#endif
gfx.data.flags = 0;
#ifdef DTNO1_F5
gfx.data.flags = JSGRAPHICSFLAGS_INVERT_X | JSGRAPHICSFLAGS_INVERT_Y;
#endif
gfx.data.fontSize = JSGRAPHICS_FONTSIZE_6X8+1; // 2x size is default
gfx.graphicsVar = graphics;
if (firstRun) {
#ifdef LCD_CONTROLLER_LPM013M126
lcdMemLCD_init(&gfx);
jswrap_banglejs_pwrBacklight(true);
#endif
#ifdef LCD_CONTROLLER_ST7789_8BIT
lcdST7789_init(&gfx);
#endif
#if defined(LCD_CONTROLLER_ST7789V) || defined(LCD_CONTROLLER_ST7735) || defined(LCD_CONTROLLER_GC9A01)
lcdInit_SPILCD(&gfx);
#endif
} else {
// Just reset any graphics settings that may need updating
jswrap_banglejs_setLCDOffset(0);
}
graphicsSetVar(&gfx);
jsvObjectSetChild(execInfo.root, "g", graphics);
jsvObjectSetChild(execInfo.hiddenRoot, JS_GRAPHICS_VAR, graphics);
graphicsGetFromVar(&gfx, graphics);
// Create 'flip' fn
JsVar *fn;
fn = jsvNewNativeFunction((void (*)(void))lcd_flip, JSWAT_VOID|JSWAT_THIS_ARG|(JSWAT_BOOL << (JSWAT_BITS*1)));
jsvObjectSetChildAndUnLock(graphics,"flip",fn);
if (!firstRun) {
// Not first run - reset the LCD mode if it was set
#ifdef LCD_CONTROLLER_ST7789_8BIT
if (lcdST7789_getMode()!=LCDST7789_MODE_UNBUFFERED) {
lcdST7789_setMode( LCDST7789_MODE_UNBUFFERED );
// screen will now be garbled - clear it
graphicsClear(&gfx);
}
#endif
}
bool showSplashScreen = true;
/* If we're doing a flash load, don't show
the logo because it'll just get overwritten
in a fraction of a second anyway */
if (jsiStatus & JSIS_TODO_FLASH_LOAD) {
showSplashScreen = false;
#ifndef ESPR_NO_LOADING_SCREEN
if (!firstRun) {
// Display a loading screen
int x = LCD_WIDTH/2;
int y = LCD_HEIGHT/2;
graphicsFillRect(&gfx, x-49, y-19, x+49, y+19, graphicsTheme.bg);
gfx.data.fgColor = graphicsTheme.fg;
graphicsDrawRect(&gfx, x-50, y-20, x+50, y+20);
y -= 4;
x -= 4*6;
const char *s = "Loading...";
while (*s) {
graphicsDrawChar6x8(&gfx, x, y, *s, 1, 1, false);
x+=6;
s++;
}
#ifdef BANGLEJS_Q3
lcdMemLCD_flip(&gfx);
#endif
#if defined(LCD_CONTROLLER_GC9A01)
lcdFlip_SPILCD(&gfx);
#endif
}
#endif
}
#ifdef DICKENS
// don't show splash screen unless the watch has been totally reset - stops flicker on boot
if (!(jsiStatus & JSIS_COMPLETELY_RESET))
showSplashScreen = false;
#endif
if (showSplashScreen) {
graphicsClear(&gfx);
bool drawInfo = false;
JsVar *img = jsfReadFile(jsfNameFromString(".splash"),0,0);