diff --git a/libs/banglejs/jswrap_bangle.c b/libs/banglejs/jswrap_bangle.c index 7de66b771..5b239c28c 100644 --- a/libs/banglejs/jswrap_bangle.c +++ b/libs/banglejs/jswrap_bangle.c @@ -6240,3 +6240,30 @@ void jsbangle_push_event(JsBangleEvent type, uint16_t value) { evt.data.chars[2] = (char)(value & 0xFF); jshPushEvent(&evt); } + +/*JSON{ + "type" : "powerusage", + "generate" : "jswrap_banglejs_powerusage" +}*/ +void jswrap_banglejs_powerusage(JsVar *devices) { + // https://www.espruino.com/Bangle.js2#power-consumption +#ifdef BANGLEJS_F18 + if (jswrap_banglejs_isLCDOn()) + jsvObjectSetChildAndUnLock(devices, "LCD", jsvNewFromInteger(40000)); +#endif +#ifdef BANGLEJS_Q3 + if (jswrap_banglejs_isBacklightOn()) + jsvObjectSetChildAndUnLock(devices, "LCD_backlight", jsvNewFromInteger(16000)); + if (!jswrap_banglejs_isLocked()) + jsvObjectSetChildAndUnLock(devices, "LCD_touch", jsvNewFromInteger(2500)); +#endif + if (jswrap_banglejs_isHRMOn()) + jsvObjectSetChildAndUnLock(devices, "HRM", jsvNewFromInteger(700)); + if (jswrap_banglejs_isGPSOn()) + jsvObjectSetChildAndUnLock(devices, "GPS", jsvNewFromInteger(20000)); + if (jswrap_banglejs_isCompassOn()) + jsvObjectSetChildAndUnLock(devices, "compass", jsvNewFromInteger(600)); + if (jswrap_banglejs_isBarometerOn()) + jsvObjectSetChildAndUnLock(devices, "baro", jsvNewFromInteger(200)); + jsvObjectSetChildAndUnLock(devices, "polling", jsvNewFromInteger(15 * 1000 / pollInterval)); +} \ No newline at end of file diff --git a/libs/banglejs/jswrap_bangle.h b/libs/banglejs/jswrap_bangle.h index a1a8e6201..a9fc3ee58 100644 --- a/libs/banglejs/jswrap_bangle.h +++ b/libs/banglejs/jswrap_bangle.h @@ -103,3 +103,4 @@ void jsbangle_exec_pending(IOEvent *event); /// queue an event for Bangle.js (usually called from inside an IRQ) void jsbangle_push_event(JsBangleEvent type, uint16_t value); +void jswrap_banglejs_powerusage(JsVar *devices); \ No newline at end of file diff --git a/libs/bluetooth/bluetooth.h b/libs/bluetooth/bluetooth.h index f69a78858..86180da92 100644 --- a/libs/bluetooth/bluetooth.h +++ b/libs/bluetooth/bluetooth.h @@ -212,7 +212,10 @@ extern volatile BLEStatus bleStatus; /// Filter to use when discovering BLE Services/Characteristics extern ble_uuid_t bleUUIDFilter; -extern uint16_t bleAdvertisingInterval; /**< The advertising interval (in units of 0.625 ms). */ +/// The advertising interval (in units of 0.625 ms) +extern uint16_t bleAdvertisingInterval; +/// The interval for the current peripheral connection (in units of 0.625 ms) +extern uint16_t blePeriphConnectionInterval; extern volatile uint16_t m_peripheral_conn_handle; /**< Handle of the current connection. */ #if CENTRAL_LINK_COUNT>0 @@ -319,7 +322,7 @@ void jsble_setup_advdata(ble_advdata_t *advdata); #define TAG_HEADER_LEN 0x0A /* -TT = Tag Type +TT = Tag Type ML = NDEF Message Length RT = Record Type TF = TNF and Flags @@ -331,7 +334,7 @@ IC = URI Identifier Code "\x00\x00\x00\x00" /* | UID/BCC | */ \ "\x00\x00\xFF\xFF" /* | UID/BCC | LOCK | */ \ "\xE1\x11\x7C\x0F" /* | Cap. Container | */ \ - "\x03\x00\x00\x00" /* | TT | ML (1 or 3 bytes)| */ + "\x03\x00\x00\x00" /* | TT | ML (1 or 3 bytes)| */ #define NDEF_HEADER_LEN_SHORT 0x12 /* with 1 byte length */ #define NDEF_HEADER_LEN_LONG 0x14 /* with 3 byte length */ diff --git a/libs/bluetooth/jswrap_bluetooth.c b/libs/bluetooth/jswrap_bluetooth.c index b66240c05..5e40e1a5e 100644 --- a/libs/bluetooth/jswrap_bluetooth.c +++ b/libs/bluetooth/jswrap_bluetooth.c @@ -4566,3 +4566,27 @@ JsVar *jswrap_ble_BluetoothRemoteGATTCharacteristic_stopNotifications(JsVar *cha return 0; #endif } + + +/*JSON{ + "type" : "powerusage", + "generate" : "jswrap_ble_powerusage" +}*/ +void jswrap_ble_powerusage(JsVar *devices) { +#ifdef NRF5X + // https://devzone.nordicsemi.com/power/w/opp/2/online-power-profiler-for-bluetooth-le + if (jsble_has_peripheral_connection()) { + int perSec = 1600 / blePeriphConnectionInterval; // blePeriphConnectionInterval is in units of 0.625ms + jsvObjectSetChildAndUnLock(devices, "BLE_periph", jsvNewFromInteger(6*perSec)); // ~6uA per connection + } + if (jsble_has_central_connection()) { + jsvObjectSetChildAndUnLock(devices, "BLE_central", jsvNewFromInteger(500)); + // Could do finer grained central connection + } + if (bleStatus & BLE_IS_ADVERTISING) { + int perSec = 1600 / bleAdvertisingInterval; // bleAdvertisingInterval is in units of 0.625ms + jsvObjectSetChildAndUnLock(devices, "BLE_advertise", jsvNewFromInteger(12*perSec)); // ~12uA per advertisement + // Could try and take advertising length into account? + } +#endif +} \ No newline at end of file diff --git a/libs/bluetooth/jswrap_bluetooth.h b/libs/bluetooth/jswrap_bluetooth.h index 4d5686f48..b92f61103 100644 --- a/libs/bluetooth/jswrap_bluetooth.h +++ b/libs/bluetooth/jswrap_bluetooth.h @@ -158,3 +158,5 @@ JsVar *jswrap_ble_BluetoothRemoteGATTCharacteristic_writeValue(JsVar *characteri JsVar *jswrap_ble_BluetoothRemoteGATTCharacteristic_readValue(JsVar *characteristic); JsVar *jswrap_ble_BluetoothRemoteGATTCharacteristic_startNotifications(JsVar *characteristic); JsVar *jswrap_ble_BluetoothRemoteGATTCharacteristic_stopNotifications(JsVar *characteristic); + +void jswrap_ble_powerusage(JsVar *devices); \ No newline at end of file diff --git a/libs/joltjs/jswrap_jolt.c b/libs/joltjs/jswrap_jolt.c index 054513334..3e03e405e 100644 --- a/libs/joltjs/jswrap_jolt.c +++ b/libs/joltjs/jswrap_jolt.c @@ -644,3 +644,25 @@ bool jswrap_jolt_idle() { } return busy; } + + +/*JSON{ + "type" : "powerusage", + "generate" : "jswrap_jolt_powerusage" +}*/ +void jswrap_jolt_powerusage(JsVar *devices) { + if (!(driverMode[0]==JDM_OFF || driverMode[0]==JDM_AUTO)) + jsvObjectSetChildAndUnLock(devices, "driver0", jsvNewFromInteger(1000)); + if (!(driverMode[1]==JDM_OFF || driverMode[1]==JDM_AUTO)) + jsvObjectSetChildAndUnLock(devices, "driver1", jsvNewFromInteger(1000)); + int v; + v = (int)(jshPinAnalog(JSH_PORTH_OFFSET+0) * 400); + if (v>10) jsvObjectSetChildAndUnLock(devices, "pin0_internal_resistance", jsvNewFromInteger(v)); + v = (int)(jshPinAnalog(JSH_PORTH_OFFSET+2) * 400); + if (v>10) jsvObjectSetChildAndUnLock(devices, "pin2_internal_resistance", jsvNewFromInteger(v)); + v = (int)(jshPinAnalog(JSH_PORTH_OFFSET+4) * 400); + if (v>10) jsvObjectSetChildAndUnLock(devices, "pin4_internal_resistance", jsvNewFromInteger(v)); + v = (int)(jshPinAnalog(JSH_PORTH_OFFSET+6) * 400); + if (v>10) jsvObjectSetChildAndUnLock(devices, "pin6_internal_resistance", jsvNewFromInteger(v)); + +} \ No newline at end of file diff --git a/libs/joltjs/jswrap_jolt.h b/libs/joltjs/jswrap_jolt.h index b688971db..9f467401b 100644 --- a/libs/joltjs/jswrap_jolt.h +++ b/libs/joltjs/jswrap_jolt.h @@ -25,3 +25,4 @@ void jswrap_jolt_hwinit(); void jswrap_jolt_init(); void jswrap_jolt_kill(); bool jswrap_jolt_idle(); +void jswrap_jolt_powerusage(JsVar *devices); \ No newline at end of file diff --git a/libs/pixljs/jswrap_pixljs.c b/libs/pixljs/jswrap_pixljs.c index 347db8e86..e4650aa3d 100644 --- a/libs/pixljs/jswrap_pixljs.c +++ b/libs/pixljs/jswrap_pixljs.c @@ -33,6 +33,7 @@ #include "lcd_arraybuffer.h" const Pin PIXL_IO_PINS[] = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19}; +bool lcdIsOn; /*JSON{ "type": "class", @@ -349,6 +350,7 @@ static bool pixl_selfTest() { }*/ void jswrap_pixljs_init() { // LCD Init 1 + lcdIsOn = true; jshPinOutput(LCD_SPI_CS,0); jshPinOutput(LCD_SPI_DC,0); jshPinOutput(LCD_SPI_SCK,0); @@ -730,3 +732,11 @@ E.showAlert("These are\nLots of\nLines","My Title").then(function() { To remove the window, call `E.showAlert()` with no arguments. */ + +/*JSON{ + "type" : "powerusage", + "generate" : "jswrap_pixljs_powerusage" +}*/ +void jswrap_pixljs_powerusage(JsVar *devices) { + jsvObjectSetChildAndUnLock(devices, "LCD", jsvNewFromInteger(lcdIsOn ? 170 : 20)); +} \ No newline at end of file diff --git a/libs/pixljs/jswrap_pixljs.h b/libs/pixljs/jswrap_pixljs.h index adefe0ed7..64543e6e9 100644 --- a/libs/pixljs/jswrap_pixljs.h +++ b/libs/pixljs/jswrap_pixljs.h @@ -21,3 +21,4 @@ void jswrap_pixljs_init(); void jswrap_pixljs_kill(); bool jswrap_pixljs_idle(); JsVarInt jswrap_pixljs_getBattery(); +void jswrap_pixljs_powerusage(JsVar *devices); \ No newline at end of file diff --git a/libs/puckjs/jswrap_puck.c b/libs/puckjs/jswrap_puck.c index ccc5b37bf..9b8d6ac7d 100644 --- a/libs/puckjs/jswrap_puck.c +++ b/libs/puckjs/jswrap_puck.c @@ -58,11 +58,13 @@ const Pin PUCK_IO_PINS[] = {1,2,4,6,7,8,23,24,28,29,30,31}; #endif bool mag_enabled = false; //< Has the magnetometer been turned on? +uint16_t mag_power; // est mag power in uA int16_t mag_reading[3]; //< magnetometer xyz reading //int mag_zero[3]; //< magnetometer 'zero' reading, only for Puck 2.1 right now volatile bool mag_data_ready = false; bool accel_enabled = false; //< Has the accelerometer been turned on? +uint16_t accel_power; // est mag power in uA int16_t accel_reading[3]; int16_t gyro_reading[3]; @@ -278,21 +280,22 @@ void mag_pin_on() { bool mag_on(int milliHz, bool instant) { //jsiConsolePrintf("mag_on\n"); mag_pin_on(); + mag_power = 0; if (puckVersion == PUCKJS_1V0) { // MAG3110 jshDelayMicroseconds(2500); // 1.7ms from power on to ok if (instant) milliHz = 80000; int reg1 = 0; - if (milliHz == 80000) reg1 |= (0x00)<<3; // 900uA - else if (milliHz == 40000) reg1 |= (0x04)<<3; // 550uA - else if (milliHz == 20000) reg1 |= (0x08)<<3; // 275uA - else if (milliHz == 10000) reg1 |= (0x0C)<<3; // 137uA - else if (milliHz == 5000) reg1 |= (0x10)<<3; // 69uA - else if (milliHz == 2500) reg1 |= (0x14)<<3; // 34uA - else if (milliHz == 1250) reg1 |= (0x18)<<3; // 17uA - else if (milliHz == 630) reg1 |= (0x1C)<<3; // 8uA - else if (milliHz == 310) reg1 |= (0x1D)<<3; // 8uA - else if (milliHz == 160) reg1 |= (0x1E)<<3; // 8uA - else if (milliHz == 80) reg1 |= (0x1F)<<3; // 8uA + if (milliHz == 80000) { reg1 |= (0x00)<<3; mag_power = 900; } + else if (milliHz == 40000) { reg1 |= (0x04)<<3; mag_power = 550; } + else if (milliHz == 20000) { reg1 |= (0x08)<<3; mag_power = 275; } + else if (milliHz == 10000) { reg1 |= (0x0C)<<3; mag_power = 137; } + else if (milliHz == 5000) { reg1 |= (0x10)<<3; mag_power = 69; } + else if (milliHz == 2500) { reg1 |= (0x14)<<3; mag_power = 34; } + else if (milliHz == 1250) { reg1 |= (0x18)<<3; mag_power = 17; } + else if (milliHz == 630) { reg1 |= (0x1C)<<3; mag_power = 8; } + else if (milliHz == 310) { reg1 |= (0x1D)<<3; mag_power = 8; } + else if (milliHz == 160) { reg1 |= (0x1E)<<3; mag_power = 8; } + else if (milliHz == 80) { reg1 |= (0x1F)<<3; mag_power = 8; } else return false; jshDelayMicroseconds(2000); // 1.7ms from power on to ok @@ -303,16 +306,16 @@ bool mag_on(int milliHz, bool instant) { if (instant) milliHz = 80000; bool lowPower = false; int reg1 = 0x80; // temp sensor, low power - if (milliHz == 80000) reg1 |= 7<<2; // 900uA - else if (milliHz == 40000) reg1 |= 6<<2; // 550uA - else if (milliHz == 20000) reg1 |= 5<<2; // 275uA - else if (milliHz == 10000) reg1 |= 4<<2; // 137uA - else if (milliHz == 5000) reg1 |= 3<<2; // 69uA - else if (milliHz == 2500) reg1 |= 2<<2; // 34uA - else if (milliHz == 1250) reg1 |= 1<<2; // 17uA + if (milliHz == 80000) { reg1 |= 7<<2; mag_power = 900; } + else if (milliHz == 40000) { reg1 |= 6<<2; mag_power = 550; } + else if (milliHz == 20000) { reg1 |= 5<<2; mag_power = 275; } + else if (milliHz == 10000) { reg1 |= 4<<2; mag_power = 137; } + else if (milliHz == 5000) { reg1 |= 3<<2; mag_power = 69; } + else if (milliHz == 2500) { reg1 |= 2<<2; mag_power = 34; } + else if (milliHz == 1250) { reg1 |= 1<<2; mag_power = 17; } else if (milliHz <= 630) { /*if (milliHz == 630 || milliHz == 625)*/ // We just go for the lowest power mode - reg1 |= 0<<2; // 8uA + reg1 |= 0<<2; mag_power = 8; lowPower = true; } else return false; @@ -498,17 +501,19 @@ bool accel_on(int milliHz) { // CTRL1_XL / CTRL2_G int reg = 0; bool gyro = true; + accel_power = 0; if (milliHz<12500) { // 1.6Hz, no gyro reg = 11<<4; gyro = false; - } else if (milliHz==12500) reg=1<<4; // 12.5 Hz (low power) - else if (milliHz==26000) reg=2<<4; // 26 Hz (low power) - else if (milliHz==52000) reg=3<<4; // 52 Hz (low power) - else if (milliHz==104000) reg=4<<4; // 104 Hz (normal mode) - else if (milliHz==208000) reg=5<<4; // 208 Hz (normal mode) - else if (milliHz==416000) reg=6<<4; // 416 Hz (high performance) - else if (milliHz==833000) reg=7<<4; // 833 Hz (high performance) - else if (milliHz==1660000) reg=8<<4; // 1.66 kHz (high performance) + accel_power = 40; + } else if (milliHz==12500) { reg=1<<4; accel_power = 350; } // 12.5 Hz (low power) + else if (milliHz==26000) { reg=2<<4;accel_power = 450; } // 26 Hz (low power) + else if (milliHz==52000) { reg=3<<4; accel_power = 600; }// 52 Hz (low power) + else if (milliHz==104000) { reg=4<<4; accel_power = 1700; }// 104 Hz (normal mode) + else if (milliHz==208000) { reg=5<<4; accel_power = 3000; }// 208 Hz (normal mode) + else if (milliHz==416000) { reg=6<<4; accel_power = 5300; }// 416 Hz (high performance) + else if (milliHz==833000) { reg=7<<4; accel_power = 5500; }// 833 Hz (high performance) + else if (milliHz==1660000) { reg=8<<4; accel_power = 5500; }// 1.66 kHz (high performance) else return false; @@ -1830,3 +1835,14 @@ bool jswrap_puck_idle() { } return busy; } + +/*JSON{ + "type" : "powerusage", + "generate" : "jswrap_puck_powerusage" +}*/ +void jswrap_puck_powerusage(JsVar *devices) { + if (mag_enabled) + jsvObjectSetChildAndUnLock(devices, "magnetometer", jsvNewFromInteger(mag_power)); + if (accel_enabled) + jsvObjectSetChildAndUnLock(devices, "accelerometer", jsvNewFromInteger(accel_power)); +} \ No newline at end of file diff --git a/libs/puckjs/jswrap_puck.h b/libs/puckjs/jswrap_puck.h index 4b0ffa838..5e6410af6 100644 --- a/libs/puckjs/jswrap_puck.h +++ b/libs/puckjs/jswrap_puck.h @@ -57,3 +57,4 @@ bool jswrap_puck_selfTest(); void jswrap_puck_init(); void jswrap_puck_kill(); bool jswrap_puck_idle(); +void jswrap_puck_powerusage(JsVar *devices); \ No newline at end of file diff --git a/scripts/build_jswrapper.py b/scripts/build_jswrapper.py index b13ae7cf1..349bd1cd7 100755 --- a/scripts/build_jswrapper.py +++ b/scripts/build_jswrapper.py @@ -729,6 +729,15 @@ def removeBlacklistForWrapper(blacklistfile,datas): codeOut(" "+jsondata["generate"]+"();") codeOut('}') +codeOut("/** When called with an Object, fields are added for each device that is used with estimated power usage in uA */") +codeOut('void jswGetPowerUsage(JsVar *devices) {') +for jsondata in jsondatas: + if "type" in jsondata and jsondata["type"]=="powerusage": + codeOut(" "+jsondata["generate"]+"(devices);") +codeOut('}') + + + codeOut("/** Tasks to run when a character event is received */") codeOut('bool jswOnCharEvent(IOEventFlags channel, char charData) {') codeOut(' NOT_USED(channel);') diff --git a/scripts/common.py b/scripts/common.py index 3c4df5b1a..bbe6daef9 100644 --- a/scripts/common.py +++ b/scripts/common.py @@ -61,7 +61,7 @@ def f(*popenargs, **kwargs): # # Comments look like: # -#/*JSON{ "type":"staticmethod|staticproperty|constructor|method|property|function|variable|class|library|idle|init|kill|EV_xxx", +#/*JSON{ "type":"staticmethod|staticproperty|constructor|method|property|function|variable|class|library|idle|init|kill|EV_xxx|powerusage", # // class = built-in class that does not require instantiation # // library = built-in class that needs require('classname') # // idle = function to run on idle regardless @@ -69,6 +69,7 @@ def f(*popenargs, **kwargs): # // init = function to run on Initialisation (eg boot/load/reset/after save/etc) # // kill = function to run on Deinitialisation (eg before save/reset/etc) # // EV_xxx = Something to be called with a character in an IRQ when it is received (eg. EV_SERIAL1) +# // powerusage = fn(JsVar*) called with an object, and should insert fields for deviec names and estimated power usage in uA # "class" : "Double", "name" : "doubleToIntBits", # "needs_parentName":true, // optional - if for a method, this makes the first 2 args parent+parentName (not just parent) # "generate_full|generate|wrap" : "*(JsVarInt*)&x", // if generate=false, it'll only be used for docs diff --git a/src/jshardware.h b/src/jshardware.h index 789e8ac1e..e8ef97858 100644 --- a/src/jshardware.h +++ b/src/jshardware.h @@ -453,6 +453,9 @@ unsigned int jshGetRandomNumber(); * speed in Hz though. */ unsigned int jshSetSystemClock(JsVar *options); +/* Adds the estimated power usage of the microcontroller in uA to the 'devices' object. The CPU should be called 'CPU' */ +void jsvGetProcessorPowerUsage(JsVar *devices); + /// Perform a proper hard-reboot of the device void jshReboot(); diff --git a/src/jshardware_common.c b/src/jshardware_common.c index 75702b6d8..6474820fd 100644 --- a/src/jshardware_common.c +++ b/src/jshardware_common.c @@ -159,4 +159,9 @@ void jshKickSoftWatchDog() { if (execInfo.execute & EXEC_CTRL_C_WAIT) { execInfo.execute = (execInfo.execute & ~EXEC_CTRL_C_WAIT) | EXEC_CTRL_C; } +} + +/* Returns the estimated power usage of the microcontroller */ +__attribute__((weak))void jsvGetProcessorPowerUsage(JsVar *devices) { + // not implemented by default } \ No newline at end of file diff --git a/src/jswrap_espruino.c b/src/jswrap_espruino.c index cb21e1d92..584727771 100644 --- a/src/jswrap_espruino.c +++ b/src/jswrap_espruino.c @@ -2426,6 +2426,78 @@ int jswrap_espruino_getRTCPrescaler(bool calibrate) { #endif } +/*JSON{ + "type" : "staticmethod", + "class" : "E", + "name" : "getPowerUsage", + "generate" : "jswrap_espruino_getPowerUsage", + "return" : ["JsVar","An object detailing power usage in microamps"] +} +This function returns an object detailing the current **estimated** power usage +of the Espruino device in microamps (uA). It is not intended to be a replacement +for measuring actual power consumption, but can be useful for finding obvious power +draws. + +Where an Espruino device has outputs that are connected to other things, those +are not included in the power usage figures. + +Results look like: + +``` +{ + device: { + CPU : 2000, // microcontroller + LCD : 100, // LCD + // ... + }, + total : 5500 // estimated usage in microamps +} +``` + +**Note:** Currently only nRF52-based devices have variable CPU power usage +figures. These are based on the time passed for each SysTick event, so under heavy +usage the figure will update within 0.3s, but under low CPU usage it could take +minutes for the CPU usage figure to update. + +**Note:** On Jolt.js we take account of internal resistance on H0/H2/H4/H6 where +we can measure voltage. H1/H3/H5/H7 cannot be measured. +*/ +JsVar *jswrap_espruino_getPowerUsage() { + JsVar *devices = jsvNewObject(); + if (!devices) return 0; + // add stuff we know about + jsvGetProcessorPowerUsage(devices); // CPU/etc +#ifdef LED1_PININDEX + if (jshPinGetState(LED1_PININDEX) & JSHPINSTATE_PIN_IS_ON) + jsvObjectSetChildAndUnLock(devices, "LED1", jsvNewFromInteger(8000)); +#endif +#ifdef LED2_PININDEX + if (jshPinGetState(LED2_PININDEX) & JSHPINSTATE_PIN_IS_ON) + jsvObjectSetChildAndUnLock(devices, "LED2", jsvNewFromInteger(8000)); +#endif +#ifdef LED3_PININDEX + if (jshPinGetState(LED3_PININDEX) & JSHPINSTATE_PIN_IS_ON) + jsvObjectSetChildAndUnLock(devices, "LED3", jsvNewFromInteger(8000)); +#endif + // Get data from jswrap_ files + jswGetPowerUsage(devices); + // sum up responses + JsVarFloat total = 0; + JsvObjectIterator it; + jsvObjectIteratorNew(&it, devices); + while (jsvObjectIteratorHasValue(&it)) { + total += jsvGetFloatAndUnLock(jsvObjectIteratorGetValue(&it)); + jsvObjectIteratorNext(&it); + } + jsvObjectIteratorFree(&it); + // return object + JsVar *usage = jsvNewObject(); + jsvObjectSetChildAndUnLock(usage, "device", devices); + jsvObjectSetChildAndUnLock(usage, "total", jsvNewFromFloat(total)); + return usage; +} + + /*JSON{ "type" : "staticmethod", "ifndef" : "SAVE_ON_FLASH", diff --git a/src/jswrap_espruino.h b/src/jswrap_espruino.h index 47c8ee3ea..428c9070a 100644 --- a/src/jswrap_espruino.h +++ b/src/jswrap_espruino.h @@ -77,6 +77,7 @@ bool jswrap_espruino_sendUSBHID(JsVar *arr); JsVarInt jswrap_espruino_getBattery(); void jswrap_espruino_setRTCPrescaler(int prescale); int jswrap_espruino_getRTCPrescaler(bool calibrate); +JsVar *jswrap_espruino_getPowerUsage(); JsVar *jswrap_espruino_decodeUTF8(JsVar *str, JsVar *lookup, JsVar *replaceFn); void jswrap_espruino_stopEventPropagation(); diff --git a/src/jswrapper.h b/src/jswrapper.h index 89bbfdccc..54b85bc92 100644 --- a/src/jswrapper.h +++ b/src/jswrapper.h @@ -149,6 +149,9 @@ void jswInit(); /** Tasks to run on Deinitialisation */ void jswKill(); +/** When called with an Object, fields are added for each device that is used with estimated power usage in uA */ +void jswGetPowerUsage(JsVar *devices); + /** Tasks to run when a character is received on a certain event channel. True if handled and shouldn't go to IRQ */ bool jswOnCharEvent(IOEventFlags channel, char charData); diff --git a/targets/esp32/jshardware.c b/targets/esp32/jshardware.c index 09e00f7b5..e12f32af8 100644 --- a/targets/esp32/jshardware.c +++ b/targets/esp32/jshardware.c @@ -142,7 +142,7 @@ void jshPinDefaultPullup() { jshPinSetStateRange(21,22,JSHPINSTATE_GPIO_IN_PULLUP); jshPinSetStateRange(25,27,JSHPINSTATE_GPIO_IN_PULLUP); jshPinSetStateRange(34,39,JSHPINSTATE_GPIO_IN_PULLUP); - + } /** @@ -205,7 +205,7 @@ int jshGetSerialNumber(unsigned char *data, int maxChars) { return 6; } -void jshInterruptOff() { +void jshInterruptOff() { taskDISABLE_INTERRUPTS(); } @@ -308,12 +308,12 @@ void jshPinSetState( * \return The current state of the selected pin. */ JshPinState jshPinGetState(Pin pin) { - if ( jshPinGetValue(pin) & 1 ) + if ( jshPinGetValue(pin) & 1 ) return g_pinState[pin] | JSHPINSTATE_PIN_IS_ON; return g_pinState[pin]; } -/** +/** * Check if state is default - return true if default */ bool jshIsPinStateDefault(Pin pin, JshPinState state) { @@ -375,7 +375,7 @@ JshPinFunction jshPinAnalogOutput(Pin pin, } else{ if(flags & JSAOF_ALLOW_SOFTWARE){ - if (!jshGetPinStateIsManual(pin)){ + if (!jshGetPinStateIsManual(pin)){ BITFIELD_SET(jshPinSoftPWM, pin, 0); jshPinSetState(pin, JSHPINSTATE_GPIO_OUT); } @@ -526,7 +526,7 @@ void jshUSARTKick(IOEventFlags device) { #ifdef BLUETOOTH case EV_BLUETOOTH: gatts_sendNUSNotification(c); - break; + break; #endif case EV_SERIAL1: uart_tx_one_char((uint8_t)c); @@ -534,7 +534,7 @@ void jshUSARTKick(IOEventFlags device) { default: writeSerial(device,(uint8_t)c); break; - //if(device == EV_SERIAL1) uart_tx_one_char((uint8_t)c); + //if(device == EV_SERIAL1) uart_tx_one_char((uint8_t)c); //else writeSerial(device,(uint8_t)c); } c = jshGetCharToTransmit(device); @@ -576,7 +576,7 @@ JsSysTime CALLED_FROM_INTERRUPT jshGetSystemTime() { // in us -- can be called a void jshSetSystemTime(JsSysTime newTime) { struct timeval tm; struct timezone tz; - + tm.tv_sec=(time_t)(newTime/1000000L); tm.tv_usec=(suseconds_t) (newTime - tm.tv_sec * 1000000L); tz.tz_minuteswest=0; @@ -685,7 +685,7 @@ bool jshFlashGetPage( if (addr >= FLASH_MAX) return false; *startAddr = addr & ~(FLASH_PAGE-1); *pageSize = FLASH_PAGE; - return true; + return true; } void addFlashArea(JsVar *jsFreeFlash, uint32_t addr, uint32_t length) { @@ -750,3 +750,9 @@ gpio_num_t pinToESP32Pin(Pin pin) { void jshReboot() { esp_restart(); // Call the ESP-IDF to restart the ESP32. } + +/* Adds the estimated power usage of the microcontroller in uA to the 'devices' object. The CPU should be called 'CPU' */ +void jsvGetProcessorPowerUsage(JsVar *devices) { + jsvObjectSetChildAndUnLock(devices, "CPU", jsvNewFromInteger(20000)); + // standard power usage of ESP32S3 without Wifi +} \ No newline at end of file diff --git a/targets/nrf5x/bluetooth.c b/targets/nrf5x/bluetooth.c index a3926df87..64e8e025a 100644 --- a/targets/nrf5x/bluetooth.c +++ b/targets/nrf5x/bluetooth.c @@ -203,7 +203,8 @@ const uint16_t m_central_effective_mtu = GATT_MTU_SIZE_DEFAULT; volatile bool nfcEnabled = false; #endif -uint16_t bleAdvertisingInterval = MSEC_TO_UNITS(BLUETOOTH_ADVERTISING_INTERVAL, UNIT_0_625_MS); /**< The advertising interval (in units of 0.625 ms). */ +/// The advertising interval (in units of 0.625 ms) +uint16_t bleAdvertisingInterval = MSEC_TO_UNITS(BLUETOOTH_ADVERTISING_INTERVAL, UNIT_0_625_MS); volatile BLEStatus bleStatus = 0; ble_uuid_t bleUUIDFilter; @@ -238,6 +239,9 @@ bool bleHighInterval; #define DEFAULT_PERIPH_MAX_CONN_INTERVAL 20 #endif +/// The interval for the current connection (periph/central may be mixed) (in units of 0.625 ms) +uint16_t blePeriphConnectionInterval = DEFAULT_PERIPH_MAX_CONN_INTERVAL; + static ble_gap_sec_params_t get_gap_sec_params(); #if PEER_MANAGER_ENABLED static bool jsble_can_pair_with_peer(const ble_gap_sec_params_t *own_params, const ble_gap_sec_params_t *peer_params); @@ -1118,16 +1122,25 @@ static void ble_evt_handler(ble_evt_t const * p_ble_evt, void * p_context) { // comes in between sd_ble_gap_disconnect being called and the DISCONNECT // event being received. The SD obviously does the checks for us, so lets // avoid crashing because of it! + } break; // BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST #endif + case BLE_GAP_EVT_CONN_PARAM_UPDATE: + // Connection interval changed + if (p_ble_evt->evt.gap_evt.conn_handle == m_peripheral_conn_handle) + blePeriphConnectionInterval = p_ble_evt->evt.gap_evt.params.conn_param_update.conn_params.max_conn_interval; + break; case BLE_GAP_EVT_CONNECTED: // set connection transmit power #if NRF_SD_BLE_API_VERSION > 5 sd_ble_gap_tx_power_set(BLE_GAP_TX_POWER_ROLE_CONN, p_ble_evt->evt.gap_evt.conn_handle, m_tx_power); #endif + + if (p_ble_evt->evt.gap_evt.params.connected.role == BLE_GAP_ROLE_PERIPH) { m_peripheral_conn_handle = p_ble_evt->evt.gap_evt.conn_handle; + blePeriphConnectionInterval = p_ble_evt->evt.gap_evt.params.conn_param_update.conn_params.max_conn_interval; #ifdef EXTENSIBLE_MTU m_peripheral_effective_mtu = GATT_MTU_SIZE_DEFAULT; #endif @@ -3360,6 +3373,9 @@ JsVar *jsble_get_security_status(uint16_t conn_handle) { bool isAdvertising = bleStatus & BLE_IS_ADVERTISING; jsvObjectSetChildAndUnLock(result, "advertising", jsvNewFromBool(isAdvertising)); } + if (conn_handle == m_peripheral_conn_handle) { + jsvObjectSetChildAndUnLock(result, "connectionInterval", jsvNewFromInteger(blePeriphConnectionInterval)); + } if (conn_handle == BLE_CONN_HANDLE_INVALID) { jsvObjectSetChildAndUnLock(result, "connected", jsvNewFromBool(false)); return result; diff --git a/targets/nrf5x/jshardware.c b/targets/nrf5x/jshardware.c index d00fe83e2..31584b020 100644 --- a/targets/nrf5x/jshardware.c +++ b/targets/nrf5x/jshardware.c @@ -115,6 +115,11 @@ volatile JsSysTime baseSystemTime __attribute__((section(".noinit"))) __attribut volatile uint32_t lastSystemTime __attribute__((section(".noinit"))) __attribute__((aligned(4))); volatile uint32_t lastSystemTimeInv __attribute__((section(".noinit"))) __attribute__((aligned(4))); +/// The last RTC time a system time 'tick' happened at +JsSysTime lastSysTickTime; +/// time taken for a system 'tick' - we can use this to work out how long we've been sleeping for +uint32_t sysTickTime; + #ifdef NRF_USB #include "app_usbd_core.h" #include "app_usbd.h" @@ -652,6 +657,12 @@ void SysTick_Handler(void) { if (ticksSinceStart == 6) { jsiOneSecondAfterStartup(); } + + JsSysTime currTime = jshGetSystemTime(); + JsSysTime t = (currTime - lastSysTickTime); + if (t>0xFFFFFFFFU) t = 0xFFFFFFFFU; + sysTickTime = (uint32_t)t; + lastSysTickTime = currTime; } #ifdef NRF52_SERIES @@ -842,6 +853,7 @@ void jshInit() { } lastSystemTime = 0; lastSystemTimeInv = ~lastSystemTime; + lastSysTickTime = jshGetSystemTime(); memset(pinStates, 0, sizeof(pinStates)); memset(extiToPin, PIN_UNDEFINED, sizeof(extiToPin)); @@ -2878,3 +2890,23 @@ unsigned int jshSetSystemClock(JsVar *options) { void jshReboot() { NVIC_SystemReset(); } + +/* Adds the estimated power usage of the microcontroller in uA to the 'devices' object. The CPU should be called 'CPU' */ +void jsvGetProcessorPowerUsage(JsVar *devices) { + // draws 4mA flat out, 3uA nothing otherwise + jsvObjectSetChildAndUnLock(devices, "CPU", jsvNewFromInteger(3 + ((4000 * 273152) / sysTickTime))); + // check UART - draws about 1mA when on + bool uartOn = false; + for (int i=0;i