{"id":46632,"date":"2021-02-17T00:00:00","date_gmt":"2021-02-17T08:00:00","guid":{"rendered":"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/blog\/a-python-based-iot-data-dashboard-part-one\/"},"modified":"2025-11-13T12:55:11","modified_gmt":"2025-11-13T20:55:11","slug":"a-python-based-iot-data-dashboard-part-one","status":"publish","type":"post","link":"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/blog\/a-python-based-iot-data-dashboard-part-one\/","title":{"rendered":"A Python-Based IoT Data Dashboard (Part One)"},"content":{"rendered":"<h2>Introduction and Purpose<\/h2>\n<h3>New Series: A Python-Based IoT Data Dashboard<\/h3>\n<p>Many of the Internet of Things businesses are startups and have limited resources. But a cautious spending strategy never hurts even for a big corporation.<\/p>\n<p>There are so many data visualization and real-time streaming tools around there. But, in fact, if all you need is a couple of charts that should be refreshed every two minutes, you do not have to spend your money on anything like that.<\/p>\n<p>This article is the opening one of a series of three that we prepared for you. In this series, we will show you how to build a simple Python app that will pull the IoT data from GridDB and display three essential IoT metrics.<\/p>\n<p>Based on the code we provide, you can create as many charts as you want, as well as customize them.<\/p>\n<p>The series consists of the following tutorials: * scheduling the dashboard refresh * building a simple app with Python kivy * finalizing the app and adding the desktop icon<\/p>\n<p>In the end, you will have a small but independent desktop application that you can start each morning and let it run during the day.<\/p>\n<p>The app will show the data with the minimum time lag, almost in real-time actually, without any active participation on your side.<\/p>\n<h3>The Use Case<\/h3>\n<p>The article uses a test dataset that was created with the Python packages for generating random data. It imitates the standard connectivity data that would come from any IoT devices. Or, to be precise, the data that the SIM cards installed inside such devices send in the background.<\/p>\n<p>Connectivity data is typically used by service engineers and support managers for troubleshooting and anomaly detection.<\/p>\n<h3>The Objectives<\/h3>\n<p>In this article, we will query the data and build three charts showing: * data usage * number of events * number of alerts<\/p>\n<p>We will focus on the \u00e2\u20ac\u0153Create PDP context\u00e2\u20ac\u009d events as an example. Read more about PDP context <a href=\"https:\/\/en.wikipedia.org\/wiki\/GPRS_core_network\">here<\/a>.<\/p>\n<p>Then, we will make the query run every ten minutes and refresh the data accordingly.<\/p>\n<h3>Methods<\/h3>\n<p>To start, we will use Jupyter Notebook. You may as well run the final code in a Python IDE.<\/p>\n<h3>Prerequisites<\/h3>\n<p>One of the previous posts explains in detail how to <a href=\"https:\/\/griddb.net\/en\/blog\/using-python-to-interface-with-griddb-via-jdbc-with-jaydebeapi\/\">query GridDB from a Jupyter Notebook<\/a>.<\/p>\n<h2>Main Part<\/h2>\n<h3>Preparations<\/h3>\n<h4>Fetch the Data From the Database<\/h4>\n<p>To get the data from the database, we establish a connection with the <strong>jaydebeapi<\/strong> Python package and use an SQL-based query with a <strong>WHERE<\/strong> clause.<\/p>\n<p>We used a GridDB-native function <strong>TIMESTAMP_ADD()<\/strong> to fetch only the data for the last hour. You can customize it as described in the <a href=\"https:\/\/docs.griddb.net\/sqlreference\/sql-commands-supported\/#time-functions\">GridDB SQL reference<\/a>.<\/p>\n<div class=\"clipboard\">\n<pre><code class=\"language-python\">import pandas as pd\nimport jaydebeapi\n\nconn = jaydebeapi.connect(\"com.toshiba.mwcloud.gs.sql.Driver\",\n                           \"jdbc:gs:\/\/griddb:20001\/defaultCluster\/public?notificationMember:127.0.0.1:20001\",\n                           [\"admin\", \"admin\"],\n                          \"\/usr\/share\/java\/gridstore-jdbc-4.5.0.jar\",)\niot = ('''SELECT \n\n    TIMESTAMP(timestamp) as timestamp,\n    event, simid, data_usage\nFROM IoT \n\nWHERE TIMESTAMP(timestamp) &lt; TIMESTAMP_ADD(HOUR, NOW(), -1)''')\niotdf = pd.read_sql_query(iot, conn)\niotdf.head()<\/code><\/pre>\n<\/div>\n<div>\n<style scoped>\n    .dataframe tbody tr th:only-of-type {\n        vertical-align: middle;\n    }<\/p>\n<p>    .dataframe tbody tr th {\n        vertical-align: top;\n    }<\/p>\n<p>    .dataframe thead th {\n        text-align: right;\n    }\n  <\/style>\n<table border=\"1\" class=\"dataframe\">\n<thead>\n<tr style=\"text-align: right;\">\n<th>\n        <\/th>\n<th>\n          timestamp\n        <\/th>\n<th>\n          event\n        <\/th>\n<th>\n          simid\n        <\/th>\n<th>\n          data_usage\n        <\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<th>\n          0\n        <\/th>\n<td>\n          2021-01-18 09:39:27.200000\n        <\/td>\n<td>\n          Create PDP context\n        <\/td>\n<td>\n          0003\n        <\/td>\n<td>\n          0.00\n        <\/td>\n<\/tr>\n<tr>\n<th>\n          1\n        <\/th>\n<td>\n          2021-01-18 09:39:28.200000\n        <\/td>\n<td>\n          data\n        <\/td>\n<td>\n          0003\n        <\/td>\n<td>\n          0.03\n        <\/td>\n<\/tr>\n<tr>\n<th>\n          2\n        <\/th>\n<td>\n          2021-01-18 09:39:29.200000\n        <\/td>\n<td>\n          Delete PDP context\n        <\/td>\n<td>\n          0003\n        <\/td>\n<td>\n          0.00\n        <\/td>\n<\/tr>\n<tr>\n<th>\n          3\n        <\/th>\n<td>\n          2021-01-18 09:40:05.200000\n        <\/td>\n<td>\n          Create PDP context\n        <\/td>\n<td>\n          0003\n        <\/td>\n<td>\n          0.00\n        <\/td>\n<\/tr>\n<tr>\n<th>\n          4\n        <\/th>\n<td>\n          2021-01-18 09:40:06.200000\n        <\/td>\n<td>\n          data\n        <\/td>\n<td>\n          0003\n        <\/td>\n<td>\n          0.03\n        <\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<p>This is a time series dataset. It contains timestamps, events assigned to these timestamps, IDs of the SIM cards that sent the timestamps, as well as &#8211; where applicable &#8211; the amount of internet traffic, the data usage in kB.<\/p>\n<h3>Pivoting the data<\/h3>\n<p>In Jupyter, the timestamp column may sometimes appear as a string column in the data frame, even if it has a correct timestamp format in the database.<\/p>\n<p>Convert it to the timestamp:<\/p>\n<div class=\"clipboard\">\n<pre><code class=\"language-sh\">iotdf['timestamp']= iotdf['timestamp'].apply(pd.to_datetime)<\/code><\/pre>\n<\/div>\n<p>We have a so-called &#8220;slim&#8221; database that has few columns. We need to unfold or spread it first. We split the event column into a few more ones, one column per event type. If an event occurs, the respective column is filled with the value 1.<\/p>\n<p>We transform the iot data frame into a pandas&#8217; pivot table using the <strong>pivot_table()<\/strong> function.<\/p>\n<div class=\"clipboard\">\n<pre><code class=\"language-python\">pivotdf = iotdf.pivot_table(index=['timestamp', 'simid', 'data_usage'],\n                                     columns='event', \n                                     values= 'event',\n                                     aggfunc=lambda x: 1)<\/code><\/pre>\n<\/div>\n<div class=\"clipboard\">\n<pre><code class=\"language-python\">pivotdf.head()<\/code><\/pre>\n<\/div>\n<div>\n<style scoped>\n    .dataframe tbody tr th:only-of-type {\n        vertical-align: middle;\n    }<\/p>\n<p>    .dataframe tbody tr th {\n        vertical-align: top;\n    }<\/p>\n<p>    .dataframe thead th {\n        text-align: right;\n    }\n  <\/style>\n<table border=\"1\" class=\"dataframe\">\n<thead>\n<tr style=\"text-align: right;\">\n<th>\n        <\/th>\n<th>\n        <\/th>\n<th>\n          event\n        <\/th>\n<th>\n          Create PDP context\n        <\/th>\n<th>\n          Delete PDP context\n        <\/th>\n<th>\n          alert\n        <\/th>\n<th>\n          data\n        <\/th>\n<\/tr>\n<tr>\n<th>\n          timestamp\n        <\/th>\n<th>\n          simid\n        <\/th>\n<th>\n          data_usage\n        <\/th>\n<th>\n        <\/th>\n<th>\n        <\/th>\n<th>\n        <\/th>\n<th>\n        <\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<th>\n          2021-01-18 09:39:27.200\n        <\/th>\n<th>\n          0003\n        <\/th>\n<th>\n          0.00\n        <\/th>\n<td>\n          1.0\n        <\/td>\n<td>\n          NaN\n        <\/td>\n<td>\n          NaN\n        <\/td>\n<td>\n          NaN\n        <\/td>\n<\/tr>\n<tr>\n<th>\n          2021-01-18 09:39:28.200\n        <\/th>\n<th>\n          0003\n        <\/th>\n<th>\n          0.03\n        <\/th>\n<td>\n          NaN\n        <\/td>\n<td>\n          NaN\n        <\/td>\n<td>\n          NaN\n        <\/td>\n<td>\n          1.0\n        <\/td>\n<\/tr>\n<tr>\n<th>\n          2021-01-18 09:39:29.200\n        <\/th>\n<th>\n          0003\n        <\/th>\n<th>\n          0.00\n        <\/th>\n<td>\n          NaN\n        <\/td>\n<td>\n          1.0\n        <\/td>\n<td>\n          NaN\n        <\/td>\n<td>\n          NaN\n        <\/td>\n<\/tr>\n<tr>\n<th>\n          2021-01-18 09:40:05.200\n        <\/th>\n<th>\n          0003\n        <\/th>\n<th>\n          0.00\n        <\/th>\n<td>\n          1.0\n        <\/td>\n<td>\n          NaN\n        <\/td>\n<td>\n          NaN\n        <\/td>\n<td>\n          NaN\n        <\/td>\n<\/tr>\n<tr>\n<th>\n          2021-01-18 09:40:06.200\n        <\/th>\n<th>\n          0003\n        <\/th>\n<th>\n          0.03\n        <\/th>\n<td>\n          NaN\n        <\/td>\n<td>\n          NaN\n        <\/td>\n<td>\n          NaN\n        <\/td>\n<td>\n          1.0\n        <\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<p>Since the data frame has complex column names, and three columns are indexes now, we polish it to make it look like a normal data frame again. We drop the indexes with the <strong>reset_index()<\/strong> function.<\/p>\n<p>Besides, we need to replace the NaNs with zero to be able to perform any calculations and actually plot the time series data. We use the <strong>fillna()<\/strong> function.<\/p>\n<div class=\"clipboard\">\n<pre><code class=\"language-python\">pivotdf = pivotdf.reset_index()    \npivotdf = pivotdf.fillna(0)<\/code><\/pre>\n<\/div>\n<p><code>pivotdf.head()<\/code><\/p>\n<div>\n<style scoped>\n    .dataframe tbody tr th:only-of-type {\n        vertical-align: middle;\n    }<\/p>\n<p>    .dataframe tbody tr th {\n        vertical-align: top;\n    }<\/p>\n<p>    .dataframe thead th {\n        text-align: right;\n    }\n  <\/style>\n<table border=\"1\" class=\"dataframe\">\n<thead>\n<tr style=\"text-align: right;\">\n<th>\n          event\n        <\/th>\n<th>\n          timestamp\n        <\/th>\n<th>\n          simid\n        <\/th>\n<th>\n          data_usage\n        <\/th>\n<th>\n          Create PDP context\n        <\/th>\n<th>\n          Delete PDP context\n        <\/th>\n<th>\n          alert\n        <\/th>\n<th>\n          data\n        <\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<th>\n          0\n        <\/th>\n<td>\n          2021-01-18 09:39:27.200\n        <\/td>\n<td>\n          0003\n        <\/td>\n<td>\n          0.00\n        <\/td>\n<td>\n          1.0\n        <\/td>\n<td>\n          0.0\n        <\/td>\n<td>\n          0.0\n        <\/td>\n<td>\n          0.0\n        <\/td>\n<\/tr>\n<tr>\n<th>\n          1\n        <\/th>\n<td>\n          2021-01-18 09:39:28.200\n        <\/td>\n<td>\n          0003\n        <\/td>\n<td>\n          0.03\n        <\/td>\n<td>\n          0.0\n        <\/td>\n<td>\n          0.0\n        <\/td>\n<td>\n          0.0\n        <\/td>\n<td>\n          1.0\n        <\/td>\n<\/tr>\n<tr>\n<th>\n          2\n        <\/th>\n<td>\n          2021-01-18 09:39:29.200\n        <\/td>\n<td>\n          0003\n        <\/td>\n<td>\n          0.00\n        <\/td>\n<td>\n          0.0\n        <\/td>\n<td>\n          1.0\n        <\/td>\n<td>\n          0.0\n        <\/td>\n<td>\n          0.0\n        <\/td>\n<\/tr>\n<tr>\n<th>\n          3\n        <\/th>\n<td>\n          2021-01-18 09:40:05.200\n        <\/td>\n<td>\n          0003\n        <\/td>\n<td>\n          0.00\n        <\/td>\n<td>\n          1.0\n        <\/td>\n<td>\n          0.0\n        <\/td>\n<td>\n          0.0\n        <\/td>\n<td>\n          0.0\n        <\/td>\n<\/tr>\n<tr>\n<th>\n          4\n        <\/th>\n<td>\n          2021-01-18 09:40:06.200\n        <\/td>\n<td>\n          0003\n        <\/td>\n<td>\n          0.03\n        <\/td>\n<td>\n          0.0\n        <\/td>\n<td>\n          0.0\n        <\/td>\n<td>\n          0.0\n        <\/td>\n<td>\n          1.0\n        <\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<p>Now, we are set to create visualizations.<\/p>\n<h3>Charts<\/h3>\n<p>We split each metric per device or SIM ID. It allows us to catch abnormalities in their behavior.<\/p>\n<p>When it comes to creating grouped charts, the <strong>seaborn<\/strong> package has proven to be the fastest way. We do win a couple of seconds. It would not have been so important in any other situation. But if we want a real-time dashboard, we need our charts to be generated very quickly.<\/p>\n<p>The seaborn package offers more than one opportunity to build grouped charts. We use the <strong>relational plot<\/strong> instead of the face grid, the second available option since the former works better with the line charts. And even though we have one event per timestamp, the line chart is a good choice to show continuity in the data. It also works better if we need to customize such chart parameters like the x-axis ticks rotations, etc.<\/p>\n<p>Below is an example with the data usage:<\/p>\n<div class=\"clipboard\">\n<pre><code class=\"language-python\">import seaborn as sns\nimport matplotlib.pyplot as plt\n\ng = sns.relplot(\ndata=pivotdf, x=\"timestamp\", y=\"data_usage\", col=\"simid\", hue=\"simid\",col_order = ['0001', '0002', '0003', '0004'],\n    kind=\"line\", palette=\"Set2\", linewidth=4, zorder=5,\n    col_wrap=1, height=2, aspect=7, legend=False,\n    )\n\nfor simid, ax in g.axes_dict.items():\n    ax.text(.8, .85, simid, transform=ax.transAxes, fontweight=\"bold\")\n\n    sns.lineplot(data=pivotdf, x=\"timestamp\", y=\"data_usage\", units=\"simid\",\n            estimator=None, color=\".7\", linewidth=1, ax=ax,\n        )\n\nax.set_xticks(ax.get_xticks()[::1])\ng.set_xticklabels(rotation=90)\n\ng.set_titles(\"\")\ng.fig.suptitle('DATA USAGE', horizontalalignment = 'right')\nText(0.5, 0.98, 'DATA USAGE')<\/code><\/pre>\n<\/div>\n<p><a href=\"https:\/\/griddb.net\/wp-content\/uploads\/2021\/02\/output_31_1.png\"><img fetchpriority=\"high\" decoding=\"async\" src=\"https:\/\/griddb.net\/wp-content\/uploads\/2021\/02\/output_31_1.png\" alt=\"\" width=\"998\" height=\"617\" class=\"aligncenter size-full wp-image-27288\" srcset=\"\/wp-content\/uploads\/2021\/02\/output_31_1.png 998w, \/wp-content\/uploads\/2021\/02\/output_31_1-300x185.png 300w, \/wp-content\/uploads\/2021\/02\/output_31_1-768x475.png 768w, \/wp-content\/uploads\/2021\/02\/output_31_1-600x371.png 600w\" sizes=\"(max-width: 998px) 100vw, 998px\" \/><\/a><\/p>\n<h2>Creating the Automatic Refresh<\/h2>\n<h3>Introducing the While Loop<\/h3>\n<p>We eventually arrive at the most exciting part!<\/p>\n<p>All we need is to make the code run again and again without our active participation.<\/p>\n<p>We use the <strong>while loop<\/strong> for this purpose. We simply wrap up the whole thing above in it (+ another two charts). And we start it once. Voal\u00c3\u00a1! As long as your Jupyter Notebook or the Python IDE remains open and the script has been run once, it will continue to fetch the data and display refreshed charts.<\/p>\n<h3>A Few Optimization Remarks<\/h3>\n<p>Before sharing the complete code, a few best practices you need to know: * You definitely do not want to scroll through numerous identical charts. To spare yourself this effort, add the <strong>clear_output()<\/strong> into the loop, <em>before<\/em> anything else. * Since you cannot clear the output that does not exist, use <strong>try\/except\/pass<\/strong> to prevent the script from failing when it runs for the first time. * Optional: use try\/except\/pass for the charts that can have too little data to be rendered properly. It lets the script jump to the next section without breaking and throwing the error message. * Use the <strong>plt.show()<\/strong> function after each plot from the <strong>matplotlib<\/strong> package: otherwise, only the last plot in the code will be displayed. * Use the <strong>sleep()<\/strong> function at the end of each loop run to <em>create a gap<\/em> if it does not make sense to query the database in &#8220;real&#8221; real time. Give both your database and your CPU some breath. * Last but not least, add your custom status messages all over the code to watch the progress and do some troubleshooting. For instance, add <em>print(&#8220;got new data&#8221;, datetime.now())<\/em> to mark when the new data was successfully fetched. Use <strong>datetime<\/strong> package to create your own timestamps.<\/p>\n<p>There you go!<\/p>\n<h3>The Final Code<\/h3>\n<div class=\"clipboard\">\n<pre><code class=\"language-python\">from datetime import datetime\nfrom IPython.display import clear_output\n\nimport time\nimport seaborn as sns\nimport matplotlib.pyplot as plt\nfrom PIL import Image, ImageDraw\nimport pandas as pd\nimport jaydebeapi\n\nconn = jaydebeapi.connect(\"com.toshiba.mwcloud.gs.sql.Driver\",\n                           \"jdbc:gs:\/\/griddb:20001\/defaultCluster\/public?notificationMember:127.0.0.1:20001\",\n                           [\"admin\", \"admin\"],\n                          \"\/usr\/share\/java\/gridstore-jdbc-4.5.0.jar\",)\n\ncurs = conn.cursor()\n\nstop = 1\nwhile stop > 0:\n    \n    #delete the previous charts if any\n    try:\n        clear_output(wait=True)\n    except:\n        pass\n    \n    #querying the data for the last hour\n    iot = ('''SELECT \n    TIMESTAMP(timestamp) as timestamp,\n    event, simid, data_usage\n    FROM IoT \n    WHERE TIMESTAMP(timestamp) > TIMESTAMP_ADD(HOUR, NOW(), -1)''')\n    iotdf = pd.read_sql_query(iot, conn)\n    print('got new data')\n    print(datetime.now())\n    \n    #data preparation\n    iotdf['timestamp']= iotdf['timestamp'].apply(pd.to_datetime)\n    pivotdf = iotdf.pivot_table(index=['timestamp', 'simid', 'data_usage'],\n                                     columns='event', \n                                     values= 'event',\n                                     aggfunc=lambda x: 1)\n    pivotdf = pivotdf.reset_index()    \n    pivotdf = pivotdf.fillna(0)\n    \n    #data visualization\n    order = ['0001', '0002', '0003', '0004']\n\n    \n    \n    #data usage\n    g = sns.relplot(\n    data=pivotdf,\n    x=\"timestamp\", y=\"data_usage\", col=\"simid\", hue=\"simid\",col_order = ['0001', '0002', '0003', '0004'],\n    kind=\"line\", palette=\"Set2\", linewidth=4, zorder=5,\n    col_wrap=1, height=2, aspect=7, legend=False,\n    )\n\n    for simid, ax in g.axes_dict.items():\n\n\n        ax.text(.8, .85, simid, transform=ax.transAxes, fontweight=\"bold\")\n\n\n        sns.lineplot(\n            data=pivotdf, x=\"timestamp\", y=\"data_usage\", units=\"simid\",\n            estimator=None, color=\".7\", linewidth=1, ax=ax,\n        )\n\n    ax.set_xticks(ax.get_xticks()[::1])\n    g.set_xticklabels(rotation=90)\n\n    g.set_titles(\"\")\n    g.fig.suptitle('DATA USAGE                   ', horizontalalignment = 'right')\n    \n    plt.show()\n    \n    #number of pdp events\n    g = sns.relplot(\n    data=pivotdf,\n    x=\"timestamp\", y=\"Create PDP context\", col=\"simid\", hue=\"simid\",col_order = ['0001', '0002', '0003', '0004'],\n    kind=\"line\", palette=\"Set2\", linewidth=4, zorder=5,\n    col_wrap=1, height=2, aspect=7, legend=False,\n    )\n\n    for simid, ax in g.axes_dict.items():\n\n\n        ax.text(.8, .85, simid, transform=ax.transAxes, fontweight=\"bold\")\n\n\n        sns.lineplot(\n            data=pivotdf, x=\"timestamp\", y=\"Create PDP context\", units=\"simid\",\n            estimator=None, color=\".7\", linewidth=1, ax=ax,\n        )\n\n    ax.set_xticks(ax.get_xticks()[::1])\n    g.set_xticklabels(rotation=90)\n\n    g.set_titles(\"\")\n    g.fig.suptitle('NUMBER OF PDP EVENTS                   ', horizontalalignment = 'right')\n    \n    plt.show()\n    \n    #alerts\n    g = sns.relplot(\n    data=pivotdf,\n    x=\"timestamp\", y=\"alert\", col=\"simid\", hue=\"simid\",col_order = ['0001', '0002', '0003', '0004'],\n    kind=\"line\", palette=\"Set2\", linewidth=4, zorder=5,\n    col_wrap=1, height=2, aspect=7, legend=False,\n    )\n\n    for simid, ax in g.axes_dict.items():\n\n\n        ax.text(.8, .85, simid, transform=ax.transAxes, fontweight=\"bold\")\n\n\n        sns.lineplot(\n            data=pivotdf, x=\"timestamp\", y=\"alert\", units=\"simid\",\n            estimator=None, color=\".7\", linewidth=1, ax=ax,\n        )\n\n    ax.set_xticks(ax.get_xticks()[::1])\n    g.set_xticklabels(rotation=90)\n\n    g.set_titles(\"\")\n    g.fig.suptitle('NUMBER OF ALERTS                      ', horizontalalignment = 'right')\n    \n    plt.show()\n\n    #putting it into sleep for 10 seconds\n    print('falling asleep')\n    print(datetime.now())\n    time.sleep(60)\n<\/code><\/pre>\n<\/div>\n<p><code>got new data<br \/>\n2021-01-19 11:46:05.329856<\/code><\/p>\n<p><a href=\"https:\/\/griddb.net\/wp-content\/uploads\/2021\/02\/output_38_1.png\"><img decoding=\"async\" src=\"https:\/\/griddb.net\/wp-content\/uploads\/2021\/02\/output_38_1.png\" alt=\"\" width=\"1015\" height=\"617\" class=\"aligncenter size-full wp-image-27289\" srcset=\"\/wp-content\/uploads\/2021\/02\/output_38_1.png 1015w, \/wp-content\/uploads\/2021\/02\/output_38_1-300x182.png 300w, \/wp-content\/uploads\/2021\/02\/output_38_1-768x467.png 768w, \/wp-content\/uploads\/2021\/02\/output_38_1-600x365.png 600w\" sizes=\"(max-width: 1015px) 100vw, 1015px\" \/><\/a><\/p>\n<p><a href=\"https:\/\/griddb.net\/wp-content\/uploads\/2021\/02\/output_38_2.png\"><img decoding=\"async\" src=\"https:\/\/griddb.net\/wp-content\/uploads\/2021\/02\/output_38_2.png\" alt=\"\" width=\"1014\" height=\"617\" class=\"aligncenter size-full wp-image-27283\" srcset=\"\/wp-content\/uploads\/2021\/02\/output_38_2.png 1014w, \/wp-content\/uploads\/2021\/02\/output_38_2-300x183.png 300w, \/wp-content\/uploads\/2021\/02\/output_38_2-768x467.png 768w, \/wp-content\/uploads\/2021\/02\/output_38_2-600x365.png 600w\" sizes=\"(max-width: 1014px) 100vw, 1014px\" \/><\/a><\/p>\n<p><a href=\"https:\/\/griddb.net\/wp-content\/uploads\/2021\/02\/output_38_3.png\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/griddb.net\/wp-content\/uploads\/2021\/02\/output_38_3.png\" alt=\"\" width=\"1014\" height=\"617\" class=\"aligncenter size-full wp-image-27290\" srcset=\"\/wp-content\/uploads\/2021\/02\/output_38_3.png 1014w, \/wp-content\/uploads\/2021\/02\/output_38_3-300x183.png 300w, \/wp-content\/uploads\/2021\/02\/output_38_3-768x467.png 768w, \/wp-content\/uploads\/2021\/02\/output_38_3-600x365.png 600w\" sizes=\"(max-width: 1014px) 100vw, 1014px\" \/><\/a><\/p>\n<p><code>falling asleep<br \/>\n2021-01-19 11:46:18.786249<\/code><\/p>\n<h4>Note<\/h4>\n<p>If you prefer to use Jupyter, you can expand the output section by clicking on it with the mouse and then using the CMD + O (the letter <em>o<\/em>).<\/p>\n<h2>Wait!<\/h2>\n<p>What about the while loop condition? Aren&#8217;t we going to descrease <em>stop<\/em>? Where does it come from, by the way?<\/p>\n<p>No, we won&#8217;t descrease the <em>stop<\/em> variable. We do not want the loop to stop at all. You will stop it, when you close the Jupyter Notebook.<\/p>\n<p>The condition was chosen arbitrarily. We just needed some random variable.<\/p>\n<h2>A Short Afterward<\/h2>\n<p>We have built an IoT connectivity data dashboard and make it refresh itself every ten seconds. Next time, we will integrate it into a small independent application.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Introduction and Purpose New Series: A Python-Based IoT Data Dashboard Many of the Internet of Things businesses are startups and have limited resources. But a cautious spending strategy never hurts even for a big corporation. There are so many data visualization and real-time streaming tools around there. But, in fact, if all you need is [&hellip;]<\/p>\n","protected":false},"author":41,"featured_media":27283,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[121],"tags":[],"class_list":["post-46632","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-blog"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.1.1 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>A Python-Based IoT Data Dashboard (Part One) | GridDB: Open Source Time Series Database for IoT<\/title>\n<meta name=\"description\" content=\"Introduction and Purpose New Series: A Python-Based IoT Data Dashboard Many of the Internet of Things businesses are startups and have limited resources.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/griddb.net\/en\/blog\/a-python-based-iot-data-dashboard-part-one\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"A Python-Based IoT Data Dashboard (Part One) | GridDB: Open Source Time Series Database for IoT\" \/>\n<meta property=\"og:description\" content=\"Introduction and Purpose New Series: A Python-Based IoT Data Dashboard Many of the Internet of Things businesses are startups and have limited resources.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/griddb.net\/en\/blog\/a-python-based-iot-data-dashboard-part-one\/\" \/>\n<meta property=\"og:site_name\" content=\"GridDB: Open Source Time Series Database for IoT\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/griddbcommunity\/\" \/>\n<meta property=\"article:published_time\" content=\"2021-02-17T08:00:00+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2025-11-13T20:55:11+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/wp-content\/uploads\/2021\/02\/output_38_2.png\" \/>\n\t<meta property=\"og:image:width\" content=\"1014\" \/>\n\t<meta property=\"og:image:height\" content=\"617\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"author\" content=\"griddb-admin\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@GridDBCommunity\" \/>\n<meta name=\"twitter:site\" content=\"@GridDBCommunity\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"griddb-admin\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"10 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/griddb.net\/en\/blog\/a-python-based-iot-data-dashboard-part-one\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/griddb.net\/en\/blog\/a-python-based-iot-data-dashboard-part-one\/\"},\"author\":{\"name\":\"griddb-admin\",\"@id\":\"https:\/\/griddb.net\/en\/#\/schema\/person\/4fe914ca9576878e82f5e8dd3ba52233\"},\"headline\":\"A Python-Based IoT Data Dashboard (Part One)\",\"datePublished\":\"2021-02-17T08:00:00+00:00\",\"dateModified\":\"2025-11-13T20:55:11+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/griddb.net\/en\/blog\/a-python-based-iot-data-dashboard-part-one\/\"},\"wordCount\":1317,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/griddb.net\/en\/#organization\"},\"image\":{\"@id\":\"https:\/\/griddb.net\/en\/blog\/a-python-based-iot-data-dashboard-part-one\/#primaryimage\"},\"thumbnailUrl\":\"\/wp-content\/uploads\/2021\/02\/output_38_2.png\",\"articleSection\":[\"Blog\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/griddb.net\/en\/blog\/a-python-based-iot-data-dashboard-part-one\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/griddb.net\/en\/blog\/a-python-based-iot-data-dashboard-part-one\/\",\"url\":\"https:\/\/griddb.net\/en\/blog\/a-python-based-iot-data-dashboard-part-one\/\",\"name\":\"A Python-Based IoT Data Dashboard (Part One) | GridDB: Open Source Time Series Database for IoT\",\"isPartOf\":{\"@id\":\"https:\/\/griddb.net\/en\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/griddb.net\/en\/blog\/a-python-based-iot-data-dashboard-part-one\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/griddb.net\/en\/blog\/a-python-based-iot-data-dashboard-part-one\/#primaryimage\"},\"thumbnailUrl\":\"\/wp-content\/uploads\/2021\/02\/output_38_2.png\",\"datePublished\":\"2021-02-17T08:00:00+00:00\",\"dateModified\":\"2025-11-13T20:55:11+00:00\",\"description\":\"Introduction and Purpose New Series: A Python-Based IoT Data Dashboard Many of the Internet of Things businesses are startups and have limited resources.\",\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/griddb.net\/en\/blog\/a-python-based-iot-data-dashboard-part-one\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/griddb.net\/en\/blog\/a-python-based-iot-data-dashboard-part-one\/#primaryimage\",\"url\":\"\/wp-content\/uploads\/2021\/02\/output_38_2.png\",\"contentUrl\":\"\/wp-content\/uploads\/2021\/02\/output_38_2.png\",\"width\":1014,\"height\":617},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/griddb.net\/en\/#website\",\"url\":\"https:\/\/griddb.net\/en\/\",\"name\":\"GridDB: Open Source Time Series Database for IoT\",\"description\":\"GridDB is an open source time-series database with the performance of NoSQL and convenience of SQL\",\"publisher\":{\"@id\":\"https:\/\/griddb.net\/en\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/griddb.net\/en\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/griddb.net\/en\/#organization\",\"name\":\"Fixstars\",\"url\":\"https:\/\/griddb.net\/en\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/griddb.net\/en\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/griddb.net\/wp-content\/uploads\/2019\/04\/fixstars_logo_web_tagline.png\",\"contentUrl\":\"https:\/\/griddb.net\/wp-content\/uploads\/2019\/04\/fixstars_logo_web_tagline.png\",\"width\":200,\"height\":83,\"caption\":\"Fixstars\"},\"image\":{\"@id\":\"https:\/\/griddb.net\/en\/#\/schema\/logo\/image\/\"},\"sameAs\":[\"https:\/\/www.facebook.com\/griddbcommunity\/\",\"https:\/\/x.com\/GridDBCommunity\",\"https:\/\/www.linkedin.com\/company\/griddb-by-toshiba\"]},{\"@type\":\"Person\",\"@id\":\"https:\/\/griddb.net\/en\/#\/schema\/person\/4fe914ca9576878e82f5e8dd3ba52233\",\"name\":\"griddb-admin\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/griddb.net\/en\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/5bceca1cafc06886a7ba873e2f0a28011a1176c4dea59709f735b63ae30d0342?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/5bceca1cafc06886a7ba873e2f0a28011a1176c4dea59709f735b63ae30d0342?s=96&d=mm&r=g\",\"caption\":\"griddb-admin\"},\"url\":\"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/author\/griddb-admin\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"A Python-Based IoT Data Dashboard (Part One) | GridDB: Open Source Time Series Database for IoT","description":"Introduction and Purpose New Series: A Python-Based IoT Data Dashboard Many of the Internet of Things businesses are startups and have limited resources.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/griddb.net\/en\/blog\/a-python-based-iot-data-dashboard-part-one\/","og_locale":"en_US","og_type":"article","og_title":"A Python-Based IoT Data Dashboard (Part One) | GridDB: Open Source Time Series Database for IoT","og_description":"Introduction and Purpose New Series: A Python-Based IoT Data Dashboard Many of the Internet of Things businesses are startups and have limited resources.","og_url":"https:\/\/griddb.net\/en\/blog\/a-python-based-iot-data-dashboard-part-one\/","og_site_name":"GridDB: Open Source Time Series Database for IoT","article_publisher":"https:\/\/www.facebook.com\/griddbcommunity\/","article_published_time":"2021-02-17T08:00:00+00:00","article_modified_time":"2025-11-13T20:55:11+00:00","og_image":[{"width":1014,"height":617,"url":"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/wp-content\/uploads\/2021\/02\/output_38_2.png","type":"image\/png"}],"author":"griddb-admin","twitter_card":"summary_large_image","twitter_creator":"@GridDBCommunity","twitter_site":"@GridDBCommunity","twitter_misc":{"Written by":"griddb-admin","Est. reading time":"10 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/griddb.net\/en\/blog\/a-python-based-iot-data-dashboard-part-one\/#article","isPartOf":{"@id":"https:\/\/griddb.net\/en\/blog\/a-python-based-iot-data-dashboard-part-one\/"},"author":{"name":"griddb-admin","@id":"https:\/\/griddb.net\/en\/#\/schema\/person\/4fe914ca9576878e82f5e8dd3ba52233"},"headline":"A Python-Based IoT Data Dashboard (Part One)","datePublished":"2021-02-17T08:00:00+00:00","dateModified":"2025-11-13T20:55:11+00:00","mainEntityOfPage":{"@id":"https:\/\/griddb.net\/en\/blog\/a-python-based-iot-data-dashboard-part-one\/"},"wordCount":1317,"commentCount":0,"publisher":{"@id":"https:\/\/griddb.net\/en\/#organization"},"image":{"@id":"https:\/\/griddb.net\/en\/blog\/a-python-based-iot-data-dashboard-part-one\/#primaryimage"},"thumbnailUrl":"\/wp-content\/uploads\/2021\/02\/output_38_2.png","articleSection":["Blog"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/griddb.net\/en\/blog\/a-python-based-iot-data-dashboard-part-one\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/griddb.net\/en\/blog\/a-python-based-iot-data-dashboard-part-one\/","url":"https:\/\/griddb.net\/en\/blog\/a-python-based-iot-data-dashboard-part-one\/","name":"A Python-Based IoT Data Dashboard (Part One) | GridDB: Open Source Time Series Database for IoT","isPartOf":{"@id":"https:\/\/griddb.net\/en\/#website"},"primaryImageOfPage":{"@id":"https:\/\/griddb.net\/en\/blog\/a-python-based-iot-data-dashboard-part-one\/#primaryimage"},"image":{"@id":"https:\/\/griddb.net\/en\/blog\/a-python-based-iot-data-dashboard-part-one\/#primaryimage"},"thumbnailUrl":"\/wp-content\/uploads\/2021\/02\/output_38_2.png","datePublished":"2021-02-17T08:00:00+00:00","dateModified":"2025-11-13T20:55:11+00:00","description":"Introduction and Purpose New Series: A Python-Based IoT Data Dashboard Many of the Internet of Things businesses are startups and have limited resources.","inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/griddb.net\/en\/blog\/a-python-based-iot-data-dashboard-part-one\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/griddb.net\/en\/blog\/a-python-based-iot-data-dashboard-part-one\/#primaryimage","url":"\/wp-content\/uploads\/2021\/02\/output_38_2.png","contentUrl":"\/wp-content\/uploads\/2021\/02\/output_38_2.png","width":1014,"height":617},{"@type":"WebSite","@id":"https:\/\/griddb.net\/en\/#website","url":"https:\/\/griddb.net\/en\/","name":"GridDB: Open Source Time Series Database for IoT","description":"GridDB is an open source time-series database with the performance of NoSQL and convenience of SQL","publisher":{"@id":"https:\/\/griddb.net\/en\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/griddb.net\/en\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/griddb.net\/en\/#organization","name":"Fixstars","url":"https:\/\/griddb.net\/en\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/griddb.net\/en\/#\/schema\/logo\/image\/","url":"https:\/\/griddb.net\/wp-content\/uploads\/2019\/04\/fixstars_logo_web_tagline.png","contentUrl":"https:\/\/griddb.net\/wp-content\/uploads\/2019\/04\/fixstars_logo_web_tagline.png","width":200,"height":83,"caption":"Fixstars"},"image":{"@id":"https:\/\/griddb.net\/en\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/www.facebook.com\/griddbcommunity\/","https:\/\/x.com\/GridDBCommunity","https:\/\/www.linkedin.com\/company\/griddb-by-toshiba"]},{"@type":"Person","@id":"https:\/\/griddb.net\/en\/#\/schema\/person\/4fe914ca9576878e82f5e8dd3ba52233","name":"griddb-admin","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/griddb.net\/en\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/5bceca1cafc06886a7ba873e2f0a28011a1176c4dea59709f735b63ae30d0342?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/5bceca1cafc06886a7ba873e2f0a28011a1176c4dea59709f735b63ae30d0342?s=96&d=mm&r=g","caption":"griddb-admin"},"url":"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/author\/griddb-admin\/"}]}},"_links":{"self":[{"href":"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/wp-json\/wp\/v2\/posts\/46632","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/wp-json\/wp\/v2\/users\/41"}],"replies":[{"embeddable":true,"href":"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/wp-json\/wp\/v2\/comments?post=46632"}],"version-history":[{"count":1,"href":"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/wp-json\/wp\/v2\/posts\/46632\/revisions"}],"predecessor-version":[{"id":51308,"href":"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/wp-json\/wp\/v2\/posts\/46632\/revisions\/51308"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/wp-json\/wp\/v2\/media\/27283"}],"wp:attachment":[{"href":"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/wp-json\/wp\/v2\/media?parent=46632"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/wp-json\/wp\/v2\/categories?post=46632"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/wp-json\/wp\/v2\/tags?post=46632"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}