{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# 12. Requesting and receiving data from Arduino\n", "\n", "
" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "
\n", " \n", " Loading BokehJS ...\n", "
" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "application/javascript": [ "\n", "(function(root) {\n", " function now() {\n", " return new Date();\n", " }\n", "\n", " var force = true;\n", "\n", " if (typeof root._bokeh_onload_callbacks === \"undefined\" || force === true) {\n", " root._bokeh_onload_callbacks = [];\n", " root._bokeh_is_loading = undefined;\n", " }\n", "\n", " var JS_MIME_TYPE = 'application/javascript';\n", " var HTML_MIME_TYPE = 'text/html';\n", " var EXEC_MIME_TYPE = 'application/vnd.bokehjs_exec.v0+json';\n", " var CLASS_NAME = 'output_bokeh rendered_html';\n", "\n", " /**\n", " * Render data to the DOM node\n", " */\n", " function render(props, node) {\n", " var script = document.createElement(\"script\");\n", " node.appendChild(script);\n", " }\n", "\n", " /**\n", " * Handle when an output is cleared or removed\n", " */\n", " function handleClearOutput(event, handle) {\n", " var cell = handle.cell;\n", "\n", " var id = cell.output_area._bokeh_element_id;\n", " var server_id = cell.output_area._bokeh_server_id;\n", " // Clean up Bokeh references\n", " if (id != null && id in Bokeh.index) {\n", " Bokeh.index[id].model.document.clear();\n", " delete Bokeh.index[id];\n", " }\n", "\n", " if (server_id !== undefined) {\n", " // Clean up Bokeh references\n", " var cmd = \"from bokeh.io.state import curstate; print(curstate().uuid_to_server['\" + server_id + \"'].get_sessions()[0].document.roots[0]._id)\";\n", " cell.notebook.kernel.execute(cmd, {\n", " iopub: {\n", " output: function(msg) {\n", " var id = msg.content.text.trim();\n", " if (id in Bokeh.index) {\n", " Bokeh.index[id].model.document.clear();\n", " delete Bokeh.index[id];\n", " }\n", " }\n", " }\n", " });\n", " // Destroy server and session\n", " var cmd = \"import bokeh.io.notebook as ion; ion.destroy_server('\" + server_id + \"')\";\n", " cell.notebook.kernel.execute(cmd);\n", " }\n", " }\n", "\n", " /**\n", " * Handle when a new output is added\n", " */\n", " function handleAddOutput(event, handle) {\n", " var output_area = handle.output_area;\n", " var output = handle.output;\n", "\n", " // limit handleAddOutput to display_data with EXEC_MIME_TYPE content only\n", " if ((output.output_type != \"display_data\") || (!output.data.hasOwnProperty(EXEC_MIME_TYPE))) {\n", " return\n", " }\n", "\n", " var toinsert = output_area.element.find(\".\" + CLASS_NAME.split(' ')[0]);\n", "\n", " if (output.metadata[EXEC_MIME_TYPE][\"id\"] !== undefined) {\n", " toinsert[toinsert.length - 1].firstChild.textContent = output.data[JS_MIME_TYPE];\n", " // store reference to embed id on output_area\n", " output_area._bokeh_element_id = output.metadata[EXEC_MIME_TYPE][\"id\"];\n", " }\n", " if (output.metadata[EXEC_MIME_TYPE][\"server_id\"] !== undefined) {\n", " var bk_div = document.createElement(\"div\");\n", " bk_div.innerHTML = output.data[HTML_MIME_TYPE];\n", " var script_attrs = bk_div.children[0].attributes;\n", " for (var i = 0; i < script_attrs.length; i++) {\n", " toinsert[toinsert.length - 1].firstChild.setAttribute(script_attrs[i].name, script_attrs[i].value);\n", " toinsert[toinsert.length - 1].firstChild.textContent = bk_div.children[0].textContent\n", " }\n", " // store reference to server id on output_area\n", " output_area._bokeh_server_id = output.metadata[EXEC_MIME_TYPE][\"server_id\"];\n", " }\n", " }\n", "\n", " function register_renderer(events, OutputArea) {\n", "\n", " function append_mime(data, metadata, element) {\n", " // create a DOM node to render to\n", " var toinsert = this.create_output_subarea(\n", " metadata,\n", " CLASS_NAME,\n", " EXEC_MIME_TYPE\n", " );\n", " this.keyboard_manager.register_events(toinsert);\n", " // Render to node\n", " var props = {data: data, metadata: metadata[EXEC_MIME_TYPE]};\n", " render(props, toinsert[toinsert.length - 1]);\n", " element.append(toinsert);\n", " return toinsert\n", " }\n", "\n", " /* Handle when an output is cleared or removed */\n", " events.on('clear_output.CodeCell', handleClearOutput);\n", " events.on('delete.Cell', handleClearOutput);\n", "\n", " /* Handle when a new output is added */\n", " events.on('output_added.OutputArea', handleAddOutput);\n", "\n", " /**\n", " * Register the mime type and append_mime function with output_area\n", " */\n", " OutputArea.prototype.register_mime_type(EXEC_MIME_TYPE, append_mime, {\n", " /* Is output safe? */\n", " safe: true,\n", " /* Index of renderer in `output_area.display_order` */\n", " index: 0\n", " });\n", " }\n", "\n", " // register the mime type if in Jupyter Notebook environment and previously unregistered\n", " if (root.Jupyter !== undefined) {\n", " var events = require('base/js/events');\n", " var OutputArea = require('notebook/js/outputarea').OutputArea;\n", "\n", " if (OutputArea.prototype.mime_types().indexOf(EXEC_MIME_TYPE) == -1) {\n", " register_renderer(events, OutputArea);\n", " }\n", " }\n", "\n", " \n", " if (typeof (root._bokeh_timeout) === \"undefined\" || force === true) {\n", " root._bokeh_timeout = Date.now() + 5000;\n", " root._bokeh_failed_load = false;\n", " }\n", "\n", " var NB_LOAD_WARNING = {'data': {'text/html':\n", " \"
\\n\"+\n", " \"

\\n\"+\n", " \"BokehJS does not appear to have successfully loaded. If loading BokehJS from CDN, this \\n\"+\n", " \"may be due to a slow or bad network connection. Possible fixes:\\n\"+\n", " \"

\\n\"+\n", " \"\\n\"+\n", " \"\\n\"+\n", " \"from bokeh.resources import INLINE\\n\"+\n", " \"output_notebook(resources=INLINE)\\n\"+\n", " \"\\n\"+\n", " \"
\"}};\n", "\n", " function display_loaded() {\n", " var el = document.getElementById(\"1001\");\n", " if (el != null) {\n", " el.textContent = \"BokehJS is loading...\";\n", " }\n", " if (root.Bokeh !== undefined) {\n", " if (el != null) {\n", " el.textContent = \"BokehJS \" + root.Bokeh.version + \" successfully loaded.\";\n", " }\n", " } else if (Date.now() < root._bokeh_timeout) {\n", " setTimeout(display_loaded, 100)\n", " }\n", " }\n", "\n", "\n", " function run_callbacks() {\n", " try {\n", " root._bokeh_onload_callbacks.forEach(function(callback) {\n", " if (callback != null)\n", " callback();\n", " });\n", " } finally {\n", " delete root._bokeh_onload_callbacks\n", " }\n", " console.debug(\"Bokeh: all callbacks have finished\");\n", " }\n", "\n", " function load_libs(css_urls, js_urls, callback) {\n", " if (css_urls == null) css_urls = [];\n", " if (js_urls == null) js_urls = [];\n", "\n", " root._bokeh_onload_callbacks.push(callback);\n", " if (root._bokeh_is_loading > 0) {\n", " console.debug(\"Bokeh: BokehJS is being loaded, scheduling callback at\", now());\n", " return null;\n", " }\n", " if (js_urls == null || js_urls.length === 0) {\n", " run_callbacks();\n", " return null;\n", " }\n", " console.debug(\"Bokeh: BokehJS not loaded, scheduling load and callback at\", now());\n", " root._bokeh_is_loading = css_urls.length + js_urls.length;\n", "\n", " function on_load() {\n", " root._bokeh_is_loading--;\n", " if (root._bokeh_is_loading === 0) {\n", " console.debug(\"Bokeh: all BokehJS libraries/stylesheets loaded\");\n", " run_callbacks()\n", " }\n", " }\n", "\n", " function on_error() {\n", " console.error(\"failed to load \" + url);\n", " }\n", "\n", " for (var i = 0; i < css_urls.length; i++) {\n", " var url = css_urls[i];\n", " const element = document.createElement(\"link\");\n", " element.onload = on_load;\n", " element.onerror = on_error;\n", " element.rel = \"stylesheet\";\n", " element.type = \"text/css\";\n", " element.href = url;\n", " console.debug(\"Bokeh: injecting link tag for BokehJS stylesheet: \", url);\n", " document.body.appendChild(element);\n", " }\n", "\n", " const hashes = {\"https://cdn.bokeh.org/bokeh/release/bokeh-2.2.1.min.js\": \"qkRvDQVAIfzsJo40iRBbxt6sttt0hv4lh74DG7OK4MCHv4C5oohXYoHUM5W11uqS\", \"https://cdn.bokeh.org/bokeh/release/bokeh-widgets-2.2.1.min.js\": \"Sb7Mr06a9TNlet/GEBeKaf5xH3eb6AlCzwjtU82wNPyDrnfoiVl26qnvlKjmcAd+\", \"https://cdn.bokeh.org/bokeh/release/bokeh-tables-2.2.1.min.js\": \"HaJ15vgfmcfRtB4c4YBOI4f1MUujukqInOWVqZJZZGK7Q+ivud0OKGSTn/Vm2iso\"};\n", "\n", " for (var i = 0; i < js_urls.length; i++) {\n", " var url = js_urls[i];\n", " var element = document.createElement('script');\n", " element.onload = on_load;\n", " element.onerror = on_error;\n", " element.async = false;\n", " element.src = url;\n", " if (url in hashes) {\n", " element.crossOrigin = \"anonymous\";\n", " element.integrity = \"sha384-\" + hashes[url];\n", " }\n", " console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n", " document.head.appendChild(element);\n", " }\n", " };\n", "\n", " function inject_raw_css(css) {\n", " const element = document.createElement(\"style\");\n", " element.appendChild(document.createTextNode(css));\n", " document.body.appendChild(element);\n", " }\n", "\n", " \n", " var js_urls = [\"https://cdn.bokeh.org/bokeh/release/bokeh-2.2.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-widgets-2.2.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-tables-2.2.1.min.js\"];\n", " var css_urls = [];\n", " \n", "\n", " var inline_js = [\n", " function(Bokeh) {\n", " Bokeh.set_log_level(\"info\");\n", " },\n", " function(Bokeh) {\n", " \n", " \n", " }\n", " ];\n", "\n", " function run_inline_js() {\n", " \n", " if (root.Bokeh !== undefined || force === true) {\n", " \n", " for (var i = 0; i < inline_js.length; i++) {\n", " inline_js[i].call(root, root.Bokeh);\n", " }\n", " if (force === true) {\n", " display_loaded();\n", " }} else if (Date.now() < root._bokeh_timeout) {\n", " setTimeout(run_inline_js, 100);\n", " } else if (!root._bokeh_failed_load) {\n", " console.log(\"Bokeh: BokehJS failed to load within specified timeout.\");\n", " root._bokeh_failed_load = true;\n", " } else if (force !== true) {\n", " var cell = $(document.getElementById(\"1001\")).parents('.cell').data().cell;\n", " cell.output_area.append_execute_result(NB_LOAD_WARNING)\n", " }\n", "\n", " }\n", "\n", " if (root._bokeh_is_loading === 0) {\n", " console.debug(\"Bokeh: BokehJS loaded, going straight to plotting\");\n", " run_inline_js();\n", " } else {\n", " load_libs(css_urls, js_urls, function() {\n", " console.debug(\"Bokeh: BokehJS plotting callback run at\", now());\n", " run_inline_js();\n", " });\n", " }\n", "}(window));" ], "application/vnd.bokehjs_load.v0+json": "\n(function(root) {\n function now() {\n return new Date();\n }\n\n var force = true;\n\n if (typeof root._bokeh_onload_callbacks === \"undefined\" || force === true) {\n root._bokeh_onload_callbacks = [];\n root._bokeh_is_loading = undefined;\n }\n\n \n\n \n if (typeof (root._bokeh_timeout) === \"undefined\" || force === true) {\n root._bokeh_timeout = Date.now() + 5000;\n root._bokeh_failed_load = false;\n }\n\n var NB_LOAD_WARNING = {'data': {'text/html':\n \"
\\n\"+\n \"

\\n\"+\n \"BokehJS does not appear to have successfully loaded. If loading BokehJS from CDN, this \\n\"+\n \"may be due to a slow or bad network connection. Possible fixes:\\n\"+\n \"

\\n\"+\n \"\\n\"+\n \"\\n\"+\n \"from bokeh.resources import INLINE\\n\"+\n \"output_notebook(resources=INLINE)\\n\"+\n \"\\n\"+\n \"
\"}};\n\n function display_loaded() {\n var el = document.getElementById(\"1001\");\n if (el != null) {\n el.textContent = \"BokehJS is loading...\";\n }\n if (root.Bokeh !== undefined) {\n if (el != null) {\n el.textContent = \"BokehJS \" + root.Bokeh.version + \" successfully loaded.\";\n }\n } else if (Date.now() < root._bokeh_timeout) {\n setTimeout(display_loaded, 100)\n }\n }\n\n\n function run_callbacks() {\n try {\n root._bokeh_onload_callbacks.forEach(function(callback) {\n if (callback != null)\n callback();\n });\n } finally {\n delete root._bokeh_onload_callbacks\n }\n console.debug(\"Bokeh: all callbacks have finished\");\n }\n\n function load_libs(css_urls, js_urls, callback) {\n if (css_urls == null) css_urls = [];\n if (js_urls == null) js_urls = [];\n\n root._bokeh_onload_callbacks.push(callback);\n if (root._bokeh_is_loading > 0) {\n console.debug(\"Bokeh: BokehJS is being loaded, scheduling callback at\", now());\n return null;\n }\n if (js_urls == null || js_urls.length === 0) {\n run_callbacks();\n return null;\n }\n console.debug(\"Bokeh: BokehJS not loaded, scheduling load and callback at\", now());\n root._bokeh_is_loading = css_urls.length + js_urls.length;\n\n function on_load() {\n root._bokeh_is_loading--;\n if (root._bokeh_is_loading === 0) {\n console.debug(\"Bokeh: all BokehJS libraries/stylesheets loaded\");\n run_callbacks()\n }\n }\n\n function on_error() {\n console.error(\"failed to load \" + url);\n }\n\n for (var i = 0; i < css_urls.length; i++) {\n var url = css_urls[i];\n const element = document.createElement(\"link\");\n element.onload = on_load;\n element.onerror = on_error;\n element.rel = \"stylesheet\";\n element.type = \"text/css\";\n element.href = url;\n console.debug(\"Bokeh: injecting link tag for BokehJS stylesheet: \", url);\n document.body.appendChild(element);\n }\n\n const hashes = {\"https://cdn.bokeh.org/bokeh/release/bokeh-2.2.1.min.js\": \"qkRvDQVAIfzsJo40iRBbxt6sttt0hv4lh74DG7OK4MCHv4C5oohXYoHUM5W11uqS\", \"https://cdn.bokeh.org/bokeh/release/bokeh-widgets-2.2.1.min.js\": \"Sb7Mr06a9TNlet/GEBeKaf5xH3eb6AlCzwjtU82wNPyDrnfoiVl26qnvlKjmcAd+\", \"https://cdn.bokeh.org/bokeh/release/bokeh-tables-2.2.1.min.js\": \"HaJ15vgfmcfRtB4c4YBOI4f1MUujukqInOWVqZJZZGK7Q+ivud0OKGSTn/Vm2iso\"};\n\n for (var i = 0; i < js_urls.length; i++) {\n var url = js_urls[i];\n var element = document.createElement('script');\n element.onload = on_load;\n element.onerror = on_error;\n element.async = false;\n element.src = url;\n if (url in hashes) {\n element.crossOrigin = \"anonymous\";\n element.integrity = \"sha384-\" + hashes[url];\n }\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n document.head.appendChild(element);\n }\n };\n\n function inject_raw_css(css) {\n const element = document.createElement(\"style\");\n element.appendChild(document.createTextNode(css));\n document.body.appendChild(element);\n }\n\n \n var js_urls = [\"https://cdn.bokeh.org/bokeh/release/bokeh-2.2.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-widgets-2.2.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-tables-2.2.1.min.js\"];\n var css_urls = [];\n \n\n var inline_js = [\n function(Bokeh) {\n Bokeh.set_log_level(\"info\");\n },\n function(Bokeh) {\n \n \n }\n ];\n\n function run_inline_js() {\n \n if (root.Bokeh !== undefined || force === true) {\n \n for (var i = 0; i < inline_js.length; i++) {\n inline_js[i].call(root, root.Bokeh);\n }\n if (force === true) {\n display_loaded();\n }} else if (Date.now() < root._bokeh_timeout) {\n setTimeout(run_inline_js, 100);\n } else if (!root._bokeh_failed_load) {\n console.log(\"Bokeh: BokehJS failed to load within specified timeout.\");\n root._bokeh_failed_load = true;\n } else if (force !== true) {\n var cell = $(document.getElementById(\"1001\")).parents('.cell').data().cell;\n cell.output_area.append_execute_result(NB_LOAD_WARNING)\n }\n\n }\n\n if (root._bokeh_is_loading === 0) {\n console.debug(\"Bokeh: BokehJS loaded, going straight to plotting\");\n run_inline_js();\n } else {\n load_libs(css_urls, js_urls, function() {\n console.debug(\"Bokeh: BokehJS plotting callback run at\", now());\n run_inline_js();\n });\n }\n}(window));" }, "metadata": {}, "output_type": "display_data" } ], "source": [ "import time\n", "\n", "import numpy as np\n", "\n", "import serial\n", "import serial.tools.list_ports\n", "\n", "import bokeh.plotting\n", "import bokeh.io\n", "bokeh.io.output_notebook()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "\n", "In the previous lessons, we learned how to send a signal to Arduino from Python to ask it to do something, in that case to turn on an LED. Now, we will ask Arduino to send data back. We will store the data in RAM (or we could write it to disk) for later use.\n", "\n", "We will read in a voltage that we control with a potentiometer. The idea is that we send a request from Python to Arduino, and then it reads the voltage and sends it back, with all communication over USB. You should work in your own Jupyter notebook for this.\n", "\n", "To begin, wire up the following schematic.\n", "\n", "
\n", " \n", "![Requesting and receiving data from arduino schematic](requesting_and_receiving_data_from_arduino_schem.svg)\n", " \n", "
\n", "\n", "Upload the following sketch.\n", "\n", "```arduino\n", "const int voltagePin = A0;\n", "\n", "const int HANDSHAKE = 0;\n", "const int VOLTAGE_REQUEST = 1;\n", "\n", "int inByte;\n", "\n", "\n", "void printVoltage() {\n", " // Read value from analog pin\n", " int value = analogRead(voltagePin);\n", "\n", " // Get the time point\n", " unsigned long time_ms = millis();\n", "\n", " // Write the result\n", " if (Serial.availableForWrite()) {\n", " String outstr = String(String(time_ms, DEC) + \",\" + String(value, DEC));\n", " Serial.println(outstr);\n", " }\n", "}\n", "\n", "\n", "void setup() {\n", " // Initialize serial communication\n", " Serial.begin(115200);\n", "}\n", "\n", "\n", "void loop() {\n", " // Check if data has been sent to Arduino and respond accordingly\n", " if (Serial.available() > 0) {\n", " // Read in request\n", " inByte = Serial.read();\n", "\n", " // If data is requested, fetch it and write it, or handshake\n", " switch(inByte) {\n", " case VOLTAGE_REQUEST:\n", " printVoltage();\n", " break;\n", " case HANDSHAKE:\n", " if (Serial.availableForWrite()) {\n", " Serial.println(\"Message received.\");\n", " }\n", " break;\n", " }\n", " }\n", "}\n", "```\n", "\n", "This sketch warrants some explanation.\n", "\n", "- I have written a function `printVoltage()` that handles reading in the voltage on the appropriate pin, adding a time stamp, and then printing the result to the serial connection. Note that I have used `String` objects to write a comma-delimited string, separating the time and the voltage. I have chosen not to convert the time to seconds nor the 10-bit integer from the `analogRead()` to volts on Arduino. I usually make this choice; conversions on the Python side are faster, and it is easier to send integers.\n", "- The `DEC` argument in `String()` specifies the format of the number as decimal, as opposed to, e.g., `HEX` for hexidecimal. It is not necessary (`DEC` is the default), but I left it in there because it is better to be explicit than implicit.\n", "- The structure of the `loop()` function is much the same as we have seen, except that we conveniently call the `printVoltage()` function. Writing modular code is always a good idea.\n", "\n", "We'll use the same global variable names on the Python side." ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "HANDSHAKE = 0\n", "VOLTAGE_REQUEST = 1" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "And, of course, we have our usual connectivity functions." ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "def find_arduino(port=None):\n", " \"\"\"Get the name of the port that is connected to Arduino.\"\"\"\n", " if port is None:\n", " ports = serial.tools.list_ports.comports()\n", " for p in ports:\n", " if p.manufacturer is not None and \"Arduino\" in p.manufacturer:\n", " port = p.device\n", " return port\n", "\n", "\n", "def handshake_arduino(\n", " arduino, sleep_time=1, print_handshake_message=False, handshake_code=0\n", "):\n", " \"\"\"Make sure connection is established by sending\n", " and receiving bytes.\"\"\"\n", " # Close and reopen\n", " arduino.close()\n", " arduino.open()\n", "\n", " # Chill out while everything gets set\n", " time.sleep(sleep_time)\n", "\n", " # Set a long timeout to complete handshake\n", " timeout = arduino.timeout\n", " arduino.timeout = 2\n", " \n", " # Read and discard everything that may be in the input buffer\n", " _ = arduino.read_all()\n", "\n", " # Send request to Arduino\n", " arduino.write(bytes([handshake_code]))\n", "\n", " # Read in what Arduino sent\n", " handshake_message = arduino.read_until()\n", "\n", " # Send and receive request again\n", " arduino.write(bytes([handshake_code]))\n", " handshake_message = arduino.read_until()\n", "\n", " # Print the handshake message, if desired\n", " if print_handshake_message:\n", " print(\"Handshake message: \" + handshake_message.decode())\n", "\n", " # Reset the timeout\n", " arduino.timeout = timeout" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "And now, let's make a connection. We'll leave it open through the exercise and close it at the end." ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Handshake message: Message received.\n", "\n" ] } ], "source": [ "port = find_arduino()\n", "arduino = serial.Serial(port, baudrate=115200)\n", "handshake_arduino(arduino, handshake_code=HANDSHAKE, print_handshake_message=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's ask for data from Arduino and receive it. We will again use `arduino.read_until()`, forcing reading until a newline character is encountered. This makes Python wait until Arduino has finished sending the message (or until the timeout, which is one second by default)." ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "b'20423,0\\r\\n'" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Ask Arduino for data\n", "arduino.write(bytes([VOLTAGE_REQUEST]))\n", "\n", "# Receive data\n", "raw = arduino.read_until()\n", "\n", "# Look at what we got\n", "raw" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The `b` in front of the string signifies that the string is a bytes literal. This is not a Python string, but a representation of an ASCII encoded set of characters as binary. To convert it to a string, we need to use the `decode()` method." ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'20423,0\\r\\n'" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "raw_str = raw.decode()\n", "\n", "# Take a look\n", "raw_str" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now it is a Python string that we can convert to two integers. The carriage return (`\\r`) and the newline (`\\n`) should be stripped off the end, and we split the string at the comma." ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "('20423', '0')" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "t, V = raw_str.rstrip().split(\",\")\n", "\n", "# Take a look\n", "t, V" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We now have two strings, the time and the voltage. We can convert these to integers and then to whatever units we like. I'll leave time in milliseconds and convert the voltage to volts." ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(20423, 0.0)" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "t = int(t)\n", "V = int(V) * 5 / 1023\n", "\n", "# Take a look\n", "t, V" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Since modular programming is a good idea, let's write this parsing into a function." ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [], "source": [ "def parse_raw(raw):\n", " \"\"\"Parse bytes output from Arduino.\"\"\"\n", " raw = raw.decode()\n", " if raw[-1] != \"\\n\":\n", " raise ValueError(\n", " \"Input must end with newline, otherwise message is incomplete.\"\n", " )\n", "\n", " t, V = raw.rstrip().split(\",\")\n", "\n", " return int(t), int(V) * 5 / 1023" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Finally, we can write a function to acquire a data point from the Arduino." ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [], "source": [ "def request_single_voltage(arduino):\n", " \"\"\"Ask Arduino for a single data point\"\"\"\n", " # Ask Arduino for data\n", " arduino.write(bytes([VOLTAGE_REQUEST]))\n", "\n", " # Read in the data\n", " raw = arduino.read_until()\n", "\n", " # Parse and return\n", " return parse_raw(raw)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can call the function to get single time, voltage pairs." ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(23638, 0.0)" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "request_single_voltage(arduino)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We could set up lists containing the time and voltages we acquire and use the functions to get them programmatically, say every 20 ms. I'll run the following code cell while twisting the potentiometer knob back and forth." ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [], "source": [ "time_ms = []\n", "voltage = []\n", "\n", "for i in range(400):\n", " # Request and append\n", " t, V = request_single_voltage(arduino)\n", " time_ms.append(t)\n", " voltage.append(V)\n", " \n", " # Wait 20 ms\n", " time.sleep(0.02)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can make a plot of the results." ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "\n", "\n", "\n", "\n", "\n", "
\n" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "application/javascript": [ "(function(root) {\n", " function embed_document(root) {\n", " \n", " var docs_json = {\"b1898008-df2c-4f94-9d1b-2a639faae338\":{\"roots\":{\"references\":[{\"attributes\":{\"below\":[{\"id\":\"1011\"}],\"center\":[{\"id\":\"1014\"},{\"id\":\"1018\"}],\"frame_height\":175,\"frame_width\":500,\"left\":[{\"id\":\"1015\"}],\"renderers\":[{\"id\":\"1036\"}],\"title\":{\"id\":\"1039\"},\"toolbar\":{\"id\":\"1026\"},\"x_range\":{\"id\":\"1003\"},\"x_scale\":{\"id\":\"1007\"},\"y_range\":{\"id\":\"1005\"},\"y_scale\":{\"id\":\"1009\"}},\"id\":\"1002\",\"subtype\":\"Figure\",\"type\":\"Plot\"},{\"attributes\":{\"data_source\":{\"id\":\"1033\"},\"glyph\":{\"id\":\"1034\"},\"hover_glyph\":null,\"muted_glyph\":null,\"nonselection_glyph\":{\"id\":\"1035\"},\"selection_glyph\":null,\"view\":{\"id\":\"1037\"}},\"id\":\"1036\",\"type\":\"GlyphRenderer\"},{\"attributes\":{\"axis\":{\"id\":\"1015\"},\"dimension\":1,\"ticker\":null},\"id\":\"1018\",\"type\":\"Grid\"},{\"attributes\":{},\"id\":\"1045\",\"type\":\"Selection\"},{\"attributes\":{},\"id\":\"1046\",\"type\":\"UnionRenderers\"},{\"attributes\":{\"text\":\"\"},\"id\":\"1039\",\"type\":\"Title\"},{\"attributes\":{\"axis_label\":\"time (s)\",\"formatter\":{\"id\":\"1043\"},\"ticker\":{\"id\":\"1012\"}},\"id\":\"1011\",\"type\":\"LinearAxis\"},{\"attributes\":{\"line_color\":\"#1f77b4\",\"x\":{\"field\":\"x\"},\"y\":{\"field\":\"y\"}},\"id\":\"1034\",\"type\":\"Line\"},{\"attributes\":{},\"id\":\"1005\",\"type\":\"DataRange1d\"},{\"attributes\":{\"end\":35.042,\"start\":24.267},\"id\":\"1003\",\"type\":\"Range1d\"},{\"attributes\":{},\"id\":\"1022\",\"type\":\"SaveTool\"},{\"attributes\":{\"source\":{\"id\":\"1033\"}},\"id\":\"1037\",\"type\":\"CDSView\"},{\"attributes\":{},\"id\":\"1041\",\"type\":\"BasicTickFormatter\"},{\"attributes\":{},\"id\":\"1020\",\"type\":\"WheelZoomTool\"},{\"attributes\":{\"active_drag\":\"auto\",\"active_inspect\":\"auto\",\"active_multi\":null,\"active_scroll\":\"auto\",\"active_tap\":\"auto\",\"tools\":[{\"id\":\"1019\"},{\"id\":\"1020\"},{\"id\":\"1021\"},{\"id\":\"1022\"},{\"id\":\"1023\"},{\"id\":\"1024\"}]},\"id\":\"1026\",\"type\":\"Toolbar\"},{\"attributes\":{},\"id\":\"1012\",\"type\":\"BasicTicker\"},{\"attributes\":{},\"id\":\"1007\",\"type\":\"LinearScale\"},{\"attributes\":{\"axis_label\":\"voltage (V)\",\"formatter\":{\"id\":\"1041\"},\"ticker\":{\"id\":\"1016\"}},\"id\":\"1015\",\"type\":\"LinearAxis\"},{\"attributes\":{},\"id\":\"1019\",\"type\":\"PanTool\"},{\"attributes\":{},\"id\":\"1024\",\"type\":\"HelpTool\"},{\"attributes\":{\"data\":{\"x\":{\"__ndarray__\":\"MQisHFpEOEAlBoGVQ0s4QN9PjZduUjhAf2q8dJNYOEDl0CLb+V44QEw3iUFgZThAJQaBlUNrOEDFILByaHE4QEa28/3UeDhAx0s3iUGAOEC6SQwCK4c4QAIrhxbZjjhAL90kBoGVOEBcj8L1KJw4QPyp8dJNojhAKVyPwvWoOEDjpZvEILA4QGQ730+NtzhAH4XrUbi+OED4U+Olm8Q4QLKd76fGyzhAppvEILDSOEC4HoXrUdg4QHNoke183zhALbKd76fmOEBaZDvfT+04QMHKoUW28zhAJzEIrBz6OEDhehSuRwE5QNV46SYxCDlAyXa+nxoPOUAv3SQGgRU5QJZDi2znGzlAw/UoXI8iOUApXI/C9Sg5QKrx0k1iMDlAK4cW2c43OUCR7Xw/NT45QIXrUbgeRTlAJQaBlUNLOUAZBFYOLVI5QNNNYhBYWTlAx0s3iUFgOUDVeOkmMWg5QFYOLbKdbzlA9ihcj8J1OUB3vp8aL305QKRwPQrXgzlAmG4Sg8CKOUAZBFYOLZI5QGDl0CLbmTlAjZduEoOgOUDVeOkmMag5QDvfT42XrjlAaJHtfD+1OUAIrBxaZLs5QDVeukkMwjlAfT81XrrJOUAdWmQ73885QJ7vp8ZL1zlA5dAi2/neOUBMN4lBYOU5QAaBlUOL7DlAMzMzMzPzOUBg5dAi2/k5QKjGSzeJATpAYhBYObQIOkCPwvUoXA86QPYoXI/CFTpA6SYxCKwcOkDdJAaBlSM6QOxRuB6FKzpAbef7qfEyOkBg5dAi2zk6QI2XbhKDQDpAukkMAitHOkDJdr6fGk86QEoMAiuHVjpAPQrXo3BdOkAxCKwcWmQ6QJhuEoPAajpAN4lBYOVwOkDy0k1iEHg6QFg5tMh2fjpA+FPjpZuEOkAlBoGVQ4s6QBkEVg4tkjpAf2q8dJOYOkAAAAAAAKA6QPT91HjppjpAdZMYBFauOkD2KFyPwrU6QHe+nxovvTpA+FPjpZvEOkCYbhKDwMo6QBkEVg4t0jpARrbz/dTYOkCsHFpkO986QKAaL90k5jpAIbByaJHtOkBOYhBYOfQ6QCcxCKwc+jpAx0s3iUEAO0CBlUOLbAc7QK5H4XoUDjtAFK5H4XoUO0C0yHa+nxo7QFTjpZvEIDtAukkMAisnO0B1kxgEVi47QGiR7Xw/NTtAlkOLbOc7O0BQjZduEkM7QArXo3A9SjtAN4lBYOVQO0Dy0k1iEFg7QAAAAAAAYDtAukkMAitnO0CuR+F6FG47QLx0kxgEdjtA6SYxCKx8O0CJQWDl0II7QESLbOf7iTtAN4lBYOWQO0Dy0k1iEJg7QKwcWmQ7nztAZmZmZmamO0AhsHJoka07QKJFtvP9tDtAXI/C9Si8O0BQjZduEsM7QH0/NV66yTtA/tR46SbRO0Dy0k1iENg7QAAAAAAA4DtAgZVDi2znO0B1kxgEVu47QLx0kxgE9jtAPQrXo3D9O0CkcD0K1wM8QF66SQwCCzxAGQRWDi0SPEC4HoXrURg8QHNoke18HzxAoBov3SQmPEAhsHJokS08QKJFtvP9NDxAXI/C9Sg8PEBQjZduEkM8QESLbOf7STxA46WbxCBQPECe76fGS1c8QARWDi2yXTxAvp8aL91kPEDsUbgehWs8QFK4HoXrcTxAf2q8dJN4PEAfhetRuH48QBKDwMqhhTxAPzVeukmMPEAZBFYOLZI8QNNNYhBYmTxAVOOlm8SgPECBlUOLbKc8QHWTGARWrjxAvHSTGAS2PEA9CtejcL08QDEIrBxaxDxAJQaBlUPLPEBSuB6F69E8QH9qvHST2DxAAAAAAADgPED0/dR46eY8QDvfT42X7jxAvHSTGAT2PEDpJjEIrPw8QMP1KFyPAj1ARIts5/sJPUD+1HjpJhE9QH9qvHSTGD1AAAAAAAAgPUCBlUOLbCc9QDvfT42XLj1AL90kBoE1PUAIrBxaZDs9QPyp8dJNQj1ARIts5/tJPUDFILByaFE9QLgehetRWD1AAAAAAABgPUAtsp3vp2Y9QJMYBFYObT1A2/l+arx0PUDP91PjpXs9QKjGSzeJgT1AnMQgsHKIPUDJdr6fGo89QPYoXI/ClT1AsHJoke2cPUBqvHSTGKQ9QOxRuB6Fqz1AGQRWDi2yPUB/arx0k7g9QOXQItv5vj1Avp8aL93EPUB56SYxCMw9QN9PjZdu0j1Af2q8dJPYPUDHSzeJQeA9QLpJDAIr5z1AAiuHFtnuPUBoke18P/U9QCPb+X5q/D1Aw/UoXI8CPkAK16NwPQo+QOOlm8QgED5AZDvfT40XPkDl0CLb+R4+QEw3iUFgJT5ABoGVQ4ssPkBt5/up8TI+QCcxCKwcOj5AVOOlm8RAPkCBlUOLbEc+QAIrhxbZTj5AaJHtfD9VPkCwcmiR7Vw+QGq8dJMYZD5AJQaBlUNrPkAZBFYOLXI+QAwCK4cWeT5AjZduEoOAPkCBlUOLbIc+QDvfT42Xjj5AFK5H4XqUPkCWQ4ts55s+QDVeukkMoj5AtvP91HipPkBxPQrXo7A+QNejcD0Ktz5Ake18PzW+PkDZzvdT48U+QHnpJjEIzD5Abef7qfHSPkDufD81Xto+QOF6FK5H4T5AnMQgsHLoPkDJdr6fGu8+QLx0kxgE9j5APQrXo3D9PkAxCKwcWgQ/QF66SQwCCz9AxSCwcmgRP0B/arx0kxg/QDm0yHa+Hz9ASOF6FK4nP0CuR+F6FC4/QNv5fmq8ND9AI9v5fmo8P0DdJAaBlUM/QCUGgZVDSz9AppvEILBSP0DTTWIQWFk/QOF6FK5HYT9AYhBYObRoP0BWDi2ynW8/QBBYObTIdj9Ay6FFtvN9P0DdJAaBlYM/QF66SQwCiz9AxSCwcmiRP0BGtvP91Jg/QMdLN4lBoD9AgZVDi2ynP0B1kxgEVq4/QGiR7Xw/tT9AI9v5fmq8P0CkcD0K18M/QNEi2/l+yj9AqvHSTWLQP0Ce76fGS9c/QKwcWmQ73z9A2c73U+PlP0AhsHJoke0/QNv5fmq89D9AlkOLbOf7P0CLbOf7qQFAQBSuR+F6BEBASOF6FK4HQECYbhKDwApAQHWTGARWDkBANV66SQwSQEASg8DKoRVAQEa28/3UGEBAI9v5fmocQEAdWmQ73x9AQDMzMzMzI0BAukkMAisnQECYbhKDwCpAQFg5tMh2LkBAi2zn+6kxQEBoke18PzVAQJzEILByOEBAz/dT46U7QECsHFpkOz9AQN9PjZduQkBAvHSTGARGQEBg5dAi20lAQLByaJHtTEBAAAAAAABQQEDByqFFtlNAQLpJDAIrV0BAQmDl0CJbQEACK4cW2V5AQFK4HoXrYUBATDeJQWBlQEDTTWIQWGlAQAaBlUOLbEBA46WbxCBwQEAX2c73U3NAQBBYObTIdkBAtMh2vp96QEAEVg4tsn1AQMUgsHJogUBAokW28/2EQEC4HoXrUYhAQCUGgZVDi0BAH4XrUbiOQEAZBFYOLZJAQGiR7Xw/lUBAKVyPwvWYQEDpJjEIrJxAQDm0yHa+n0BAMzMzMzOjQED0/dR46aZAQESLbOf7qUBAsHJoke2sQECNl24Sg7BAQMHKoUW2s0BAgZVDi2y3QECYbhKDwLpAQK5H4XoUvkBAUrgehevBQECF61G4HsVAQGIQWDm0yEBAI9v5fmrMQEDjpZvEINBAQDMzMzMz00BA9P3UeOnWQEC0yHa+n9pAQJHtfD813kBAUrgehevhQEAv3SQGgeVAQPCnxks36UBA6SYxCKzsQEAAAAAAAPBAQBfZzvdT80BA16NwPQr3QEBEi2zn+/lAQARWDi2y/UBAqMZLN4kBQUD4U+OlmwRBQNV46SYxCEFAJQaBlUMLQUDJdr6fGg9BQIlBYOXQEkFA9ihcj8IVQUC28/3UeBlBQJMYBFYOHUFAcT0K16MgQUBOYhBYOSRBQGQ730+NJ0FAXrpJDAIrQUA730+Nly5BQPyp8dJNMkFAaJHtfD81QUCcxCCwcjhBQM/3U+OlO0FAAiuHFtk+QUA1XrpJDEJBQIXrUbgeRUFAf2q8dJNIQUAj2/l+akxBQOOlm8QgUEFAF9nO91NTQUBmZmZmZlZBQGDl0CLbWUFAzczMzMxcQUAAAAAAAGBBQFCNl24SY0FAZmZmZmZmQUBg5dAi22lBQLByaJHtbEFAx0s3iUFwQUDdJAaBlXNBQIGVQ4tsd0FAXrpJDAJ7QUACK4cW2X5BQDVeukkMgkFATDeJQWCFQUA=\",\"dtype\":\"float64\",\"order\":\"little\",\"shape\":[400]},\"y\":[0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.02932551319648094,0.12218963831867058,0.1466275659824047,0.22482893450635386,0.2590420332355816,0.3128054740957967,0.33235581622678395,0.33724340175953077,0.33724340175953077,0.3421309872922776,0.34701857282502446,0.34701857282502446,0.3567937438905181,0.37145650048875856,0.4252199413489736,0.46432062561094817,0.5034213098729228,0.5718475073313783,0.6256109481915934,0.6793743890518084,0.7624633431085044,0.8406647116324536,0.8993157380254154,0.9384164222873901,0.9579667644183774,0.9970674486803519,1.04594330400782,1.099706744868035,1.1485826001955035,1.1925708699902249,1.2365591397849462,1.270772238514174,1.3196480938416422,1.3929618768328447,1.436950146627566,1.4858260019550342,1.5102639296187683,1.5493646138807429,1.6031280547409579,1.6959921798631477,1.8084066471163245,1.906158357771261,1.9843597262952102,2.043010752688172,2.121212121212121,2.179863147605083,2.2189638318670575,2.2434017595307916,2.2629521016617793,2.3118279569892475,2.3362658846529816,2.3900293255131966,2.4437927663734116,2.487781036168133,2.5464320625610948,2.5855327468230693,2.6295210166177907,2.6686217008797652,2.702834799608993,2.7517106549364616,2.8103616813294234,2.8934506353861194,2.9716520039100685,3.113391984359726,3.2795698924731185,3.455522971652004,3.587487781036168,3.675464320625611,3.7341153470185726,3.7829912023460412,3.8269794721407626,3.900293255131965,3.9687194525904204,4.032258064516129,4.1006842619745845,4.193548387096774,4.281524926686217,4.374389051808406,4.511241446725318,4.687194525904204,4.848484848484849,5.0,5.0,5.0,5.0,5.0,5.0,5.0,5.0,5.0,5.0,5.0,5.0,5.0,5.0,5.0,5.0,5.0,5.0,5.0,5.0,5.0,5.0,4.9853372434017595,4.921798631476051,4.848484848484849,4.79960899315738,4.780058651026393,4.765395894428153,4.760508308895406,4.7702834799608995,4.794721407624634,4.79960899315738,4.79960899315738,4.79960899315738,4.79960899315738,4.79960899315738,4.79960899315738,4.79960899315738,4.78494623655914,4.775171065493646,4.7702834799608995,4.760508308895406,4.736070381231672,4.736070381231672,4.696969696969697,4.618768328445748,4.560117302052786,4.521016617790812,4.486803519061583,4.462365591397849,4.418377321603128,4.374389051808406,4.330400782013685,4.27663734115347,4.232649071358749,4.18377321603128,4.130009775171065,4.051808406647116,3.978494623655914,3.8856304985337244,3.7536656891495603,3.5972629521016617,3.4066471163245358,3.225806451612903,3.005865102639296,2.8054740957966766,2.6344086021505375,2.4389051808406648,2.3362658846529816,2.2825024437927666,2.2482893450635384,2.19941348973607,2.1652003910068425,2.126099706744868,2.101661779081134,2.0772238514173997,2.0576735092864125,2.0234604105571847,1.9941348973607038,1.964809384164223,1.911045943304008,1.847507331378299,1.7741935483870968,1.7204301075268817,1.656891495601173,1.598240469208211,1.5493646138807429,1.4858260019550342,1.4173998044965788,1.2805474095796676,1.1192570869990224,0.9384164222873901,0.6793743890518084,0.5034213098729228,0.37145650048875856,0.24926686217008798,0.10752688172043011,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.14173998044965788,0.5034213098729228,0.7820136852394917,0.8699902248289345,0.8748778103616813,0.8211143695014663,0.4789833822091887,0.04398826979472141,0.0,0.0,0.0,0.0,0.053763440860215055,0.23460410557184752,0.4398826979472141,0.5571847507331378,0.5913978494623656,0.5913978494623656,0.5767350928641252,0.36656891495601174,0.1466275659824047,0.13196480938416422,0.1710654936461388,0.3176930596285435,0.7135874877810362,1.133919843597263,1.2805474095796676,1.2658846529814272,1.1925708699902249,1.021505376344086,0.9237536656891495,0.9188660801564027,0.967741935483871,1.1485826001955035,1.3929618768328447,1.5835777126099706,1.6422287390029326,1.6422287390029326,1.632453567937439,1.6373411534701858,1.6422287390029326,1.6422287390029326,1.6471163245356795,1.6911045943304008,1.7595307917888563,1.8132942326490713,1.8719452590420331,1.8914956011730206,1.8768328445747802,1.8279569892473118,1.739980449657869,1.686217008797654,1.6911045943304008,1.7302052785923754,1.9159335288367547,2.096774193548387,2.2531769305962857,2.355816226783969,2.375366568914956,2.3607038123167157,2.350928641251222,2.297165200391007,2.2385141739980448,2.209188660801564,2.209188660801564,2.2434017595307916,2.463343108504399,2.6148582600195502,2.732160312805474,2.7419354838709675,2.7468230694037143,2.7517106549364616,2.732160312805474,2.653958944281525,2.649071358748778,2.653958944281525,2.790811339198436,2.9423264907135875,3.1036168132942326,3.196480938416422,3.2355816226783967,3.2404692082111435,3.2502443792766376,3.2551319648093844,3.23069403714565,3.176930596285435,3.118279569892473,3.12316715542522,3.1378299120234603,3.2795698924731185,3.4408602150537635,3.6265884652981426,3.729227761485826,3.763440860215054,3.7781036168132944,3.787878787878788,3.792766373411535,3.7829912023460412,3.768328445747801,3.7438905180840663,3.758553274682307,3.8025415444770285,3.919843597262952,4.066471163245357]},\"selected\":{\"id\":\"1045\"},\"selection_policy\":{\"id\":\"1046\"}},\"id\":\"1033\",\"type\":\"ColumnDataSource\"},{\"attributes\":{\"overlay\":{\"id\":\"1025\"}},\"id\":\"1021\",\"type\":\"BoxZoomTool\"},{\"attributes\":{},\"id\":\"1043\",\"type\":\"BasicTickFormatter\"},{\"attributes\":{},\"id\":\"1009\",\"type\":\"LinearScale\"},{\"attributes\":{},\"id\":\"1023\",\"type\":\"ResetTool\"},{\"attributes\":{\"axis\":{\"id\":\"1011\"},\"ticker\":null},\"id\":\"1014\",\"type\":\"Grid\"},{\"attributes\":{\"bottom_units\":\"screen\",\"fill_alpha\":0.5,\"fill_color\":\"lightgrey\",\"left_units\":\"screen\",\"level\":\"overlay\",\"line_alpha\":1.0,\"line_color\":\"black\",\"line_dash\":[4,4],\"line_width\":2,\"right_units\":\"screen\",\"top_units\":\"screen\"},\"id\":\"1025\",\"type\":\"BoxAnnotation\"},{\"attributes\":{\"line_alpha\":0.1,\"line_color\":\"#1f77b4\",\"x\":{\"field\":\"x\"},\"y\":{\"field\":\"y\"}},\"id\":\"1035\",\"type\":\"Line\"},{\"attributes\":{},\"id\":\"1016\",\"type\":\"BasicTicker\"}],\"root_ids\":[\"1002\"]},\"title\":\"Bokeh Application\",\"version\":\"2.2.1\"}};\n", " var render_items = [{\"docid\":\"b1898008-df2c-4f94-9d1b-2a639faae338\",\"root_ids\":[\"1002\"],\"roots\":{\"1002\":\"8f68f3f0-7a3e-42b8-8138-3b28106e117a\"}}];\n", " root.Bokeh.embed.embed_items_notebook(docs_json, render_items);\n", "\n", " }\n", " if (root.Bokeh !== undefined) {\n", " embed_document(root);\n", " } else {\n", " var attempts = 0;\n", " var timer = setInterval(function(root) {\n", " if (root.Bokeh !== undefined) {\n", " clearInterval(timer);\n", " embed_document(root);\n", " } else {\n", " attempts++;\n", " if (attempts > 100) {\n", " clearInterval(timer);\n", " console.log(\"Bokeh: ERROR: Unable to run BokehJS code because BokehJS library is missing\");\n", " }\n", " }\n", " }, 10, root)\n", " }\n", "})(window);" ], "application/vnd.bokehjs_exec.v0+json": "" }, "metadata": { "application/vnd.bokehjs_exec.v0+json": { "id": "1002" } }, "output_type": "display_data" } ], "source": [ "time_s = np.array(time_ms) / 1000\n", "\n", "p = bokeh.plotting.figure(\n", " x_axis_label='time (s)',\n", " y_axis_label='voltage (V)',\n", " frame_height=175,\n", " frame_width=500,\n", " x_range=[time_s[0], time_s[-1]],\n", ")\n", "p.line(time_s, voltage)\n", "\n", "bokeh.io.show(p)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As it turns out, this is *not* the preferred method to stream data from Arduino, though it works fine if you are sampling at much longer time intervals. The call-and-response and the use of Python's `time.sleep()` to do the delays leads to inconsistent timing. We can see this by computing the time difference between subsequent data points." ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([27, 28, 24, 25, 25, 23, 24, 29, 29, 27, 30, 26, 26, 24, 26, 28, 29,\n", " 28, 23, 28, 27, 22, 28, 28, 26, 25, 25, 28, 27, 27, 25, 25, 26, 25,\n", " 29, 29, 25, 27, 24, 27, 28, 27, 31, 29, 24, 29, 26, 27, 29, 30, 26,\n", " 30, 25, 26, 24, 26, 30, 24, 29, 30, 25, 28, 26, 26, 30, 28, 26, 25,\n", " 27, 27, 31, 29, 27, 26, 26, 31, 29, 27, 27, 25, 24, 28, 25, 24, 26,\n", " 27, 25, 29, 27, 29, 29, 29, 29, 24, 29, 26, 25, 27, 29, 26, 23, 24,\n", " 28, 26, 25, 24, 24, 25, 28, 27, 26, 28, 28, 26, 28, 31, 28, 27, 31,\n", " 26, 24, 28, 27, 28, 28, 28, 28, 29, 28, 27, 26, 29, 27, 31, 29, 27,\n", " 30, 29, 25, 28, 28, 24, 28, 26, 29, 29, 28, 27, 27, 24, 28, 25, 28,\n", " 26, 25, 26, 24, 27, 26, 23, 28, 29, 26, 27, 30, 29, 27, 27, 26, 26,\n", " 29, 27, 30, 29, 26, 23, 29, 28, 29, 29, 29, 28, 27, 23, 27, 30, 29,\n", " 27, 30, 26, 25, 30, 27, 23, 27, 26, 26, 28, 28, 29, 26, 25, 25, 23,\n", " 28, 25, 24, 30, 27, 30, 25, 28, 24, 30, 23, 29, 29, 25, 28, 25, 28,\n", " 26, 26, 29, 25, 30, 28, 28, 27, 27, 29, 27, 28, 23, 29, 24, 29, 28,\n", " 25, 28, 30, 24, 27, 29, 27, 28, 26, 27, 29, 27, 26, 25, 28, 28, 31,\n", " 25, 26, 30, 28, 30, 29, 26, 31, 29, 27, 28, 28, 22, 29, 25, 29, 29,\n", " 28, 27, 27, 28, 29, 26, 23, 27, 31, 26, 30, 28, 28, 29, 22, 25, 24,\n", " 28, 29, 28, 25, 28, 27, 26, 31, 28, 29, 25, 28, 25, 25, 28, 25, 28,\n", " 30, 24, 24, 29, 27, 31, 29, 24, 27, 31, 25, 28, 25, 27, 30, 24, 29,\n", " 28, 26, 23, 27, 27, 24, 29, 29, 24, 27, 29, 24, 23, 28, 25, 29, 26,\n", " 26, 30, 25, 28, 29, 29, 24, 29, 29, 28, 29, 28, 29, 27, 26, 26, 29,\n", " 23, 29, 30, 24, 28, 24, 30, 29, 23, 29, 28, 28, 28, 26, 27, 28, 29,\n", " 23, 25, 25, 25, 25, 24, 27, 30, 29, 25, 24, 27, 23, 25, 24, 26, 27,\n", " 24, 26, 26, 30, 28, 30, 25, 26])" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "np.diff(time_ms)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This is not the 20 ms we wanted, and the time between acquisitions jumps around a lot. In future lessons, we will cover how to get better streaming data from Arduino. For now, you will code up a dashboard for a more commonly used mode of data-upon-request, where you hit a button and get data back." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "\n", "## Do-it-yourself exercise 5: Asking for data with buttons\n", "\n", "Using the same setup as we have so far in this lesson, make a small dashboard with two buttons. One of them blanks out lists `time_ms` and `voltage`, while the other one asks for a time, voltage pair from Arduino and appends them to the lists. You can embed them in the notebook with either Bokeh. Since you would presumably be using the data right away, leave them in the notebook and not as a stand along Bokeh app.\n", "\n", "Use your button to ask for ten or more data points while messing around with the potentiometer knob and then make a plot of the result.\n", "\n", "*Hint*: Check out [Bokeh's set of widgets](https://docs.bokeh.org/en/latest/docs/user_guide/interaction/widgets.html). A Button is different that a Toggle. If you use a button, there is no `on_change` method, but rather an `on_click` method.\n", "\n", "*Another hint*: Say I have two widgets, `widget_1` and `widget_2`. To lay them out, you can use, e.g., `pn.Row(widget_1, pn.Spacer(width=15), widget_2)`.\n", "\n", "
" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [], "source": [ "arduino.close()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Computing environment" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "CPython 3.8.5\n", "IPython 7.18.1\n", "\n", "serial 3.4\n", "bokeh 2.2.1\n", "jupyterlab 2.2.6\n" ] } ], "source": [ "%load_ext watermark\n", "%watermark -v -p serial,bokeh,jupyterlab" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.8" } }, "nbformat": 4, "nbformat_minor": 4 }