This commit is contained in:
Eric Yu 2023-02-06 22:57:05 -08:00
parent 81d1b12079
commit 3d78a2a733
27 changed files with 223 additions and 518 deletions

8
.idea/.gitignore vendored
View File

@ -1,8 +0,0 @@
# Default ignored files
/shelf/
/workspace.xml
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
# Editor-based HTTP Client requests
/httpRequests/

View File

@ -1 +0,0 @@
SeeDatResistance

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding">
<file url="file://$PROJECT_DIR$/test.txt" charset="UTF-8" />
</component>
</project>

View File

@ -1,11 +1,14 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="PyPep8NamingInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<option name="ignoredErrors">
<list>
<option value="N806" />
</list>
<inspection_tool class="PyPackageRequirementsInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoredPackages">
<value>
<list size="2">
<item index="0" class="java.lang.String" itemvalue="wxPython" />
<item index="1" class="java.lang.String" itemvalue="requests" />
</list>
</value>
</option>
</inspection_tool>
</profile>

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.9" project-jdk-type="Python SDK">
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.9 (Read-Sensor-Resistances)" project-jdk-type="Python SDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

View File

@ -2,7 +2,7 @@
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/SeeDatResistance.iml" filepath="$PROJECT_DIR$/SeeDatResistance.iml" />
<module fileurl="file://$PROJECT_DIR$/Read-Sensor-Resistances.iml" filepath="$PROJECT_DIR$/Read-Sensor-Resistances.iml" />
</modules>
</component>
</project>

126
.idea/workspace.xml Normal file
View File

@ -0,0 +1,126 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ChangeListManager">
<list default="true" id="76f71a68-f2de-44e3-b845-3cd5ad93cdd1" name="Default Changelist" comment="">
<change afterPath="$PROJECT_DIR$/Read-Sensor-Resistances.iml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/.gitignore" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/.name" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/encodings.xml" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/inspectionProfiles/Project_Default.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/inspectionProfiles/Project_Default.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/misc.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/misc.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/modules.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/modules.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/README.md" beforeDir="false" afterPath="$PROJECT_DIR$/README.md" afterDir="false" />
<change beforePath="$PROJECT_DIR$/ReadAnalog/.gitignore" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/ReadAnalog/.vscode/extensions.json" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/ReadAnalog/flash.py" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/ReadAnalog/include/README" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/ReadAnalog/lib/README" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/ReadAnalog/platformio.ini" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/ReadAnalog/src/main.cpp" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/ReadAnalog/test/README" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/espVoltmeter/espVoltmeter.ino" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/read_arduino.py" beforeDir="false" afterPath="$PROJECT_DIR$/read_arduino.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/requirements.txt" beforeDir="false" afterPath="$PROJECT_DIR$/requirements.txt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/sealammonia.fbp" beforeDir="false" afterPath="$PROJECT_DIR$/sealammonia.fbp" afterDir="false" />
<change beforePath="$PROJECT_DIR$/serial_plot.py" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/test.py" beforeDir="false" afterPath="$PROJECT_DIR$/test.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/toplevel.py" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/ui.py" beforeDir="false" afterPath="$PROJECT_DIR$/ui.py" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="Git.Settings">
<option name="PUSH_TAGS">
<GitPushTagMode>
<option name="argument" value="--tags" />
<option name="title" value="All" />
</GitPushTagMode>
</option>
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
</component>
<component name="ProjectId" id="2LOnAsJg3v8rGTYtWcMJdPjM4DK" />
<component name="ProjectViewState">
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent">
<property name="RunOnceActivity.OpenProjectViewOnStart" value="true" />
<property name="RunOnceActivity.ShowReadmeOnStart" value="true" />
<property name="SHARE_PROJECT_CONFIGURATION_FILES" value="true" />
<property name="WebServerToolWindowFactoryState" value="true" />
<property name="aspect.path.notification.shown" value="true" />
<property name="nodejs_package_manager_path" value="npm" />
</component>
<component name="RunManager">
<configuration default="true" type="ArquillianJUnit" factoryName="" nameIsGenerated="true">
<option name="arquillianRunConfiguration">
<value>
<option name="containerStateName" value="" />
</value>
</option>
<option name="TEST_OBJECT" value="class" />
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
<configuration name="test" type="PythonConfigurationType" factoryName="Python" temporary="true" nameIsGenerated="true">
<module name="Read-Sensor-Resistances" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="PARENT_ENVS" value="true" />
<envs>
<env name="PYTHONUNBUFFERED" value="1" />
</envs>
<option name="SDK_HOME" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="IS_MODULE_SDK" value="true" />
<option name="ADD_CONTENT_ROOTS" value="true" />
<option name="ADD_SOURCE_ROOTS" value="true" />
<EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" />
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/test.py" />
<option name="PARAMETERS" value="" />
<option name="SHOW_COMMAND_LINE" value="false" />
<option name="EMULATE_TERMINAL" value="false" />
<option name="MODULE_MODE" value="false" />
<option name="REDIRECT_INPUT" value="false" />
<option name="INPUT_FILE" value="" />
<method v="2" />
</configuration>
<recent_temporary>
<list>
<item itemvalue="Python.test" />
</list>
</recent_temporary>
</component>
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
<component name="TaskManager">
<task active="true" id="Default" summary="Default task">
<changelist id="76f71a68-f2de-44e3-b845-3cd5ad93cdd1" name="Default Changelist" comment="" />
<created>1675750856334</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1675750856334</updated>
<workItem from="1675750858175" duration="2126000" />
</task>
<servers />
</component>
<component name="TypeScriptGeneratedFilesManager">
<option name="version" value="3" />
</component>
<component name="Vcs.Log.Tabs.Properties">
<option name="TAB_STATES">
<map>
<entry key="MAIN">
<value>
<State />
</value>
</entry>
</map>
</option>
</component>
<component name="com.intellij.coverage.CoverageDataManagerImpl">
<SUITE FILE_PATH="coverage/Read_Sensor_Resistances$test.coverage" NAME="test Coverage Results" MODIFIED="1675751285394" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
</component>
</project>

View File

@ -1,5 +1,5 @@
# Read Resistances
Read the resistance of a voltage divider using ESP32
Read the resistance of a voltage divider using Arduino boards
## Required & Dependencies
### Required
@ -8,20 +8,23 @@ Read the resistance of a voltage divider using ESP32
- matplotlib
- pyserial
- wxPython
- PlatformIO IDE, which could be downloaded as an extension in vs code
- ESP32 WROOM-32 Development Board
- PlatformIO extension, if you use VSCode for embedded programmings
- An Arduino-based microcontroller
- ADS1115 16-bit ADC chip
### Recommended
- Code Runner (for VSCode only)
- wxFormBuilder
- [Arduino IDE](https://makeabilitylab.github.io/physcomp/arduino/arduino-ide.html)
## How to use
To get a general idea of this project as well as hardware setup, please refer to [this video demo](https://youtu.be/vbVPXj8gbmI)
### Direct use
First download the `.ino` file from `espVoltmeter` folder using Arduino IDE. ~~Or if you are using Arduino Mega 2560 you can
First open the `.ino` file from `nano_c_mk1` directory using Arduino IDE and download it onto an Arduino board. ~~Or if you are using Arduino Mega 2560 you can
use the code in `ReadAnalog` folder by open the folder in VSCode and run with PlatformIO, or just run `flash.py` in a terminal~~
(Arduino Mega and Uno boards are no longer supported. Please use [this link](https://docs.espressif.com/projects/arduino-esp32/en/latest/installing.html) to
use on Arduino IDE or PlatformIO for VSCode)
For Windows you can just [download the released executable](https://github.com/Seal-Ammonia/Read-Sensor-Resistances/releases/download/beta/SeeDatResistance.exe) to use. Make sure to disable any virus-scanning software when download as Microsoft incorrectly recognized PyInstaller as virus. For non-Windows systems you can just run `python test.py` and start from the UI. Enter the relevant information and start collect and measuring data. If you do not have a board avaliable, leave the dropdown menu in the UI as "no device" and it will run a simulation.
~~(Arduino Mega and Uno boards are no longer supported. Please use [this link](https://docs.espressif.com/projects/arduino-esp32/en/latest/installing.html) to
use on Arduino IDE or PlatformIO for VSCode)~~ (There's a hardware upgrade so that the firmware has been re-written for the new hardware.
Therefore the old hardware are no longer in use. You may still find them in `OldFirmware` directory for reference but we are not using them anymore)
For Windows you can just [download the released executable](https://github.com/Seal-Ammonia/Read-Sensor-Resistances/releases/download/beta/SeeDatResistance.exe) from the release section to use. Make sure to disable any virus-scanning software when download as Microsoft incorrectly recognized PyInstaller as virus. For non-Windows systems you can just run `python test.py` and start from the UI. Enter the relevant information and start collect and measuring data. If you do not have a board avaliable, leave the dropdown menu in the UI as "no device" and it will run a simulation.
### develop & build
Beside the c-based code for the Arduino board to read raw data from the board, everything has now been done in the python
files. The main program is `test.py`, which calls `UI.py` to prompt for input. It is NOT recommended modifying the `ui.py`
@ -29,22 +32,22 @@ as it is generated from wxFormBuilder from `sealammonia.fbp`, which could be mod
the main program will generate `settings.json` which saves all required parameters needed to calculate and graph and save the
sensor's values. It also creates a .csv file under `RecordedData` directory to save all recorded+calculated data.
`read_arduino.py` read raw data from Arduino board and convert it to the correct resistance, which could be plotted out
using `serial_plotter.py`. `frontend.py` is the alternative version of the frontend writting using `tkinter`, and `serial_plot.py`
was an older version of `serial_plotter.py`. They are not longer supported.
using `serial_plotter.py`. To make your own executable, use `PyInstaller` using this command in your python environment:
`pyinstaller build.spec`.
ATTENTION MAC USER: currently the software cannot correctly functional on mac (especially arm-based ones). You might need to consider to use it on a windows VM or using parallel desktop
## Issues
- Newer hardware will require 3.3V. ~~Currently can only use 5V as input voltage.~~ May need to change the algorithm in `read_arduino.py`.
- axis window size is not correct
- on PyInstaller, windows misdetected our executable as a virus
- on PyInstaller, windows misdetected our executable as a virus (seems resolved)
- change the scale on Y-axis so that graph looks smoother
- Errors thrown when >1 reference resistances entered
- ~~Errors thrown when >1 reference resistances entered~~
## Todos & [Old Specs](https://docs.google.com/document/d/1Km2HZel7rILOvgHG5iXlUcXFx4Of1ot2I7Dbnq8aTwY/edit?usp=sharing):
## Todos:
- [ ] Fix Issues
- [ ] Automatically setup the driver for ESP32
- [ ] programically to stablize the graph (change the y-scale of the graph to make the less dramatic)
- [ ] (not urgent) make if runnable on Mac
- [ ] (not urgent) make it runnable on Mac
- [x] display sensor value in either the UI or on the plot
- [x] Reopen plot window from the UI when closed
- [x] Need to continue writing to file when graphing window is closed
@ -55,8 +58,8 @@ ATTENTION MAC USER: currently the software cannot correctly functional on mac (e
- [x] Accept any inputs (such as resistor values, plot window size, base voltage etc.) as either command line argument or as input at beginning of program
- [x] Write random values to simulate a sensor if no sensor available (this feature might be removed in the future)
- [x] Advanced option menu in the UI to allow user to make more adjustments
- [ ] Add resolution option in the UI to allow interchange between arduino (10bit) and esp32 (12 bits)
- [X] Add resolution option in the UI to allow interchange between arduino (10bit) and esp32 (12 bits) (Not Applicable in new hardware)
- [x] Possibly adding new module to support ESP32
- [ ] Support for non-Windows platforms (and maybe mobile)
- [ ] Make it into one executable
- [ ] Possibly adding new module to support Arduino MEGA and older firmwares
- [ ] Possibly adding new module to support Arduino MEGA and older firmwares (Need testing)

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

42
build.spec Normal file
View File

@ -0,0 +1,42 @@
# -*- mode: python ; coding: utf-8 -*-
block_cipher = None
a = Analysis(['test.py', 'serial_plotter.py', 'read_arduino.py', 'ui.py'],
pathex=['D:\\github\\Read-Sensor-Resistances'],
binaries=[],
datas=[],
hiddenimports=[],
hookspath=[],
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False)
splash = Splash('splash.png',
binaries=a.binaries,
datas=a.datas,
text_pos=(145, 142),
text_size=8,
text_color='black')
pyz = PYZ(a.pure, a.zipped_data,
cipher=block_cipher)
exe = EXE(pyz,
a.scripts,
splash, # <-- both, splash target
splash.binaries, # <-- and splash binaries
a.binaries,
a.zipfiles,
a.datas,
[],
name='SeeDatResistance',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=False )

View File

@ -16,7 +16,7 @@ def read():
"""
baud = 19200
settings = json.load(open('settings.json', 'r'))
resolution = settings["resolution"] # this is not in use, but just in case if we decide to switch back to analogRead() instead of ESP32's analogReadMiliVolts() on the firmware
# resolution = settings["resolution"] # this is not in use, but just in case if we decide to switch back to analogRead()
v_in = settings["v_in"]
refRes = np.array(settings["refRes"])
sensor_ports = np.array(settings["sensor_ports"]) # ports that sensor(s) are connected to
@ -44,19 +44,9 @@ def read():
writer.writerow(header)
f.close()
while True:
#dat_list = np.random.randint(0, v_in * 1000, SENSORS_MAX) # create a randomized voltage data
dat_list = np.random.randint(0, v_in * 1000, SENSORS_MAX) # create a randomized voltage data
# take only the nonzero indices, and truncated to two decimal places to "filter" out some hardware errors
#dat_sel = np.trunc((np.take(dat_list, sensor_ports) / 1000) * 10**2) / 10**2
'''ser = serial.Serial(port)
ser.flushInput()
while True:
try:
ser_bytes = ser.readline()
decoded_bytes = float(ser_bytes[0:len(ser_bytes)-2].decode("utf-8"))
except:
print("Keyboard Interrupt")
break'''
#dat_sel = np.trunc((np.take(decoded_bytes, sensor_ports) / 1000) * 10**2) / 10**2
dat_sel = np.trunc((np.take(dat_list, sensor_ports) / 1000) * 10**2) / 10**2
r_arr = np.take(refRes, sensor_ports) * (v_in / dat_sel - 1) # *2 <-- change with actual formula for ammonia concentration
# created a new array to convert resistance values to sci notation
r_arr2 = np.empty(len(r_arr), dtype=object)
@ -81,7 +71,7 @@ def read():
# headers based on number of ports used
if len(sensor_ports) == 1:
header = ['Time', 'Resistance']
if len(sensor_ports)== 2:
if len(sensor_ports) == 2:
header = ['Time', 'Resistance1', 'Resistance2']
if len(sensor_ports) == 3:
header = ['Time', 'Resistance1', 'Resistance2', 'Resistance3']
@ -91,19 +81,14 @@ def read():
f.close()
while controller.isOpen():
try:
#controller.flushInput()
read_data = controller.readline().decode("utf-8")
# use numpy so it can make list calculations easier (and possibly faster)
dat_list = np.asarray(json.loads(read_data), dtype=np.float32)[:SENSORS_MAX]
#ser_bytes = controller.readline()
#decoded_bytes = float(ser_bytes[0:len(ser_bytes)-2].decode("utf-8"))
#dat_sel = np.trunc((np.take(dat_list, sensor_ports) / 1000) * 10**2) / 10**2
dat_sel = np.take(dat_list, sensor_ports)
# if we decided to switch back to analogRead(), replace this line of comment to the algorithm to something like the commented line below
# dat_sel = np.take(dat_list, sensor_ports) * v_in / resolution
# take only the nonzero indices, and truncated to two decimal places to "filter" out some hardware errors
#dat_sel = np.trunc((np.take(dat_list, sensor_ports) / 1000) * 10**2) / 10**2
r_arr = np.take(refRes, sensor_ports) * (v_in / dat_sel - 1) # *2 <-- change with actual formula for ammonia concentration
# created a new array to convert resistance values to sci notation
r_arr2 = np.empty(len(r_arr), dtype=object)

View File

@ -1,12 +1,6 @@
matplotlib
pyserial
wxPython
os
requests
urllib.requests
zipfile
datetime
serial
time
json
numpy
pyinstaller

View File

@ -1024,186 +1024,6 @@
<event name="OnUpdateUI"></event>
</object>
</object>
<object class="gbsizeritem" expanded="0">
<property name="border">5</property>
<property name="colspan">1</property>
<property name="column">0</property>
<property name="flag">wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT</property>
<property name="row">1</property>
<property name="rowspan">1</property>
<object class="wxStaticText" expanded="0">
<property name="BottomDockable">1</property>
<property name="LeftDockable">1</property>
<property name="RightDockable">1</property>
<property name="TopDockable">1</property>
<property name="aui_layer"></property>
<property name="aui_name"></property>
<property name="aui_position"></property>
<property name="aui_row"></property>
<property name="best_size"></property>
<property name="bg"></property>
<property name="caption"></property>
<property name="caption_visible">1</property>
<property name="center_pane">0</property>
<property name="close_button">1</property>
<property name="context_help"></property>
<property name="context_menu">1</property>
<property name="default_pane">0</property>
<property name="dock">Dock</property>
<property name="dock_fixed">0</property>
<property name="docking">Left</property>
<property name="enabled">1</property>
<property name="fg"></property>
<property name="floatable">1</property>
<property name="font"></property>
<property name="gripper">0</property>
<property name="hidden">0</property>
<property name="id">wxID_ANY</property>
<property name="label">ADC bit rate</property>
<property name="max_size"></property>
<property name="maximize_button">0</property>
<property name="maximum_size"></property>
<property name="min_size"></property>
<property name="minimize_button">0</property>
<property name="minimum_size"></property>
<property name="moveable">1</property>
<property name="name">vadc_prompt</property>
<property name="pane_border">1</property>
<property name="pane_position"></property>
<property name="pane_size"></property>
<property name="permission">protected</property>
<property name="pin_button">1</property>
<property name="pos"></property>
<property name="resize">Resizable</property>
<property name="show">1</property>
<property name="size"></property>
<property name="style">wxALIGN_RIGHT|wxST_ELLIPSIZE_END</property>
<property name="subclass">; ; forward_declare</property>
<property name="toolbar_pane">0</property>
<property name="tooltip"></property>
<property name="window_extra_style"></property>
<property name="window_name"></property>
<property name="window_style"></property>
<property name="wrap">-1</property>
<event name="OnChar"></event>
<event name="OnEnterWindow"></event>
<event name="OnEraseBackground"></event>
<event name="OnKeyDown"></event>
<event name="OnKeyUp"></event>
<event name="OnKillFocus"></event>
<event name="OnLeaveWindow"></event>
<event name="OnLeftDClick"></event>
<event name="OnLeftDown"></event>
<event name="OnLeftUp"></event>
<event name="OnMiddleDClick"></event>
<event name="OnMiddleDown"></event>
<event name="OnMiddleUp"></event>
<event name="OnMotion"></event>
<event name="OnMouseEvents"></event>
<event name="OnMouseWheel"></event>
<event name="OnPaint"></event>
<event name="OnRightDClick"></event>
<event name="OnRightDown"></event>
<event name="OnRightUp"></event>
<event name="OnSetFocus"></event>
<event name="OnSize"></event>
<event name="OnUpdateUI"></event>
</object>
</object>
<object class="gbsizeritem" expanded="0">
<property name="border">5</property>
<property name="colspan">1</property>
<property name="column">1</property>
<property name="flag">wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL|wxALL</property>
<property name="row">1</property>
<property name="rowspan">1</property>
<object class="wxTextCtrl" expanded="0">
<property name="BottomDockable">1</property>
<property name="LeftDockable">1</property>
<property name="RightDockable">1</property>
<property name="TopDockable">1</property>
<property name="aui_layer"></property>
<property name="aui_name"></property>
<property name="aui_position"></property>
<property name="aui_row"></property>
<property name="best_size"></property>
<property name="bg"></property>
<property name="caption"></property>
<property name="caption_visible">1</property>
<property name="center_pane">0</property>
<property name="close_button">1</property>
<property name="context_help"></property>
<property name="context_menu">1</property>
<property name="default_pane">0</property>
<property name="dock">Dock</property>
<property name="dock_fixed">0</property>
<property name="docking">Left</property>
<property name="enabled">1</property>
<property name="fg"></property>
<property name="floatable">1</property>
<property name="font"></property>
<property name="gripper">0</property>
<property name="hidden">0</property>
<property name="id">wxID_ANY</property>
<property name="max_size"></property>
<property name="maximize_button">0</property>
<property name="maximum_size"></property>
<property name="maxlength"></property>
<property name="min_size"></property>
<property name="minimize_button">0</property>
<property name="minimum_size"></property>
<property name="moveable">1</property>
<property name="name">adjusted_volt</property>
<property name="pane_border">1</property>
<property name="pane_position"></property>
<property name="pane_size"></property>
<property name="permission">protected</property>
<property name="pin_button">1</property>
<property name="pos"></property>
<property name="resize">Resizable</property>
<property name="show">1</property>
<property name="size"></property>
<property name="style"></property>
<property name="subclass">; ; forward_declare</property>
<property name="toolbar_pane">0</property>
<property name="tooltip">Bit size of the board&apos;s ADC, default is 12 bits (4096) minus 1</property>
<property name="validator_data_type"></property>
<property name="validator_style">wxFILTER_NONE</property>
<property name="validator_type">wxDefaultValidator</property>
<property name="validator_variable"></property>
<property name="value">4095</property>
<property name="window_extra_style"></property>
<property name="window_name"></property>
<property name="window_style"></property>
<event name="OnChar"></event>
<event name="OnEnterWindow"></event>
<event name="OnEraseBackground"></event>
<event name="OnKeyDown"></event>
<event name="OnKeyUp"></event>
<event name="OnKillFocus"></event>
<event name="OnLeaveWindow"></event>
<event name="OnLeftDClick"></event>
<event name="OnLeftDown"></event>
<event name="OnLeftUp"></event>
<event name="OnMiddleDClick"></event>
<event name="OnMiddleDown"></event>
<event name="OnMiddleUp"></event>
<event name="OnMotion"></event>
<event name="OnMouseEvents"></event>
<event name="OnMouseWheel"></event>
<event name="OnPaint"></event>
<event name="OnRightDClick"></event>
<event name="OnRightDown"></event>
<event name="OnRightUp"></event>
<event name="OnSetFocus"></event>
<event name="OnSize"></event>
<event name="OnText"></event>
<event name="OnTextEnter"></event>
<event name="OnTextMaxLen"></event>
<event name="OnTextURL"></event>
<event name="OnUpdateUI"></event>
</object>
</object>
<object class="gbsizeritem" expanded="0">
<property name="border">5</property>
<property name="colspan">1</property>
@ -1351,7 +1171,7 @@
<property name="validator_style">wxFILTER_NONE</property>
<property name="validator_type">wxDefaultValidator</property>
<property name="validator_variable"></property>
<property name="value">480</property>
<property name="value">50</property>
<property name="window_extra_style"></property>
<property name="window_name"></property>
<property name="window_style"></property>
@ -1883,7 +1703,7 @@
<property name="caption"></property>
<property name="caption_visible">1</property>
<property name="center_pane">0</property>
<property name="checked">1</property>
<property name="checked">0</property>
<property name="close_button">1</property>
<property name="context_help"></property>
<property name="context_menu">1</property>

View File

@ -1,69 +0,0 @@
import matplotlib.pyplot as plt
import matplotlib.animation as animate
import math, json, traceback
try:
import matplotlib
matplotlib.use("WXAgg") # to force use one of the matplot's UI backend instead of an IDE's choice
except ImportError:
traceback.print_exc()
def plotter():
a = json.load(open('settings.json', 'r'))
sensors = len(a['sensor_ports'])
windowsize = a['winSize']
delay = a["delay"] / 1000
file = open(a["file_name"], "r")
colors = ['blue', 'orange', 'green', 'yellow']
fig, axs = plt.subplots(1, 1)
timeStamps = []
sensorsData = []
i = 0
while i <= sensors:
sensorsData.append([])
i = i + 1
def animation(t):
next_line = file.readline()
if next_line:
line = next_line.split(',')
if len(line) >= sensors:
i = 1
timeStamps.append(line[0])
# get rid of the zeroth value if it exceeds pre-defined window size
if windowsize != 0 and len(timeStamps) > windowsize:
timeStamps.pop(0)
val_lists = []
while i <= sensors:
if not line[i]: # pass on invalid values
continue
sensorsData[i - 1].append(float(line[i])) # cast it to float so the y-axis will not jump around
# get rid of the zeroth value if it exceeds pre-defined window size
if windowsize != 0 and len(sensorsData[i - 1]) > windowsize:
sensorsData[i - 1].pop(0)
val_lists.append(sensorsData[i - 1])
i += 1
for j in range(len(val_lists)):
axs.plot(timeStamps, val_lists[j], color=colors[j],
label=f'sensor {j + 1}') # TODO: display sensor number to the actual arduino's port &
# axs.annotate('%0.2f' % val_lists[j][-1], xy=(1, val_lists[j][-1]), xytext=(8, 0),
# xycoords=('axes fraction', 'data'), textcoords='offset points')
# Acknowledgement: https://stackoverflow.com/a/13589144
handles, labels = plt.gca().get_legend_handles_labels()
by_label = dict(zip(labels, handles))
axs.legend(by_label.values(), by_label.keys(), loc='best')
print(by_label.items())
return animate.FuncAnimation(plt.gcf(), func=animation) # , interval=delay * 500)
# ani.save("plot.gif")
# plt.ion()
# plt.show(block=True)
# plotter()

BIN
splash.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 529 KiB

15
test.py
View File

@ -29,9 +29,9 @@ def main():
r_ref[r_ref == ''] = '0' # correcting the emply values
resistors = r_ref.astype(np.float32).tolist() # convert string to numbers
input_voltage = float(frame.input_voltage.GetValue())
bit_rate = float(frame.adjusted_volt.GetValue())
# bit_rate = float(frame.adjusted_volt.GetValue())
port = frame.dev_list.GetValue()
# typical window size: 480
# typical window size: 50
window_size = int(frame.m_textCtrl26.GetValue())
#################### END USER INPUTS ########################
@ -46,10 +46,10 @@ def main():
if not (len(resistors) == 5):
raise ValueError(f"expecting 5 resistor values, but got {len(resistors)}!!!")
gen_settings(resistors, input_voltage, bit_rate, port, filename, window_size, delay)
gen_settings(resistors, input_voltage, port, filename, window_size, delay)
def gen_settings(resistors, input_voltage, bits, port, filename, window_size, delay):
def gen_settings(resistors, input_voltage, port, filename, window_size, delay):
"""
export all inputs from main() to a .json file
:param resistors: list of reference resistances of the sensors
@ -66,7 +66,7 @@ def gen_settings(resistors, input_voltage, bits, port, filename, window_size, de
settings["sensor_ports"] = np.where(np.array(resistors) > 0)[0].tolist()
settings["v_in"] = input_voltage
settings["port"] = port
settings["resolution"] = bits
# settings["resolution"] = bits
settings["winSize"] = window_size
settings["file_name"] = filename
settings["delay"] = delay
@ -104,7 +104,6 @@ if __name__ == '__main__':
if sys.platform.startswith('win'):
# On Windows calling this function is necessary.
freeze_support()
#global t1
app = wx.App(useBestVisual=True)
frame = Frame(None)
app.SetTopWindow(frame)
@ -115,7 +114,7 @@ if __name__ == '__main__':
sys.stdout.write("this is an alpha version of the design. This debug terminal will be gone on official release\n")
ports = [comport.device for comport in serial.tools.list_ports.comports()] # get all available ports
frame.dev_list.AppendItems(ports)
frame.SetTitle("SeeDatResistance - Beta 0.1.0 RC2")
frame.SetTitle("SeeDatResistance - Beta 0.1.1")
frame.btLaunch.Bind(wx.EVT_BUTTON, run)
if os.path.isfile("settings.json"):
try:
@ -126,7 +125,7 @@ if __name__ == '__main__':
frame.r_ref_3.SetValue(str(settings["refRes"][2]))
frame.r_ref_4.SetValue(str(settings["refRes"][3]))
frame.input_voltage.SetValue(str(settings["v_in"]))
frame.adjusted_volt.SetValue(str(settings["resolution"]))
# frame.adjusted_volt.SetValue(str(settings["resolution"]))
frame.m_textCtrl26.SetValue(str(settings["winSize"]))
if settings["port"] in ports: # auto-select device port if exist
frame.dev_list.SetValue(settings["port"])

View File

@ -1,183 +0,0 @@
"""
Top level script to prompt user for inputs, and then make those input as a .json file so that other modules can use it.
to run stuff. This module can have the frontend UI merged into, or can have separate one but should be the first things
to call from frontend before running other stuff.
"""
from datetime import datetime
from threading import *
import os, json, sys, subprocess
import serial.tools.list_ports
import numpy as np
import tkinter as tk
HEIGHT = 800
WIDTH = 800
def main():
#################### USER INPUTS ###########################
# TODO: if there is already a setting.json exist, loads all inputs from there as 'default'
# 9840
resistors = [int(text3.get()), int(text4.get()), 0, 0, 0] # resisters for each An port, where n is an integer from 0-3. Use 0 if none. in Ohms
input_voltage = 5
adjusted_volt = 5
port = get_devices()[0]
# typical window size: 480
window_size = 480
#################### END USER INPUTS ########################
# filename, disable customize name but should inform the user the current name of the data file on the front end
# alternatively, could be default input if no other input given. But if there is might have problem...
dat_folder = "RecordedData"
os.makedirs(dat_folder, exist_ok=True)
filename = os.path.join(os.getcwd(), dat_folder, f"{datetime.now().strftime('%Y-%m-%d_%H%M%S')}.csv")
resolution = 1023 # Arduino's analogRead()'s input resolution. We don't change this number usually.
delay = 1000 # millisec pe r data, defined in the firmware
if not (len(resistors) == 5):
raise ValueError(f"expecting 5 resistor values, but got {len(resistors)}!!!")
gen_settings(resistors, input_voltage, adjusted_volt, port, filename, resolution, window_size, delay)
return filename
def gen_settings(resistors, input_voltage, adjusted_volt, port, filename, resolution, window_size, delay):
name = "settings.json"
settings = {}
settings["refRes"] = resistors
settings["sensor_ports"] = np.where(np.array(resistors) > 0)[0].tolist()
settings["v_src"] = input_voltage
settings["v_in"] = adjusted_volt
settings["port"] = port
settings["resolution"] = resolution
settings["winSize"] = window_size
settings["file_name"] = filename
settings["delay"] = delay
open(name, 'w').write(json.dumps(settings, indent=4))
open(filename, "a", newline="", encoding="utf-8")
def get_devices():
ports = [comport.device for comport in serial.tools.list_ports.comports()] # get all available ports
if not ports:
raise ConnectionError("No devices detected!") # TODO: set to writerandomvalues.py thing instead raise error
return ports
# for scheduler maybe?
def task1():
print("run task 1")
# exec(open("read_arduino.py").read())
run_t1 = ["start", sys.executable, "read_arduino.pyw"]
out1 = subprocess.Popen(run_t1, shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE)
running = True
init = 0
while running:
cline = out1.stdout.readline()
print(cline.decode())
if not cline and init > 0:
print(cline.decode())
running = False
init += 1
if out1.returncode:
print('Something went wrong:', out1.returncode)
def task2():
print("run task 2")
run_t2 = ["start", os.path.join(sys.exec_prefix, 'pythonw'), "serial_plot.pyw"]
out2 = subprocess.Popen(run_t2, shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE)
running = True
init = 0
while running:
cline = out2.stdout.readline()
print(cline.decode())
if not cline and init > 0:
print(cline.decode())
running = False
init += 1
if out2.returncode:
print('Something went wrong:', out2.returncode)
def test_func():
file = main()
label['text'] = f"File has saved as {os.path.split(file)[1]} under {os.path.split(file)[0]} directory!\n"
t1 = Thread(target=task1, args=())
t1.setDaemon(True)
t1.start()
t2 = Thread(target=task2, args=())
t2.setDaemon(True)
t2.start()
if __name__ == '__main__':
# file = main()
# print(f"File has saved as {os.path.split(file)[1]} under {os.path.split(file)[0]} directory!")
# t1 = Thread(target=task1, args=())
# t1.setDaemon(True)
# t1.start()
#
# t2 = Thread(target=task2, args=())
# t2.setDaemon(True)
# t2.start()
# # while True:
# # pass
# t1.join()
# t2.join()
root = tk.Tk()
canvas = tk.Canvas(root, height=HEIGHT, width=WIDTH)
canvas.pack()
# backgroud_image = tk.PhotoImage(file='im1.gif')
backIm = tk.Label(root, bg='#80c1ff')
backIm.place(relwidth=1, relheight=1)
frame = tk.Frame(root, bg="#80c1ff", bd=5)
frame.place(relx=0.5, rely=0.1, relwidth=0.75, relheight=0.1, anchor='n')
button1 = tk.Button(frame, text="update", font=50, fg='green', bg='grey', command=lambda: test_func())
button1.place(relx=0.8, relheight=0.5, relwidth=0.2)
button2 = tk.Button(frame, text="clear", font=50, fg='red', bg='grey')
button2.place(relx=0.8, rely=0.5, relheight=0.5, relwidth=0.2)
lb1 = tk.Label(frame, text="filename")
lb1.place(relx=0.1, relheight=0.4, relwidth=0.2)
text1 = tk.StringVar()
box1 = tk.Entry(frame, textvariable=text1)
box1.place(relx=0.1, rely=0.5, relheight=0.5, relwidth=0.2)
lb2 = tk.Label(frame, text="num of sensor")
lb2.place(relx=0.3, relheight=0.4, relwidth=0.2)
text2 = tk.StringVar()
box2 = tk.Entry(frame, textvariable=text2)
box2.place(relx=0.3, rely=0.5, relheight=0.5, relwidth=0.2)
lb3 = tk.Label(frame, text="res1")
lb3.place(relx=0.5, relheight=0.4, relwidth=0.1)
text3 = tk.StringVar()
box3 = tk.Entry(frame, textvariable=text3)
box3.place(relx=0.5, rely=0.5, relheight=0.5, relwidth=0.1)
lb4 = tk.Label(frame, text="res2")
lb4.place(relx=0.6, relheight=0.4, relwidth=0.1)
text4 = tk.StringVar()
box4 = tk.Entry(frame, textvariable=text4)
box4.place(relx=0.6, rely=0.5, relheight=0.5, relwidth=0.1)
lower_frame = tk.Frame(root, bg="#80c1ff", bd=10)
lower_frame.place(relx=0.5, rely=0.25, relwidth=0.75, relheight=0.6, anchor='n')
label = tk.Label(lower_frame, font=40)
label.place(relwidth=1, relheight=1)
root.mainloop()

17
ui.py
View File

@ -8,6 +8,7 @@
###########################################################################
import wx
import wx.xrc
###########################################################################
## Class Frame
@ -16,7 +17,7 @@ import wx
class Frame ( wx.Frame ):
def __init__( self, parent ):
wx.Frame.__init__ ( self, parent, id = wx.ID_ANY, title = wx.EmptyString, pos = wx.DefaultPosition, size = wx.Size( 470,337 ), style = wx.DEFAULT_FRAME_STYLE )
wx.Frame.__init__ ( self, parent, id = wx.ID_ANY, title = wx.EmptyString, pos = wx.DefaultPosition, size = wx.Size( 470,330 ), style = wx.DEFAULT_FRAME_STYLE )
self.SetSizeHintsSz( wx.Size( 470,330 ), wx.DefaultSize )
self.SetForegroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_BTNTEXT ) )
@ -32,11 +33,11 @@ class Frame ( wx.Frame ):
self.r1_prompt = wx.StaticText( ref_entre.GetStaticBox(), wx.ID_ANY, u"Resistor 1", wx.DefaultPosition, wx.DefaultSize, 0 )
self.r1_prompt.Wrap( -1 )
res_grid.Add( self.r1_prompt, 0, wx.ALIGN_BOTTOM|wx.ALIGN_CENTER_HORIZONTAL, 5 )
self.r2_prompt = wx.StaticText( ref_entre.GetStaticBox(), wx.ID_ANY, u"Resistor 2", wx.DefaultPosition, wx.DefaultSize, 0 )
self.r2_prompt.Wrap( -1 )
res_grid.Add( self.r2_prompt, 0, wx.ALIGN_BOTTOM|wx.ALIGN_CENTER_HORIZONTAL, 5 )
self.r3_prompt = wx.StaticText( ref_entre.GetStaticBox(), wx.ID_ANY, u"Resistor 3", wx.DefaultPosition, wx.DefaultSize, 0 )
self.r3_prompt.Wrap( -1 )
res_grid.Add( self.r3_prompt, 0, wx.ALIGN_BOTTOM|wx.ALIGN_CENTER_HORIZONTAL, 5 )
@ -78,15 +79,6 @@ class Frame ( wx.Frame ):
gbSizer8.Add( self.input_voltage, wx.GBPosition( 0, 1 ), wx.GBSpan( 1, 1 ), wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_CENTER_HORIZONTAL|wx.ALL, 5 )
self.vadc_prompt = wx.StaticText( v_entre.GetStaticBox(), wx.ID_ANY, u"ADC bit rate", wx.DefaultPosition, wx.DefaultSize, wx.ALIGN_RIGHT|wx.ST_ELLIPSIZE_END )
self.vadc_prompt.Wrap( -1 )
gbSizer8.Add( self.vadc_prompt, wx.GBPosition( 1, 0 ), wx.GBSpan( 1, 1 ), wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT, 5 )
self.adjusted_volt = wx.TextCtrl( v_entre.GetStaticBox(), wx.ID_ANY, u"4095", wx.DefaultPosition, wx.DefaultSize, 0 )
self.adjusted_volt.SetToolTipString( u"Bit size of the board's ADC, default is 12 bits (4096) minus 1" )
gbSizer8.Add( self.adjusted_volt, wx.GBPosition( 1, 1 ), wx.GBSpan( 1, 1 ), wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_CENTER_HORIZONTAL|wx.ALL, 5 )
self.sizer_prompt = wx.StaticText( v_entre.GetStaticBox(), wx.ID_ANY, u"Window Size", wx.DefaultPosition, wx.DefaultSize, wx.ALIGN_RIGHT|wx.ST_ELLIPSIZE_END )
self.sizer_prompt.Wrap( -1 )
gbSizer8.Add( self.sizer_prompt, wx.GBPosition( 2, 0 ), wx.GBSpan( 1, 1 ), wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT, 5 )
@ -140,7 +132,6 @@ class Frame ( wx.Frame ):
bSizer1.Add( launch_opt, 0, wx.ALIGN_CENTER_HORIZONTAL, 5 )
self.show_msg = wx.CheckBox( self, wx.ID_ANY, u"Make launcher stays open after started", wx.DefaultPosition, wx.DefaultSize, 0 )
self.show_msg.SetValue(True)
bSizer1.Add( self.show_msg, 0, wx.ALL|wx.ALIGN_CENTER_HORIZONTAL, 5 )