From 532d196378506f724d03648dc2c6aa9c4cfa9061 Mon Sep 17 00:00:00 2001 From: Eishan Lawrence Date: Thu, 20 Feb 2020 18:30:46 -0800 Subject: [PATCH 1/3] Output dependency installation failures to an output channel (#210) --- src/constants.ts | 4 ++++ src/extension_utils/utils.ts | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/src/constants.ts b/src/constants.ts index 09008492c..14b020f7b 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -71,6 +71,10 @@ export const CONSTANTS = { "error.incorrectFileNameForDevicePopup", 'Seems like you have a different file name than what CPX requires, please rename it to "code.py" or "main.py".' ), + INSTALLATION_ERROR: localize( + "error.installationError", + "Installation Error" + ), INVALID_FILE_EXTENSION_DEBUG: localize( "error.invalidFileExtensionDebug", "The file you tried to run isn't a Python file." diff --git a/src/extension_utils/utils.ts b/src/extension_utils/utils.ts index ed6f83989..5a97f7ef2 100644 --- a/src/extension_utils/utils.ts +++ b/src/extension_utils/utils.ts @@ -22,6 +22,10 @@ import { DependencyChecker } from "./dependencyChecker"; const exec = util.promisify(cp.exec); +const errorChannel = vscode.window.createOutputChannel( + CONSTANTS.ERROR.INSTALLATION_ERROR +); + // tslint:disable-next-line: export-name export const getPathToScript = ( context: vscode.ExtensionContext, @@ -369,6 +373,7 @@ export const installPythonDependencies = async ( }); console.error(err); + this.logToOutputChannel(errorChannel, err.toString(), true /* show */); installed = false; } return installed; From 97cb35818296a0bea3b1e9f379c724f259ba0c95 Mon Sep 17 00:00:00 2001 From: Vandy Liu <33995460+vandyliu@users.noreply.github.com> Date: Fri, 21 Feb 2020 14:24:50 -0800 Subject: [PATCH 2/3] Separated telemetry metrics for debugger (#214) --- src/constants.ts | 8 ++++++-- src/extension.ts | 42 ++++++++++++++++++++++++++++++++++++------ 2 files changed, 42 insertions(+), 8 deletions(-) diff --git a/src/constants.ts b/src/constants.ts index 14b020f7b..aad989cf3 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -285,8 +285,12 @@ export enum CONFIG_KEYS { export enum TelemetryEventName { FAILED_TO_OPEN_SIMULATOR = "SIMULATOR.FAILED_TO_OPEN", - DEBUGGER_INIT_SUCCESS = "DEBUGGER.INIT.SUCCESS", - DEBUGGER_INIT_FAIL = "DEBUGGER.INIT.FAIL", + + // Debugger + CPX_DEBUGGER_INIT_SUCCESS = "CPX.DEBUGGER.INIT.SUCCESS", + CPX_DEBUGGER_INIT_FAIL = "CPX.DEBUGGER.INIT.FAIL", + MICROBIT_DEBUGGER_INIT_SUCCESS = "MICROBIT.DEBUGGER.INIT.SUCCESS", + MICROBIT_DEBUGGER_INIT_FAIL = "MICROBIT.DEBUGGER.INIT.FAIL", // Extension commands COMMAND_RUN_SIMULATOR_BUTTON = "COMMAND.RUN.SIMULATOR_BUTTON", diff --git a/src/extension.ts b/src/extension.ts index 0a3538ee8..21c9b31d5 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -934,9 +934,7 @@ export async function activate(context: vscode.ExtensionContext) { utils.getServerPortConfig() ); - telemetryAI.trackFeatureUsage( - TelemetryEventName.DEBUGGER_INIT_SUCCESS - ); + handleDebuggerTelemetry(); openWebview(); if (currentPanel) { @@ -952,9 +950,7 @@ export async function activate(context: vscode.ExtensionContext) { `Error trying to init the server on port ${utils.getServerPortConfig()}` ); - telemetryAI.trackFeatureUsage( - TelemetryEventName.DEBUGGER_INIT_FAIL - ); + handleDebuggerFailTelemetry(); vscode.window.showErrorMessage( CONSTANTS.ERROR.DEBUGGER_SERVER_INIT_FAILED( @@ -1039,6 +1035,40 @@ const updateCurrentFileIfPython = async ( } }; +const handleDebuggerTelemetry = () => { + switch (currentActiveDevice) { + case CONSTANTS.DEVICE_NAME.CPX: + telemetryAI.trackFeatureUsage( + TelemetryEventName.CPX_DEBUGGER_INIT_SUCCESS + ); + break; + case CONSTANTS.DEVICE_NAME.MICROBIT: + telemetryAI.trackFeatureUsage( + TelemetryEventName.MICROBIT_DEBUGGER_INIT_SUCCESS + ); + break; + default: + break; + } +}; + +const handleDebuggerFailTelemetry = () => { + switch (currentActiveDevice) { + case CONSTANTS.DEVICE_NAME.CPX: + telemetryAI.trackFeatureUsage( + TelemetryEventName.CPX_DEBUGGER_INIT_FAIL + ); + break; + case CONSTANTS.DEVICE_NAME.MICROBIT: + telemetryAI.trackFeatureUsage( + TelemetryEventName.MICROBIT_DEBUGGER_INIT_FAIL + ); + break; + default: + break; + } +}; + const handleButtonPressTelemetry = (buttonState: any) => { switch (currentActiveDevice) { case CONSTANTS.DEVICE_NAME.CPX: From 72b9f1edd5754204b830c0ce7f2203ce8a5929a3 Mon Sep 17 00:00:00 2001 From: Vandy Liu <33995460+vandyliu@users.noreply.github.com> Date: Fri, 21 Feb 2020 18:20:06 -0800 Subject: [PATCH 3/3] Add telemetry for python (#213) --- src/adafruit_circuitplayground/constants.py | 11 ------ src/adafruit_circuitplayground/express.py | 33 +++++++++++------ src/adafruit_circuitplayground/pixel.py | 13 ++++--- src/adafruit_circuitplayground/telemetry.py | 41 --------------------- src/common/telemetry.py | 31 ++++++++++++++++ src/common/telemetry_events.py | 33 +++++++++++++++++ src/microbit/__init__.py | 3 ++ src/microbit/__model/accelerometer.py | 10 +++++ src/microbit/__model/button.py | 7 ++++ src/microbit/__model/display.py | 15 +++++++- src/microbit/__model/image.py | 24 +++++++++++- src/process_user_code.py | 3 +- 12 files changed, 150 insertions(+), 74 deletions(-) delete mode 100644 src/adafruit_circuitplayground/telemetry.py create mode 100644 src/common/telemetry.py create mode 100644 src/common/telemetry_events.py diff --git a/src/adafruit_circuitplayground/constants.py b/src/adafruit_circuitplayground/constants.py index 4e6100d97..98924bfcc 100644 --- a/src/adafruit_circuitplayground/constants.py +++ b/src/adafruit_circuitplayground/constants.py @@ -27,17 +27,6 @@ VALID_PIXEL_ASSIGN_ERROR = "The pixel color value should be a tuple with three values between 0 and 255 or a hexadecimal color between 0x000000 and 0xFFFFFF." -TELEMETRY_EVENT_NAMES = { - "TAPPED": "API.TAPPED", - "PLAY_FILE": "API.PLAY.FILE", - "PLAY_TONE": "API.PLAY.TONE", - "START_TONE": "API.START.TONE", - "STOP_TONE": "API.STOP.TONE", - "DETECT_TAPS": "API.DETECT.TAPS", - "ADJUST_THRESHOLD": "API.ADJUST.THRESHOLD", - "RED_LED": "API.RED.LED", - "PIXELS": "API.PIXELS", -} ERROR_SENDING_EVENT = "Error trying to send event to the process : " TIME_DELAY = 0.03 diff --git a/src/adafruit_circuitplayground/express.py b/src/adafruit_circuitplayground/express.py index 9e20af4b2..5eb9a3d7e 100644 --- a/src/adafruit_circuitplayground/express.py +++ b/src/adafruit_circuitplayground/express.py @@ -5,13 +5,13 @@ import sys import os import playsound + from common import utils +from common.telemetry import telemetry_py +from common.telemetry_events import TelemetryEvent from .pixel import Pixel - from . import constants as CONSTANTS from collections import namedtuple -from applicationinsights import TelemetryClient -from .telemetry import telemetry_py from . import debugger_communication_client Acceleration = namedtuple("acceleration", ["x", "y", "z"]) @@ -52,25 +52,29 @@ def __init__(self): @property def acceleration(self): + telemetry_py.send_telemetry(TelemetryEvent.CPX_API_ACCELERATION) return Acceleration( self.__state["motion_x"], self.__state["motion_y"], self.__state["motion_z"] ) @property def button_a(self): + telemetry_py.send_telemetry(TelemetryEvent.CPX_API_BUTTON_A) return self.__state["button_a"] @property def button_b(self): + telemetry_py.send_telemetry(TelemetryEvent.CPX_API_BUTTON_B) return self.__state["button_b"] @property def detect_taps(self): - telemetry_py.send_telemetry("DETECT_TAPS") + telemetry_py.send_telemetry(TelemetryEvent.CPX_API_DETECT_TAPS) return self.__state["detect_taps"] @detect_taps.setter def detect_taps(self, value): + telemetry_py.send_telemetry(TelemetryEvent.CPX_API_DETECT_TAPS) value_int = int(value) self.__state["detect_taps"] = ( value_int if (value_int == 1 or value_int == 2) else 1 @@ -80,30 +84,33 @@ def detect_taps(self, value): def tapped(self): """ Not Implemented! """ - telemetry_py.send_telemetry("TAPPED") + telemetry_py.send_telemetry(TelemetryEvent.CPX_API_TAPPED) raise NotImplementedError(CONSTANTS.NOT_IMPLEMENTED_ERROR) @property def red_led(self): - telemetry_py.send_telemetry("RED_LED") + telemetry_py.send_telemetry(TelemetryEvent.CPX_API_RED_LED) return self.__state["red_led"] @red_led.setter def red_led(self, value): - telemetry_py.send_telemetry("RED_LED") + telemetry_py.send_telemetry(TelemetryEvent.CPX_API_RED_LED) self.__state["red_led"] = bool(value) self.__show() @property def switch(self): + telemetry_py.send_telemetry(TelemetryEvent.CPX_API_SWITCH) return self.__state["switch"] @property def temperature(self): + telemetry_py.send_telemetry(TelemetryEvent.CPX_API_TEMPERATURE) return self.__state["temperature"] @property def light(self): + telemetry_py.send_telemetry(TelemetryEvent.CPX_API_LIGHT) return self.__state["light"] def __show(self): @@ -113,6 +120,7 @@ def __show(self): utils.send_to_simulator(self.__state, CONSTANTS.CPX) def __touch(self, i): + telemetry_py.send_telemetry(TelemetryEvent.CPX_API_TOUCH) return self.__state["touch"][i - 1] @property @@ -147,14 +155,15 @@ def adjust_touch_threshold(self, adjustement): """Not implemented! The CPX Simulator doesn't use capacitive touch threshold. """ - telemetry_py.send_telemetry("ADJUST_THRESHOLD") + telemetry_py.send_telemetry(TelemetryEvent.CPX_API_ADJUST_THRESHOLD) raise NotImplementedError(CONSTANTS.NOT_IMPLEMENTED_ERROR) def shake(self, shake_threshold=30): + telemetry_py.send_telemetry(TelemetryEvent.CPX_API_SHAKE) return self.__state["shake"] def play_file(self, file_name): - telemetry_py.send_telemetry("PLAY_FILE") + telemetry_py.send_telemetry(TelemetryEvent.CPX_API_PLAY_FILE) file_name = utils.remove_leading_slashes(file_name) abs_path_parent_dir = os.path.abspath( os.path.join(self.__abs_path_to_code_file, os.pardir) @@ -179,19 +188,19 @@ def play_file(self, file_name): def play_tone(self, frequency, duration): """ Not Implemented! """ - telemetry_py.send_telemetry("PLAY_TONE") + telemetry_py.send_telemetry(TelemetryEvent.CPX_API_PLAY_TONE) raise NotImplementedError(CONSTANTS.NOT_IMPLEMENTED_ERROR) def start_tone(self, frequency): """ Not Implemented! """ - telemetry_py.send_telemetry("START_TONE") + telemetry_py.send_telemetry(TelemetryEvent.CPX_API_START_TONE) raise NotImplementedError(CONSTANTS.NOT_IMPLEMENTED_ERROR) def stop_tone(self): """ Not Implemented! """ - telemetry_py.send_telemetry("STOP_TONE") + telemetry_py.send_telemetry(TelemetryEvent.CPX_API_STOP_TONE) raise NotImplementedError(CONSTANTS.NOT_IMPLEMENTED_ERROR) def update_state(self, new_state): diff --git a/src/adafruit_circuitplayground/pixel.py b/src/adafruit_circuitplayground/pixel.py index 631311ce8..410f2861d 100644 --- a/src/adafruit_circuitplayground/pixel.py +++ b/src/adafruit_circuitplayground/pixel.py @@ -3,12 +3,11 @@ import json import sys -from common import utils -from . import constants as CONSTANTS -from applicationinsights import TelemetryClient +from common import utils +from common.telemetry import telemetry_py +from common.telemetry_events import TelemetryEvent from . import constants as CONSTANTS -from .telemetry import telemetry_py from . import debugger_communication_client @@ -38,11 +37,11 @@ def __getitem__(self, index): if type(index) is not slice: if not self.__valid_index(index): raise IndexError(CONSTANTS.INDEX_ERROR) - telemetry_py.send_telemetry("PIXELS") + telemetry_py.send_telemetry(TelemetryEvent.CPX_API_PIXELS) return self.__state["pixels"][index] def __setitem__(self, index, val): - telemetry_py.send_telemetry("PIXELS") + telemetry_py.send_telemetry(TelemetryEvent.CPX_API_PIXELS) is_slice = False if type(index) is slice: is_slice = True @@ -115,12 +114,14 @@ def __valid_rgb_value(self, pixValue): @property def brightness(self): + telemetry_py.send_telemetry(TelemetryEvent.CPX_API_BRIGHTNESS) return self.__state["brightness"] @brightness.setter def brightness(self, brightness): if not self.__valid_brightness(brightness): raise ValueError(CONSTANTS.BRIGHTNESS_RANGE_ERROR) + telemetry_py.send_telemetry(TelemetryEvent.CPX_API_BRIGHTNESS) self.__state["brightness"] = brightness self.__show_if_auto_write() diff --git a/src/adafruit_circuitplayground/telemetry.py b/src/adafruit_circuitplayground/telemetry.py deleted file mode 100644 index 9fd1edb1c..000000000 --- a/src/adafruit_circuitplayground/telemetry.py +++ /dev/null @@ -1,41 +0,0 @@ -from . import constants as CONSTANTS -from applicationinsights import TelemetryClient - - -class Telemetry: - def __init__(self): - # State of the telemetry - self.__enable_telemetry = True - self.telemetry_state = { - "DETECT_TAPS": False, - "TAPPED": False, - "RED_LED": False, - "ADJUST_THRESHOLD": False, - "PLAY_FILE": False, - "PLAY_TONE": False, - "START_TONE": False, - "STOP_TONE": False, - "PIXELS": False, - } - self.telemetry_client = TelemetryClient("__AIKEY__") - self.extension_name = "__EXTENSIONNAME__" - - def send_telemetry(self, event_name): - if ( - self.__enable_telemetry - and self.telemetry_available() - and not self.telemetry_state[event_name] - ): - self.telemetry_client.track_event( - "{}/{}".format( - self.extension_name, CONSTANTS.TELEMETRY_EVENT_NAMES[event_name] - ) - ) - self.telemetry_client.flush() - self.telemetry_state[event_name] = True - - def telemetry_available(self): - return self.telemetry_client.context.instrumentation_key != "__AIKEY__" - - -telemetry_py = Telemetry() diff --git a/src/common/telemetry.py b/src/common/telemetry.py new file mode 100644 index 000000000..44a22d058 --- /dev/null +++ b/src/common/telemetry.py @@ -0,0 +1,31 @@ +from applicationinsights import TelemetryClient +from .telemetry_events import TelemetryEvent + + +class Telemetry: + def __init__(self): + # State of the telemetry + self.__enable_telemetry = True + self.telemetry_client = TelemetryClient("__AIKEY__") + self.telemetry_state = dict.fromkeys( + [name for name, _ in TelemetryEvent.__members__.items()], False + ) + self.extension_name = "Device Simulator Express" + + def send_telemetry(self, event_name: TelemetryEvent): + if ( + self.__enable_telemetry + and self.telemetry_available() + and not self.telemetry_state[event_name.name] + ): + self.telemetry_client.track_event( + f"{self.extension_name}/{event_name.value}" + ) + self.telemetry_client.flush() + self.telemetry_state[event_name.name] = True + + def telemetry_available(self): + return self.telemetry_client.context.instrumentation_key == "__AIKEY__" + + +telemetry_py = Telemetry() diff --git a/src/common/telemetry_events.py b/src/common/telemetry_events.py new file mode 100644 index 000000000..d150076d0 --- /dev/null +++ b/src/common/telemetry_events.py @@ -0,0 +1,33 @@ +import enum + + +class TelemetryEvent(enum.Enum): + CPX_API_ACCELERATION = "CPX.API.ACCELERATION" + CPX_API_BUTTON_A = "CPX.API.BUTTON.A" + CPX_API_BUTTON_B = "CPX.API.BUTTON.B" + CPX_API_SWITCH = "CPX.API.SWITCH" + CPX_API_TEMPERATURE = "CPX.API.TEMPERATURE" + CPX_API_BRIGHTNESS = "CPX.API.BRIGHTNESS" + CPX_API_LIGHT = "CPX.API.LIGHT" + CPX_API_TOUCH = "CPX.API.TOUCH" + CPX_API_SHAKE = "CPX.API.SHAKE" + CPX_API_TAPPED = "CPX.API.TAPPED" + CPX_API_PLAY_FILE = "CPX.API.PLAY.FILE" + CPX_API_PLAY_TONE = "CPX.API.PLAY.TONE" + CPX_API_START_TONE = "CPX.API.START.TONE" + CPX_API_STOP_TONE = "CPX.API.STOP.TONE" + CPX_API_DETECT_TAPS = "CPX.API.DETECT.TAPS" + CPX_API_ADJUST_THRESHOLD = "CPX.API.ADJUST.THRESHOLD" + CPX_API_RED_LED = "CPX.API.RED.LED" + CPX_API_PIXELS = "CPX.API.PIXELS" + MICROBIT_API_TEMPERATURE = "MICROBIT.API.TEMPERATURE" + MICROBIT_API_ACCELEROMETER = "MICROBIT.API.ACCELEROMETER" + MICROBIT_API_GESTURE = "MICROBIT.API.GESTURE" + MICROBIT_API_DISPLAY_SCROLL = "MICROBIT.API.DISPLAY.SCROLL" + MICROBIT_API_DISPLAY_SHOW = "MICROBIT.API.DISPLAY.SHOW" + MICROBIT_API_DISPLAY_OTHER = "MICROBIT.API.DISPLAY_OTHER" + MICROBIT_API_LIGHT_LEVEL = "MICROBIT.API.LIGHT.LEVEL" + MICROBIT_API_IMAGE_CREATION = "MICROBIT.API.IMAGE.CREATION" + MICROBIT_API_IMAGE_OTHER = "MICROBIT.API.IMAGE.OTHER" + MICROBIT_API_IMAGE_STATIC = "MICROBIT.API.IMAGE.STATIC" + MICROBIT_API_BUTTON = "MICROBIT.API.BUTTON" diff --git a/src/microbit/__init__.py b/src/microbit/__init__.py index bdf3f0779..e444b34f0 100644 --- a/src/microbit/__init__.py +++ b/src/microbit/__init__.py @@ -1,5 +1,7 @@ from .__model.image import Image from .__model.microbit_model import __mb +from common.telemetry import telemetry_py +from common.telemetry_events import TelemetryEvent accelerometer = __mb.accelerometer button_a = __mb.button_a @@ -29,4 +31,5 @@ def temperature(): """ Return the temperature of the micro:bit in degrees Celcius. """ + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_TEMPERATURE) return __mb.temperature() diff --git a/src/microbit/__model/accelerometer.py b/src/microbit/__model/accelerometer.py index 919d3c9db..6360ce982 100644 --- a/src/microbit/__model/accelerometer.py +++ b/src/microbit/__model/accelerometer.py @@ -1,4 +1,6 @@ from . import constants as CONSTANTS +from common.telemetry import telemetry_py +from common.telemetry_events import TelemetryEvent class Accelerometer: @@ -17,6 +19,7 @@ def get_x(self): negative integer, depending on the direction. The measurement is given in milli-g. """ + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_ACCELEROMETER) return self.__x def get_y(self): @@ -25,6 +28,7 @@ def get_y(self): negative integer, depending on the direction. The measurement is given in milli-g. """ + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_ACCELEROMETER) return self.__y def get_z(self): @@ -33,6 +37,7 @@ def get_z(self): negative integer, depending on the direction. The measurement is given in milli-g. """ + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_ACCELEROMETER) return self.__z def get_values(self): @@ -40,12 +45,14 @@ def get_values(self): Get the acceleration measurements in all axes at once, as a three-element tuple of integers ordered as X, Y, Z. """ + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_ACCELEROMETER) return (self.__x, self.__y, self.__z) def current_gesture(self): """ Return the name of the current gesture. """ + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_GESTURE) self.__add_current_gesture_to_gesture_lists() return self.__current_gesture @@ -53,6 +60,7 @@ def is_gesture(self, name): """ Return True or False to indicate if the named gesture is currently active. """ + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_GESTURE) self.__add_current_gesture_to_gesture_lists() if name not in CONSTANTS.GESTURES: raise ValueError(CONSTANTS.INVALID_GESTURE_ERR) @@ -63,6 +71,7 @@ def was_gesture(self, name): Return True or False to indicate if the named gesture was active since the last [was_gesture] call. """ + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_GESTURE) self.__add_current_gesture_to_gesture_lists() if name not in CONSTANTS.GESTURES: raise ValueError(CONSTANTS.INVALID_GESTURE_ERR) @@ -75,6 +84,7 @@ def get_gestures(self): Return a tuple of the gesture history. The most recent is listed last. Also clears the gesture history before returning. """ + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_GESTURE) self.__add_current_gesture_to_gesture_lists() gestures = tuple(self.__gestures) self.__gestures.clear() diff --git a/src/microbit/__model/button.py b/src/microbit/__model/button.py index 32689f485..1bbe4ddbc 100644 --- a/src/microbit/__model/button.py +++ b/src/microbit/__model/button.py @@ -1,3 +1,7 @@ +from common.telemetry import telemetry_py +from common.telemetry_events import TelemetryEvent + + class Button: # The implementation is based off of https://microbit-micropython.readthedocs.io/en/v1.0.1/button.html. def __init__(self): @@ -10,6 +14,7 @@ def is_pressed(self): Returns ``True`` if the specified button ``button`` is currently being held down, and ``False`` otherwise. """ + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_BUTTON) return self.__pressed def was_pressed(self): @@ -20,6 +25,7 @@ def was_pressed(self): that the button must be pressed again before this method will return ``True`` again. """ + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_BUTTON) res = self.__prev_pressed self.__prev_pressed = False return res @@ -29,6 +35,7 @@ def get_presses(self): Returns the running total of button presses, and resets this total to zero before returning. """ + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_BUTTON) res = self.__presses self.__presses = 0 return res diff --git a/src/microbit/__model/display.py b/src/microbit/__model/display.py index ff62e4e59..791e1ea8b 100644 --- a/src/microbit/__model/display.py +++ b/src/microbit/__model/display.py @@ -1,8 +1,10 @@ import copy import time import threading -from common import utils +from common import utils +from common.telemetry import telemetry_py +from common.telemetry_events import TelemetryEvent from . import constants as CONSTANTS from .image import Image @@ -44,6 +46,8 @@ def scroll(self, value, delay=150, wait=True, loop=False, monospace=False): thread.start() return + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_DISPLAY_SCROLL) + # Set current_pid to the thread's identifier self.__lock.acquire() self.__current_pid = threading.get_ident() @@ -123,6 +127,8 @@ def show(self, value, delay=400, wait=True, loop=False, clear=False): thread.start() return + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_DISPLAY_SHOW) + # Set current_pid to the thread's identifier self.__lock.acquire() self.__current_pid = threading.get_ident() @@ -183,6 +189,7 @@ def get_pixel(self, x, y): Return the brightness of the LED at column ``x`` and row ``y`` as an integer between 0 (off) and 9 (bright). """ + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_DISPLAY_OTHER) self.__lock.acquire() pixel = self.__image.get_pixel(x, y) self.__lock.release() @@ -193,6 +200,7 @@ def set_pixel(self, x, y, value): Set the brightness of the LED at column ``x`` and row ``y`` to ``value``, which has to be an integer between 0 and 9. """ + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_DISPLAY_OTHER) self.__lock.acquire() self.__image.set_pixel(x, y, value) self.__lock.release() @@ -202,6 +210,7 @@ def clear(self): """ Set the brightness of all LEDs to 0 (off). """ + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_DISPLAY_OTHER) self.__lock.acquire() self.__image = Image() self.__lock.release() @@ -211,18 +220,21 @@ def on(self): """ Use on() to turn on the display. """ + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_DISPLAY_OTHER) self.__on = True def off(self): """ Use off() to turn off the display. """ + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_DISPLAY_OTHER) self.__on = False def is_on(self): """ Returns ``True`` if the display is on, otherwise returns ``False``. """ + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_DISPLAY_OTHER) return self.__on def read_light_level(self): @@ -231,6 +243,7 @@ def read_light_level(self): falling on the display. Returns an integer between 0 and 255 representing the light level, with larger meaning more light. """ + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_LIGHT_LEVEL) return self.__light_level def __set_light_level(self, level): diff --git a/src/microbit/__model/image.py b/src/microbit/__model/image.py index a1aef09c6..391e17e1e 100644 --- a/src/microbit/__model/image.py +++ b/src/microbit/__model/image.py @@ -1,5 +1,7 @@ from . import constants as CONSTANTS from .producer_property import ProducerProperty +from common.telemetry import telemetry_py +from common.telemetry_events import TelemetryEvent class Image: @@ -114,7 +116,7 @@ class Image: def __init__(self, *args, **kwargs): # Depending on the number of arguments # in constructor, it treat args differently. - + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_IMAGE_CREATION) if len(args) == 0: # default constructor self.__LED = self.__string_to_square_array(CONSTANTS.BLANK_5X5) @@ -140,13 +142,13 @@ def __init__(self, *args, **kwargs): self.__LED = self.__bytes_to_array(width, height, byte_arr) else: self.__LED = self.__create_leds(width, height) - self.read_only = False def width(self): """ Return the number of columns in the image. """ + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_IMAGE_OTHER) if len(self.__LED) > 0: return len(self.__LED[0]) else: @@ -156,6 +158,7 @@ def height(self): """ Return the numbers of rows in the image. """ + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_IMAGE_OTHER) return len(self.__LED) def set_pixel(self, x, y, value): @@ -166,6 +169,7 @@ def set_pixel(self, x, y, value): This method will raise an exception when called on any of the built-in read-only images, like ``Image.HEART``. """ + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_IMAGE_OTHER) if self.read_only: raise TypeError(CONSTANTS.COPY_ERR_MESSAGE) elif not self.__valid_pos(x, y): @@ -180,6 +184,7 @@ def get_pixel(self, x, y): Return the brightness of pixel at column ``x`` and row ``y`` as an integer between 0 and 9. """ + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_IMAGE_OTHER) if self.__valid_pos(x, y): return self.__LED[y][x] else: @@ -189,12 +194,14 @@ def shift_up(self, n): """ Return a new image created by shifting the picture up by ``n`` rows. """ + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_IMAGE_OTHER) return self.__shift_vertical(-n) def shift_down(self, n): """ Return a new image created by shifting the picture down by ``n`` rows. """ + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_IMAGE_OTHER) return self.__shift_vertical(n) def shift_right(self, n): @@ -202,6 +209,7 @@ def shift_right(self, n): Return a new image created by shifting the picture right by ``n`` columns. """ + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_IMAGE_OTHER) return self.__shift_horizontal(n) def shift_left(self, n): @@ -209,6 +217,7 @@ def shift_left(self, n): Return a new image created by shifting the picture left by ``n`` columns. """ + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_IMAGE_OTHER) return self.__shift_horizontal(-n) def crop(self, x, y, w, h): @@ -216,6 +225,7 @@ def crop(self, x, y, w, h): Return a new image by cropping the picture to a width of ``w`` and a height of ``h``, starting with the pixel at column ``x`` and row ``y``. """ + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_IMAGE_OTHER) res = Image(w, h) res.blit(self, x, y, w, h) return res @@ -224,6 +234,7 @@ def copy(self): """ Return an exact copy of the image. """ + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_IMAGE_OTHER) return Image(self.__create_string()) # This inverts the brightness of each LED. @@ -234,6 +245,7 @@ def invert(self): Return a new image by inverting the brightness of the pixels in the source image. """ + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_IMAGE_OTHER) for y in range(self.height()): for x in range(self.width()): self.set_pixel(x, y, CONSTANTS.BRIGHTNESS_MAX - self.get_pixel(x, y)) @@ -247,6 +259,7 @@ def fill(self, value): This method will raise an exception when called on any of the built-in read-only images, like ``Image.HEART``. """ + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_IMAGE_OTHER) for y in range(self.height()): for x in range(self.width()): self.set_pixel(x, y, value) @@ -258,6 +271,7 @@ def blit(self, src, x, y, w, h, xdest=0, ydest=0): this image at ``xdest``, ``ydest``. Areas in the source rectangle, but outside the source image are treated as having a value of 0. """ + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_IMAGE_OTHER) if not src.__valid_pos(x, y): raise ValueError(CONSTANTS.INDEX_ERR) @@ -276,6 +290,7 @@ def __add__(self, other): """ Create a new image by adding the brightness values from the two images for each pixel. """ + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_IMAGE_OTHER) if not isinstance(other, Image): raise TypeError( CONSTANTS.UNSUPPORTED_ADD_TYPE + f"'{type(self)}', '{type(other)}'" @@ -298,6 +313,7 @@ def __mul__(self, other): """ Create a new image by multiplying the brightness of each pixel by n. """ + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_IMAGE_OTHER) try: float_val = float(other) except TypeError: @@ -316,6 +332,7 @@ def __repr__(self): """ Get a compact string representation of the image. """ + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_IMAGE_OTHER) ret_str = "Image('" for index_y in range(self.height()): ret_str += self.__row_to_str(index_y) @@ -328,6 +345,7 @@ def __str__(self): """ Get a readable string representation of the image. """ + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_IMAGE_OTHER) ret_str = "Image('\n" for index_y in range(self.height()): ret_str += "\t" + self.__row_to_str(index_y) + "\n" @@ -486,6 +504,8 @@ def __same_image(i1, i2): # This is for generating functions like Image.HEART # that return a new read-only Image def create_const_func(func_name): + telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_IMAGE_STATIC) + def func(*args): const_instance = Image(CONSTANTS.IMAGE_PATTERNS[func_name]) const_instance.read_only = True diff --git a/src/process_user_code.py b/src/process_user_code.py index e34c19c21..0748cd501 100644 --- a/src/process_user_code.py +++ b/src/process_user_code.py @@ -28,8 +28,9 @@ sys.path.insert(0, abs_path_to_lib) # This import must happen after the sys.path is modified +from common.telemetry import telemetry_py + from adafruit_circuitplayground.express import cpx -from adafruit_circuitplayground.telemetry import telemetry_py from adafruit_circuitplayground.constants import CPX from microbit.__model.microbit_model import __mb as mb