From 3e782060fb42ca0a13c3927e00ff4f388f79156e Mon Sep 17 00:00:00 2001 From: jaseg Date: Tue, 4 Feb 2020 16:49:58 +0100 Subject: Working on analysis --- gm_platform/fw/grid_scope.ipynb | 3649 +++++++++++++++++++++++++++++++++++++ gm_platform/fw/reader.py | 30 + gm_platform/fw/tw_test.py | 18 +- gm_platform/platform/platform.sch | 2 +- grid_frequency_spectra.ipynb | 11 +- 5 files changed, 3700 insertions(+), 10 deletions(-) create mode 100644 gm_platform/fw/grid_scope.ipynb create mode 100644 gm_platform/fw/reader.py diff --git a/gm_platform/fw/grid_scope.ipynb b/gm_platform/fw/grid_scope.ipynb new file mode 100644 index 0000000..ea641f0 --- /dev/null +++ b/gm_platform/fw/grid_scope.ipynb @@ -0,0 +1,3649 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 92, + "metadata": {}, + "outputs": [], + "source": [ + "import math\n", + "import sqlite3\n", + "import struct\n", + "import datetime\n", + "\n", + "import matplotlib\n", + "from matplotlib import pyplot as plt\n", + "from matplotlib import patches\n", + "import numpy as np\n", + "from scipy import signal, optimize\n", + "from tqdm.notebook import tnrange, tqdm" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "%matplotlib notebook" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "db = sqlite3.connect('waveform.sqlite3')" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Run 000: 2020-01-30 14:24:25 - 2020-01-30 14:24:33 ( 0:00:07.571, 6880sp)\n", + "Run 001: 2020-01-30 14:24:56 - 2020-01-30 14:25:25 ( 0:00:28.305, 8992sp)\n", + "Run 002: 2020-01-30 14:25:33 - 2020-01-30 14:30:37 ( 0:05:04.016, 290112sp)\n", + "Run 003: 2020-01-30 14:31:31 - 2020-01-30 14:37:59 ( 0:06:27.900, 384192sp)\n", + "Run 004: 2020-01-30 14:38:30 - 2020-01-30 14:45:27 ( 0:06:56.604, 417216sp)\n", + "Run 005: 2020-01-30 15:18:50 - 2020-01-30 15:19:01 ( 0:00:10.690, 10240sp)\n", + "Run 006: 2020-01-30 16:37:15 - 2020-01-30 16:37:17 ( 0:00:02.527, 2560sp)\n", + "Run 007: 2020-01-30 16:44:23 - 2020-01-30 16:58:48 ( 0:14:24.293, 665600sp)\n", + "Run 008: 2020-01-30 17:06:35 - 2020-01-30 17:46:16 ( 0:39:41.608, 2163168sp)\n", + "Run 009: 2020-01-30 17:46:20 - 2020-01-30 18:11:16 ( 0:24:55.928, 1492480sp)\n", + "Run 010: 2020-01-30 18:11:39 - 2020-01-30 18:22:29 ( 0:10:50.025, 642560sp)\n", + "Run 011: 2020-01-30 18:22:32 - 2020-01-30 19:33:52 ( 1:11:20.495, 4280320sp)\n", + "Run 012: 2020-01-31 13:16:53 - 2020-01-31 13:18:12 ( 0:01:19.317, 79360sp)\n", + "Run 013: 2020-01-31 13:30:54 - 2020-01-31 13:31:35 ( 0:00:40.762, 40960sp)\n", + "Run 014: 2020-01-31 13:45:37 - 2020-01-31 13:45:42 ( 0:00:05.090, 5120sp)\n", + "Run 015: 2020-01-31 13:53:56 - 2020-01-31 13:54:01 ( 0:00:05.089, 5120sp)\n", + "Run 016: 2020-01-31 13:54:21 - 2020-01-31 13:54:26 ( 0:00:05.088, 5120sp)\n", + "Run 017: 2020-01-31 13:55:41 - 2020-01-31 13:55:46 ( 0:00:05.087, 5120sp)\n", + "Run 018: 2020-01-31 13:56:13 - 2020-01-31 13:56:19 ( 0:00:05.091, 5120sp)\n", + "Run 019: 2020-01-31 13:56:27 - 2020-01-31 13:56:30 ( 0:00:02.527, 2560sp)\n", + "Run 020: 2020-01-31 13:56:40 - 2020-01-31 13:56:48 ( 0:00:07.649, 7680sp)\n", + "Run 021: 2020-01-31 13:57:10 - 2020-01-31 13:58:34 ( 0:01:24.342, 84416sp)\n", + "Run 022: 2020-01-31 14:05:08 - 2020-01-31 14:05:24 ( 0:00:15.242, 15360sp)\n", + "Run 023: 2020-01-31 14:05:33 - 2020-01-31 14:12:50 ( 0:07:17.092, 435200sp)\n", + "Run 024: 2020-01-31 14:13:21 - 2020-01-31 14:15:39 ( 0:02:18.190, 138240sp)\n", + "Run 025: 2020-01-31 14:18:16 - 2020-01-31 18:58:25 ( 4:40:09.251, 16611840sp)\n" + ] + } + ], + "source": [ + "for run_id, start, end, count in db.execute('SELECT run_id, MIN(rx_ts), MAX(rx_ts), COUNT(*) FROM measurements GROUP BY run_id'):\n", + " foo = lambda x: datetime.datetime.fromtimestamp(x/1000)\n", + " start, end = foo(start), foo(end)\n", + " print(f'Run {run_id:03d}: {start:%Y-%m-%d %H:%M:%S} - {end:%Y-%m-%d %H:%M:%S} ({str(end-start)[:-3]:>13}, {count*32:>9d}sp)')\n", + "last_run, n_records = run_id, count\n", + "sampling_rate = 1000.0" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "par = lambda *rs: 1/sum(1/r for r in rs) # resistor parallel calculation\n", + "\n", + "# FIXME: These are for the first prototype only!\n", + "vmeas_source_impedance = 330e3\n", + "vmeas_source_scale = 0.5\n", + "\n", + "vcc = 15.0\n", + "vmeas_div_high = 27e3\n", + "vmeas_div_low = par(4.7e3, 10e3)\n", + "vmeas_div_voltage = vcc * vmeas_div_low / (vmeas_div_high + vmeas_div_low)\n", + "vmeas_div_impedance = par(vmeas_div_high, vmeas_div_low)\n", + "\n", + "#vmeas_overall_factor = vmeas_div_impedance / (vmeas_source_impedance + vmeas_div_impedance)\n", + "v0 = 1.5746\n", + "v100 = 2.004\n", + "vn100 = 1.1452\n", + "\n", + "adc_vcc = 3.3 # V\n", + "adc_fullscale = 4095\n", + "\n", + "adc_val_to_voltage_factor = 1/adc_fullscale * adc_vcc\n", + "\n", + "adc_count_to_vmeas = lambda x: (x*adc_val_to_voltage_factor - v0) / (v100-v0) * 100" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "scrolled": false + }, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "e5ad4355e2a540f9a4c56bccad434a09", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "HBox(children=(IntProgress(value=0, max=519120), HTML(value='')))" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] + } + ], + "source": [ + "limit = n_records\n", + "record_size = 32\n", + "skip_dropped_sections = False\n", + "\n", + "data = np.zeros(limit*record_size)\n", + "data[:] = np.nan\n", + "\n", + "last_seq = None\n", + "write_index = 0\n", + "for i, (seq, chunk) in tqdm(enumerate(db.execute(\n", + " 'SELECT seq, data FROM measurements WHERE run_id = ? ORDER BY rx_ts LIMIT ? OFFSET ?',\n", + " (last_run, limit, n_records-limit))), total=n_records):\n", + " \n", + " if last_seq is None or seq == (last_seq + 1)%0xffff:\n", + " last_seq = seq\n", + " idx = write_index if skip_dropped_sections else i\n", + " data[idx*record_size:(idx+1)*record_size] = np.frombuffer(chunk, dtype=' last_seq:\n", + " last_seq = seq\n", + " # nans = np.empty((record_size,))\n", + " # nans[:] = np.nan\n", + " # data = np.append(data, nans) FIXME\n", + " \n", + "data = (data * adc_val_to_voltage_factor - v0) / (v100-v0) * 100" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "227.138252895397" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "data_not_nan = data[~np.isnan(data)]\n", + "np.sqrt(np.mean(np.square(data_not_nan)))" + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "metadata": { + "scrolled": false + }, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "window.mpl = {};\n", + "\n", + "\n", + "mpl.get_websocket_type = function() {\n", + " if (typeof(WebSocket) !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof(MozWebSocket) !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert('Your browser does not have WebSocket support.' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.');\n", + " };\n", + "}\n", + "\n", + "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = (this.ws.binaryType != undefined);\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById(\"mpl-warnings\");\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent = (\n", + " \"This browser does not support binary websocket messages. \" +\n", + " \"Performance may be slow.\");\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = $('
');\n", + " this._root_extra_style(this.root)\n", + " this.root.attr('style', 'display: inline-block');\n", + "\n", + " $(parent_element).append(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", + " fig.send_message(\"send_image_mode\", {});\n", + " if (mpl.ratio != 1) {\n", + " fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n", + " }\n", + " fig.send_message(\"refresh\", {});\n", + " }\n", + "\n", + " this.imageObj.onload = function() {\n", + " if (fig.image_mode == 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function() {\n", + " fig.ws.close();\n", + " }\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "}\n", + "\n", + "mpl.figure.prototype._init_header = function() {\n", + " var titlebar = $(\n", + " '
');\n", + " var titletext = $(\n", + " '
');\n", + " titlebar.append(titletext)\n", + " this.root.append(titlebar);\n", + " this.header = titletext[0];\n", + "}\n", + "\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._init_canvas = function() {\n", + " var fig = this;\n", + "\n", + " var canvas_div = $('
');\n", + "\n", + " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", + "\n", + " function canvas_keyboard_event(event) {\n", + " return fig.key_event(event, event['data']);\n", + " }\n", + "\n", + " canvas_div.keydown('key_press', canvas_keyboard_event);\n", + " canvas_div.keyup('key_release', canvas_keyboard_event);\n", + " this.canvas_div = canvas_div\n", + " this._canvas_extra_style(canvas_div)\n", + " this.root.append(canvas_div);\n", + "\n", + " var canvas = $('');\n", + " canvas.addClass('mpl-canvas');\n", + " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", + "\n", + " this.canvas = canvas[0];\n", + " this.context = canvas[0].getContext(\"2d\");\n", + "\n", + " var backingStore = this.context.backingStorePixelRatio ||\n", + "\tthis.context.webkitBackingStorePixelRatio ||\n", + "\tthis.context.mozBackingStorePixelRatio ||\n", + "\tthis.context.msBackingStorePixelRatio ||\n", + "\tthis.context.oBackingStorePixelRatio ||\n", + "\tthis.context.backingStorePixelRatio || 1;\n", + "\n", + " mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband = $('');\n", + " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", + "\n", + " var pass_mouse_events = true;\n", + "\n", + " canvas_div.resizable({\n", + " start: function(event, ui) {\n", + " pass_mouse_events = false;\n", + " },\n", + " resize: function(event, ui) {\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " stop: function(event, ui) {\n", + " pass_mouse_events = true;\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " });\n", + "\n", + " function mouse_event_fn(event) {\n", + " if (pass_mouse_events)\n", + " return fig.mouse_event(event, event['data']);\n", + " }\n", + "\n", + " rubberband.mousedown('button_press', mouse_event_fn);\n", + " rubberband.mouseup('button_release', mouse_event_fn);\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband.mousemove('motion_notify', mouse_event_fn);\n", + "\n", + " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", + " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", + "\n", + " canvas_div.on(\"wheel\", function (event) {\n", + " event = event.originalEvent;\n", + " event['data'] = 'scroll'\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " mouse_event_fn(event);\n", + " });\n", + "\n", + " canvas_div.append(canvas);\n", + " canvas_div.append(rubberband);\n", + "\n", + " this.rubberband = rubberband;\n", + " this.rubberband_canvas = rubberband[0];\n", + " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", + " this.rubberband_context.strokeStyle = \"#000000\";\n", + "\n", + " this._resize_canvas = function(width, height) {\n", + " // Keep the size of the canvas, canvas container, and rubber band\n", + " // canvas in synch.\n", + " canvas_div.css('width', width)\n", + " canvas_div.css('height', height)\n", + "\n", + " canvas.attr('width', width * mpl.ratio);\n", + " canvas.attr('height', height * mpl.ratio);\n", + " canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n", + "\n", + " rubberband.attr('width', width);\n", + " rubberband.attr('height', height);\n", + " }\n", + "\n", + " // Set the figure to an initial 600x600px, this will subsequently be updated\n", + " // upon first draw.\n", + " this._resize_canvas(600, 600);\n", + "\n", + " // Disable right mouse context menu.\n", + " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", + " return false;\n", + " });\n", + "\n", + " function set_focus () {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "}\n", + "\n", + "mpl.figure.prototype._init_toolbar = function() {\n", + " var fig = this;\n", + "\n", + " var nav_element = $('
')\n", + " nav_element.attr('style', 'width: 100%');\n", + " this.root.append(nav_element);\n", + "\n", + " // Define a callback function for later on.\n", + " function toolbar_event(event) {\n", + " return fig.toolbar_button_onclick(event['data']);\n", + " }\n", + " function toolbar_mouse_event(event) {\n", + " return fig.toolbar_button_onmouseover(event['data']);\n", + " }\n", + "\n", + " for(var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " // put a spacer in here.\n", + " continue;\n", + " }\n", + " var button = $('');\n", + " button.click(method_name, toolbar_event);\n", + " button.mouseover(tooltip, toolbar_mouse_event);\n", + " nav_element.append(button);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = $('');\n", + " nav_element.append(status_bar);\n", + " this.message = status_bar[0];\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = $('
');\n", + " var button = $('');\n", + " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", + " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", + " buttongrp.append(button);\n", + " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", + " titlebar.prepend(buttongrp);\n", + "}\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(el){\n", + " var fig = this\n", + " el.on(\"remove\", function(){\n", + "\tfig.close_ws(fig, {});\n", + " });\n", + "}\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(el){\n", + " // this is important to make the div 'focusable\n", + " el.attr('tabindex', 0)\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " }\n", + " else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._key_event_extra = function(event, name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager)\n", + " manager = IPython.keyboard_manager;\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which == 13) {\n", + " this.canvas_div.blur();\n", + " event.shiftKey = false;\n", + " // Send a \"J\" for go to next cell\n", + " event.which = 74;\n", + " event.keyCode = 74;\n", + " manager.command_mode();\n", + " manager.handle_keydown(event);\n", + " }\n", + "}\n", + "\n", + "mpl.figure.prototype.handle_save = function(fig, msg) {\n", + " fig.ondownload(fig, null);\n", + "}\n", + "\n", + "\n", + "mpl.find_output_cell = function(html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i=0; i= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] == html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "}\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel != null) {\n", + " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "fig, ax = plt.subplots(figsize=(9, 3))\n", + "fig.tight_layout(pad=2, h_pad=0.1)\n", + "\n", + "ax.pcolormesh(t[-200:-100], f[:250], np.abs(Zxx[:250,-200:-100]))\n", + "ax.set_title(f\"Run {last_run}\", pad=-20, color='white')\n", + "ax.grid()\n", + "ax.set_ylabel('f [Hz]')\n", + "ax.set_ylim([30, 75]) # Hz\n", + "ax.set_xlabel('simulation time t [s]')\n", + "None" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "scrolled": false + }, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "bb14b5b4747148898f1d8f046318bb55", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "HBox(children=(IntProgress(value=0, max=166118), HTML(value='')))" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] + } + ], + "source": [ + "f_t = t\n", + "\n", + "n_f, n_t = Zxx.shape\n", + "# start, stop = 180, 220\n", + "# start, stop = 90, 110\n", + "# start, stop = 15, 35\n", + "# bounds_f = slice(start // 4 * nfft_factor, stop // 4 * nfft_factor)\n", + "f_min, f_max = 30, 70 # Hz\n", + "bounds_f = slice(np.argmax(f > f_min), np.argmin(f < f_max))\n", + "\n", + "\n", + "f_mean = np.zeros(Zxx.shape[1])\n", + "for le_t in tnrange(1, Zxx.shape[1] - 1):\n", + " frame_f = f[bounds_f]\n", + " frame_step = frame_f[1] - frame_f[0]\n", + " time_step = f_t[1] - f_t[0]\n", + " #if t == 10:\n", + " # axs[-1].plot(frame_f, frame_Z)\n", + " frame_Z = np.abs(Zxx[bounds_f, le_t])\n", + " # frame_f = f[180:220]\n", + " # frame_Z = np.abs(Zxx[180:220, 40])\n", + " # frame_f = f[15:35]\n", + " # frame_Z = np.abs(Zxx[15:35, 40])\n", + " # plt.plot(frame_f, frame_Z)\n", + "\n", + " # peak_f = frame_f[np.argmax(frame)]\n", + " # plt.axvline(peak_f, color='red')\n", + "\n", + "# def gauss(x, *p):\n", + "# A, mu, sigma, o = p\n", + "# return A*np.exp(-(x-mu)**2/(2.*sigma**2)) + o\n", + "\n", + " def gauss(x, *p):\n", + " A, mu, sigma = p\n", + " return A*np.exp(-(x-mu)**2/(2.*sigma**2))\n", + "\n", + " f_start = frame_f[np.argmax(frame_Z)]\n", + " A_start = np.max(frame_Z)\n", + " p0 = [A_start, f_start, 1.]\n", + " try:\n", + " coeff, var = optimize.curve_fit(gauss, frame_f, frame_Z, p0=p0)\n", + " # plt.plot(frame_f, gauss(frame_f, *coeff))\n", + " #print(coeff)\n", + " A, mu, sigma, *_ = coeff\n", + " f_mean[le_t] = mu\n", + " except Exception:\n", + " f_mean[le_t] = np.nan" + ] + }, + { + "cell_type": "code", + "execution_count": 106, + "metadata": { + "scrolled": false + }, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "window.mpl = {};\n", + "\n", + "\n", + "mpl.get_websocket_type = function() {\n", + " if (typeof(WebSocket) !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof(MozWebSocket) !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert('Your browser does not have WebSocket support.' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.');\n", + " };\n", + "}\n", + "\n", + "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = (this.ws.binaryType != undefined);\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById(\"mpl-warnings\");\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent = (\n", + " \"This browser does not support binary websocket messages. \" +\n", + " \"Performance may be slow.\");\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = $('
');\n", + " this._root_extra_style(this.root)\n", + " this.root.attr('style', 'display: inline-block');\n", + "\n", + " $(parent_element).append(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", + " fig.send_message(\"send_image_mode\", {});\n", + " if (mpl.ratio != 1) {\n", + " fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n", + " }\n", + " fig.send_message(\"refresh\", {});\n", + " }\n", + "\n", + " this.imageObj.onload = function() {\n", + " if (fig.image_mode == 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function() {\n", + " fig.ws.close();\n", + " }\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "}\n", + "\n", + "mpl.figure.prototype._init_header = function() {\n", + " var titlebar = $(\n", + " '
');\n", + " var titletext = $(\n", + " '
');\n", + " titlebar.append(titletext)\n", + " this.root.append(titlebar);\n", + " this.header = titletext[0];\n", + "}\n", + "\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._init_canvas = function() {\n", + " var fig = this;\n", + "\n", + " var canvas_div = $('
');\n", + "\n", + " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", + "\n", + " function canvas_keyboard_event(event) {\n", + " return fig.key_event(event, event['data']);\n", + " }\n", + "\n", + " canvas_div.keydown('key_press', canvas_keyboard_event);\n", + " canvas_div.keyup('key_release', canvas_keyboard_event);\n", + " this.canvas_div = canvas_div\n", + " this._canvas_extra_style(canvas_div)\n", + " this.root.append(canvas_div);\n", + "\n", + " var canvas = $('');\n", + " canvas.addClass('mpl-canvas');\n", + " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", + "\n", + " this.canvas = canvas[0];\n", + " this.context = canvas[0].getContext(\"2d\");\n", + "\n", + " var backingStore = this.context.backingStorePixelRatio ||\n", + "\tthis.context.webkitBackingStorePixelRatio ||\n", + "\tthis.context.mozBackingStorePixelRatio ||\n", + "\tthis.context.msBackingStorePixelRatio ||\n", + "\tthis.context.oBackingStorePixelRatio ||\n", + "\tthis.context.backingStorePixelRatio || 1;\n", + "\n", + " mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband = $('');\n", + " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", + "\n", + " var pass_mouse_events = true;\n", + "\n", + " canvas_div.resizable({\n", + " start: function(event, ui) {\n", + " pass_mouse_events = false;\n", + " },\n", + " resize: function(event, ui) {\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " stop: function(event, ui) {\n", + " pass_mouse_events = true;\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " });\n", + "\n", + " function mouse_event_fn(event) {\n", + " if (pass_mouse_events)\n", + " return fig.mouse_event(event, event['data']);\n", + " }\n", + "\n", + " rubberband.mousedown('button_press', mouse_event_fn);\n", + " rubberband.mouseup('button_release', mouse_event_fn);\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband.mousemove('motion_notify', mouse_event_fn);\n", + "\n", + " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", + " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", + "\n", + " canvas_div.on(\"wheel\", function (event) {\n", + " event = event.originalEvent;\n", + " event['data'] = 'scroll'\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " mouse_event_fn(event);\n", + " });\n", + "\n", + " canvas_div.append(canvas);\n", + " canvas_div.append(rubberband);\n", + "\n", + " this.rubberband = rubberband;\n", + " this.rubberband_canvas = rubberband[0];\n", + " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", + " this.rubberband_context.strokeStyle = \"#000000\";\n", + "\n", + " this._resize_canvas = function(width, height) {\n", + " // Keep the size of the canvas, canvas container, and rubber band\n", + " // canvas in synch.\n", + " canvas_div.css('width', width)\n", + " canvas_div.css('height', height)\n", + "\n", + " canvas.attr('width', width * mpl.ratio);\n", + " canvas.attr('height', height * mpl.ratio);\n", + " canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n", + "\n", + " rubberband.attr('width', width);\n", + " rubberband.attr('height', height);\n", + " }\n", + "\n", + " // Set the figure to an initial 600x600px, this will subsequently be updated\n", + " // upon first draw.\n", + " this._resize_canvas(600, 600);\n", + "\n", + " // Disable right mouse context menu.\n", + " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", + " return false;\n", + " });\n", + "\n", + " function set_focus () {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "}\n", + "\n", + "mpl.figure.prototype._init_toolbar = function() {\n", + " var fig = this;\n", + "\n", + " var nav_element = $('
')\n", + " nav_element.attr('style', 'width: 100%');\n", + " this.root.append(nav_element);\n", + "\n", + " // Define a callback function for later on.\n", + " function toolbar_event(event) {\n", + " return fig.toolbar_button_onclick(event['data']);\n", + " }\n", + " function toolbar_mouse_event(event) {\n", + " return fig.toolbar_button_onmouseover(event['data']);\n", + " }\n", + "\n", + " for(var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " // put a spacer in here.\n", + " continue;\n", + " }\n", + " var button = $('');\n", + " button.click(method_name, toolbar_event);\n", + " button.mouseover(tooltip, toolbar_mouse_event);\n", + " nav_element.append(button);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = $('');\n", + " nav_element.append(status_bar);\n", + " this.message = status_bar[0];\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = $('
');\n", + " var button = $('');\n", + " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", + " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", + " buttongrp.append(button);\n", + " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", + " titlebar.prepend(buttongrp);\n", + "}\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(el){\n", + " var fig = this\n", + " el.on(\"remove\", function(){\n", + "\tfig.close_ws(fig, {});\n", + " });\n", + "}\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(el){\n", + " // this is important to make the div 'focusable\n", + " el.attr('tabindex', 0)\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " }\n", + " else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._key_event_extra = function(event, name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager)\n", + " manager = IPython.keyboard_manager;\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which == 13) {\n", + " this.canvas_div.blur();\n", + " event.shiftKey = false;\n", + " // Send a \"J\" for go to next cell\n", + " event.which = 74;\n", + " event.keyCode = 74;\n", + " manager.command_mode();\n", + " manager.handle_keydown(event);\n", + " }\n", + "}\n", + "\n", + "mpl.figure.prototype.handle_save = function(fig, msg) {\n", + " fig.ondownload(fig, null);\n", + "}\n", + "\n", + "\n", + "mpl.find_output_cell = function(html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i=0; i= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] == html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "}\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel != null) {\n", + " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "f_copy = np.copy(f_mean[1:-1])\n", + "f_copy[np.isnan(f_copy)] = np.mean(f_copy[~np.isnan(f_copy)])\n", + "b, a = signal.cheby2(7, 86, 100, 'low', output='ba', fs=1000)\n", + "filtered = signal.lfilter(b, a, f_copy)\n", + "\n", + "b2, a2 = signal.cheby2(3, 30, 1, 'high', output='ba', fs=1000)\n", + "filtered2 = signal.lfilter(b2, a2, filtered)\n", + "\n", + "fig, (ax2, ax1) = plt.subplots(2, figsize=(9,7))\n", + "ax1.plot(f_t[1:-1], f_copy, color='lightgray')\n", + "ax1.set_ylim([49.90, 50.10])\n", + "ax1.grid()\n", + "formatter = matplotlib.ticker.FuncFormatter(lambda s, x: str(datetime.timedelta(seconds=s)))\n", + "ax1.xaxis.set_major_formatter(formatter)\n", + "zoom_offx = 7000 # s\n", + "zoom_len = 300 # s\n", + "ax1.set_xlim([zoom_offx, zoom_offx + zoom_len])\n", + "\n", + "ax1.plot(f_t[1:-1], filtered, color='orange')\n", + "ax1r = ax1.twinx()\n", + "ax1r.plot(f_t[1:-1], filtered2, color='red')\n", + "ax1r.set_ylim([-0.015, 0.015])\n", + "ax1.set_title(f'Zoomed trace ({datetime.timedelta(seconds=zoom_len)})', pad=-20)\n", + "\n", + "\n", + "ax2.set_title(f'Run {last_run}')\n", + "ax2.plot(f_t[1:-1], f_copy, color='orange')\n", + "\n", + "ax2r = ax2.twinx()\n", + "ax2r.set_ylim([-0.1, 0.1])\n", + "ax2r.plot(f_t[1:-1], filtered2, color='red')\n", + "#ax2.plot(f_t[1:-1], filtered, color='orange', zorder=1)\n", + "ax2.set_ylim([49.90, 50.10])\n", + "ax2.set_xlim([0, f_t[-2]])\n", + "ax2.grid()\n", + "formatter = matplotlib.ticker.FuncFormatter(lambda s, x: str(datetime.timedelta(seconds=s)))\n", + "ax2.xaxis.set_major_formatter(formatter)\n", + "\n", + "ax2.legend(handles=[\n", + " patches.Patch(color='lightgray', label='Raw frequency'),\n", + " patches.Patch(color='orange', label='low-pass filtered'),\n", + " patches.Patch(color='red', label='band-pass filtered')])\n", + "\n", + "ax1.set_ylabel('f [Hz]')\n", + "ax1r.set_ylabel('band-pass Δf [Hz]')\n", + "ax2.set_ylabel('f [Hz]')\n", + "ax2r.set_ylabel('band-pass Δf [Hz]')\n", + "\n", + "# Cut out first 10min of filtered data to give filters time to settle\n", + "rms_slice = filtered2[np.where(f_t[1:] > 10*60)[0][0]:]\n", + "rms = np.sqrt(np.mean(np.square(rms_slice)))\n", + "ax1.text(0.5, 0.1, f'RMS (filtered): {rms*1e3:.3f}mHz', transform=ax1.transAxes, color='white', bbox=bbox, ha='center')\n", + "None" + ] + } + ], + "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.7.5" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/gm_platform/fw/reader.py b/gm_platform/fw/reader.py new file mode 100644 index 0000000..c60edb7 --- /dev/null +++ b/gm_platform/fw/reader.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 + +import struct + +import sqlite3 + +import serial +from cobs import cobs + +if __name__ == '__main__': + import argparse + + parser = argparse.ArgumentParser() + parser.add_argument('-b', '--baudrate', type=int, default=250000) + parser.add_argument('port') + parser.add_argument('dbfile') + args = parser.parse_args() + + db = sqlite3.connect(args.db) + ser = serial.Serial(args.port, args.baudrate) + + while True: + packet = ser.read_until(b'\0') + try: + packet = cobs.decode(packet) + crc, seq, struct.decode('IBxH', packet[:8]) + + except Exception as e: + print(e) + diff --git a/gm_platform/fw/tw_test.py b/gm_platform/fw/tw_test.py index cde59f0..e329abd 100644 --- a/gm_platform/fw/tw_test.py +++ b/gm_platform/fw/tw_test.py @@ -29,7 +29,6 @@ ctrl_reset = lambda: ctrl_packet(CtrlPacketTypes.RESET) ctrl_ack = lambda pid: ctrl_packet(CtrlPacketTypes.ACK, pid) ctrl_retransmit = lambda pid: ctrl_packet(CtrlPacketTypes.RETRANSMIT, pid) - if __name__ == '__main__': import argparse parser = argparse.ArgumentParser() @@ -71,6 +70,7 @@ if __name__ == '__main__': last_pid = None lines_written = 0 cur = db.cursor() + capture_start = time() while True: #ser.write(cobs.encode(b'\x01\xff') + b'\0') data = ser.read_until(b'\0') @@ -91,17 +91,25 @@ if __name__ == '__main__': # Calculate byte-wise CRC32 our_crc = zlib.crc32(bytes(b for x in payload for b in (0, 0, 0, x))) #log.append((time(), seq, crc32, our_crc, pid, data)) - print(f'{ts:>7.3f} {seq:05d} {crc32:08x} {our_crc:08x} {pid} {hexlify(data).decode()}', end='') + bars = '\u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588' + sparkline = ''.join(bars[int(x/4096*8)] for x in struct.unpack('<32H', data)) + print(f'\033[38;5;249m{ts-capture_start:>10.3f}', + f'\033[94m{seq:05d}', + f'\033[38;5;243m{crc32:08x}', + f'\033[38;5;243m{our_crc:08x}', + f'\033[38;5;243m{pid}', + f'\033[0m{hexlify(data).decode()}', + f'\033[94m{sparkline}\033[0m', end='') error = False suppress_ack = False if crc32 != our_crc: - print(' CRC ERROR', end='') + print(' \033[1;91mCRC ERROR\033[0m', end='') suppress_ack = True error = True if last_pid is not None and pid != (last_pid+1)%8: - print(' PID ERROR', end='') + print(' \033[1;93mPID ERROR\033[0m', end='') error = True else: last_pid = pid @@ -121,6 +129,8 @@ if __name__ == '__main__': if lines_written == 80: lines_written = 0 print('\033[2J\033[H', end='') + delta = ts-capture_start + print(f'\033[7mRun {run_id}, capturing for {delta//3600//24:> 3.0f}:{delta//3600%24:02.0f}:{delta//60%60:02.0f}:{delta%60:06.3f}\033[0m') db.commit() except Exception as e: diff --git a/gm_platform/platform/platform.sch b/gm_platform/platform/platform.sch index 8483ed7..40bb140 100644 --- a/gm_platform/platform/platform.sch +++ b/gm_platform/platform/platform.sch @@ -3021,5 +3021,5 @@ F 5 "SDCIT/8GB" H 12700 7050 50 0001 C CNN "Reichelt" 1 0 0 -1 $EndComp Text Notes 850 11050 0 50 ~ 0 -TO-DO\n* Add adc buffer op amp\n* join input resistors, adjust divider\n* earth secondary side\n* add slots for large isolation barrier\n* add slots for small isolation barrier\n* fixup/remove ocxo footprint\n* fixup converter footprint, use smaller part? use 5V one and power raspi?\n* remove uart isolation?\n* fix fuse rating\n* remove one fuse\n* remove lid switches?\n* fixup incorrect bom items\n* CH340 V3 cap -> 100n\n* replace CH340 w/ silabs cp2102\n* replace optocoupler pullups w/ 350R +TO-DO\n* Add adc buffer op amp\n* join input resistors, adjust divider\n* earth secondary side\n* add slots for large isolation barrier\n* add slots for small isolation barrier\n* fixup/remove ocxo footprint\n* fixup converter footprint, use smaller part? use 5V one and power raspi?\n* remove uart isolation?\n* fix fuse rating\n* remove one fuse\n* remove lid switches?\n* fixup incorrect bom items\n* CH340 V3 cap -> 100n\n* replace CH340 w/ silabs cp2102\n* replace optocoupler pullups w/ 350R\n* Remove 3V6 limiting zener in vmeas circuit $EndSCHEMATC diff --git a/grid_frequency_spectra.ipynb b/grid_frequency_spectra.ipynb index c861490..dc3bdee 100644 --- a/grid_frequency_spectra.ipynb +++ b/grid_frequency_spectra.ipynb @@ -821,7 +821,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -833,7 +833,7 @@ { "data": { "text/plain": [ - "[]" + "[]" ] }, "execution_count": 4, @@ -3337,7 +3337,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 12, "metadata": {}, "outputs": [ { @@ -4131,7 +4131,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -4154,9 +4154,10 @@ "\n", "#xs = np.linspace(len(data)/2, 1, len(data)/2)\n", "\n", - "fig, ax = plt.subplots()\n", + "fig, ax = plt.subplots(figsize=(9,5))\n", "ax.loglog(xs[s//2:-s//2+1], ys)\n", "ax.xaxis.set_major_formatter(plt.FuncFormatter(lambda x, _pos: 1/x))\n", + "ax.grid()\n", "plt.show()" ] }, -- cgit