diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..90f8fbb
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,176 @@
+# Created by https://www.toptal.com/developers/gitignore/api/python
+# Edit at https://www.toptal.com/developers/gitignore?templates=python
+
+### Python ###
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+share/python-wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+# Usually these files are written by a python script from a template
+# before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.nox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+*.py,cover
+.hypothesis/
+.pytest_cache/
+cover/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+db.sqlite3
+db.sqlite3-journal
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+.pybuilder/
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# IPython
+profile_default/
+ipython_config.py
+
+# pyenv
+# For a library or package, you might want to ignore these files since the code is
+# intended to run in multiple environments; otherwise, check them in:
+# .python-version
+
+# pipenv
+# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
+# However, in case of collaboration, if having platform-specific dependencies or dependencies
+# having no cross-platform support, pipenv may install dependencies that don't work, or not
+# install all needed dependencies.
+#Pipfile.lock
+
+# poetry
+# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
+# This is especially recommended for binary packages to ensure reproducibility, and is more
+# commonly ignored for libraries.
+# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
+#poetry.lock
+
+# pdm
+# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
+#pdm.lock
+# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
+# in version control.
+# https://pdm.fming.dev/#use-with-ide
+.pdm.toml
+
+# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
+__pypackages__/
+
+# Celery stuff
+celerybeat-schedule
+celerybeat.pid
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+.dmypy.json
+dmypy.json
+
+# Pyre type checker
+.pyre/
+
+# pytype static type analyzer
+.pytype/
+
+# Cython debug symbols
+cython_debug/
+
+# PyCharm
+# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
+# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
+# and can be added to the global gitignore or merged into this file. For a more nuclear
+# option (not recommended) you can uncomment the following to ignore the entire idea folder.
+#.idea/
+
+### Python Patch ###
+# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration
+poetry.toml
+
+# ruff
+.ruff_cache/
+
+# LSP config files
+pyrightconfig.json
+
+# End of https://www.toptal.com/developers/gitignore/api/python
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..8ae0b95
--- /dev/null
+++ b/README.md
@@ -0,0 +1,17 @@
+# RespDoppler
+
+## 説明
+
+- ドプラーセンサで呼吸データを取得
+- 参照として呼吸計測バンドを使う
+
+## ファイル説明
+
+- gdx/ : GoDirect ライブラリ
+- gdx_getting_started_usb.py : GoDirect サンプルコード
+
+## 参考
+
+- [VernierST/godirect-examples](https://github.com/VernierST/godirect-examples)
+- [Seeed XIAO RP2040 を試してみました](https://aloseed.com/it/xiao_rp2040/)
+- [シリアルポートからくるデータをリアルタイムにプロットする](https://qiita.com/aerialist/items/ad2cfa95ca5a195f1e09)
diff --git a/gdx/__init__.py b/gdx/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gdx/__init__.py
diff --git a/gdx/gdx.py b/gdx/gdx.py
new file mode 100644
index 0000000..2a5f7c5
--- /dev/null
+++ b/gdx/gdx.py
@@ -0,0 +1,977 @@
+# To use Go Direct sensors with Python 3 you must install the godirect module
+# with the command: pip3 install godirect
+
+from godirect import GoDirect
+
+import logging
+import time
+
+# gdx_vpython.py contains functions for a canvas with data collection buttons
+from gdx import gdx_vpython
+vp = gdx_vpython.ver_vpython()
+
+
+logging.basicConfig()
+#logging.getLogger('godirect').setLevel(logging.DEBUG)
+#logging.getLogger('bleak').setLevel(logging.DEBUG)
+#logging.getLogger('pygatt').setLevel(logging.DEBUG)
+
+
+class gdx:
+
+ # 1.0.0 is the first time a version was created. This was when the VPython functions were added
+ VERSION = "1.0.0"
+
+ # Variables passed between the gdx functions.
+
+ # devices - a 1D list of the connected Go Direct device objects.
+ devices = []
+ # device_sensors - a 2D list of the sensor numbers selected by the user [[1,2],[1]]
+ device_sensors = []
+ # enabled_sensors - a 2D list of the sensor objects that have been enabled for data collection.
+ enabled_sensors = []
+ # buffer - a 2D list to store the excess data from a sensor when multi-points are collected from a read due to fast collection.
+ buffer = []
+ # ble_open - this is a flag to keep track of when godirect is asked to open ble, to make sure it's not asked twice.
+ ble_open = False
+ # is this a vpython program
+ vpython = False
+ # is there a vpython buttons?
+ vpython_buttons = False
+ # is there a vpython chart?
+ vpython_chart = False
+ # is there a vpython meters?
+ vpython_meters = False
+ # is there a vpython slider for sample period?
+ vpython_slider = False
+ # is this the first time calling start() in a vpython program?
+ vp_first_start = True
+ # store the period. may be used in vp_start_button()
+ period = 100
+ # vp start button flag. Use this to determine if gdx.start() or gdx.stop() need to be called
+ vp_start_button_flag = False
+
+ def __init__(self):
+
+ self.godirect = GoDirect(use_ble=False, use_usb=False)
+
+ def get_version(self):
+ """ get the version of the gdx module
+ """
+ version = self.VERSION
+
+ return version
+
+ # this open() function combines the original open_ble() and open_usb()
+ def open(self, connection='usb', device_to_open=None):
+ """ Open a Go Direct device via Bluetooth or USB for data collection.
+
+ Args:
+ connection (str): set as 'usb' or 'ble'
+
+ device_to_open: Leave this argument blank to provide a list in the terminal of all
+ discovered Go Direct devices. The user then chooses the device or devices from
+ the prompt. To run code without a prompt, the argument can be set to a specific
+ Go Direct device name or names. For example, "GDX-FOR 071000U9" or
+ "GDX-FOR 071000U9, GDX-HD 151000C1". In addition, if connection='ble', the argument
+ can be set to "proximity_pairing" to open the device with the highest
+ rssi (closest proximity).
+ """
+
+ if connection == 'ble' or connection == 'BLE':
+ self.open_ble(device_to_open)
+ elif connection == 'usb' or connection == 'USB':
+ self.open_usb(device_to_open)
+ else:
+ print("Unknown value for connection in gdx.open(). Use 'usb' or 'ble'.")
+
+ def open_usb(self, device_to_open=None):
+ """ Discovers all Go Direct devices with a USB connection and opens those devices
+ for data collection.
+ """
+
+ # Call the godirect module to open a USB connection
+ self.godirect.__init__(use_ble=False, use_usb=True)
+
+ found_devices, number_found_devices = self.find_devices()
+
+ if number_found_devices >= 1:
+ # need to have the usb device open in order to get its name
+ open_usb_devices = self.open_all_usb_devices_to_get_name(found_devices)
+ if open_usb_devices >= 1:
+ if device_to_open != None:
+ self.select_dev_using_sn(found_devices, device_to_open)
+ else:
+ # if just one device is connected, then automatically connect that device (no prompt)
+ if open_usb_devices == 1:
+ gdx.devices = found_devices
+ else:
+ self.user_chooses_device(found_devices)
+ else:
+ print("USB device found but error trying to open")
+ print(f"The number of USB devices found is {number_found_devices}")
+ print("If this is more than connected Go Direct devices, there might")
+ print("be another USB device (like a hub) that is being detected")
+ print("Try moving the hub to a different port.")
+ print("Otherwise, open Graphical Analysis to verify a connection")
+ else:
+ str1 = "\nNo Go Direct device found \n"
+ str2 = "Troubleshooting tips... \n\n"
+ str3 = " - Reconnect the USB cable \n"
+ str4 = " - Try a different USB port \n"
+ str5 = " - Try a different USB cable \n"
+ str6 = " - Open GA (Graphical Analysis) to verify a good connection \n"
+ print(str1 + str2 + str3 +str4 +str5 +str6)
+
+ def open_ble(self, device_to_open=None):
+ """ Open a Go Direct device via bluetooth for data collection.
+
+ Args:
+ device_to_open: Leave this argument blank to provide a list in the termial of all discovered
+ Go Direct devices. The user then chooses the device or devices from the prompt. To run code
+ without a prompt, the argument can be set to a specific Go Direct device name or names.
+ For example, "GDX-FOR 071000U9" or "GDX-FOR 071000U9, GDX-HD 151000C1". In addition, the argument
+ can be set to "proximity_pairing" to open the device with the highest rssi (closest proximity).
+ """
+
+ if gdx.ble_open == True:
+ #print("open_ble() - ble already open")
+ return
+
+ print("wait for bluetooth initialization...")
+
+ # Tell godirect you want to use ble. If you need to use the Bluegiga dongle, set the
+ # use_ble_bg equal to True (uncomment the command)
+ self.godirect.__init__(use_ble=True, use_ble_bg=False, use_usb=False)
+ #self.godirect.__init__(use_ble=True, use_ble_bg=True, use_usb=False)
+ found_devices, number_found_devices = self.find_devices()
+
+ # Was there 1 or more Go Direct ble devices found?
+ # print("found " +str(number_found_devices) + " devices:")
+ if number_found_devices >= 1:
+
+ if device_to_open == "proximity_pairing":
+ self.proximity_pairing(found_devices, number_found_devices)
+ elif device_to_open != None:
+ self.select_dev_using_sn(found_devices, device_to_open)
+ else:
+ # if it is just 1 device, then connect without the popup
+ if number_found_devices == 1:
+ gdx.devices = found_devices
+ else:
+ self.user_chooses_device(found_devices)
+
+ open_success = self.open_selected_device()
+ if open_success == False:
+ print("Error while trying to open device. ")
+ print("Troubleshoot by opening Graphical Analysis to test")
+
+ else:
+ str1 = "No Go Direct device found \n\n"
+ str2 = "Troubleshooting tips... \n"
+ str3 = "Make sure device is powered on \n"
+ str4 = "Confirm computer Bluetooth is on \n"
+ str5 = "Open GA (Graphical Analysis) to verify a good connection \n"
+ print(str1 + str2 + str3 +str4 +str5)
+
+ def find_devices(self):
+ """ determine how many Go Direct devices are found (usb or ble). Returns a list
+ of GoDirectDevice objects and the number of devices.
+ """
+ try:
+ found_devices = self.godirect.list_devices()
+ number_found_devices = len(found_devices)
+ #print("number of devices found = " +str(number_found_devices))
+ except:
+ #print("No Go Direct devices found")
+ found_devices = 0
+ number_found_devices = 0
+ gdx.devices = []
+ if number_found_devices == 0:
+ gdx.devices = []
+ return found_devices, number_found_devices
+
+ def open_all_usb_devices_to_get_name(self, found_devices):
+ """ Unfortunately, cannot get the name (like, 'GDX-FOR 071000U9') from
+ a USB device until it is open. So, open all available USB devices.
+ """
+
+ #print("attempting to open", len(found_devices), "device(s)...")
+ i = 0
+ open_usb_devices = 0
+ while i < len(found_devices):
+ try:
+ open_device_success = found_devices[i].open()
+ if open_device_success:
+ open_usb_devices += 1
+ #print("open device ",i, " = ", open_device_success, sep="")
+ i += 1
+ except:
+ open_usb_devices = 0
+ break
+
+ return open_usb_devices
+
+ def select_dev_using_sn(self, found_devices, device_to_open):
+ """ The case below occurs when the device_to_open argument is given a specific device
+ name or names, such as "GDX-FOR 071000U9" or "GDX-FOR 071000U9, GDX-HD 151000C1"
+ In the for loop each device to open is compared to the devices found in the list of
+ found_devices. If the names match, then we store the device as a device to open.
+ """
+
+ device_name_list = []
+ for device in found_devices:
+ #print("name of available device: ", str(device.name))
+ device_name_list.append(str(device.name))
+
+ device_to_open_list = device_to_open.split(", ")
+ for x in device_to_open_list:
+ #print("name of device wanting to open: ", x)
+ for device in found_devices:
+ if x == str(device.name):
+ #print("device names match = True")
+ gdx.devices.append(device)
+ else:
+ #print("device names match = False")
+ pass
+
+ if len(device_to_open_list) == len(gdx.devices):
+ pass
+ else:
+ print("serial number matching error. Check for typos in device_to_open")
+ print("device_to_open = ", device_to_open_list)
+ print("found devices = ", device_name_list)
+
+ def user_chooses_device(self, found_devices):
+ """ The case below occurs when there is no device_to_open argument. In this case, provide
+ a list of all discovered ble sensors and the user chooses which device or devices to open.
+ """
+
+ i=1
+ print('\n')
+ print("List of found devices")
+ for d in found_devices:
+ print(str(i)+": "+str(d))
+ i += 1
+
+ if len(found_devices) == 1:
+ # If there is just 1 usb or ble device the user only has to hit Enter
+ print('\n')
+ print("One device found. Press 'enter' to connect", end=' ')
+ input()
+ gdx.devices.append(found_devices[0])
+ else:
+ print('\n')
+ print("- If connecting a single device, type the number (e.g., 1) that")
+ print("corresponds with the device, and then press 'enter'.")
+ print("- If connnecting multiple devices, type in each number")
+ print("separated with commas with no spaces(e.g., 1,2), and then ")
+ print("press 'enter':", end=' ')
+
+ user_selected_device = []
+ for s in input().split(','):
+ user_selected_device.append(int(s))
+ for selected in user_selected_device:
+ gdx.devices.append(found_devices[selected-1])
+ print('\n')
+
+ def proximity_pairing(self, found_devices, number_found_devices):
+ """ The case below occurs when the device_to_open parameter = "proximity_pairing"
+ In the for loop each device, in the list of found_devices, is pulled out one at a time.
+ That device's rssi is compared to the previous highest rssi.
+ The device with the highest rssi is stored as the device to open
+ """
+
+ print ("begin proximity pairing")
+ i=1
+ rmax=-99
+ dmax=0
+ for device in found_devices:
+ print(str(i)+": "+str(device))
+ v=device.rssi
+ if v>rmax:
+ dmax=i
+ rmax=v
+ #print("rmax: ", rmax," dmax: ", dmax)
+ i+= 1
+ x=dmax
+ selected = int(x)
+ if selected <= number_found_devices:
+ gdx.devices.append(found_devices[selected-1])
+ print("proximity device to open = ", found_devices[selected-1] )
+ else:
+ print("Error in proximity selection")
+
+ def open_selected_device(self):
+ """ Open the device or devices that were selected in one of the cases above.
+ """
+
+ open_success = False
+ i = 0
+ print("attempting to open", len(gdx.devices), "device(s)...")
+ while i < len(gdx.devices):
+ try:
+ open_device_success = gdx.devices[i].open()
+ print("open device ",i, " = ", open_device_success, sep="")
+ if open_device_success:
+ open_success = True
+ gdx.ble_open = True
+ else:
+ open_success = False
+ return open_success
+ time.sleep(1)
+ i +=1
+ except:
+ open_success = False
+ break
+
+ return open_success
+
+ def select_sensors(self, sensors=None):
+ """ Select the sensors you wish to enable for data collection.
+
+ Args:
+ sensors []: if the sensors argument is left blank, a list of all available sensors is provided
+ by a prompt in the terminal for the user to select from. To run code without a prompt, set this argument
+ as a 1D list or a 2D list of lists of the sensors you wish to enable, such as [1,2,3] to enable
+ sensors 1,2 and 3 for one device, or [[1,2,3],[1,2]] to enable sensors 1,2 and 3 for one device and
+ sensors 1 and 2 for a second device.
+ """
+
+ # First check to make sure there are devices connected.
+ if not gdx.devices:
+ print("select_sensors() - no device connected")
+ return
+
+ # If the sensors argument is left blank provide an input prompt for the user to select sensors
+ if sensors == None:
+ i = 0
+ while i < len(gdx.devices):
+ selected_sensors = []
+ print('\n')
+ print("List of sensors for", gdx.devices[i])
+ sensors = gdx.devices[i].list_sensors()
+ for s in sensors:
+ c = sensors[s]
+ print(str(c))
+
+ print('\n')
+ print("- If connecting a single sensor, type the number (e.g., 1) that")
+ print("corresponds with the sensor, and then press 'enter'.")
+ print("- If connecting multiple sensors, type in each number")
+ print("separated with commas with no spaces(e.g., 1,2), and then ")
+ print("click 'enter':", end=' ')
+
+ for x in input().split(','):
+ selected_sensors.append(int(x))
+ gdx.device_sensors.append(selected_sensors)
+ i += 1
+
+ # If there is a sensor argument, it could be an integer, a list (1D), or a list of lists (2D).
+ else:
+ # Checks if the variable is a list
+ if type(sensors) == list:
+ # It is a list. Checks if it is a 2D list
+ if isinstance(sensors[0], list):
+ # Does this 2D sensor list have a list of sensors for each device?
+ if len(sensors)!= len(gdx.devices):
+ print("the sensor parameter in select_sensors() does not match number of devices")
+ gdx.devices = []
+ return
+ else:
+ # Save the 2D list of sensors in device_sensors, such as [[1],[1,2,3]]
+ #print("2d list of sensors = ", sensors)
+ gdx.device_sensors = sensors
+ # it is a 1D list
+ else:
+ # A 1D list is appropriate if one device is connected. Make sure just one device is connected
+ if len(gdx.devices)!= 1:
+ print("the sensor parameter in select_sensors() does not match number of devices")
+ gdx.devices = []
+ return
+ else:
+ # Save the 1D list as a 2D list in device_sensors - [[1,2]]
+ gdx.device_sensors.append(sensors)
+ # It's not a list. Check if it is an integer
+ else:
+ if isinstance(sensors, int):
+ # sensors are stored as a list, so change the int to a list
+ sensors = [sensors]
+ # Save the 1D list as a 2D list in device_sensors - [[1,2]]
+ gdx.device_sensors.append(sensors)
+
+ #print("sensors for data collection = ", gdx.device_sensors)
+
+ # check to make sure the user setup the device with a valid sensor number
+ valid_sensor_num = self.check_sensor_number()
+ if valid_sensor_num:
+ # Enable the sensors that were selected for data collection.
+ i = 0
+ while i < len(gdx.devices):
+ #print("device ",i, " enabled sensors = ", gdx.device_sensors[i], sep="")
+ gdx.devices[i].enable_sensors(sensors = gdx.device_sensors[i])
+ i +=1
+
+ # The enabled sensor objects are stored in a variable, to be used in the read() function.
+ i = 0
+ while i < len(gdx.devices):
+ # The variable "enabled_sensors" is a 2D list that stores each device's enabled sensor objects [[obj],[obj,obj]].
+ gdx.enabled_sensors.append(gdx.devices[i].get_enabled_sensors())
+ i +=1
+ # if it's not a valid number then empty the gdx.devices array so that no other functions are called
+ else:
+ gdx.devices = []
+
+ def check_sensor_number(self):
+ """ check to see if the user set an appropriate, available sensor number for this
+ device.
+ """
+
+ i = 0
+ # Get the sensors from each device, one device at a time
+ while i < len(gdx.devices):
+ all_sensor_numbers = []
+ sensors = gdx.devices[i].list_sensors()
+
+ # the all_sensor_numbers list will be used in the code below to determine incompatible sensors
+ for x in sensors:
+ c = sensors[x]
+ number = c.sensor_number
+ all_sensor_numbers.append(number)
+
+ sensors_selected_by_user = gdx.device_sensors[i]
+ for sensor_selected in sensors_selected_by_user:
+ #print(f"sensor selected = {sensor_selected}, available sensors = {all_sensor_numbers}")
+ if sensor_selected in all_sensor_numbers:
+ valid_sensor_num = True
+ else:
+ valid_sensor_num = False
+ print('select_sensors() setup error')
+ print("The value ", sensor_selected, " in select_sensors() is not valid")
+ print(f"Valid sensor values for device{i}:", '\n')
+ for x in sensors:
+ c = sensors[x]
+ number = c.sensor_number
+ description = c.sensor_description
+ units = c.sensor_units
+ all_sensor_numbers.append(number)
+ print(f'{number} - {description} ({units})')
+ print('\n')
+ i += 1
+
+ return valid_sensor_num
+
+ def start(self, period=None):
+ """ Start collecting data from the sensors that were selected in the select_sensors() function.
+
+ Args:
+ period (int): If period is left blank, a prompt in the terminal allows the user to enter
+ the period (time between samples). To run the code without this prompt, set this argument to
+ a period in milliseconds, e.g. period=1000
+ """
+
+ # First check to make sure there are devices connected.
+ if not gdx.devices:
+ print("start() - no device connected")
+ return
+
+ # if they are using vpython and the slider then the period is controlled there
+ if gdx.vpython_slider:
+ # if this is the very first call, then configure the slider with a starting value
+ if gdx.vp_first_start == True:
+ # if the period arg is left blank, just set a default starting value for the slider
+ if period == None:
+ vp.slider_set(sample_rate=10)
+ period = 100
+ # otherwise, use the period to set the slider with an initial value
+ else:
+ sample_rate = (1/period) * 1000
+ vp.slider_set(sample_rate)
+ # if it is not the first start(), then just get the period value from the slider
+ else:
+ period = vp.slider_get()
+
+ # This is not a vpython program.
+ else:
+ # If the period argument is left blank provide an input prompt for the user to enter the period.
+ if period == None:
+ print('\n')
+ print("Enter the sampling period (milliseconds):", end=' ')
+ period = int(input())
+ print('\n')
+ sample_rate = 1/(period/1000)
+ #print("sample rate = ", sample_rate, "samples/second")
+
+ # Provide a warning message if the user is attempting fast data collection
+ if period < 10:
+ input("Be aware that sampling at a period less than 10ms may be problemeatic. Press Enter to continue ")
+
+ # if this is a vpython program, and this is the very first time that start() has
+ # been called, do not start data collection yet. Why? Because the user's vpython
+ # code will have a start() call in their code, but they don't want to actually 'start'
+ # data collection at that point. Instead they will want to 'start' data collection
+ # when they click the 'collect' button.
+ if gdx.vpython == True and gdx.vpython_buttons == True and gdx.vp_first_start == True:
+ # set the period variable in gdx_vpython.py, but do not start the devices
+ gdx_vpython.ver_vpython.period = period
+ gdx.vp_first_start = False
+
+ # Start the devices collecting data.
+ else:
+ if gdx.vpython:
+ # if this is a vpython program, then clear the chart, if there is a chart
+ if gdx.vpython_chart:
+ column_headers= self.enabled_sensor_info()
+ vp.chart_clear(column_headers)
+ gdx_vpython.ver_vpython.time = 0
+ # store the period in case it is needed in vp_start_button()
+ gdx.period = period
+ # if this is the first call to start() change this flag
+ if gdx.vp_first_start == True:
+ gdx.vp_first_start = False
+
+ # Start all devices
+ i = 0
+ while i < len(gdx.devices):
+ #print("start device ", i, sep="")
+ gdx.devices[i].start(period=period)
+ i +=1
+
+ def read(self):
+ """ Take single point readings from the enabled sensors.
+
+ Returns:
+ retvalues[]: a 1D list of sensor readings. A single data point
+ for each enabled sensor.
+ """
+
+ retvalues = []
+ values = []
+
+ # First check to make sure there are devices connected.
+ if not gdx.devices:
+ print("read() - no device connected")
+ return
+
+ # Are there data in the buffer? If so, pull data from the buffer to get the retvalues
+ if gdx.buffer:
+ i = 0
+ for i in range(len(gdx.buffer)):
+ pop_values = gdx.buffer[i].pop(0)
+ retvalues.append(pop_values)
+ # if this was the last value in the buffer, clear the list so that it is not a list of empty lists
+ if not gdx.buffer[0]:
+ gdx.buffer = []
+
+ # The buffer is empty, so take readings from the sensor to get retvalues
+ else:
+ gdx.buffer = []
+ i = 0
+ # Read from each device, one at a time
+ while i < len(gdx.devices):
+ if gdx.devices[i].read():
+ sensors = gdx.enabled_sensors[i]
+ # Take readings from each sensor in the device, one at time
+ if sensors:
+ for sensor in sensors:
+ # The sensor.values call may read one sensor value, or multiple sensor values (if fast sampling)
+ values[:] = sensor.values
+ # Pull the first value off the values list
+ pop_values = values.pop(0)
+ # Build a list of each sensors' first value (this builds the return list)
+ retvalues.append(pop_values)
+ # Build a list of lists for each sensors data that is not returned and put it in the buffer
+ if values:
+ gdx.buffer.append(values)
+ sensor.clear()
+ values = []
+ i +=1
+
+ if not retvalues:
+ return None
+ else:
+ # if this is vpython and there are meters, update them with the retvalues. Note that we do
+ # not need to know if the start button has been clicked (like the chart, below) because we
+ # update the meters, even when data collection is not occuring.
+ if gdx.vpython:
+ if gdx.vpython_meters:
+ column_headers= self.enabled_sensor_info()
+ vp.meter_data(column_headers, retvalues)
+ # if there is a chart AND the start button has been clicked
+ if gdx.vpython_chart == True and gdx.vp_start_button_flag == True:
+ vp.chart_plot(retvalues)
+
+ return retvalues
+
+ def readValues(self):
+ """ Take multiple point readings from the enabled sensors and return the readings as a 2D list.
+
+ Returns:
+ retvalues[]: a 2D list of sensor readings. Multiple points for each enabled sensor.
+ """
+
+ retvalues = []
+ i = 0
+ # Read from each device, one at a time
+ while i < len(gdx.devices):
+ if gdx.devices[i].read():
+ sensors = gdx.enabled_sensors[i]
+ # Take readings from each sensor in the device, one at time
+ if sensors:
+ for sensor in sensors:
+ # The sensor.values call may read one sensor value, or multiple sensor values (if fast sampling)
+ retvalues[:] = sensor.values
+ sensor.clear()
+ i +=1
+ return retvalues
+
+ def listOfListsReadValues(self, dev2=False): #Ex 11
+
+ """ Same functionality as read() above, however value sensor.values is copied into
+ values[] by value instead of by reference, allowing sensor.clear() to be called.
+ Only the most recent measurements are returned from readValues() and then cleared
+ from both sensor.values and values[]
+ Returns:
+ value[]: a list that includes a data point from each of the enabled sensors
+ """
+ '''if dev2 == False: #this first code sets up dev1. If dev2 = True, then do not call this code.
+ if gdx.selected_device == None:
+ return
+ device = gdx.selected_device
+
+ elif dev2 == True: #is this function being called to configure dev2? then use this code
+ if gdx.selected_device2 == None:
+ return
+ device = gdx.selected_device2
+
+ retValues = []
+
+ #if self.selected_device == None:
+ #return None
+ #if self.selected_device.read():
+ if device.read():
+ sensors = device.get_enabled_sensors()
+ if sensors != None:
+ for sensor in sensors:
+ values = []
+ values[:] = sensor.values #New Examples
+ sensor.clear()
+ retValues.append(values)
+ return retValues
+ else:
+ return None'''
+
+ def stop(self):
+ """ Stop data collection on the enabled sensors.
+ """
+
+ # First check to make sure there are devices connected.
+ if not gdx.devices:
+ print("stop() - no device connected")
+ return
+
+ i = 0
+ while i < len(gdx.devices):
+ # print("stop device ",i, sep="")
+ gdx.devices[i].stop()
+ i+=1
+
+ def close(self):
+ """ Disconnect the USB or BLE device and quit godirect.
+ """
+
+ # First check to make sure there are devices connected.
+ if not gdx.devices:
+ print("close() - no device connected")
+ return
+
+ i = 0
+ while i < len(gdx.devices):
+ #print("close device ", i, sep="")
+ gdx.devices[i].close()
+ i+=1
+ gdx.devices = []
+
+ gdx.ble_open = False
+ self.godirect.quit()
+ #print("quit godirect")
+
+ def device_info(self):
+ """ Returns information about the device. The device must be opened first,
+ using the open() function, before this function can be called.
+
+ Returns:
+ device_info[]: a 1D list for one device or a 2D list for multiple. The list
+ includes name, description, battery %, charger state, rssi
+ """
+
+ if not gdx.devices:
+ print("device_info - no device connected")
+ return
+
+ # The elements in the device_info list are: 0 = name, 1 = description, 2 = battery %, 3 = charger state, 4 = rssi
+ device_info = []
+
+ # If there is just one device connected, package the info in a 1D list [device info]
+ if len(gdx.devices) ==1:
+ device_info.append(gdx.devices[0]._name)
+ device_info.append(gdx.devices[0]._description)
+ device_info.append(gdx.devices[0]._battery_level_percent)
+ charger_state = ["Idle", "Charging", "Complete", "Error"]
+ device_info.append(charger_state[gdx.devices[0]._charger_state])
+ device_info.append(gdx.devices[0]._rssi)
+ return device_info
+
+ # If there is more than one device connected, package the info in a 2D list [[device0 info], [device1 info]]
+ else:
+ i = 0
+ while i < len(gdx.devices):
+ one_device_info = []
+ one_device_info.append(gdx.devices[i]._name)
+ one_device_info.append(gdx.devices[i]._description)
+ one_device_info.append(gdx.devices[i]._battery_level_percent)
+ charger_state = ["Idle", "Charging", "Complete", "Error"]
+ one_device_info.append(charger_state[gdx.devices[i]._charger_state])
+ one_device_info.append(gdx.devices[i]._rssi)
+ i+=1
+ device_info.append(one_device_info)
+ return device_info
+
+ def enabled_sensor_info(self):
+ """ Returns each enabled sensors' description and units (good for column headers).
+
+ Returns:
+ sensor_info[]: a 1D list that includes each enabled sensors' description
+ with units, e.g. ['Force (N)', 'X-axis acceleration (m/s²)']
+ """
+
+ if not gdx.devices:
+ print("enabled_sensor_info() - no device connected")
+ return
+
+ sensor_info = []
+
+ i = 0
+ # Get the enabled sensors from each device, one device at a time
+ while i < len(gdx.devices):
+ sensors = gdx.enabled_sensors[i]
+ for sensor in sensors:
+ info = sensor.sensor_description + " (" + sensor.sensor_units + ")"
+ sensor_info.append(info)
+ i += 1
+
+ return sensor_info
+ # if it is just a single value, don't send it as a list
+ # if len(sensor_info) == 1:
+ # return sensor_info[0]
+ # else:
+ # return sensor_info
+
+ def sensor_info(self):
+ """ Information about all of the available sensors on a connected Go Direct device.
+
+ Returns:
+ available_sensors[]: a 2D list containing information about each
+ sensor found on the device. This includes sensor number, description, units, and
+ a list of incompatible sensors (if any). An incompatible sensor is a sensor that can
+ not run at the same time as this sensor. For example, Go Direct EKG cannot run the EKG
+ sensor at the same time as the EMG sensor.
+ """
+
+ if not gdx.devices:
+ print("sensor_info() - no device connected")
+ return
+
+ available_sensors = []
+ all_sensor_numbers = []
+
+ i = 0
+ # Get the sensors from each device, one device at a time
+ while i < len(gdx.devices):
+ sensors = gdx.devices[i].list_sensors()
+
+ # the all_sensor_numbers list will be used in the code below to determine incompatible sensors
+ for x in sensors:
+ c = sensors[x]
+ number = c.sensor_number
+ all_sensor_numbers.append(number)
+
+ for x in sensors:
+ incompatible_sensors = []
+ s = sensors[x]
+ number = s.sensor_number
+ description = s.sensor_description
+ units = s.sensor_units
+
+ # The exclusion_mask is a number that represents sensor numbers that are incompatible with this sensor.
+ exclusion_mask = s._mutual_exclusion_mask
+ # Convert the exclusion_mask number to a list of Trues and Falses representing the mask.
+ bin_string = format(exclusion_mask, '32b')
+ # Reverse the bin_string (with [::-1]) to format it with the most significant bit first.
+ # The answer is a True False list [TRUE, TRUE, FALSE]
+ answer = [x == '1' for x in bin_string[::-1]]
+
+ e = 0
+ # Change the True/False list to a list of sensor numbers. e.g, [TRUE, TRUE, FALSE] = [1,2]
+ # Pull out the True/False values of the list one at a time and if it is TRUE, then add it to
+ # the list of incompatible sensors.
+ for channel in answer:
+ # If this value of the list is TRUE and it is a confirmed sensor number.
+ if channel == True and e in all_sensor_numbers:
+ incompatible_sensors.append(e)
+ e+=1
+
+ available_sensors.append([number, description, units, incompatible_sensors])
+
+ i+=1
+ # Return the available_sensor list [0 = sensor number, 1 = description, 2 = units, 3 = incompatible sensors[]]
+ return available_sensors
+
+ def discover_ble_devices(self, init=True):
+ """ Enables bluetooth, and returns the name and rssi of all discovered GoDirect devices.
+ This function should be called prior to opening a device. The name returned
+ by this function can be used as an argurment in the ble_open() function to open a specific device.
+
+ Returns:
+ discovered_ble_devices[]: a 2D list. A list containing a list of name and rssi for each device
+ [[name1,rssi1],[name2,rssi2],[name3,rssi3]]
+ """
+
+ # If you are going to call this several times, there might be a reason to only call
+ # the init code once.
+ # The first time you call this function set init = True, the following times set init = False.
+ if init == True:
+ self.godirect.__init__(use_ble=True, use_usb=False)
+ gdx.ble_open = False
+ print("Begin search for ble devices...")
+
+ # Find all available bluetooth devices
+ found_devices = self.godirect.list_devices()
+ number_found_devices = len(found_devices)
+ #print("Number of ble devices found = " +str(number_found_devices))
+ discovered_ble_devices = []
+
+ if number_found_devices >= 1:
+ for device in found_devices:
+ device_name = device.name
+ # Note that you can get the rssi from this call before opening the device
+ device_rssi = device.rssi
+ discovered_ble_devices.append([device_name, device_rssi])
+
+ return discovered_ble_devices
+
+#### VPYTHON FUNCTIONS ####
+
+ def vp_vernier_canvas(self, buttons=True, slider=True, meters=True, chart=False, cvs=True):
+ """ Create vptyhon objects that are used for controlling data collection.
+
+ Args:
+ buttons (bool): Create a Collect/Stop and Close button
+ slider (bool): Create a slider to control sampling rate
+ meters (bool): Create meters to display live sensor data
+ chart (bool): Create a chart to plot live sensor data
+ cvs (bool): Create a default canvas, ready for vpython objects
+
+ """
+ # keep track of what objects have been selected
+ gdx.vpython = True
+ gdx.vpython_buttons = buttons
+ gdx.vpython_chart = chart
+ gdx.vpython_meters = meters
+ gdx.vpython_slider = slider
+
+ # setup the canvas based on what was selected
+ if buttons or slider:
+ vp.setup_canvas(buttons, slider)
+ if chart:
+ column_headers= self.enabled_sensor_info()
+ vp.chart_init(column_headers)
+ if meters:
+ vp.meter_init()
+ if cvs:
+ vp.create_default_canvas()
+
+ def vp_close_is_pressed(self):
+ """ Monitor the state of the vpython canvas Close button. When true,
+ a gdx.stop() and gdx.close() are called to stop data collection and
+ disconnect the device. When false, and if there are meters, they are
+ updated with live readings.
+
+ Returns:
+ close_button_state (bool): True if Close button has been pressed
+ """
+ # Note that there is no vpython rate() call here, but there is in the
+ # gdx_vpython.py module in the collect_button() function
+
+ # First check to make sure there are devices connected.
+ if not gdx.devices:
+ print("vp_close_button() - no device connected")
+ close_button_state = True
+ else:
+ # get the state of the vpython Close button
+ close_button_state = vp.closed_button()
+
+ # If the user has clicked the Close button
+ if close_button_state == True:
+ self.stop()
+ self.close()
+ if gdx.vpython_chart:
+ vp.chart_delete()
+ if gdx.vpython_meters:
+ vp.meter_delete()
+ if gdx.vpython_slider:
+ vp.slider_delete()
+ if gdx.vpython_buttons:
+ vp.button_delete()
+ vp.canvas_delete()
+ # the Close button has not been pressed
+ else:
+ # if there are meters, update their values using a short data collection loop
+ if gdx.vpython_meters:
+ for device in gdx.devices:
+ device.start(period=250)
+ for x in range(4):
+ # The read() function has code to send the value to the meter
+ self.read()
+ self.stop()
+
+ return close_button_state
+
+ def vp_collect_is_pressed(self):
+ """ Monitor the state of the vpython canvas Collect/Stop button. When Collect
+ is clicked, a gdx.start() is called. When Stop is clicked, a gdx.stop() is
+ called.
+
+ Returns:
+ collect_button_state (bool): True if button is in the 'COLLECT' state. False
+ if the button is in the 'STOP' state.
+ """
+
+ # First check to make sure there are devices connected.
+ if not gdx.devices:
+ print("vp_collect_button() - no device connected")
+ return
+
+ # get the state of the collect button
+ collect_button_state = vp.collect_button()
+
+ # It is not enough to know the state of the button, it is also
+ # important to know if the button was just clicked (just been pressed)
+ if gdx.vp_start_button_flag != collect_button_state:
+ # if it was just clicked into the True state, start data collection
+ if collect_button_state == True:
+ self.start(gdx.period)
+ gdx.vp_start_button_flag = True
+ # if it was just clicked into the False state, stop data collection
+ else:
+ self.stop()
+ gdx.vp_start_button_flag = False
+
+ return collect_button_state
+
+ def vp_get_slider_period(self):
+ """ Get the value of the slider as the period (time between samples).
+ Returns the value in milliseconds.
+ """
+ period = vp.slider_get()
+ return period
diff --git a/gdx/gdx_vpython.py b/gdx/gdx_vpython.py
new file mode 100644
index 0000000..5c4af13
--- /dev/null
+++ b/gdx/gdx_vpython.py
@@ -0,0 +1,305 @@
+import logging
+
+# from gdx_modules.gdx import gdx_class
+# gdx = gdx_class()
+#import gdx_modules.gdx
+
+
+#Are these variable required outside the class?
+# woutput = wtext(text='')
+# collectbutton = None
+# closebutton = None
+
+class ver_vpython:
+
+
+ closed = False
+ collect_button_state = False
+ period = 100 # period in ms
+ time = 0
+ plot_1 = None # vpython gcurve for the graph canvas
+ plot_2 = None
+ plot_3 = None
+ plot_4 = None
+ plot_5 = None
+ graph_canvas = None
+ meter_canvas = None
+ button_canvas = None
+ meter_text = None
+ slider_text = None
+ cb = None # collect button
+ clsb = None # close button
+ sl = None # slider
+
+ def __init__(self):
+ """
+ """
+
+ def setup_canvas(self, vp_button=False, slider_control=False):
+
+ # Can we install the vpython library, with godirect?
+ #from vpython import canvas, button, box, wtext, checkbox, rate
+ from vpython import button, scene, slider, wtext, canvas, color
+ #global collectbutton, closebutton
+ # if they are using vpython, then create the canvas and collect/stop/close buttons
+
+
+ ver_button_scene = canvas()
+ ver_vpython.button_canvas = ver_button_scene
+ ver_button_scene.width = 0
+ ver_button_scene.height = 10
+
+ ver_button_scene.append_to_title('\n')
+ if vp_button:
+ collectbutton = button(text=' COLLECT ',
+ pos=ver_button_scene.title_anchor, bind=vp_collect_stop)
+ ver_vpython.cb = collectbutton
+ ver_button_scene.append_to_title(' ')
+ closebutton = button(text=' CLOSE ',
+ pos=ver_button_scene.title_anchor, bind=vp_closed)
+ ver_vpython.clsb = closebutton
+
+ if slider_control:
+ ver_button_scene.append_to_title(' ')
+ slider_control = slider(pos=ver_button_scene.title_anchor, min=1, max=100, value=10, step=1, length=200, bind=vp_slider)
+ ver_vpython.sl = slider_control
+ #scene.append_to_caption('\n\n')
+ #scene.append_to_title('\n')
+ slider_text = wtext(pos=ver_button_scene.title_anchor, text='10 samples/second')
+ ver_vpython.period = 100
+ ver_vpython.slider_text = slider_text
+
+
+ ver_button_scene.append_to_title('\n')
+
+ def create_default_canvas(self):
+ """ Add a small canvas below the button canvas and the meter canvas. This should allow
+ the user to create vpython objects without having to create a scene. If they do create
+ a scene it should simply overwrite this canvas.
+ """
+
+ from vpython import canvas, scene, color
+
+ scene = canvas(width=800, height=150)
+ scene.background = color.black
+
+ def button_delete(self):
+ ver_vpython.cb.delete()
+ ver_vpython.clsb.delete()
+
+ def slider_delete(self):
+ from vpython import canvas
+
+ canvas.delete(ver_vpython.slider_text)
+ ver_vpython.sl.delete()
+
+ def canvas_delete(self):
+ from vpython import canvas, scene
+
+ # this also seems to work for deleting the button object
+ #canvas.delete(ver_vpython.cb)
+ # ver_vpython.cb.delete()
+ # ver_vpython.clsb.delete()
+ ver_vpython.button_canvas.delete()
+
+ scene.delete()
+ current = canvas.get_selected()
+ if current:
+ current.delete()
+
+ def slider_set(self, sample_rate):
+ ver_vpython.sl.value = sample_rate
+ ver_vpython.period = (1/sample_rate) * 1000
+ ver_vpython.slider_text.text = f'{sample_rate} samples/second'
+
+ def slider_get(self):
+ period = ver_vpython.period
+ return period
+
+ def chart_init(self, column_headers):
+ from vpython import graph, gcurve, color, vector
+
+ if column_headers == None:
+ column_headers = 'Data'
+ gd = graph(xtitle='Time', ytitle=column_headers, scroll=True,
+ width=500, height=300, xmin=0, xmax=5, fast=False)
+ ver_vpython.graph_canvas = gd
+ plot_1 = gcurve(color=vector(0.37, 0.57, 0.74))
+ ver_vpython.plot_1 = plot_1
+ ver_vpython.plot_1.plot(0,0)
+ plot_2 = gcurve(color=vector(0.75, 0.75, 0.3))
+ ver_vpython.plot_2 = plot_2
+ ver_vpython.plot_2.plot(0,0)
+ plot_3 = gcurve(color=vector(0, 0, 0.57))
+ ver_vpython.plot_3 = plot_3
+ ver_vpython.plot_3.plot(0,0)
+ plot_4 = gcurve(color=vector(0.8, 0.38, 0.44))
+ ver_vpython.plot_4 = plot_4
+ ver_vpython.plot_4.plot(0,0)
+ plot_5 = gcurve(color=vector(0.35, 0.2, 0.49))
+ ver_vpython.plot_5 = plot_5
+ ver_vpython.plot_5.plot(0,0)
+
+ def chart_plot(self, data):
+ if data == None:
+ return
+ else:
+ if not isinstance(data, list):
+ # measurements needs to be a list, if it is not, change it to a list
+ data = [data]
+ len_data = len(data)
+ if len_data == 1:
+ ver_vpython.plot_1.plot(ver_vpython.time, data[0])
+ elif len_data == 2:
+ ver_vpython.plot_1.plot(ver_vpython.time, data[0])
+ ver_vpython.plot_2.plot(ver_vpython.time, data[1])
+ elif len_data == 3:
+ ver_vpython.plot_1.plot(ver_vpython.time, data[0])
+ ver_vpython.plot_2.plot(ver_vpython.time, data[1])
+ ver_vpython.plot_3.plot(ver_vpython.time, data[2])
+ elif len_data == 4:
+ ver_vpython.plot_1.plot(ver_vpython.time, data[0])
+ ver_vpython.plot_2.plot(ver_vpython.time, data[1])
+ ver_vpython.plot_3.plot(ver_vpython.time, data[2])
+ ver_vpython.plot_4.plot(ver_vpython.time, data[3])
+ else:
+ ver_vpython.plot_1.plot(ver_vpython.time, data[0])
+ ver_vpython.plot_2.plot(ver_vpython.time, data[1])
+ ver_vpython.plot_3.plot(ver_vpython.time, data[2])
+ ver_vpython.plot_4.plot(ver_vpython.time, data[3])
+ ver_vpython.plot_5.plot(ver_vpython.time, data[4])
+
+ ver_vpython.time = ver_vpython.time + (ver_vpython.period/1000)
+
+
+ def chart_clear(self, column_headers):
+ if column_headers == None:
+ column_headers = 'Data'
+ ver_vpython.plot_1.delete()
+ ver_vpython.plot_2.delete()
+ ver_vpython.plot_3.delete()
+ ver_vpython.plot_4.delete()
+ ver_vpython.plot_5.delete()
+ ver_vpython.graph_canvas.ytitle = column_headers
+ ver_vpython.graph_canvas.xmin = 0
+ ver_vpython.graph_canvas.xmax = 5
+
+ def chart_delete(self):
+ ver_vpython.graph_canvas.delete()
+
+ def meter_init(self):
+ from vpython import canvas, wtext, scene, color
+
+ ver_meter_canvas = canvas(width=0, height=20)
+ ver_vpython.meter_canvas = ver_meter_canvas
+ ver_meter_canvas.append_to_title('\n')
+ woutput = wtext(text='', pos=ver_meter_canvas.title_anchor)
+ ver_vpython.meter_text = woutput
+ #mass M
+ #woutput.text = "{ch_string}\n".format(ch_string)
+
+ woutput.text = f""
+
+ def meter_data(self, column_headers, data):
+ if data == None:
+ meter_string = 'No data'
+ else:
+ if not isinstance(data, list):
+ # data needs to be a list, if it is not, change it to a list
+ data = [data]
+ if not isinstance(column_headers, list):
+ # column_headers needs to be a list, if it is not, change it to a list
+ column_headers = [column_headers]
+
+ meter_string = ' '
+ for (ch, d) in zip(column_headers, data):
+ round_data = str(round(d, 2))
+ meter_string = meter_string + ch + ": " + round_data + ' '
+
+ ver_vpython.meter_text.text = f"{meter_string}"
+
+ def meter_delete(self):
+ from vpython import canvas
+
+ canvas.delete(ver_vpython.meter_text)
+ ver_meter_canvas = ver_vpython.meter_canvas
+ # this worked
+ ver_meter_canvas.delete()
+ # this worked too
+ #canvas.delete(ver_meter_canvas)
+
+ # def slider_init(self):
+ # from vpython import canvas, slider, wtext, scene
+
+ # sc = canvas(width=0, height=20)
+ # ver_vpython.slider_canvas = sc
+ # # when the slider is changed, the function vp_slider is called
+ # sl = slider(align='right', min=1, max=100, value=10, step=1, length=200, bind=vp_slider, left=20)
+ # #scene.append_to_caption('\n\n')
+ # #scene.append_to_title('\n')
+ # slider_text = wtext(text='')
+ # ver_vpython.slider_text = slider_text
+
+ # def print_to_canvas(self):
+ # """ Feedback to the user on the vpython screen
+ # """
+ # from vpython import canvas
+
+ # canvas.get_selected().append_to_caption('Must specify device sensors.')
+ # canvas.get_selected().caption = 'test caption'
+ # raise AttributeError('Must specify device sensors.')
+
+ def collect_button(self):
+ """ Return value = True if the button is in the Collect state. Return
+ value = False if it is in the Stop state.
+ """
+ from vpython import rate, color
+
+ if ver_vpython.collect_button_state:
+ return True
+
+ else:
+ # assumption is that rate() is only needed when data collection is not occurring
+ # When it is, the computer will automatically be slowed because of the time
+ # required to talk to the hardware.
+ rate(50)
+ return False
+
+ def closed_button(self):
+ if ver_vpython.closed:
+ return True
+ else:
+ return False
+
+
+
+
+def vp_collect_stop(f):
+ """ This function gets called only when the button has been pressed. Return
+ value = True if the button is in the Collect state.
+ """
+
+ if f.text == ' COLLECT ':
+ f.text = ' STOP '
+ #
+ # "Change the class variable’s value using the class name only."
+ ver_vpython.collect_button_state = True
+
+ else:
+ f.text = ' COLLECT '
+ ver_vpython.collect_button_state = False
+
+def vp_closed():
+
+ ver_vpython.closed = True
+ ver_vpython.collect_button_state = False
+
+def vp_slider(s):
+ ver_vpython.period = (1/s.value) * 1000
+ ver_vpython.slider_text.text = f'{s.value} samples/second'
+
+
+
+
+
+
\ No newline at end of file
diff --git a/gdx/readme.md b/gdx/readme.md
new file mode 100644
index 0000000..f62887c
--- /dev/null
+++ b/gdx/readme.md
@@ -0,0 +1,40 @@
+# The gdx Module
+
+The `gdx` module is used to simplify interaction with Go Direct® devices. While the implementation of the communication is actually done through the lower level [godirect module](https://pypi.org/project/godirect/), the `gdx` module exposes the features that are most commonly used when writing code that deals with Go Direct devices. In other words, the `gdx` module provides a small set of simple functions to write Python and VPython code. Of course you can always modify the `gdx` module as needed for your own custom Go Direct sensor functions.
+
+When writing Python code using the `gdx` module you must first import it.
+
+```python
+from gdx import gdx
+gdx = gdx.gdx()
+```
+
+An important factor for this import is that Python must be able to find the `gdx` module in order to import it. Here are three ways to help insure that Python finds the `gdx` module:
+
+- Locate the /gdx/ folder in the same directory as the example that you are running.
+- Manually move the /gdx/ folder into your Python /site-packages/ directory. This is the same directory that all Python libraries are placed, and it is a "path" that Python looks for modules.
+- Add code to provide a system 'path' to the /gdx/ folder. A common example is having /gdx/ one directory up. Here is the code used to add a system 'path' one directory up:
+
+```python
+import os
+import sys
+
+file_path = os.path.abspath(os.path.dirname(sys.argv[0]))
+os.chdir(file_path)
+os.chdir("..")
+gdx_module_path = os.getcwd()
+if gdx_module_path not in sys.path:
+ sys.path.append(gdx_module_path)
+```
+
+For information on using the gdx module for data collection refer to [Getting Started with Vernier Go Direct® Sensors and Python](https://github.com/VernierST/godirect-examples/tree/main/python) manual.
+
+For information on using the gdx module for VPython refer to the [Getting Started with Vernier Go Direct® Sensors and VPython](https://github.com/VernierST/godirect-examples/tree/main/python/vpython_examples) manual.
+
+All of the examples in the godirect-examples repository use the `gdx` module, except for the example located in the ../example_without_gdx/ folder. Run this example if you want to communicate directly to your Go Direct device with the `godirect` module, or you want to do some troubleshooting.
+
+## License
+
+All of the content in this repository is available under the terms of the [BSD 3-Clause License](../../LICENSE).
+
+Vernier products are designed for educational use. Our products are not designed nor are they recommended for any industrial, medical, or commercial process such as life support, patient diagnosis, control of a manufacturing process, or industrial testing of any kind.
\ No newline at end of file
diff --git a/gdx_getting_started_usb.py b/gdx_getting_started_usb.py
new file mode 100644
index 0000000..152f619
--- /dev/null
+++ b/gdx_getting_started_usb.py
@@ -0,0 +1,49 @@
+'''
+Simple starter program that uses the gdx functions to collect data from a Go Direct device
+connected via USB.
+
+When using gdx.open(), note it has two arguments that can be set. They are 'connection'
+and 'device_to_open'. Here are some ways to configure gdx.open() for a USB connection:
+
+gdx.open(connection='usb')
+ When the 'device_to_open' argument is left blank, the function finds all available
+ Go Direct devices connected via USB. If only one device is found it will
+ automatically connect to that device. If more than one device is found it prints
+ the list to the terminal, and prompts the user to select the device to connect.
+
+gdx.open(connection='usb', device_to_open='GDX-FOR 071000U9')
+ Use your device's name as the argument. The function will search for a
+ USB device with this name. If found it will connect it. If connecting to
+ multiple devices separate the names with a comma, such as,
+ device_to_open='GDX-FOR 071000U9, GDX-HD 151000C1'
+
+Tip: Skip the prompts to select the sensors and period by entering arguments in the functions.
+
+Example 1, collect data from sensor 1 at a period of 1000ms using:
+gdx.select_sensors([1])
+gdx.start(1000)
+
+Example 2, collect data from sensors 1, 2 and 3 at a period of 100ms using:
+gdx.select_sensors([1,2,3])
+gdx.start(100)
+'''
+
+from gdx import gdx
+gdx = gdx.gdx()
+
+
+gdx.open(connection='usb')
+gdx.select_sensors()
+gdx.start()
+column_headers= gdx.enabled_sensor_info() # returns a string with sensor description and units
+print('\n')
+print(column_headers)
+
+for i in range(0,5):
+ measurements = gdx.read()
+ if measurements == None:
+ break
+ print(measurements)
+
+gdx.stop()
+gdx.close()
\ No newline at end of file