From 75bc0132c0dd4ca3548e9e9f11da07e25a3ed205 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Fri, 11 Oct 2019 14:56:58 +0100 Subject: [PATCH] re-add f5 variant --- .gitignore | 72 +-- boards/BANGLEF5.py | 184 ++++++++ boards/BANGLEJS.py | 5 +- libs/banglejs/jswrap_bangle.c | 140 +----- libs/banglejs/jswrap_banglef5.c | 748 ++++++++++++++++++++++++++++++++ libs/banglejs/jswrap_banglef5.h | 32 ++ libs/misc/nmea.c | 146 +++++++ libs/misc/nmea.h | 30 ++ 8 files changed, 1183 insertions(+), 174 deletions(-) create mode 100644 boards/BANGLEF5.py create mode 100644 libs/banglejs/jswrap_banglef5.c create mode 100644 libs/banglejs/jswrap_banglef5.h create mode 100644 libs/misc/nmea.c create mode 100644 libs/misc/nmea.h diff --git a/.gitignore b/.gitignore index ed52c3d78..f6b00b5b9 100644 --- a/.gitignore +++ b/.gitignore @@ -10,51 +10,51 @@ *.bin *.pyc /*.zip -gen/* -.vagrant/* -boards/*.html -boards/*.json -zipcontents -tmp -benchmark/*.txt -benchmark/*.result.json +/gen/* +/.vagrant/* +/boards/*.html +/boards/*.json +/zipcontents +/tmp +/benchmark/*.txt +/benchmark/*.result.json /espruino -archives/* -diffs/* -.cproject -.project -.settings -MDN_URLS.txt +/archives/* +/diffs/* +/.cproject +/.project +/.settings +/MDN_URLS.txt node_modules /build/ -esp_iot_sdk_v1.5.0 -esp_iot_sdk_v2.0.0.p1 -ESP8266_NONOS_SDK-2.2.1 -xtensa-lx106-elf -app -esp-idf +/esp_iot_sdk_v1.5.0 +/esp_iot_sdk_v2.0.0.p1 +/ESP8266_NONOS_SDK-2.2.1 +/xtensa-lx106-elf +/app +/esp-idf xtensa-esp32-elf gcc-arm-none-eabi* *.c# /function_keywords.js /functions.html -targetlibs/nrf5x_11 -targetlibs/nrf5x_12/examples -targetlibs/nrf5x_12/documentation/ -targetlibs/nrf5x_14 -targetlibs/nrf5x_15/components -targetlibs/nrf5x_15/example_config -targetlibs/nrf5x_15/examples -targetlibs/nrf5x_15/external -targetlibs/nrf5x_15/external_tools -targetlibs/nrf5x_15/integration -targetlibs/nrf5x_15/modules -targetlibs/nrf5x_15_* -targetlibs/nrf5x_15x -targetlibs/nrf5x_mesh -targetlibs/raspberrypi +/targetlibs/nrf5x_11 +/targetlibs/nrf5x_12/examples +/targetlibs/nrf5x_12/documentation/ +/targetlibs/nrf5x_14 +/targetlibs/nrf5x_15/components +/targetlibs/nrf5x_15/example_config +/targetlibs/nrf5x_15/examples +/targetlibs/nrf5x_15/external +/targetlibs/nrf5x_15/external_tools +/targetlibs/nrf5x_15/integration +/targetlibs/nrf5x_15/modules +/targetlibs/nrf5x_15_* +/targetlibs/nrf5x_15x +/targetlibs/nrf5x_mesh +/targetlibs/raspberrypi /.vscode /CURRENT_BOARD.make /topreadonly /topstrings -misc +/misc diff --git a/boards/BANGLEF5.py b/boards/BANGLEF5.py new file mode 100644 index 000000000..9e4599a18 --- /dev/null +++ b/boards/BANGLEF5.py @@ -0,0 +1,184 @@ +#!/bin/false +# This file is part of Espruino, a JavaScript interpreter for Microcontrollers +# +# Copyright (C) 2013 Gordon Williams +# +# 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 contains information for a specific board - the available pins, and where LEDs, +# Buttons, and other in-built peripherals are. It is used to build documentation as well +# as various source and header files for Espruino. +# ---------------------------------------------------------------------------------------- + +import pinutils; + +info = { + 'name' : "Espruino Bangle.js", + 'link' : [ "http://www.espruino.com/Bangle.js" ], + 'espruino_page_link' : 'Bangle.js', + 'default_console' : "EV_BLUETOOTH", + 'variables' : 2100, # How many variables are allocated for Espruino to use. RAM will be overflowed if this number is too high and code won't compile. + 'bootloader' : 1, + 'binary_name' : 'espruino_%v_banglef5.hex', + 'build' : { + 'optimizeflags' : '-Os', + 'libraries' : [ + 'BLUETOOTH', + 'TERMINAL', + 'GRAPHICS', + 'LCD_SPI', + # 'TENSORFLOW' + ], + 'makefile' : [ + 'DEFINES += -DCONFIG_NFCT_PINS_AS_GPIOS', # Allow the reset pin to work + 'DEFINES += -DBUTTONPRESS_TO_REBOOT_BOOTLOADER', + 'DEFINES+=-DBLUETOOTH_NAME_PREFIX=\'"Bangle.js"\'', + 'DEFINES+=-DDUMP_IGNORE_VARIABLES=\'"g\\0"\'', + 'DEFINES+=-DUSE_FONT_6X8 -DGRAPHICS_PALETTED_IMAGES', + 'DFU_PRIVATE_KEY=targets/nrf5x_dfu/dfu_private_key.pem', + 'DFU_SETTINGS=--application-version 0xff --hw-version 52 --sd-req 0x8C', + 'INCLUDE += -I$(ROOT)/libs/banglejs -I$(ROOT)/libs/misc', + 'WRAPPERSOURCES += libs/banglejs/jswrap_banglef5.c', + 'SOURCES += libs/misc/nmea.c', + 'JSMODULESOURCES += libs/js/graphical_menu.min.js', + 'NRF_BL_DFU_INSECURE=1', + 'LINKER_BOOTLOADER=targetlibs/nrf5x_12/nrf5x_linkers/banglejs_dfu.ld', + 'LINKER_ESPRUINO=targetlibs/nrf5x_12/nrf5x_linkers/banglejs_espruino.ld' + ] + } +}; + + +chip = { + 'part' : "NRF52832", + 'family' : "NRF52", + 'package' : "QFN48", + 'ram' : 64, + 'flash' : 512, + 'speed' : 64, + 'usart' : 1, + 'spi' : 1, + 'i2c' : 1, + 'adc' : 1, + 'dac' : 0, + 'saved_code' : { + 'address' : 0x40000000, # put this in external flash + 'page_size' : 4096, + 'pages' : 64, # 256kb - still loads left + 'flash_available' : 512 - ((31 + 8 + 2)*4) # Softdevice uses 31 pages of flash, bootloader 8, FS 2. Each page is 4 kb. + }, +}; + +devices = { + + 'BTN1' : { 'pin' : 'D12', 'pinstate' : 'IN_PULLDOWN' }, # Top right - Pin negated in software + 'BTN2' : { 'pin' : 'D13', 'pinstate' : 'IN_PULLDOWN' }, # Bottom right - Pin negated in software + 'BTN3' : { 'pin' : 'D16' }, # Touch +# 'BTN4' : { 'pin' : 'D16', 'pinstate' : 'IN_PULLDOWN' }, # Pin negated in software + 'LED1' : { 'pin' : 'D14' }, # Pin negated in software +# 'LED2' : { 'pin' : 'D18' }, # Pin negated in software +# 'LED3' : { 'pin' : 'D19' }, # Pin negated in software +# 'LED4' : { 'pin' : 'D20' }, # Pin negated in software + 'VIBRATE' : { 'pin' : 'D11' }, # Pin negated in software + 'LCD' : { + 'width' : 128, 'height' : 96, 'bpp' : 4, + 'controller' : 'st7735', + 'pin_dc' : 'D22', + 'pin_cs' : 'D10', + 'pin_rst' : 'D23', + 'pin_sck' : 'D9', + 'pin_mosi' : 'D8', + 'pin_bl' : 'D21', + }, + 'GPS' : { + 'device' : 'M8130-KT', + 'pin_en' : 'D0', # inverted + 'pin_rx' : 'D5', + 'pin_tx' : 'D6' + }, + 'BAT' : { + 'pin_charging' : 'D7', # inverted + 'pin_voltage' : 'D4' + }, + 'HEARTRATE' : { + 'pin_led' : 'D14', + 'pin_analog' : 'D3' + }, + 'ACCEL' : { + 'device' : 'KX023', 'addr' : 0x1e, + 'pin_sda' : 'D1', + 'pin_scl' : 'D2' + }, + 'SPIFLASH' : { + 'pin_cs' : 'D18', + 'pin_sck' : 'D19', + 'pin_mosi' : 'D20', + 'pin_miso' : 'D17', + 'size' : 2097152 + }, + 'PRESSURE' : { + 'device' : 'HP203', 'addr' : 0x76, + 'pin_sda' : 'D1', + 'pin_scl' : 'D2' + }, +}; + +# left-right, or top-bottom order +board = { + 'left' : [], + 'right' : [], + '_notes' : { + } +}; +board["_css"] = """ +#board { + width: 528px; + height: 800px; + top: 0px; + left : 200px; + background-image: url(img/BANGLEF5.jpg); +} +#boardcontainer { + height: 900px; +} + +#left { + top: 219px; + right: 466px; +} +#right { + top: 150px; + left: 466px; +} + +.leftpin { height: 17px; } +.rightpin { height: 17px; } +"""; + +def get_pins(): + pins = pinutils.generate_pins(0,31) # 32 General Purpose I/O Pins. + pinutils.findpin(pins, "PD0", True)["functions"]["XL1"]=0; + pinutils.findpin(pins, "PD1", True)["functions"]["XL2"]=0; + pinutils.findpin(pins, "PD2", True)["functions"]["ADC1_IN0"]=0; + pinutils.findpin(pins, "PD3", True)["functions"]["ADC1_IN1"]=0; + pinutils.findpin(pins, "PD4", True)["functions"]["ADC1_IN2"]=0; + pinutils.findpin(pins, "PD5", True)["functions"]["ADC1_IN3"]=0; + pinutils.findpin(pins, "PD28", True)["functions"]["ADC1_IN4"]=0; + pinutils.findpin(pins, "PD29", True)["functions"]["ADC1_IN5"]=0; + pinutils.findpin(pins, "PD30", True)["functions"]["ADC1_IN6"]=0; + pinutils.findpin(pins, "PD31", True)["functions"]["ADC1_IN7"]=0; + # negate buttons + pinutils.findpin(pins, "PD12", True)["functions"]["NEGATED"]=0; # btn1 + pinutils.findpin(pins, "PD13", True)["functions"]["NEGATED"]=0; # btn2 + pinutils.findpin(pins, "PD11", True)["functions"]["NEGATED"]=0; # vibrate + pinutils.findpin(pins, "PD14", True)["functions"]["NEGATED"]=0; # HRM LED + + + # everything is non-5v tolerant + for pin in pins: + pin["functions"]["3.3"]=0; + #The boot/reset button will function as a reset button in normal operation. Pin reset on PD21 needs to be enabled on the nRF52832 device for this to work. + return pins diff --git a/boards/BANGLEJS.py b/boards/BANGLEJS.py index 9ccee9ad2..254e94acc 100644 --- a/boards/BANGLEJS.py +++ b/boards/BANGLEJS.py @@ -30,7 +30,7 @@ info = { 'TERMINAL', 'GRAPHICS', 'LCD_ST7789_8BIT', - #'TENSORFLOW' + 'TENSORFLOW' ], 'makefile' : [ 'DEFINES += -DCONFIG_NFCT_PINS_AS_GPIOS', # Allow the reset pin to work @@ -40,8 +40,9 @@ info = { 'DEFINES+=-DUSE_FONT_6X8 -DGRAPHICS_PALETTED_IMAGES', 'DFU_PRIVATE_KEY=targets/nrf5x_dfu/dfu_private_key.pem', 'DFU_SETTINGS=--application-version 0xff --hw-version 52 --sd-req 0x8C', - 'INCLUDE += -I$(ROOT)/libs/banglejs', + 'INCLUDE += -I$(ROOT)/libs/banglejs -I$(ROOT)/libs/misc', 'WRAPPERSOURCES += libs/banglejs/jswrap_bangle.c', + 'SOURCES += libs/misc/nmea.c', 'JSMODULESOURCES += libs/js/graphical_menu.min.js', 'NRF_BL_DFU_INSECURE=1', 'LINKER_BOOTLOADER=targetlibs/nrf5x_12/nrf5x_linkers/banglejs_dfu.ld', diff --git a/libs/banglejs/jswrap_bangle.c b/libs/banglejs/jswrap_bangle.c index b0b0ee766..d2dba1df8 100644 --- a/libs/banglejs/jswrap_bangle.c +++ b/libs/banglejs/jswrap_bangle.c @@ -36,6 +36,7 @@ #include "jswrap_graphics.h" #include "lcd_st7789_8bit.h" +#include "nmea.h" /*JSON{ "type": "class", @@ -161,18 +162,8 @@ If the watch is tapped, this event contains information on the way it was tapped #define IOEXP_LCD_RESET 0x40 #define IOEXP_HRM 0x80 -typedef struct { - double lat,lon,alt; - double speed, course; - int hour,min,sec,ms; - uint8_t day,month,year; - uint8_t quality; // from GGA packet, 0 = no fix - uint8_t satellites; // how many satellites -} NMEAFixInfo; - -#define NMEA_MAX_SIZE 82 // 82 is the max for NMEA uint8_t nmeaCount = 0; // how many characters of NMEA data do we have? -char nmeaIn[NMEA_MAX_SIZE]; // NMEA line being received right now +char nmeaIn[NMEA_MAX_SIZE]; // 82 is the max for NMEA char nmeaLine[NMEA_MAX_SIZE]; // A line of received NMEA data NMEAFixInfo gpsFix; @@ -783,27 +774,8 @@ bool jswrap_banglejs_idle() { } } if (bangle && (bangleTasks & JSBT_GPS_DATA)) { - JsVar *o = jsvNewObject(); + JsVar *o = nmea_to_jsVar(&gpsFix); if (o) { - jsvObjectSetChildAndUnLock(o, "lat", jsvNewFromFloat(gpsFix.lat)); - jsvObjectSetChildAndUnLock(o, "lon", jsvNewFromFloat(gpsFix.lon)); - jsvObjectSetChildAndUnLock(o, "alt", jsvNewFromFloat(gpsFix.alt)); - jsvObjectSetChildAndUnLock(o, "speed", jsvNewFromFloat(gpsFix.speed)); - jsvObjectSetChildAndUnLock(o, "course", jsvNewFromFloat(gpsFix.course)); - CalendarDate date; - date.day = gpsFix.day; - date.month = gpsFix.month; - date.year = 2000+gpsFix.year; - TimeInDay td; - td.daysSinceEpoch = fromCalenderDate(&date); - td.hour = gpsFix.hour; - td.min = gpsFix.min; - td.sec = gpsFix.sec; - td.ms = gpsFix.ms; - td.zone = 0; // jsdGetTimeZone(); - no! GPS time is always in UTC :) - jsvObjectSetChildAndUnLock(o, "time", jswrap_date_from_milliseconds(fromTimeInDay(&td))); - jsvObjectSetChildAndUnLock(o, "satellites", jsvNewFromInteger(gpsFix.satellites)); - jsvObjectSetChildAndUnLock(o, "fix", jsvNewFromInteger(gpsFix.quality)); jsiQueueObjectCallbacks(bangle, JS_EVENT_PREFIX"GPS", &o, 1); jsvUnLock(o); } @@ -812,7 +784,6 @@ bool jswrap_banglejs_idle() { JsVar *line = jsvNewFromString(nmeaLine); if (line) { jsiQueueObjectCallbacks(bangle, JS_EVENT_PREFIX"GPS-raw", &line, 1); - } jsvUnLock(line); } @@ -852,109 +823,6 @@ bool jswrap_banglejs_idle() { } -char *nmea_next_comma(char *nmea) { - while (*nmea && *nmea!=',') nmea++; // find the comma - return nmea; -} -double nmea_decode_latlon(char *nmea, char *comma) { - if (*nmea==',') return NAN; // no reading - char *dp = nmea; - while (*dp && *dp!='.' && *dp!=',') dp++; // find decimal pt - *comma = 0; - double minutes = stringToFloat(&dp[-2]); - *comma = ','; - dp[-2] = 0; - int x = stringToInt(nmea); - return x+(minutes/60); -} -double nmea_decode_float(char *nmea, char *comma) { - *comma = 0; - double r = stringToFloat(nmea); - *comma = ','; - return r; -} -uint8_t nmea_decode_1(char *nmea) { - return chtod(nmea[0]); -} -uint8_t nmea_decode_2(char *nmea) { - return chtod(nmea[0])*10 + chtod(nmea[1]); -} -bool nmea_decode(const char *nmeaLine) { - char buf[NMEA_MAX_SIZE]; - strcpy(buf, nmeaLine); - char *nmea = buf, *nextComma; - - - if (nmea[0]!='$' || nmea[1]!='G') return false; // not valid - if (nmea[3]=='R' && nmea[4]=='M' && nmea[5]=='C') { - // $GNRMC,161945.00,A,5139.11397,N,00116.07202,W,1.530,,190919,,,A*7E - nmea = nmea_next_comma(nmea)+1; - nextComma = nmea_next_comma(nmea); - // time - gpsFix.hour = nmea_decode_2(&nmea[0]); - gpsFix.min = nmea_decode_2(&nmea[2]); - gpsFix.sec = nmea_decode_2(&nmea[4]); - gpsFix.ms = nmea_decode_2(&nmea[7]); - // status - nmea = nextComma+1; nextComma = nmea_next_comma(nmea); - nmea = nextComma+1; nextComma = nmea_next_comma(nmea);//? - // lat + NS - nmea = nextComma+1; nextComma = nmea_next_comma(nmea); - nmea = nextComma+1; nextComma = nmea_next_comma(nmea); - // lon + EW - nmea = nextComma+1; nextComma = nmea_next_comma(nmea); - nmea = nextComma+1; nextComma = nmea_next_comma(nmea); - // speed - gpsFix.speed = nmea_decode_float(nmea, nextComma); - nmea = nextComma+1; nextComma = nmea_next_comma(nmea); - // course - gpsFix.course = nmea_decode_float(nmea, nextComma); - nmea = nextComma+1; nextComma = nmea_next_comma(nmea); - // date - gpsFix.day = nmea_decode_2(&nmea[0]); - gpsFix.month = nmea_decode_2(&nmea[2]); - gpsFix.year = nmea_decode_2(&nmea[4]); - // .... - } - if (nmea[3]=='G' && nmea[4]=='G' && nmea[5]=='A') { - // $GNGGA,161945.00,5139.11397,N,00116.07202,W,1,06,1.29,71.1,M,47.0,M,,*64 - nmea = nmea_next_comma(nmea)+1; - nextComma = nmea_next_comma(nmea); - // time - nmea = nextComma+1; nextComma = nmea_next_comma(nmea); - // LAT - gpsFix.lat = nmea_decode_latlon(nmea, nextComma); - nmea = nextComma+1; nextComma = nmea_next_comma(nmea); - if (*nmea=='S') gpsFix.lat=-gpsFix.lat; - nmea = nextComma+1; nextComma = nmea_next_comma(nmea); - // LON - gpsFix.lon = nmea_decode_latlon(nmea, nextComma); - nmea = nextComma+1; nextComma = nmea_next_comma(nmea); - if (*nmea=='W') gpsFix.lon=-gpsFix.lon; - nmea = nextComma+1; nextComma = nmea_next_comma(nmea); - // quality - gpsFix.quality = nmea_decode_1(nmea); - nmea = nextComma+1; nextComma = nmea_next_comma(nmea); - // num satellites - gpsFix.satellites = nmea_decode_2(nmea); - nmea = nextComma+1; nextComma = nmea_next_comma(nmea); - // dilution of precision - nmea = nextComma+1; nextComma = nmea_next_comma(nmea); - // altitude - gpsFix.alt = nmea_decode_float(nmea, nextComma); - nmea = nextComma+1; nextComma = nmea_next_comma(nmea); - // .... - } - if (nmea[3]=='G' && nmea[4]=='S' && nmea[5]=='V') { - // loads of cool data about what satellites we have - } - if (nmea[3]=='G' && nmea[4]=='L' && nmea[5]=='L') { - // Complete set of data received - return true; - } - return false; -} - /*JSON{ "type" : "EV_SERIAL1", "generate" : "jswrap_banglejs_gps_character" @@ -979,7 +847,7 @@ bool jswrap_banglejs_gps_character(char ch) { memcpy(nmeaLine, nmeaIn, nmeaCount); nmeaLine[nmeaCount-1]=0; // just overwriting \n bangleTasks |= JSBT_GPS_DATA_LINE; - if (nmea_decode(nmeaLine)) + if (nmea_decode(&gpsFix, nmeaLine)) bangleTasks |= JSBT_GPS_DATA; } nmeaCount = 0; diff --git a/libs/banglejs/jswrap_banglef5.c b/libs/banglejs/jswrap_banglef5.c new file mode 100644 index 000000000..efa200cdf --- /dev/null +++ b/libs/banglejs/jswrap_banglef5.c @@ -0,0 +1,748 @@ +/* + * This file is part of Espruino, a JavaScript interpreter for Microcontrollers + * + * Copyright (C) 2013 Gordon Williams + * + * 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 Pixl.js (http://www.espruino.com/Pixl.js) + * ---------------------------------------------------------------------------- + */ + +#include +#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_bluetooth.h" +#include "nrf_gpio.h" +#include "nrf_delay.h" +#include "nrf_soc.h" +#include "nrf5x_utils.h" +#include "jsflash.h" // for jsfRemoveCodeFromFlash +#include "bluetooth.h" // for self-test +#include "jsi2c.h" // accelerometer/etc + +#include "jswrap_graphics.h" +#include "lcd_spilcd.h" +#include "nmea.h" + +#define GPS_UART EV_SERIAL1 + +uint8_t nmeaCount = 0; // how many characters of NMEA data do we have? +char nmeaIn[NMEA_MAX_SIZE]; // 82 is the max for NMEA +char nmeaLine[NMEA_MAX_SIZE]; // A line of received NMEA data +NMEAFixInfo gpsFix; + +/*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. +*/ + +#define ACCEL_POLL_INTERVAL 100 // in msec +/// Internal I2C used for Accelerometer/Pressure +JshI2CInfo internalI2C; +/// Is I2C busy? if so we'll skip one reading in our interrupt so we don't overlap +bool i2cBusy; +/// Promise when pressure is requested +JsVar *promisePressure; +/// counter that counts up if watch has stayed face up or down +unsigned char faceUpCounter; +/// Was the watch face-up? we use this when firing events +bool wasFaceUp; +/// time since LCD contents were last modified +volatile unsigned char flipCounter; +/// Is LCD power automatic? If true this is the number of ms for the timeout, if false it's 0 +int lcdPowerTimeout = 100; +/// Is the LCD on? +bool lcdPowerOn; +/// accelerometer data +int accx,accy,accz,accdiff; +/// data on how watch was tapped +unsigned char tapInfo; + +typedef enum { + JSBT_NONE, + JSBT_LCD_ON = 1, + JSBT_LCD_OFF = 2, + JSBT_ACCEL_DATA = 4, // need to push xyz data to JS + JSBT_ACCEL_TAPPED = 8, // tap event detected + JSBT_GPS_DATA = 16, ///< we got a complete set of GPS data in 'gpsFix' + JSBT_GPS_DATA_LINE = 32, ///< we got a line of GPS data +} JsBangleTasks; +JsBangleTasks bangleTasks; + + + +/// Send buffer contents to the screen. Usually only the modified data will be output, but if all=true then the whole screen contents is sent +void lcd_flip(JsVar *parent, bool all) { + JsGraphics gfx; + if (!graphicsGetFromVar(&gfx, parent)) return; + if (all) { + gfx.data.modMinX = 0; + gfx.data.modMinY = 0; + gfx.data.modMaxX = LCD_WIDTH-1; + gfx.data.modMaxY = LCD_HEIGHT-1; + } + if (lcdPowerTimeout && !lcdPowerOn) { + // LCD was turned off, turn it back on + jswrap_banglejs_setLCDPower(1); + } + flipCounter = 0; + lcdFlip_SPILCD(&gfx); + graphicsSetVar(&gfx); +} + +/*JSON{ + "type" : "staticmethod", + "class" : "Bangle", + "name" : "setLCDPower", + "generate" : "jswrap_banglejs_setLCDPower", + "params" : [ + ["isOn","bool","True if the LCD should be on, false if not"] + ] +} +This function can be used to turn Bangle.js's LCD off or on. +*/ +void jswrap_banglejs_setLCDPower(bool isOn) { + if (isOn) { + lcdCmd_SPILCD(0x11, 0, NULL); // SLPOUT + jshPinOutput(LCD_BL,0); // backlight + } else { + lcdCmd_SPILCD(0x10, 0, NULL); // SLPIN + jshPinOutput(LCD_BL,1); // backlight + } + if (lcdPowerOn != 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); + } + lcdPowerOn = isOn; +} + +/*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."] + ] +} +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. It'll turn off automatically after the given timeout. +*/ +void jswrap_banglejs_setLCDTimeout(JsVarFloat timeout) { + if (!isfinite(timeout)) lcdPowerTimeout=0; + else lcdPowerTimeout = timeout*(1000.0/ACCEL_POLL_INTERVAL); + if (lcdPowerTimeout<0) lcdPowerTimeout=0; +} + +/*JSON{ + "type" : "staticmethod", + "class" : "Bangle", + "name" : "setLCDPalette", + "generate" : "jswrap_banglejs_setLCDPalette", + "params" : [ + ["palette","JsVar","An array of 24 bit 0xRRGGBB values"] + ] +} +Bangle.js's LCD can display colours in 12 bit, but to keep the offscreen +buffer to a reasonable size it uses a 4 bit paletted buffer. + +With this, you can change the colour palette that is used. +*/ +void jswrap_banglejs_setLCDPalette(JsVar *palette) { + if (jsvIsIterable(palette)) { + uint16_t pal[16]; + JsvIterator it; + jsvIteratorNew(&it, palette, JSIF_EVERY_ARRAY_ELEMENT); + int idx = 0; + while (idx<16 && jsvIteratorHasElement(&it)) { + unsigned int rgb = jsvIteratorGetIntegerValue(&it); + unsigned int r = rgb>>16; + unsigned int g = (rgb>>8)&0xFF; + unsigned int b = rgb&0xFF; + pal[idx++] = ((r&0xF0)<<4) | (g&0xF0) | (b>>4); + jsvIteratorNext(&it); + } + jsvIteratorFree(&it); + lcdSetPalette_SPILCD(pal); + } else + lcdSetPalette_SPILCD(0); +} + +/*JSON{ + "type" : "staticmethod", + "class" : "Bangle", + "name" : "isLCDOn", + "generate" : "jswrap_banglejs_isLCDOn", + "return" : ["bool","Is the display on or not?"] +} +*/ +bool jswrap_banglejs_isLCDOn() { + return lcdPowerOn; +} + +/*JSON{ + "type" : "staticmethod", + "class" : "Bangle", + "name" : "lcdWr", + "generate" : "jswrap_banglejs_lcdWr", + "params" : [ + ["cmd","int",""], + ["data","JsVar",""] + ] +} +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); + lcdCmd_SPILCD(cmd, dLen, dPtr); +} + +/*JSON{ + "type" : "staticmethod", + "class" : "Bangle", + "name" : "setGPSPower", + "generate" : "jswrap_banglejs_setGPSPower", + "params" : [ + ["isOn","bool","True if the GPS should be on, false if not"] + ] +} +Set the power to the GPS. +*/ +void jswrap_banglejs_setGPSPower(bool isOn) { + if (isOn) { + JshUSARTInfo inf; + jshUSARTInitInfo(&inf); + inf.baudRate = 9600; + inf.pinRX = GPS_PIN_RX; + inf.pinTX = GPS_PIN_TX; + jshUSARTSetup(GPS_UART, &inf); + jshPinOutput(GPS_PIN_EN,1); // GPS on + nmeaCount = 0; + } else { + jshPinOutput(GPS_PIN_EN,0); // GPS off + // 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); + } +} + + +// Holding down both buttons will reboot +void watchdogHandler() { + //jshPinOutput(LED1_PININDEX, 1); + // Handle watchdog + if (!(jshPinGetValue(BTN1_PININDEX) && jshPinGetValue(BTN2_PININDEX))) + jshKickWatchDog(); + // power on display if a button is pressed + if (lcdPowerTimeout && + (jshPinGetValue(BTN1_PININDEX) || jshPinGetValue(BTN2_PININDEX) || + jshPinGetValue(BTN3_PININDEX))) { + flipCounter = 0; + if (!lcdPowerOn) + bangleTasks |= JSBT_LCD_ON; + } + if (flipCounter<255) flipCounter++; + + if (lcdPowerTimeout && lcdPowerOn && flipCounter>=lcdPowerTimeout) { + // 10 seconds of inactivity, turn off display + bangleTasks |= JSBT_LCD_OFF; + } + + + if (i2cBusy) return; + // poll KX023 accelerometer (no other way as IRQ line seems disconnected!) + unsigned char buf[6]; + buf[0]=6; + jsi2cWrite(&internalI2C, ACCEL_ADDR, 1, buf, true); + jsi2cRead(&internalI2C, ACCEL_ADDR, 6, buf, true); + int newx = (buf[1]<<8)|buf[0]; + int newy = (buf[3]<<8)|buf[2]; + int newz = (buf[5]<<8)|buf[4]; + if (newx&0x8000) newx-=0x10000; + if (newy&0x8000) newy-=0x10000; + if (newz&0x8000) newz-=0x10000; + int dx = newx-accx; + int dy = newy-accy; + int dz = newz-accz; + accx = newx; + accy = newy; + accz = newz; + accdiff = dx*dx + dy*dy + dz*dz; + bangleTasks |= JSBT_ACCEL_DATA; + // read interrupt source data + buf[0]=0x12; + jsi2cWrite(&internalI2C, ACCEL_ADDR, 1, buf, true); + jsi2cRead(&internalI2C, ACCEL_ADDR, 2, buf, true); + // 0 -> 0x12 INS1 - tap event + // 1 -> 0x13 INS2 - what kind of event + int tapType = (buf[1]>>2)&3; + if (tapType) { + // report tap + tapInfo = buf[0] | (tapType<<6); + bangleTasks |= JSBT_ACCEL_TAPPED; + // clear the IRQ flags + buf[0]=0x17; + jsi2cWrite(&internalI2C, ACCEL_ADDR, 1, buf, true); + jsi2cRead(&internalI2C, ACCEL_ADDR, 1, buf, true); + } + + //jshPinOutput(LED1_PININDEX, 0); +} + +/*JSON{ + "type" : "init", + "generate" : "jswrap_banglejs_init" +}*/ +void jswrap_banglejs_init() { + jshPinOutput(GPS_PIN_EN,0); // GPS off + jshPinOutput(VIBRATE_PIN,0); // vibrate off + jshPinOutput(LED1_PININDEX,0); // LED off + lcdPowerOn = true; + + // Create backing graphics for LCD + JsVar *graphics = jspNewObject(0, "Graphics"); + if (!graphics) return; // low memory + JsGraphics gfx; + graphicsStructInit(&gfx); + gfx.data.type = JSGRAPHICSTYPE_SPILCD; + gfx.data.flags = JSGRAPHICSFLAGS_INVERT_X | JSGRAPHICSFLAGS_INVERT_Y; + gfx.graphicsVar = graphics; + gfx.data.width = LCD_WIDTH; + gfx.data.height = LCD_HEIGHT; + gfx.data.bpp = LCD_BPP; + + //gfx.data.fontSize = JSGRAPHICS_FONTSIZE_6X8; + lcdInit_SPILCD(&gfx); + 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 the button is pressed during reset, perform a self test. + * With bootloader this means apply power while holding button for >3 secs */ + static bool firstStart = true; + + graphicsClear(&gfx); + int h=6; + jswrap_graphics_drawCString(&gfx,0,h*1," ____ _ "); + jswrap_graphics_drawCString(&gfx,0,h*2,"| __|___ ___ ___ _ _|_|___ ___ "); + jswrap_graphics_drawCString(&gfx,0,h*3,"| __|_ -| . | _| | | | | . |"); + jswrap_graphics_drawCString(&gfx,0,h*4,"|____|___| _|_| |___|_|_|_|___|"); + jswrap_graphics_drawCString(&gfx,0,h*5," |_| espruino.com"); + jswrap_graphics_drawCString(&gfx,0,h*6," "JS_VERSION" (c) 2019 G.Williams"); + // Write MAC address in bottom right + JsVar *addr = jswrap_ble_getAddress(); + char buf[20]; + jsvGetString(addr, buf, sizeof(buf)); + jsvUnLock(addr); + jswrap_graphics_drawCString(&gfx,(LCD_WIDTH-1)-strlen(buf)*6,h*8,buf); + lcdFlip_SPILCD(&gfx); + +/* + if (firstStart && (jshPinGetValue(BTN1_PININDEX) == BTN1_ONSTATE || jshPinGetValue(BTN4_PININDEX) == BTN4_ONSTATE)) { + // don't do it during a software reset - only first hardware reset + jsiConsolePrintf("SELF TEST\n"); + if (pixl_selfTest()) jsiConsolePrintf("Test passed!\n"); + }*/ + + // If the button is *still* pressed, remove all code from flash memory too! + /*if (firstStart && jshPinGetValue(BTN1_PININDEX) == BTN1_ONSTATE) { + jsfRemoveCodeFromFlash(); + jsiConsolePrintf("Removed saved code from Flash\n"); + }*/ + graphicsSetVar(&gfx); + + firstStart = false; + jsvUnLock(graphics); + + // Setup touchscreen I2C + i2cBusy = true; + jshI2CInitInfo(&internalI2C); + internalI2C.bitrate = 0x7FFFFFFF; + internalI2C.pinSDA = ACCEL_PIN_SDA; + internalI2C.pinSCL = ACCEL_PIN_SCL; + jshPinSetValue(internalI2C.pinSCL, 1); + jshPinSetState(internalI2C.pinSCL, JSHPINSTATE_GPIO_OUT_OPENDRAIN_PULLUP); + jshPinSetValue(internalI2C.pinSDA, 1); + jshPinSetState(internalI2C.pinSDA, JSHPINSTATE_GPIO_OUT_OPENDRAIN_PULLUP); + // accelerometer init + jswrap_banglejs_accelWr(0x18,0x0a); // CNTL1 Off, 4g range, Wakeup + jswrap_banglejs_accelWr(0x19,0x80); // CNTL2 Software reset + jshDelayMicroseconds(2000); + /*jswrap_banglejs_accelWr(0x1b,0x02); // ODCNTL - 50Hz acceleration output data rate, filteringlow-pass ODR/9 + jswrap_banglejs_accelWr(0x1a,0xb11011110); // CNTL3 + // 50Hz tilt + // 50Hz directional tap + // 50Hz general motion detection and the high-pass filtered outputs + jswrap_banglejs_accelWr(0x1c,0); // INC1 disabled + jswrap_banglejs_accelWr(0x1d,0); // INC2 disabled + jswrap_banglejs_accelWr(0x1e,0); // INC3 disabled + jswrap_banglejs_accelWr(0x1f,0); // INC4 disabled + jswrap_banglejs_accelWr(0x20,0); // INC5 disabled + jswrap_banglejs_accelWr(0x21,0); // INC6 disabled + jswrap_banglejs_accelWr(0x23,3); // WUFC wakeupi detect counter + //jswrap_banglejs_accelWr(0x24,3); // TDTRC Tap detect enable + //jswrap_banglejs_accelWr(0x25, 0x78); // TDTC Tap detect double tap + //jswrap_banglejs_accelWr(0x26, 0x20); // TTH Tap detect threshold high (0xCB recommended) + //jswrap_banglejs_accelWr(0x27, 0x10); // TTH Tap detect threshold low (0x1A recommended) + jswrap_banglejs_accelWr(0x30,1); // ATH low wakeup detect threshold + jswrap_banglejs_accelWr(0x35,0); // LP_CNTL no averaging of samples + jswrap_banglejs_accelWr(0x3e,0); // clear the buffer*/ + jswrap_banglejs_accelWr(0x18,0b10001100); // CNTL1 On, ODR/2(high res), 4g range, Wakeup, tap + // pressure init + buf[0]=0x06; jsi2cWrite(&internalI2C, PRESSURE_ADDR, 1, (uint8_t)*buf, true); // SOFT_RST + i2cBusy = false; + + // Add watchdog timer to ensure watch always stays usable (hopefully!) + // This gets killed when _kill / _init happens + // - the bootloader probably already set this up so the + // enable will do nothing - but good to try anyway + jshEnableWatchDog(5); // 5 second watchdog + // This timer kicks the watchdog, and does some other stuff as well + JsSysTime t = jshGetTimeFromMilliseconds(ACCEL_POLL_INTERVAL); + jstExecuteFn(watchdogHandler, NULL, jshGetSystemTime()+t, t); +} + +/*JSON{ + "type" : "kill", + "generate" : "jswrap_banglejs_kill" +}*/ +void jswrap_banglejs_kill() { + jstStopExecuteFn(watchdogHandler, 0); + jsvUnLock(promisePressure); + promisePressure = 0; +} + +/*JSON{ + "type" : "idle", + "generate" : "jswrap_banglejs_idle" +}*/ +bool jswrap_banglejs_idle() { + if (bangleTasks == JSBT_NONE) return false; + JsVar *bangle =jsvObjectGetChild(execInfo.root, "Bangle", 0); + if (bangleTasks & JSBT_LCD_OFF) jswrap_banglejs_setLCDPower(0); + if (bangleTasks & JSBT_LCD_ON) jswrap_banglejs_setLCDPower(1); + if (bangleTasks & JSBT_ACCEL_DATA) { + if (bangle && jsiObjectHasCallbacks(bangle, JS_EVENT_PREFIX"accel")) { + JsVar *o = jsvNewObject(); + if (o) { + jsvObjectSetChildAndUnLock(o, "x", jsvNewFromFloat(accx/8192.0)); + jsvObjectSetChildAndUnLock(o, "y", jsvNewFromFloat(accy/8192.0)); + jsvObjectSetChildAndUnLock(o, "z", jsvNewFromFloat(accz/8192.0)); + jsvObjectSetChildAndUnLock(o, "mag", jsvNewFromFloat(sqrt(accx*accx + accy*accy + accz*accz)/8192.0)); + jsvObjectSetChildAndUnLock(o, "diff", jsvNewFromFloat(sqrt(accdiff)/8192.0)); + jsiQueueObjectCallbacks(bangle, JS_EVENT_PREFIX"accel", &o, 1); + jsvUnLock(o); + } + } + bool faceUp = (accz<7000) && abs(accx)<4096 && abs(accy)<4096; + if (faceUp!=wasFaceUp) { + faceUpCounter=0; + wasFaceUp = faceUp; + } + if (faceUpCounter<255) faceUpCounter++; + if (faceUpCounter==2) { + if (bangle) { + JsVar *v = jsvNewFromBool(faceUp); + jsiQueueObjectCallbacks(bangle, JS_EVENT_PREFIX"faceUp", &v, 1); + jsvUnLock(v); + } + if (lcdPowerTimeout && !lcdPowerOn) { + // LCD was turned off, turn it back on + jswrap_banglejs_setLCDPower(1); + flipCounter = 0; + } + } + } + if (bangle && (bangleTasks & JSBT_ACCEL_TAPPED)) { + JsVar *o = jsvNewObject(); + if (o) { + const char *string=""; + if (tapInfo&1) string="front"; + if (tapInfo&2) string="back"; + if (tapInfo&4) string="bottom"; + if (tapInfo&8) string="top"; + if (tapInfo&16) string="right"; + if (tapInfo&32) string="left"; + int n = (tapInfo&0x80)?2:1; + jsvObjectSetChildAndUnLock(o, "dir", jsvNewFromString(string)); + jsvObjectSetChildAndUnLock(o, "double", jsvNewFromBool(tapInfo&0x80)); + jsvObjectSetChildAndUnLock(o, "x", jsvNewFromInteger((tapInfo&16)?-n:(tapInfo&32)?n:0)); + jsvObjectSetChildAndUnLock(o, "y", jsvNewFromInteger((tapInfo&4)?-n:(tapInfo&8)?n:0)); + jsvObjectSetChildAndUnLock(o, "z", jsvNewFromInteger((tapInfo&1)?-n:(tapInfo&2)?n:0)); + jsiQueueObjectCallbacks(bangle, JS_EVENT_PREFIX"tap", &o, 1); + jsvUnLock(o); + } + } + if (bangle && (bangleTasks & JSBT_GPS_DATA)) { + JsVar *o = nmea_to_jsVar(&gpsFix); + if (o) { + jsiQueueObjectCallbacks(bangle, JS_EVENT_PREFIX"GPS", &o, 1); + jsvUnLock(o); + } + } + if (bangle && (bangleTasks & JSBT_GPS_DATA_LINE)) { + JsVar *line = jsvNewFromString(nmeaLine); + if (line) { + jsiQueueObjectCallbacks(bangle, JS_EVENT_PREFIX"GPS-raw", &line, 1); + } + jsvUnLock(line); + } + jsvUnLock(bangle); + bangleTasks = JSBT_NONE; + return false; +} + +/*JSON{ + "type" : "EV_SERIAL1", + "generate" : "jswrap_banglejs_gps_character" +}*/ +bool jswrap_banglejs_gps_character(char ch) { + if (ch=='\r') return true; // we don't care + // if too many chars, roll over since it's probably because we skipped a newline + if (nmeaCount>=sizeof(nmeaIn)) nmeaCount=0; + nmeaIn[nmeaCount++]=ch; + if (ch!='\n') return true; // now handled + // Now we have a line of GPS data... +/* $GNRMC,161945.00,A,5139.11397,N,00116.07202,W,1.530,,190919,,,A*7E + $GNVTG,,T,,M,1.530,N,2.834,K,A*37 + $GNGGA,161945.00,5139.11397,N,00116.07202,W,1,06,1.29,71.1,M,47.0,M,,*64 + $GNGSA,A,3,09,06,23,07,03,29,,,,,,,1.96,1.29,1.48*14 + $GPGSV,3,1,12,02,45,293,13,03,10,109,16,05,13,291,,06,56,213,25*73 + $GPGSV,3,2,12,07,39,155,18,09,76,074,33,16,08,059,,19,02,218,18*7E + $GPGSV,3,3,12,23,40,066,23,26,08,033,18,29,07,342,20,30,14,180,*7F + $GNGLL,5139.11397,N,00116.07202,W,161945.00,A,A*69 */ + // Let's just chuck it over into JS-land for now + if (nmeaCount>1) { + memcpy(nmeaLine, nmeaIn, nmeaCount); + nmeaLine[nmeaCount-1]=0; // just overwriting \n + bangleTasks |= JSBT_GPS_DATA_LINE; + if (nmea_decode(&gpsFix, nmeaLine)) + bangleTasks |= JSBT_GPS_DATA; + } + nmeaCount = 0; + return true; // handled +} + +/*JSON{ + "type" : "staticmethod", + "class" : "Bangle", + "name" : "accelWr", + "generate" : "jswrap_banglejs_accelWr", + "params" : [ + ["reg","int",""], + ["data","int",""] + ] +} +Writes a register on the KX023 Accelerometer +*/ +void jswrap_banglejs_accelWr(JsVarInt reg, JsVarInt data) { + unsigned char buf[2]; + buf[0] = (unsigned char)reg; + buf[1] = (unsigned char)data; + i2cBusy = true; + jsi2cWrite(&internalI2C, ACCEL_ADDR, 2, buf, true); + i2cBusy = false; +} + +/*JSON{ + "type" : "staticmethod", + "class" : "Bangle", + "name" : "accelRd", + "generate" : "jswrap_banglejs_accelRd", + "params" : [ + ["reg","int",""] + ], + "return" : ["int",""] +} +Reads a register from the KX023 Accelerometer +*/ +int jswrap_banglejs_accelRd(JsVarInt reg) { + unsigned char buf[1]; + buf[0] = (unsigned char)reg; + i2cBusy = true; + jsi2cWrite(&internalI2C, ACCEL_ADDR, 1, buf, true); + jsi2cRead(&internalI2C, ACCEL_ADDR, 1, buf, true); + i2cBusy = false; + return buf[0]; +} + +/*JSON{ + "type" : "staticmethod", + "class" : "Bangle", + "name" : "getPressure", + "generate" : "jswrap_banglejs_getPressure", + "return" : ["JsVar","A promise that will be resolved with `{temperature, pressure, altitude}`"] +} +Read temperature, pressure and altitude data. A promise is returned +which will be resolved with `{temperature, pressure, altitude}`. + +Conversions take roughly 100ms. + +``` +Bangle.getPressure().then(d=>{ + console.log(d); + // {temperature, pressure, altitude} +}); +``` +*/ +void jswrap_banglejs_getPressure_callback() { + JsVar *o = jsvNewObject(); + if (o) { + i2cBusy = true; + unsigned char buf[6]; + // ADC_CVT - 0b010 01 000 - pressure and temperature channel, OSR = 4096 + buf[0] = 0x48; jsi2cWrite(&internalI2C, PRESSURE_ADDR, 1, buf, true); + // wait 100ms + jshDelayMicroseconds(100*1000); // we should really have a callback + // READ_PT + buf[0] = 0x10; jsi2cWrite(&internalI2C, PRESSURE_ADDR, 1, buf, true); + jsi2cRead(&internalI2C, PRESSURE_ADDR, 6, buf, true); + int temperature = (buf[0]<<16)|(buf[1]<<8)|buf[2]; + if (temperature&0x800000) temperature-=0x1000000; + int pressure = (buf[3]<<16)|(buf[4]<<8)|buf[5]; + jsvObjectSetChildAndUnLock(o,"temperature", jsvNewFromFloat(temperature/100.0)); + jsvObjectSetChildAndUnLock(o,"pressure", jsvNewFromFloat(pressure/100.0)); + + buf[0] = 0x31; jsi2cWrite(&internalI2C, PRESSURE_ADDR, 1, buf, true); // READ_A + jsi2cRead(&internalI2C, PRESSURE_ADDR, 3, buf, true); + int altitude = (buf[0]<<16)|(buf[1]<<8)|buf[2]; + if (altitude&0x800000) altitude-=0x1000000; + jsvObjectSetChildAndUnLock(o,"altitude", jsvNewFromFloat(altitude/100.0)); + i2cBusy = false; + + jspromise_resolve(promisePressure, o); + } + jsvUnLock2(promisePressure,o); + promisePressure = 0; +} + +JsVar *jswrap_banglejs_getPressure() { + if (promisePressure) { + jsExceptionHere(JSET_ERROR, "Conversion in progress"); + return 0; + } + promisePressure = jspromise_create(); + if (!promisePressure) return 0; + + jsiSetTimeout(jswrap_banglejs_getPressure_callback, 100); + return jsvLockAgain(promisePressure); +} +/*JSON{ + "type" : "staticmethod", + "class" : "Bangle", + "name" : "off", + "generate" : "jswrap_banglejs_off" +} +Turn Bangle.js off. It can only be woken by pressing BTN1. +*/ +void jswrap_banglejs_off() { + jsiKill(); + jsvKill(); + jshKill(); + jshPinOutput(GPS_PIN_EN,0); // GPS off + jshPinOutput(VIBRATE_PIN,0); // vibrate off + jshPinOutput(LCD_BL,1); // backlight off + jshPinOutput(LED1_PININDEX,0); // LED off + lcdCmd_SPILCD(0x28, 0, NULL); // display off + + + nrf_gpio_cfg_sense_set(BTN2_PININDEX, NRF_GPIO_PIN_NOSENSE); + nrf_gpio_cfg_sense_set(BTN3_PININDEX, NRF_GPIO_PIN_NOSENSE); + nrf_gpio_cfg_sense_set(BTN1_PININDEX, NRF_GPIO_PIN_SENSE_LOW); + sd_power_system_off(); +} + +/*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` + */ +/*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" : "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. +*/ +/*JSON{ + "type" : "event", + "class" : "Bangle", + "name" : "faceUp", + "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 +``` + */ + diff --git a/libs/banglejs/jswrap_banglef5.h b/libs/banglejs/jswrap_banglef5.h new file mode 100644 index 000000000..659361710 --- /dev/null +++ b/libs/banglejs/jswrap_banglef5.h @@ -0,0 +1,32 @@ +/* + * This file is part of Espruino, a JavaScript interpreter for Microcontrollers + * + * Copyright (C) 2016 Gordon Williams + * + * 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/. + * + * ---------------------------------------------------------------------------- +* Contains JavaScript interface for Bangle.js (http://www.espruino.com/Bangle.js) + * ---------------------------------------------------------------------------- + */ +#include "jspin.h" + +void jswrap_banglejs_lcdWr(JsVarInt cmd, JsVar *data); +void jswrap_banglejs_setLCDPower(bool isOn); +void jswrap_banglejs_setLCDTimeout(JsVarFloat timeout); +void jswrap_banglejs_setLCDPalette(JsVar *palette); +bool jswrap_banglejs_isLCDOn(); + +void jswrap_banglejs_setGPSPower(bool isOn); + +void jswrap_banglejs_accelWr(JsVarInt reg, JsVarInt data); +int jswrap_banglejs_accelRd(JsVarInt reg); +JsVar *jswrap_banglejs_getPressure(); +void jswrap_banglejs_off(); + +void jswrap_banglejs_init(); +void jswrap_banglejs_kill(); +bool jswrap_banglejs_idle(); +bool jswrap_banglejs_gps_character(char ch); diff --git a/libs/misc/nmea.c b/libs/misc/nmea.c new file mode 100644 index 000000000..d6028129e --- /dev/null +++ b/libs/misc/nmea.c @@ -0,0 +1,146 @@ +/* + * This file is part of Espruino, a JavaScript interpreter for Microcontrollers + * + * Copyright (C) 2019 Gordon Williams + * + * 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/. + * + * ---------------------------------------------------------------------------- + * NMEA decoder + * ---------------------------------------------------------------------------- + */ + +#include "nmea.h" +#include "jswrap_date.h" + +char *nmea_next_comma(char *nmea) { + while (*nmea && *nmea!=',') nmea++; // find the comma + return nmea; +} +double nmea_decode_latlon(char *nmea, char *comma) { + if (*nmea==',') return NAN; // no reading + char *dp = nmea; + while (*dp && *dp!='.' && *dp!=',') dp++; // find decimal pt + *comma = 0; + double minutes = stringToFloat(&dp[-2]); + *comma = ','; + dp[-2] = 0; + int x = stringToInt(nmea); + return x+(minutes/60); +} +double nmea_decode_float(char *nmea, char *comma) { + *comma = 0; + double r = stringToFloat(nmea); + *comma = ','; + return r; +} +uint8_t nmea_decode_1(char *nmea) { + return chtod(nmea[0]); +} +uint8_t nmea_decode_2(char *nmea) { + return chtod(nmea[0])*10 + chtod(nmea[1]); +} +bool nmea_decode(NMEAFixInfo *gpsFix, const char *nmeaLine) { + char buf[NMEA_MAX_SIZE]; + strcpy(buf, nmeaLine); + char *nmea = buf, *nextComma; + + + if (nmea[0]!='$' || nmea[1]!='G') return false; // not valid + if (nmea[3]=='R' && nmea[4]=='M' && nmea[5]=='C') { + // $GNRMC,161945.00,A,5139.11397,N,00116.07202,W,1.530,,190919,,,A*7E + nmea = nmea_next_comma(nmea)+1; + nextComma = nmea_next_comma(nmea); + // time + gpsFix->hour = nmea_decode_2(&nmea[0]); + gpsFix->min = nmea_decode_2(&nmea[2]); + gpsFix->sec = nmea_decode_2(&nmea[4]); + gpsFix->ms = nmea_decode_2(&nmea[7]); + // status + nmea = nextComma+1; nextComma = nmea_next_comma(nmea); + nmea = nextComma+1; nextComma = nmea_next_comma(nmea);//? + // lat + NS + nmea = nextComma+1; nextComma = nmea_next_comma(nmea); + nmea = nextComma+1; nextComma = nmea_next_comma(nmea); + // lon + EW + nmea = nextComma+1; nextComma = nmea_next_comma(nmea); + nmea = nextComma+1; nextComma = nmea_next_comma(nmea); + // speed + gpsFix->speed = nmea_decode_float(nmea, nextComma); + nmea = nextComma+1; nextComma = nmea_next_comma(nmea); + // course + gpsFix->course = nmea_decode_float(nmea, nextComma); + nmea = nextComma+1; nextComma = nmea_next_comma(nmea); + // date + gpsFix->day = nmea_decode_2(&nmea[0]); + gpsFix->month = nmea_decode_2(&nmea[2]); + gpsFix->year = nmea_decode_2(&nmea[4]); + // .... + } + if (nmea[3]=='G' && nmea[4]=='G' && nmea[5]=='A') { + // $GNGGA,161945.00,5139.11397,N,00116.07202,W,1,06,1.29,71.1,M,47.0,M,,*64 + nmea = nmea_next_comma(nmea)+1; + nextComma = nmea_next_comma(nmea); + // time + nmea = nextComma+1; nextComma = nmea_next_comma(nmea); + // LAT + gpsFix->lat = nmea_decode_latlon(nmea, nextComma); + nmea = nextComma+1; nextComma = nmea_next_comma(nmea); + if (*nmea=='S') gpsFix->lat=-gpsFix->lat; + nmea = nextComma+1; nextComma = nmea_next_comma(nmea); + // LON + gpsFix->lon = nmea_decode_latlon(nmea, nextComma); + nmea = nextComma+1; nextComma = nmea_next_comma(nmea); + if (*nmea=='W') gpsFix->lon=-gpsFix->lon; + nmea = nextComma+1; nextComma = nmea_next_comma(nmea); + // quality + gpsFix->quality = nmea_decode_1(nmea); + nmea = nextComma+1; nextComma = nmea_next_comma(nmea); + // num satellites + gpsFix->satellites = nmea_decode_2(nmea); + nmea = nextComma+1; nextComma = nmea_next_comma(nmea); + // dilution of precision + nmea = nextComma+1; nextComma = nmea_next_comma(nmea); + // altitude + gpsFix->alt = nmea_decode_float(nmea, nextComma); + nmea = nextComma+1; nextComma = nmea_next_comma(nmea); + // .... + } + if (nmea[3]=='G' && nmea[4]=='S' && nmea[5]=='V') { + // loads of cool data about what satellites we have + } + if (nmea[3]=='G' && nmea[4]=='L' && nmea[5]=='L') { + // Complete set of data received + return true; + } + return false; +} + + +JsVar *nmea_to_jsVar(NMEAFixInfo *gpsFix) { + JsVar *o = jsvNewObject(); + if (o) { + jsvObjectSetChildAndUnLock(o, "lat", jsvNewFromFloat(gpsFix->lat)); + jsvObjectSetChildAndUnLock(o, "lon", jsvNewFromFloat(gpsFix->lon)); + jsvObjectSetChildAndUnLock(o, "alt", jsvNewFromFloat(gpsFix->alt)); + jsvObjectSetChildAndUnLock(o, "speed", jsvNewFromFloat(gpsFix->speed)); + jsvObjectSetChildAndUnLock(o, "course", jsvNewFromFloat(gpsFix->course)); + CalendarDate date; + date.day = gpsFix->day; + date.month = gpsFix->month; + date.year = 2000+gpsFix->year; + TimeInDay td; + td.daysSinceEpoch = fromCalenderDate(&date); + td.hour = gpsFix->hour; + td.min = gpsFix->min; + td.sec = gpsFix->sec; + td.ms = gpsFix->ms; + td.zone = 0; // jsdGetTimeZone(); - no! GPS time is always in UTC :) + jsvObjectSetChildAndUnLock(o, "time", jswrap_date_from_milliseconds(fromTimeInDay(&td))); + jsvObjectSetChildAndUnLock(o, "satellites", jsvNewFromInteger(gpsFix->satellites)); + jsvObjectSetChildAndUnLock(o, "fix", jsvNewFromInteger(gpsFix->quality)); + } + return 0; +} diff --git a/libs/misc/nmea.h b/libs/misc/nmea.h new file mode 100644 index 000000000..fa900a060 --- /dev/null +++ b/libs/misc/nmea.h @@ -0,0 +1,30 @@ +/* + * This file is part of Espruino, a JavaScript interpreter for Microcontrollers + * + * Copyright (C) 2019 Gordon Williams + * + * 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/. + * + * ---------------------------------------------------------------------------- + * NMEA decoder + * ---------------------------------------------------------------------------- + */ + +#include "jsvar.h" + +typedef struct { + double lat,lon,alt; + double speed, course; + int hour,min,sec,ms; + uint8_t day,month,year; + uint8_t quality; // from GGA packet, 0 = no fix + uint8_t satellites; // how many satellites +} NMEAFixInfo; + +#define NMEA_MAX_SIZE 82 // 82 is the max for NMEA + + +bool nmea_decode(NMEAFixInfo *gpsFix, const char *nmeaLine); +JsVar *nmea_to_jsVar(NMEAFixInfo *gpsFix);