Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add real time plotting functionality #1

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
117 changes: 63 additions & 54 deletions src/flow_meter.py
robinzhang24 marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
class FlowMeter(object):
"""
This class represents the flowmeter device and handles its I/O with the APIs provided by manufacturer
Sensirion."""
Sensirion.
"""
def __init__(self, port='/dev/ttyUSB0', baudrate=460800, slave_address=2):
"""
Initialize connection to the flow meter and other configurations.
Expand All @@ -22,13 +23,69 @@ def __init__(self, port='/dev/ttyUSB0', baudrate=460800, slave_address=2):
self.port = ShdlcSerialPort(port=port, baudrate=baudrate) # setup serial port
self.device = Sfc5xxxShdlcDevice(ShdlcConnection(self.port), slave_address=slave_address)
self.device.activate_calibration(3) # specify calibration file index in list; default now on Helium (3)
# set units
self.unit = Sfc5xxxMediumUnit(
# set default units
self._unit = Sfc5xxxMediumUnit(
Sfc5xxxUnitPrefix.ONE,
Sfc5xxxUnit.STANDARD_LITER,
Sfc5xxxUnitTimeBase.MINUTE
)
self.device.set_user_defined_medium_unit(self.unit)
self.device.set_user_defined_medium_unit(self._unit)


def set_units(self, prefix, unit, time_base):
"""
Set units of output for the flow rate readings. The Sensirion driver has a set of conventions for
naming the units, and you can use the get_unit_convention() method to view the valid units.

Parameters
----------
prefix : str
String literal of the unit prefix (MILLI, MICRO, KILO, etc.) Name has to match exactly with names of
the Sfx5xxxUnitPrefix enumerator items.
prefix : str
String literal of the medium units (STANDARD_LITER, BAR, etc.) Name has to match exactly with names of
the Sfx5xxxUnit enumerator items.
prefix : str
String literal of the unit time base (MILLISECOND, MINUTE, etc.) Name has to match exactly with names of
the Sfx5xxxUnitTimeBase enumerator items.
"""
_prefix = None
_unit = None
_time_base = None
for item in Sfc5xxxUnitPrefix:
if item.name == prefix : _prefix = item
if _prefix == None : raise KeyError('Invalid unit prefix. Refer to get_unit_convention() for prefix names')
for item in Sfc5xxxUnit:
if item.name == unit : _unit = item
if _unit == None : raise KeyError('Invalid unit. Refer to get_unit_convention() for unit names')
for item in Sfc5xxxUnitTimeBase:
if item.name == time_base : _time_base = item
if _time_base == None : raise KeyError('Invalid time base. Refer to get_unit_convention() for time base names')

units = Sfc5xxxMediumUnit(_prefix, _unit, _time_base)
self.device.set_user_defined_medium_unit(units)


def get_unit_convention(self, category):
"""
A helper method for getting available units for flow meter readings. You can use the 'name' string literals
to access and set units in the set_units() method.

Parameters
----------
category : str
Category of units you want to view. Available options are 'prefix', 'unit', and 'time_base'.
"""
if category == 'prefix':
for item in Sfc5xxxUnitPrefix:
print('name: ', item.name, ', description: ', item.description)
elif category == 'unit':
for item in Sfc5xxxUnit:
print('name: ', item.name, ', description: ', item.description)
elif category == 'time_base':
for item in Sfc5xxxUnitTimeBase:
print('name: ', item.name, ', description: ', item.description)
else: raise KeyError('Invalid unit category. Choose from prefix, unit, and time_base to view available units')


def set_baudrate(self, baudrate):
Expand All @@ -54,7 +111,7 @@ def get_reading(self, duration):
# dump what's already inside the buffer. this is only useful for low overhead
#buffer = self.device.read_measured_value_buffer(Sfc5xxxScaling.USER_DEFINED)
while len(reading) <= duration * 1000: # flow meter reads at 1kHz
robinzhang24 marked this conversation as resolved.
Show resolved Hide resolved
buffer = self.device.read_measured_value_buffer(Sfc5xxxScaling.USER_DEFINED)
buffer = self.device.read_measured_value_buffer(Sfc5xxxScaling.USER_DEFINED, max_reads=2)
#print(buffer.sampling_time) # the only "time" returned from read buffer command
reading.extend(buffer.values)
return reading
robinzhang24 marked this conversation as resolved.
Show resolved Hide resolved
Expand All @@ -72,52 +129,4 @@ def get_reading_single_cycle(self, duration):
reading.append(val)
return reading

# below is the standalone implementation of flow meter reading for reference
'''
with ShdlcSerialPort(port='/dev/ttyUSB0', baudrate=460800) as port:
device = Sfc5xxxShdlcDevice(ShdlcConnection(port), slave_address=2)

# select calibration
print('activate calibration...')
device.activate_calibration(3)

# set units
unit = Sfc5xxxMediumUnit(
Sfc5xxxUnitPrefix.ONE,
Sfc5xxxUnit.STANDARD_LITER,
Sfc5xxxUnitTimeBase.MINUTE
)

device.set_user_defined_medium_unit(unit)

# read flow value for 10s
# try with single value reading
print('start acquiring...')

# an implementation with buffer reading
read_time = []
reading = []
t = time.time()
buffer = device.read_measured_value_buffer(Sfc5xxxScaling.USER_DEFINED) # dump what's already inside the buffer
while len(reading) <= 3000:
buffer = device.read_measured_value_buffer(Sfc5xxxScaling.USER_DEFINED)
reading.extend(buffer.values)
if buffer.lost_values != 0:
print('lost values detected!')
#read_time.extend([t * 0.001 for t in range(counter, counter + len(buffer.values))])
#print(buffer.lost_values)
print('execution time:', time.time()-t)


output = open('flow_reading.txt', 'w', encoding='utf-8')
#output_time = open('sampling_time.txt', 'w', encoding='utf-8')
for r in reading:
output.write(str(r))
output.write("\n")
#for t in read_time:
#output_time.write(str(t))
#output_time.write("\n")
print('success')
output.close()
#output_time.close()
'''

76 changes: 57 additions & 19 deletions src/kernel.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import numpy as np
import matplotlib.pyplot as plt
import RPi.GPIO as GPIO
import time
import time, os, shutil
#from wavegen_control import wavegen_control
from flow_meter import FlowMeter
#from input import generate_pulse
Expand Down Expand Up @@ -37,38 +38,75 @@ def __init__(self, gpio_channel) -> None:

# def burst_mode(self, ncycles):
# self.wavegen.burst(enable=True, ncycles=ncycles, phase=0)

def check_disk_space(required_space_mb, path="/"):
"""
Check if there is enough disk space available.

Parameters
----------
required_space_mb : float
The required space in megabytes.
path : str, optional
The path to check the disk space of. Default is root.

Returns
-------
bool
True if there is enough space, False otherwise.
"""
total, used, free = shutil.disk_usage(path)
free_mb = free / 1024**2 # Convert bytes to MB
return free_mb >= required_space_mb

def acquire(self, duration, acquisition_limit=100):
def acquire(self, path, duration):
"""
Acquire flow rate measurements from the flow meter for a fixed duration at trigger.
Currently an upper limit of the number of acquisitions is in place.

Parameters
----------
duration : Duration of a single acquisition in seconds. This should not exceed the period of
plasma discharge to avoid malfunctioning.
acquisition_limit : Maximum number of acquisition the command can perform.
path : str
Path to the diectory that stores acquired data.
duration : float
Duration of a single acquisition in seconds. This should not exceed the period of
plasma discharge to avoid malfunctioning.
"""
shot_counts = 0
data_folder = path

plt.ion()
fig, ax = plt.subplots()
line, = ax.plot(np.zeros(int(duration*1000)))
ax.set_title('real time flow rate')
ax.set_ylabel('flow rate ('+self.flow_meter._unit.__str__()+')')
t = time.time()
try:
while shot_counts <= acquisition_limit:
while True:
if not self.check_disk_space(5, data_folder): # Check for at least 5 MB of free space
print("Insufficient disk space. Please free up space to continue.")
break

file_path = os.path.join(data_folder, f'output_{shot_counts}.csv')

print('waiting for signals...')
GPIO.wait_for_edge(self.gpio_channel, GPIO.RISING) # stop the code until receiving a trigger
#time.sleep(.1)
t = time.time()
#readings = np.array(self.flow_meter.get_reading(duration))
readings = np.array(self.flow_meter.get_reading_single_cycle(duration))
#readings = np.array(self.flow_meter.get_single_buffer())
np.savetxt(f'/home/pi/flow_meter/data/output_single_cycle_{shot_counts}.csv', readings)
readings = np.array(self.flow_meter.get_reading(duration))

np.savetxt(file_path, readings)
print('shot count {}'.format(shot_counts))
print(f'shot interval {time.time()-t}')
t = time.time()

line.set_ydata(readings[:int(duration*1000)])
ax.set_ylim(0,max(readings)*1.2)
fig.canvas.draw()
fig.canvas.flush_events()

shot_counts += 1

except KeyboardInterrupt:
GPIO.cleanup()
print('exit on ctrl-C keyboard interrupt')
except:
GPIO.cleanup()
print('an error occured')
print('Maximum number of shot records reached!')
GPIO.cleanup()
print('exit on Ctrl-C keyboard interrupt')
except Exception as e:
print('An error occured:\n', e)

3 changes: 3 additions & 0 deletions src/plot.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import numpy as np
import matplotlib.pyplot as plt
import matplotlib

#matplotlib.rcParams['backend'] = 'agg'

data = np.loadtxt('/home/pi/flow_meter/data/output_0.csv')
plt.plot(data)
Expand Down