diff --git a/package.json b/package.json index 753239860..fce1e449d 100644 --- a/package.json +++ b/package.json @@ -105,7 +105,7 @@ } }, { - "id": "badgeForegroundOverwrite", + "id": "badgeForegroundOverride", "description": "Color that fixes the issue with midnight blue ", "defaults": { "dark": "#FFFFFF", diff --git a/src/adafruit_circuitplayground/constants.py b/src/adafruit_circuitplayground/constants.py index 109b237b9..17c99e176 100644 --- a/src/adafruit_circuitplayground/constants.py +++ b/src/adafruit_circuitplayground/constants.py @@ -15,12 +15,24 @@ 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 DEFAULT_PORT = "5577" + EVENTS_BUTTON_PRESS = ['button_a', 'button_b', 'switch'] EVENTS_SENSOR_CHANGED = ['temperature', 'light', 'motion_x', 'motion_y', 'motion_z'] diff --git a/src/adafruit_circuitplayground/express.py b/src/adafruit_circuitplayground/express.py index b26d1ddf6..7b1f93bfa 100644 --- a/src/adafruit_circuitplayground/express.py +++ b/src/adafruit_circuitplayground/express.py @@ -9,6 +9,7 @@ from . import utils from . import constants as CONSTANTS from collections import namedtuple +from applicationinsights import TelemetryClient Acceleration = namedtuple('acceleration', ['x', 'y', 'z']) @@ -40,11 +41,18 @@ def __init__(self): 'motion_y': 0, 'motion_z': 0, 'touch': [False]*7, - 'detect_taps': 1, - 'tapped': False, 'shake': False, } - + 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, + } self.__debug_mode = False self.__abs_path_to_code_file = '' self.pixels = Pixel(self.__state, self.__debug_mode) @@ -63,6 +71,9 @@ def button_b(self): @property def detect_taps(self): + if(not self.telemetry_state["DETECT_TAPS"]): + utils.send_telemetry("DETECT_TAPS") + self.telemetry_state["DETECT_TAPS"] = True return self.__state['detect_taps'] @detect_taps.setter @@ -75,15 +86,23 @@ def detect_taps(self, value): def tapped(self): """ Not Implemented! """ - + if(not self.telemetry_state["TAPPED"]): + utils.send_telemetry("TAPPED") + self.telemetry_state["TAPPED"] = True raise NotImplementedError(CONSTANTS.NOT_IMPLEMENTED_ERROR) @property def red_led(self): + if(not self.telemetry_state["RED_LED"]): + utils.send_telemetry("RED_LED") + self.telemetry_state["RED_LED"] = True return self.__state['red_led'] @red_led.setter def red_led(self, value): + if(not self.telemetry_state["RED_LED"]): + utils.send_telemetry("RED_LED") + self.telemetry_state["RED_LED"] = True self.__state['red_led'] = bool(value) self.__show() @@ -137,13 +156,20 @@ def adjust_touch_threshold(self, adjustement): """Not implemented! The CPX Simulator doesn't use capacitive touch threshold. """ + if(not self.telemetry_state["ADJUST_THRESHOLD"]): + utils.send_telemetry("ADJUST_THRESHOLD") + self.telemetry_state["ADJUST_THRESHOLD"] = True + raise NotImplementedError( - "this method is not supported by the simulator") + CONSTANTS.NOT_IMPLEMENTED_ERROR) def shake(self, shake_threshold=30): return self.__state['shake'] def play_file(self, file_name): + if(not self.telemetry_state["PLAY_FILE"]): + utils.send_telemetry("PLAY_FILE") + self.telemetry_state["PLAY_FILE"] = True 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)) @@ -165,19 +191,27 @@ def play_file(self, file_name): def play_tone(self, frequency, duration): """ Not Implemented! """ + if(not self.telemetry_state["PLAY_TONE"]): + utils.send_telemetry("PLAY_TONE") + self.telemetry_state["PLAY_TONE"] = True raise NotImplementedError( CONSTANTS.NOT_IMPLEMENTED_ERROR) def start_tone(self, frequency): """ Not Implemented! """ + if(not self.telemetry_state["START_TONE"]): + utils.send_telemetry("START_TONE") + self.telemetry_state["START_TONE"] = True raise NotImplementedError( CONSTANTS.NOT_IMPLEMENTED_ERROR) def stop_tone(self): """ Not Implemented! """ - # Stop playing any tones. + if(not self.telemetry_state["STOP_TONE"]): + utils.send_telemetry("STOP_TONE") + self.telemetry_state["STOP_TONE"] = True raise NotImplementedError( CONSTANTS.NOT_IMPLEMENTED_ERROR) diff --git a/src/adafruit_circuitplayground/pixel.py b/src/adafruit_circuitplayground/pixel.py index f07e710cb..cc9044f9e 100644 --- a/src/adafruit_circuitplayground/pixel.py +++ b/src/adafruit_circuitplayground/pixel.py @@ -5,6 +5,8 @@ import sys from . import constants as CONSTANTS from . import utils +from applicationinsights import TelemetryClient +from . import constants as CONSTANTS class Pixel: @@ -12,6 +14,7 @@ def __init__(self, state, debug_mode=False): self.__state = state self.auto_write = True self.__debug_mode = debug_mode + self.telemetry_state = False def show(self): # Send the state to the extension so that React re-renders the Webview @@ -28,9 +31,15 @@ def __getitem__(self, index): if type(index) is not slice: if not self.__valid_index(index): raise IndexError(CONSTANTS.INDEX_ERROR) + if(not self.telemetry_state): + utils.send_telemetry("PIXELS") + self.telemetry_state = True return self.__state['pixels'][index] def __setitem__(self, index, val): + if(not self.telemetry_state): + utils.send_telemetry("PIXELS") + self.telemetry_state = True is_slice = False if type(index) is slice: is_slice = True @@ -80,7 +89,6 @@ def __extract_pixel_value(self, val, is_slice=False): if len(rgb_value) != 3 or any(not self.__valid_rgb_value(pix) for pix in rgb_value): raise ValueError(CONSTANTS.VALID_PIXEL_ASSIGN_ERROR) extracted_values.append(rgb_value) - return rgb_value if not is_slice else extracted_values def __hex_to_rgb(self, hexValue): @@ -90,7 +98,6 @@ def __hex_to_rgb(self, hexValue): hexToRgbValue[0] = int(hexColor[0:2], 16) # R hexToRgbValue[1] = int(hexColor[2:4], 16) # G hexToRgbValue[2] = int(hexColor[4:6], 16) # B - return tuple(hexToRgbValue) else: raise ValueError(CONSTANTS.PIXEL_RANGE_ERROR) diff --git a/src/adafruit_circuitplayground/utils.py b/src/adafruit_circuitplayground/utils.py index c03e6e15d..2eeadeae6 100644 --- a/src/adafruit_circuitplayground/utils.py +++ b/src/adafruit_circuitplayground/utils.py @@ -7,11 +7,16 @@ import time from . import constants as CONSTANTS from . import debugger_communication_client +from applicationinsights import TelemetryClient previous_state = {} +telemetry_client = TelemetryClient('__AIKEY__') +EXTENSION_NAME = '__EXTENSIONNAME__' + + def show(state, debug_mode=False): global previous_state if state != previous_state: @@ -28,3 +33,9 @@ def show(state, debug_mode=False): def remove_leading_slashes(string): string = string.lstrip('\\/') return string + + +def send_telemetry(event_name): + telemetry_client.track_event( + '{}/{}'.format(EXTENSION_NAME, CONSTANTS.TELEMETRY_EVENT_NAMES[event_name])) + telemetry_client.flush() diff --git a/src/constants.ts b/src/constants.ts index d74e64ba4..8bbd0ad89 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -244,6 +244,13 @@ export enum TelemetryEventName { SIMULATOR_BUTTON_AB = "SIMULATOR.BUTTON.AB", SIMULATOR_SWITCH = "SIMULATOR.SWITCH", + //Sensors + SIMULATOR_TEMPERATURE_SENSOR = "SIMULATOR.TEMPERATURE", + SIMULATOR_LIGHT_SENSOR = " SIMULATOR.LIGHT", + SIMULATOR_MOTION_SENSOR = "SIMULATOR.MOTION", + SIMULATOR_SHAKE = "SIMULATOR.SHAKE", + SIMULATOR_CAPACITIVE_TOUCH = "SIMULATOR.CAPACITIVE.TOUCH", + // Pop-up dialog CLICK_DIALOG_DONT_SHOW = "CLICK.DIALOG.DONT.SHOW", CLICK_DIALOG_EXAMPLE_CODE = "CLICK.DIALOG.EXAMPLE.CODE", @@ -267,7 +274,8 @@ export enum WebviewMessages { BUTTON_PRESS = "button-press", PLAY_SIMULATOR = "play-simulator", SENSOR_CHANGED = "sensor-changed", - REFRESH_SIMULATOR = "refresh-simulator" + REFRESH_SIMULATOR = "refresh-simulator", + SLIDER_TELEMETRY = "slider-telemetry" } // tslint:disable-next-line: no-namespace diff --git a/src/extension.ts b/src/extension.ts index 1425348ab..23aa5b314 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -131,6 +131,7 @@ export async function activate(context: vscode.ExtensionContext) { } break; case WebviewMessages.SENSOR_CHANGED: + checkForTelemetry(message.text); console.log(`Sensor changed ${messageJson} \n`); if (inDebugMode && debuggerCommunicationHandler) { debuggerCommunicationHandler.emitSensorChanged(messageJson); @@ -142,6 +143,9 @@ export async function activate(context: vscode.ExtensionContext) { console.log("Refresh button"); runSimulatorCommand(); break; + case WebviewMessages.SLIDER_TELEMETRY: + handleSensorTelemetry(message.text); + break; default: vscode.window.showInformationMessage( CONSTANTS.ERROR.UNEXPECTED_MESSAGE @@ -743,6 +747,45 @@ const handleButtonPressTelemetry = (buttonState: any) => { } }; +const handleSensorTelemetry = (sensor: string) => { + switch (sensor) { + case "temperature": + telemetryAI.trackFeatureUsage( + TelemetryEventName.SIMULATOR_TEMPERATURE_SENSOR + ); + break; + case "light": + telemetryAI.trackFeatureUsage(TelemetryEventName.SIMULATOR_LIGHT_SENSOR); + break; + case "motion_x": + telemetryAI.trackFeatureUsage(TelemetryEventName.SIMULATOR_MOTION_SENSOR); + break; + case "motion_y": + telemetryAI.trackFeatureUsage(TelemetryEventName.SIMULATOR_MOTION_SENSOR); + break; + case "motion_z": + telemetryAI.trackFeatureUsage(TelemetryEventName.SIMULATOR_MOTION_SENSOR); + break; + case "shake": + telemetryAI.trackFeatureUsage(TelemetryEventName.SIMULATOR_SHAKE); + break; + case "touch": + telemetryAI.trackFeatureUsage( + TelemetryEventName.SIMULATOR_CAPACITIVE_TOUCH + ); + break; + } +}; + +const checkForTelemetry = (sensorState: any) => { + if (sensorState["shake"]) { + console.log(`telemtry sending`); + handleSensorTelemetry("shake"); + } else if (sensorState["touch"]) { + handleSensorTelemetry("touch"); + } +}; + const updatePythonExtraPaths = () => { const pathToLib: string = __dirname; const currentExtraPaths: string[] = @@ -780,6 +823,7 @@ function getWebviewContent(context: vscode.ExtensionContext) { `; } +// this method is called when your extension is deactivated export async function deactivate() { const monitor: SerialMonitor = SerialMonitor.getInstance(); await monitor.closeSerialMonitor(null, false); diff --git a/src/process_user_code.py b/src/process_user_code.py index 3d1aa8faa..28bf37482 100644 --- a/src/process_user_code.py +++ b/src/process_user_code.py @@ -32,7 +32,6 @@ def __init__(self): threading.Thread.__init__(self) def run(self): - from adafruit_circuitplayground.express import cpx while True: read_val = sys.stdin.readline() sys.stdin.flush() diff --git a/src/python_constants.py b/src/python_constants.py index cfbf17730..2a1020278 100644 --- a/src/python_constants.py +++ b/src/python_constants.py @@ -13,7 +13,8 @@ "shake", "motion_x", "motion_y", - "motion_z" + "motion_z", + "touch" ] EXEC_COMMAND = "exec" diff --git a/src/requirements.txt b/src/requirements.txt index 2fc979b74..996dc964d 100644 --- a/src/requirements.txt +++ b/src/requirements.txt @@ -1,3 +1,4 @@ playsound pytest +applicationinsights python-socketio diff --git a/src/telemetry/telemetryAI.ts b/src/telemetry/telemetryAI.ts index 19702828c..addcabffa 100644 --- a/src/telemetry/telemetryAI.ts +++ b/src/telemetry/telemetryAI.ts @@ -4,52 +4,78 @@ import getPackageInfo from "./getPackageInfo"; // tslint:disable-next-line:export-name export default class TelemetryAI { - private static telemetryReporter: TelemetryReporter; - private static enableTelemetry: boolean | undefined; - - constructor(vscodeContext: vscode.ExtensionContext) { - TelemetryAI.telemetryReporter = this.createTelemetryReporter(vscodeContext); - TelemetryAI.enableTelemetry = vscode.workspace.getConfiguration().get("telemetry.enableTelemetry"); - if (TelemetryAI.enableTelemetry === undefined) { - TelemetryAI.enableTelemetry = true; - } - } + private static telemetryReporter: TelemetryReporter; + private static enableTelemetry: boolean | undefined; - public getExtensionName(context: vscode.ExtensionContext): string { - const { extensionName } = getPackageInfo(context); - return extensionName; + constructor(vscodeContext: vscode.ExtensionContext) { + TelemetryAI.telemetryReporter = this.createTelemetryReporter(vscodeContext); + TelemetryAI.enableTelemetry = vscode.workspace + .getConfiguration() + .get("telemetry.enableTelemetry"); + if (TelemetryAI.enableTelemetry === undefined) { + TelemetryAI.enableTelemetry = true; } + } - public getExtensionVersionNumber(context: vscode.ExtensionContext): string { - const { extensionVersion } = getPackageInfo(context); - return extensionVersion; - } + public getExtensionName(context: vscode.ExtensionContext): string { + const { extensionName } = getPackageInfo(context); + return extensionName; + } - public sendTelemetryIfEnabled(eventName: string, properties?: { [key: string]: string }, measurements?: { [key: string]: number }) { - if (TelemetryAI.enableTelemetry) { - TelemetryAI.telemetryReporter.sendTelemetryEvent(eventName, properties, measurements); - } - } + public getExtensionVersionNumber(context: vscode.ExtensionContext): string { + const { extensionVersion } = getPackageInfo(context); + return extensionVersion; + } - public trackFeatureUsage(eventName: string, eventProperties?: { [key: string]: string }) { - this.sendTelemetryIfEnabled(eventName, eventProperties) + public sendTelemetryIfEnabled( + eventName: string, + properties?: { [key: string]: string }, + measurements?: { [key: string]: number } + ) { + if (TelemetryAI.enableTelemetry) { + TelemetryAI.telemetryReporter.sendTelemetryEvent( + eventName, + properties, + measurements + ); } + } - public runWithLatencyMeasure(functionToRun: () => void, eventName: string): void { - const numberOfNanosecondsInSecond: number = 1000000000; - const startTime: number = Number(process.hrtime.bigint()); - functionToRun(); - const latency: number = Number(process.hrtime.bigint()) - startTime; - const measurement = { - duration: latency / numberOfNanosecondsInSecond - } - this.sendTelemetryIfEnabled(eventName, {}, measurement); - } + public trackFeatureUsage( + eventName: string, + eventProperties?: { [key: string]: string } + ) { + this.sendTelemetryIfEnabled(eventName, eventProperties); + } - private createTelemetryReporter(context: vscode.ExtensionContext): TelemetryReporter { - const { extensionName, extensionVersion, instrumentationKey } = getPackageInfo(context); - const reporter: TelemetryReporter = new TelemetryReporter(extensionName, extensionVersion, instrumentationKey); - context.subscriptions.push(reporter); - return reporter; - } -} \ No newline at end of file + public runWithLatencyMeasure( + functionToRun: () => void, + eventName: string + ): void { + const numberOfNanosecondsInSecond: number = 1000000000; + const startTime: number = Number(process.hrtime.bigint()); + functionToRun(); + const latency: number = Number(process.hrtime.bigint()) - startTime; + const measurement = { + duration: latency / numberOfNanosecondsInSecond + }; + this.sendTelemetryIfEnabled(eventName, {}, measurement); + } + + private createTelemetryReporter( + context: vscode.ExtensionContext + ): TelemetryReporter { + const { + extensionName, + extensionVersion, + instrumentationKey + } = getPackageInfo(context); + const reporter: TelemetryReporter = new TelemetryReporter( + extensionName, + extensionVersion, + instrumentationKey + ); + context.subscriptions.push(reporter); + return reporter; + } +} diff --git a/src/view/components/Simulator.tsx b/src/view/components/Simulator.tsx index 2c1df1245..e9fa08165 100644 --- a/src/view/components/Simulator.tsx +++ b/src/view/components/Simulator.tsx @@ -20,6 +20,7 @@ interface ICpxState { button_b: boolean; switch: boolean; touch: boolean[]; + shake: boolean; } interface IState { @@ -48,7 +49,8 @@ const DEFAULT_CPX_STATE: ICpxState = { ], red_led: false, switch: false, - touch: [false, false, false, false, false, false, false] + touch: [false, false, false, false, false, false, false], + shake: false }; const SIMULATOR_BUTTON_WIDTH = 60; diff --git a/src/view/components/toolbar/InputSlider.tsx b/src/view/components/toolbar/InputSlider.tsx index 955a7f487..cee2a278b 100644 --- a/src/view/components/toolbar/InputSlider.tsx +++ b/src/view/components/toolbar/InputSlider.tsx @@ -78,6 +78,8 @@ class InputSlider extends React.Component { min={this.props.minValue} max={this.props.maxValue} onChange={this.handleOnChange} + onKeyUp={this.sendTelemetry} + onMouseUp={this.sendTelemetry} aria-valuenow={this.state.value} value={this.state.value} aria-label={`${this.props.type} sensor slider`} @@ -120,6 +122,10 @@ class InputSlider extends React.Component { return newValue; }; + private sendTelemetry = () => { + vscode.postMessage({ command: "slider-telemetry", text: this.props.type }); + }; + private validateRange = (valueString: string) => { let valueInt = parseInt(valueString, 10); if (valueInt < this.props.minValue) { diff --git a/src/view/components/toolbar/SensorButton.tsx b/src/view/components/toolbar/SensorButton.tsx index 708e7e35d..8726cf1a4 100644 --- a/src/view/components/toolbar/SensorButton.tsx +++ b/src/view/components/toolbar/SensorButton.tsx @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + import * as React from "react"; import { ISensorButtonProps } from "../../viewUtils"; import "../../styles/SensorButton.css"; diff --git a/src/view/styles/Button.css b/src/view/styles/Button.css index 3c38c6cee..5f19a0e74 100644 --- a/src/view/styles/Button.css +++ b/src/view/styles/Button.css @@ -5,7 +5,7 @@ } .button-icon { - fill: var(--vscode-badgeForegroundOverwrite); + fill: var(--vscode-badgeForegroundOverride); } .play-button { diff --git a/src/view/styles/InputSlider.css b/src/view/styles/InputSlider.css index 20298ae85..8bd87a83c 100644 --- a/src/view/styles/InputSlider.css +++ b/src/view/styles/InputSlider.css @@ -17,7 +17,7 @@ margin-top: auto; margin-bottom: auto; margin-left: 5px; - color: var(--badgeForegroundOverwrite); + color: var(--badgeForegroundOverride); border-radius: 2px; font-size: 16px; font-weight: bold; diff --git a/src/view/styles/SensorButton.css b/src/view/styles/SensorButton.css index 962eb425b..df85bcef2 100644 --- a/src/view/styles/SensorButton.css +++ b/src/view/styles/SensorButton.css @@ -1,5 +1,5 @@ .sensor-button { - color: var(--vscode-badgeForegroundOverwrite); + color: var(--vscode-badgeForegroundOverride); text-align: center; background-color: var(--vscode-button-background); width: 320px; diff --git a/src/view/styles/ToolBar.css b/src/view/styles/ToolBar.css index a279c8ad1..0e15109a4 100644 --- a/src/view/styles/ToolBar.css +++ b/src/view/styles/ToolBar.css @@ -40,7 +40,7 @@ -webkit-appearance: none; font-size: 16px; font-weight: bolder; - color: var(--vscode-badgeForegroundOverwrite); + color: var(--vscode-badgeForegroundOverride); text-align: left; margin-right: 40px; position: absolute;