diff --git a/src/adafruit_circuitplayground/express.py b/src/adafruit_circuitplayground/express.py index 85e38040a..9ceff5131 100644 --- a/src/adafruit_circuitplayground/express.py +++ b/src/adafruit_circuitplayground/express.py @@ -10,6 +10,7 @@ from . import constants as CONSTANTS from collections import namedtuple from applicationinsights import TelemetryClient +from .telemetry import telemetry_py Acceleration = namedtuple('acceleration', ['x', 'y', 'z']) @@ -43,16 +44,6 @@ def __init__(self): 'touch': [False]*7, '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) @@ -71,9 +62,7 @@ def button_b(self): @property def detect_taps(self): - if(utils.telemetry_available() and not self.telemetry_state["DETECT_TAPS"]): - utils.send_telemetry("DETECT_TAPS") - self.telemetry_state["DETECT_TAPS"] = True + telemetry_py.send_telemetry("DETECT_TAPS") return self.__state['detect_taps'] @detect_taps.setter @@ -86,23 +75,17 @@ def detect_taps(self, value): def tapped(self): """ Not Implemented! """ - if(utils.telemetry_available() and not self.telemetry_state["TAPPED"]): - utils.send_telemetry("TAPPED") - self.telemetry_state["TAPPED"] = True + telemetry_py.send_telemetry("TAPPED") raise NotImplementedError(CONSTANTS.NOT_IMPLEMENTED_ERROR) @property def red_led(self): - if(utils.telemetry_available() and not self.telemetry_state["RED_LED"]): - utils.send_telemetry("RED_LED") - self.telemetry_state["RED_LED"] = True + telemetry_py.send_telemetry("RED_LED") return self.__state['red_led'] @red_led.setter def red_led(self, value): - if(utils.telemetry_available() and not self.telemetry_state["RED_LED"]): - utils.send_telemetry("RED_LED") - self.telemetry_state["RED_LED"] = True + telemetry_py.send_telemetry("RED_LED") self.__state['red_led'] = bool(value) self.__show() @@ -156,10 +139,7 @@ def adjust_touch_threshold(self, adjustement): """Not implemented! The Pacifica Simulator doesn't use capacitive touch threshold. """ - if(utils.telemetry_available() and not self.telemetry_state["ADJUST_THRESHOLD"]): - utils.send_telemetry("ADJUST_THRESHOLD") - self.telemetry_state["ADJUST_THRESHOLD"] = True - + telemetry_py.send_telemetry("ADJUST_THRESHOLD") raise NotImplementedError( CONSTANTS.NOT_IMPLEMENTED_ERROR) @@ -167,9 +147,7 @@ def shake(self, shake_threshold=30): return self.__state['shake'] def play_file(self, file_name): - if(utils.telemetry_available() and not self.telemetry_state["PLAY_FILE"]): - utils.send_telemetry("PLAY_FILE") - self.telemetry_state["PLAY_FILE"] = True + telemetry_py.send_telemetry("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)) @@ -192,27 +170,21 @@ def play_file(self, file_name): def play_tone(self, frequency, duration): """ Not Implemented! """ - if(utils.telemetry_available() and not self.telemetry_state["PLAY_TONE"]): - utils.send_telemetry("PLAY_TONE") - self.telemetry_state["PLAY_TONE"] = True + telemetry_py.send_telemetry("PLAY_TONE") raise NotImplementedError( CONSTANTS.NOT_IMPLEMENTED_ERROR) def start_tone(self, frequency): """ Not Implemented! """ - if(utils.telemetry_available() and not self.telemetry_state["START_TONE"]): - utils.send_telemetry("START_TONE") - self.telemetry_state["START_TONE"] = True + telemetry_py.send_telemetry("START_TONE") raise NotImplementedError( CONSTANTS.NOT_IMPLEMENTED_ERROR) def stop_tone(self): """ Not Implemented! """ - if(utils.telemetry_available() and not self.telemetry_state["STOP_TONE"]): - utils.send_telemetry("STOP_TONE") - self.telemetry_state["STOP_TONE"] = True + telemetry_py.send_telemetry("STOP_TONE") raise NotImplementedError( CONSTANTS.NOT_IMPLEMENTED_ERROR) diff --git a/src/adafruit_circuitplayground/pixel.py b/src/adafruit_circuitplayground/pixel.py index aee29e2b2..530dbd48b 100644 --- a/src/adafruit_circuitplayground/pixel.py +++ b/src/adafruit_circuitplayground/pixel.py @@ -7,6 +7,7 @@ from . import utils from applicationinsights import TelemetryClient from . import constants as CONSTANTS +from .telemetry import telemetry_py class Pixel: @@ -31,15 +32,11 @@ def __getitem__(self, index): if type(index) is not slice: if not self.__valid_index(index): raise IndexError(CONSTANTS.INDEX_ERROR) - if(utils.telemetry_available() and not self.telemetry_state): - utils.send_telemetry("PIXELS") - self.telemetry_state = True + telemetry_py.send_telemetry("PIXELS") return self.__state['pixels'][index] def __setitem__(self, index, val): - if(utils.telemetry_available() and not self.telemetry_state): - utils.send_telemetry("PIXELS") - self.telemetry_state = True + telemetry_py.send_telemetry("PIXELS") is_slice = False if type(index) is slice: is_slice = True diff --git a/src/adafruit_circuitplayground/telemetry.py b/src/adafruit_circuitplayground/telemetry.py new file mode 100644 index 000000000..98156469c --- /dev/null +++ b/src/adafruit_circuitplayground/telemetry.py @@ -0,0 +1,34 @@ +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/adafruit_circuitplayground/utils.py b/src/adafruit_circuitplayground/utils.py index 6fded1127..83a595d3b 100644 --- a/src/adafruit_circuitplayground/utils.py +++ b/src/adafruit_circuitplayground/utils.py @@ -10,8 +10,6 @@ from applicationinsights import TelemetryClient previous_state = {} -telemetry_client = TelemetryClient('__AIKEY__') -EXTENSION_NAME = '__EXTENSIONNAME__' def show(state, debug_mode=False): @@ -35,14 +33,4 @@ def remove_leading_slashes(string): def escape_if_OSX(file_name): if sys.platform.startswith(CONSTANTS.MAC_OS): file_name = file_name.replace(" ", "%20") - return file_name - - -def send_telemetry(event_name): - telemetry_client.track_event( - '{}/{}'.format(EXTENSION_NAME, CONSTANTS.TELEMETRY_EVENT_NAMES[event_name])) - telemetry_client.flush() - - -def telemetry_available(): - return telemetry_client.context.instrumentation_key != '__AIKEY__' + return file_name \ No newline at end of file diff --git a/src/extension.ts b/src/extension.ts index 5baf6bcd8..ca699c300 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -413,7 +413,8 @@ export async function activate(context: vscode.ExtensionContext) { childProcess = cp.spawn(pythonExecutableName, [ utils.getPathToScript(context, "out", "process_user_code.py"), - currentFileAbsPath + currentFileAbsPath, + JSON.stringify({ enable_telemetry: utils.getTelemetryState() }) ]); let dataFromTheProcess = ""; @@ -446,7 +447,7 @@ export async function activate(context: vscode.ExtensionContext) { case "print": console.log( `Process print statement output = ${ - messageToWebview.data + messageToWebview.data }` ); utils.logToOutputChannel( @@ -648,10 +649,9 @@ export async function activate(context: vscode.ExtensionContext) { "pacifica.selectSerialPort", () => { if (serialMonitor) { - telemetryAI.runWithLatencyMeasure( - () => { serialMonitor.selectSerialPort(null, null); }, - TelemetryEventName.COMMAND_SERIAL_MONITOR_CHOOSE_PORT - ); + telemetryAI.runWithLatencyMeasure(() => { + serialMonitor.selectSerialPort(null, null); + }, TelemetryEventName.COMMAND_SERIAL_MONITOR_CHOOSE_PORT); } else { vscode.window.showErrorMessage(CONSTANTS.ERROR.NO_FOLDER_OPENED); console.info("Serial monitor is not defined."); @@ -693,10 +693,9 @@ export async function activate(context: vscode.ExtensionContext) { "pacifica.closeSerialMonitor", (port, showWarning = true) => { if (serialMonitor) { - telemetryAI.runWithLatencyMeasure( - () => { serialMonitor.closeSerialMonitor(port, showWarning); }, - TelemetryEventName.COMMAND_SERIAL_MONITOR_CLOSE - ) + telemetryAI.runWithLatencyMeasure(() => { + serialMonitor.closeSerialMonitor(port, showWarning); + }, TelemetryEventName.COMMAND_SERIAL_MONITOR_CLOSE); } else { vscode.window.showErrorMessage(CONSTANTS.ERROR.NO_FOLDER_OPENED); console.info("Serial monitor is not defined."); @@ -874,7 +873,6 @@ const handleSensorTelemetry = (sensor: string) => { const checkForTelemetry = (sensorState: any) => { if (sensorState["shake"]) { - console.log(`telemtry sending`); handleSensorTelemetry("shake"); } else if (sensorState["touch"]) { handleSensorTelemetry("touch"); diff --git a/src/extension_utils/utils.ts b/src/extension_utils/utils.ts index 1d156745d..546bb8538 100644 --- a/src/extension_utils/utils.ts +++ b/src/extension_utils/utils.ts @@ -41,11 +41,12 @@ export const validCodeFileName = (filePath: string) => { }; export const showPrivacyModal = (okAction: () => void) => { - vscode.window.showInformationMessage( - `${CONSTANTS.INFO.THIRD_PARTY_WEBSITE}: ${CONSTANTS.LINKS.PRIVACY}`, - DialogResponses.AGREE_AND_PROCEED, - DialogResponses.CANCEL, - ) + vscode.window + .showInformationMessage( + `${CONSTANTS.INFO.THIRD_PARTY_WEBSITE}: ${CONSTANTS.LINKS.PRIVACY}`, + DialogResponses.AGREE_AND_PROCEED, + DialogResponses.CANCEL + ) .then((privacySelection: vscode.MessageItem | undefined) => { if (privacySelection === DialogResponses.AGREE_AND_PROCEED) { okAction(); @@ -74,10 +75,10 @@ export function tryParseJSON(jsonString: string): any | boolean { if (jsonObj && typeof jsonObj === "object") { return jsonObj; } - } catch (exception) { } + } catch (exception) {} return false; -}; +} export function fileExistsSync(filePath: string): boolean { try { @@ -85,7 +86,7 @@ export function fileExistsSync(filePath: string): boolean { } catch (error) { return false; } -}; +} export function mkdirRecursivelySync(dirPath: string): void { if (directoryExistsSync(dirPath)) { @@ -100,7 +101,7 @@ export function mkdirRecursivelySync(dirPath: string): void { mkdirRecursivelySync(dirname); fs.mkdirSync(dirPath); } -}; +} export function directoryExistsSync(dirPath: string): boolean { try { @@ -108,7 +109,7 @@ export function directoryExistsSync(dirPath: string): boolean { } catch (e) { return false; } -}; +} /** * This method pads the current string with another string (repeated, if needed) @@ -139,11 +140,11 @@ export function padStart( } else { return (sourceString as any).padStart(targetLength, padString); } -}; +} export function convertToHex(num: number, width = 0): string { return padStart(num.toString(16), width, "0"); -}; +} export function generateCPXConfig(): void { const deviceContext: DeviceContext = DeviceContext.getInstance(); @@ -156,7 +157,7 @@ export function generateCPXConfig(): void { ); mkdirRecursivelySync(path.dirname(cpxConfigFilePath)); fs.writeFileSync(cpxConfigFilePath, JSON.stringify(cpxJson, null, 4)); -}; +} export const checkPythonDependency = async () => { const dependencyChecker: DependencyChecker = new DependencyChecker(); const result = await dependencyChecker.checkDependency( @@ -171,7 +172,7 @@ export const checkPipDependency = async () => { CONSTANTS.DEPENDENCY_CHECKER.PIP3 ); return result.payload; -} +}; export const setPythonExectuableName = async () => { // Find our what command is the PATH for python @@ -180,8 +181,11 @@ export const setPythonExectuableName = async () => { if (dependencyCheck.installed) { executableName = dependencyCheck.dependency; } else { - vscode.window.showErrorMessage(CONSTANTS.ERROR.NO_PYTHON_PATH, - DialogResponses.INSTALL_PYTHON) + vscode.window + .showErrorMessage( + CONSTANTS.ERROR.NO_PYTHON_PATH, + DialogResponses.INSTALL_PYTHON + ) .then((selection: vscode.MessageItem | undefined) => { if (selection === DialogResponses.INSTALL_PYTHON) { const okAction = () => { @@ -195,35 +199,52 @@ export const setPythonExectuableName = async () => { return executableName; }; -export const addVisibleTextEditorCallback = (currentPanel: vscode.WebviewPanel, context: vscode.ExtensionContext): vscode.Disposable => { - const initialPythonEditors = filterForPythonFiles(vscode.window.visibleTextEditors); +export const addVisibleTextEditorCallback = ( + currentPanel: vscode.WebviewPanel, + context: vscode.ExtensionContext +): vscode.Disposable => { + const initialPythonEditors = filterForPythonFiles( + vscode.window.visibleTextEditors + ); currentPanel.webview.postMessage({ command: "visible-editors", state: { activePythonEditors: initialPythonEditors } }); - return vscode.window.onDidChangeVisibleTextEditors((textEditors: vscode.TextEditor[]) => { - const activePythonEditors = filterForPythonFiles(textEditors); - currentPanel.webview.postMessage({ - command: "visible-editors", - state: { activePythonEditors } - }); - }, {}, context.subscriptions) + return vscode.window.onDidChangeVisibleTextEditors( + (textEditors: vscode.TextEditor[]) => { + const activePythonEditors = filterForPythonFiles(textEditors); + currentPanel.webview.postMessage({ + command: "visible-editors", + state: { activePythonEditors } + }); + }, + {}, + context.subscriptions + ); }; export const filterForPythonFiles = (textEditors: vscode.TextEditor[]) => { - return textEditors.filter( - editor => editor.document.languageId === "python" - ).map(editor => editor.document.fileName); + return textEditors + .filter(editor => editor.document.languageId === "python") + .map(editor => editor.document.fileName); }; -export const getActiveEditorFromPath = (filePath: string): vscode.TextDocument => { - const activeEditor = vscode.window.visibleTextEditors.find((editor: vscode.TextEditor) => editor.document.fileName === filePath); +export const getActiveEditorFromPath = ( + filePath: string +): vscode.TextDocument => { + const activeEditor = vscode.window.visibleTextEditors.find( + (editor: vscode.TextEditor) => editor.document.fileName === filePath + ); return activeEditor ? activeEditor.document : undefined; }; export const getServerPortConfig = (): number => { // tslint:disable: no-backbone-get-set-outside-model prefer-type-cast - if (vscode.workspace.getConfiguration().has(SERVER_INFO.SERVER_PORT_CONFIGURATION)) { + if ( + vscode.workspace + .getConfiguration() + .has(SERVER_INFO.SERVER_PORT_CONFIGURATION) + ) { return vscode.workspace .getConfiguration() .get(SERVER_INFO.SERVER_PORT_CONFIGURATION) as number; @@ -233,7 +254,7 @@ export const getServerPortConfig = (): number => { export const checkConfig = (configName: string): boolean => { return vscode.workspace.getConfiguration().get(configName) === true; -} +}; export const checkPythonDependencies = async (context: vscode.ExtensionContext, pythonExecutable: string) => { let hasInstalledDependencies: boolean = false; @@ -241,15 +262,16 @@ export const checkPythonDependencies = async (context: vscode.ExtensionContext, if (checkConfig(CONFIG.SHOW_DEPENDENCY_INSTALL)) { hasInstalledDependencies = await promptInstallPythonDependencies(context, pythonExecutable); if (hasInstalledDependencies) { - await vscode.workspace.getConfiguration().update(CONFIG.SHOW_DEPENDENCY_INSTALL, false); + await vscode.workspace + .getConfiguration() + .update(CONFIG.SHOW_DEPENDENCY_INSTALL, false); } } } else { hasInstalledDependencies = false; } return hasInstalledDependencies; -} - +}; export const promptInstallPythonDependencies = (context: vscode.ExtensionContext, pythonExecutable: string) => { return vscode.window.showInformationMessage( @@ -273,7 +295,12 @@ export const promptInstallPythonDependencies = (context: vscode.ExtensionContext }) } }); -} +}; +export const getTelemetryState = () => { + return vscode.workspace + .getConfiguration() + .get("telemetry.enableTelemetry", true); +}; export const installPythonDependencies = async (context: vscode.ExtensionContext, pythonExecutable: string) => { let installed: boolean = false; diff --git a/src/process_user_code.py b/src/process_user_code.py index 7c1a50cbf..ad2fcac7b 100644 --- a/src/process_user_code.py +++ b/src/process_user_code.py @@ -31,6 +31,8 @@ # This import must happen after the sys.path is modified from adafruit_circuitplayground.express import cpx +from adafruit_circuitplayground.telemetry import telemetry_py + # Handle User Inputs Thread @@ -98,6 +100,9 @@ def execute_user_code(abs_path_to_code_file): user_code = threading.Thread(args=(sys.argv[1],), target=execute_user_code) +telemetry_state = json.loads(sys.argv[2]) +telemetry_py._Telemetry__enable_telemetry = telemetry_state.get( + CONSTANTS.ENABLE_TELEMETRY, True) threads.append(user_code) user_code.start() diff --git a/src/python_constants.py b/src/python_constants.py index 68d6c7788..25837377b 100644 --- a/src/python_constants.py +++ b/src/python_constants.py @@ -3,7 +3,7 @@ CPX_DRIVE_NAME = "CIRCUITPY" - +ENABLE_TELEMETRY = 'enable_telemetry' EXPECTED_INPUT_EVENTS = [ "button_a", "button_b", diff --git a/src/view/components/toolbar/InputSlider.tsx b/src/view/components/toolbar/InputSlider.tsx index cee2a278b..56eaba400 100644 --- a/src/view/components/toolbar/InputSlider.tsx +++ b/src/view/components/toolbar/InputSlider.tsx @@ -96,7 +96,6 @@ class InputSlider extends React.Component { private handleOnChange = (event: any) => { const validatedValue = this.validateRange(this.updateValue(event)); - const newSensorState = this.writeMessage(validatedValue); if (newSensorState) { sendMessage(newSensorState); @@ -109,7 +108,7 @@ class InputSlider extends React.Component { value = parseInt(this.state.value, 10); } - return this.props.type && this.state.value + return this.props.type && this.state.value !== undefined ? { [this.props.type]: value } : undefined; };