Commit 65deb7ef authored by Olivier Kaufmann's avatar Olivier Kaufmann
Browse files

Adds basic_ohmpi_flows_node-red. Updates architecture.pdf. Moves...

Adds basic_ohmpi_flows_node-red. Updates architecture.pdf. Moves mqtt_controller.py in examples and updates it.
Showing with 46 additions and 351 deletions
+46 -351
No preview for this file type
[{"id":"61c1655c50bd371c","type":"tab","label":"Flow 1","disabled":false,"info":""},{"id":"2d6881abe9336fbf","type":"mqtt in","z":"61c1655c50bd371c","name":"","topic":"ohmpi_0001/exec","qos":"2","datatype":"auto-detect","broker":"6ae7e77e.04c64","nl":false,"rap":false,"inputs":0,"x":390,"y":40,"wires":[["02e94bb48ce2cded"]]},{"id":"e2590e574c551cb9","type":"mqtt in","z":"61c1655c50bd371c","name":"","topic":"ohmpi_0001/data","qos":"2","datatype":"auto","broker":"6ae7e77e.04c64","nl":false,"rap":false,"inputs":0,"x":380,"y":140,"wires":[["b9a9d56fd4fb0b8c"]]},{"id":"e2c109f78f9e714c","type":"mqtt out","z":"61c1655c50bd371c","name":"MQTT ctrl","topic":"ohmpi_0001/ctrl","qos":"2","retain":"false","respTopic":"","contentType":"","userProps":"","correl":"","expiry":"","broker":"6ae7e77e.04c64","x":820,"y":340,"wires":[]},{"id":"e1d067d8532ff36b","type":"mqtt in","z":"61c1655c50bd371c","name":"","topic":"ohmpi_0001/soh","qos":"2","datatype":"auto","broker":"6ae7e77e.04c64","nl":false,"rap":false,"inputs":0,"x":380,"y":240,"wires":[["f5c9dacaafac51d0"]]},{"id":"a7a6f6068e01c7eb","type":"ui_button","z":"61c1655c50bd371c","name":"Run sequence","group":"142ad6ae.d55e29","order":1,"width":"1","height":"1","passthru":false,"label":"⏺","tooltip":"run sequence","color":"red","bgcolor":"lightgrey","className":"","icon":"","payload":"{\"cmd_id\" :\"0\", \"cmd\":\"run_sequence_async\"}","payloadType":"str","topic":"topic","topicType":"msg","x":380,"y":340,"wires":[["e2c109f78f9e714c"]]},{"id":"d82912d9c5b122fe","type":"ui_button","z":"61c1655c50bd371c","name":"Interrupt","group":"142ad6ae.d55e29","order":2,"width":"1","height":"1","passthru":false,"label":" ◾","tooltip":"interrupt sequence","color":"black","bgcolor":"lightgrey","className":"","icon":"","payload":"{\"cmd_id\" :\"0\", \"cmd\":\"interrupt\"}","payloadType":"str","topic":"topic","topicType":"msg","x":360,"y":400,"wires":[["e2c109f78f9e714c"]]},{"id":"02e94bb48ce2cded","type":"ui_text","z":"61c1655c50bd371c","group":"64a75353.37700c","order":2,"width":"16","height":"3","name":"MQTT exec","label":"Execution","format":"{{msg.payload}}","layout":"row-spread","className":"","x":830,"y":40,"wires":[]},{"id":"b9a9d56fd4fb0b8c","type":"ui_text","z":"61c1655c50bd371c","group":"64a75353.37700c","order":3,"width":"16","height":"3","name":"MQTT Data","label":"Data","format":"{{msg.payload}}","layout":"row-spread","className":"","x":830,"y":140,"wires":[]},{"id":"f5c9dacaafac51d0","type":"ui_text","z":"61c1655c50bd371c","group":"64a75353.37700c","order":4,"width":0,"height":0,"name":"MQTT SOH","label":"SOH","format":"{{msg.payload}}","layout":"row-spread","className":"","x":830,"y":240,"wires":[]},{"id":"329591d611aa2704","type":"ui_button","z":"61c1655c50bd371c","name":"","group":"64a75353.37700c","order":1,"width":0,"height":0,"passthru":false,"label":"clear messages","tooltip":"","color":"","bgcolor":"","className":"","icon":"","payload":" ","payloadType":"str","topic":"topic","topicType":"msg","x":580,"y":160,"wires":[["f5c9dacaafac51d0","b9a9d56fd4fb0b8c","02e94bb48ce2cded"]]},{"id":"25d69085f401beae","type":"ui_dropdown","z":"61c1655c50bd371c","name":"","label":"command","tooltip":"","place":"Select option","group":"142ad6ae.d55e29","order":5,"width":0,"height":0,"passthru":true,"multiple":false,"options":[{"label":"","value":"load_sequence","type":"str"},{"label":"","value":"reset_mux","type":"str"},{"label":"","value":"set_sequence","type":"str"},{"label":"","value":"update_settings","type":"str"}],"payload":"","topic":"command","topicType":"str","className":"","x":140,"y":480,"wires":[["182249692ee7502c"]]},{"id":"edbd2d507fbf085a","type":"ui_text_input","z":"61c1655c50bd371c","name":"","label":"kwargs","tooltip":"","group":"142ad6ae.d55e29","order":5,"width":0,"height":0,"passthru":false,"mode":"text","delay":"0","topic":"kwargs","sendOnBlur":true,"className":"","topicType":"str","x":140,"y":600,"wires":[["679471976db01c0e"]]},{"id":"679471976db01c0e","type":"json","z":"61c1655c50bd371c","name":"","property":"payload","action":"","pretty":false,"x":290,"y":600,"wires":[["b11654144c63d1b2"]]},{"id":"435a126aec0c424c","type":"debug","z":"61c1655c50bd371c","name":"debug 5","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":640,"y":480,"wires":[]},{"id":"45c95568bb348470","type":"join","z":"61c1655c50bd371c","name":"","mode":"custom","build":"merged","property":"payload","propertyType":"msg","key":"topic","joiner":"\\n","joinerType":"str","accumulate":true,"timeout":"","count":"3","reduceRight":false,"reduceExp":"","reduceInit":"","reduceInitType":"","reduceFixup":"","x":650,"y":540,"wires":[["41122cfe96dd471a","e2c109f78f9e714c"]]},{"id":"41122cfe96dd471a","type":"ui_text","z":"61c1655c50bd371c","group":"142ad6ae.d55e29","order":6,"width":"2","height":"3","name":"","label":"Command to send","format":"{{msg.payload}}","layout":"row-spread","className":"","x":870,"y":600,"wires":[]},{"id":"182249692ee7502c","type":"function","z":"61c1655c50bd371c","name":"set cmd","func":"var newMsg = { payload: {\"cmd\": msg.payload }};\nreturn newMsg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":300,"y":480,"wires":[["435a126aec0c424c","45c95568bb348470"]]},{"id":"1549f01b20537e60","type":"ui_button","z":"61c1655c50bd371c","name":"","group":"142ad6ae.d55e29","order":7,"width":0,"height":0,"passthru":false,"label":"Send command","tooltip":"","color":"","bgcolor":"","className":"","icon":"","payload":"","payloadType":"str","topic":"topic","topicType":"msg","x":160,"y":700,"wires":[["c81857e22b65ef26","ae78c7b9e5aaeede"]]},{"id":"d4f6486f114c987d","type":"change","z":"61c1655c50bd371c","name":"","rules":[{"t":"set","p":"complete","pt":"msg","to":"true","tot":"bool"}],"action":"","property":"","from":"","to":"","reg":false,"x":590,"y":700,"wires":[["45c95568bb348470"]]},{"id":"b11654144c63d1b2","type":"function","z":"61c1655c50bd371c","name":"set kwargs","func":"var newMsg = { payload: {\"kwargs\": msg.payload }};\nreturn newMsg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":450,"y":600,"wires":[["45c95568bb348470"]]},{"id":"c81857e22b65ef26","type":"uuid","z":"61c1655c50bd371c","uuidVersion":"v1","namespaceType":"","namespace":"","namespaceCustom":"","name":"","field":"payload","fieldType":"msg","x":310,"y":660,"wires":[["1f97edbce5f88461"]]},{"id":"ae78c7b9e5aaeede","type":"delay","z":"61c1655c50bd371c","name":"","pauseType":"delay","timeout":"250","timeoutUnits":"milliseconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":390,"y":700,"wires":[["d4f6486f114c987d"]]},{"id":"1f97edbce5f88461","type":"function","z":"61c1655c50bd371c","name":"set cmd_id","func":"var newMsg = { payload: {\"cmd_id\": msg.payload }};\nreturn newMsg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":490,"y":660,"wires":[["45c95568bb348470"]]},{"id":"6ae7e77e.04c64","type":"mqtt-broker","name":"ohmpi_mqtt_broker","broker":"mg3d-dev.umons.ac.be","port":"1883","clientid":"","autoConnect":true,"usetls":false,"compatmode":false,"protocolVersion":"4","keepalive":"60","cleansession":true,"birthTopic":"","birthQos":"0","birthPayload":"","birthMsg":{},"closeTopic":"","closeQos":"0","closePayload":"","closeMsg":{},"willTopic":"","willQos":"0","willPayload":"","willMsg":{},"userProps":"","sessionExpiry":""},{"id":"142ad6ae.d55e29","type":"ui_group","name":"Commands","tab":"5d888f29.07334","order":1,"disp":true,"width":"6","collapse":false,"className":""},{"id":"64a75353.37700c","type":"ui_group","name":"Messages","tab":"5d888f29.07334","order":2,"disp":true,"width":"16","collapse":true},{"id":"5d888f29.07334","type":"ui_tab","name":"Simple OhmPi controller","icon":"dashboard","disabled":false,"hidden":false}]
\ No newline at end of file
...@@ -22,13 +22,13 @@ settings = { ...@@ -22,13 +22,13 @@ settings = {
} }
cmd_id = uuid.uuid4().hex cmd_id = uuid.uuid4().hex
payload = json.dumps({'cmd_id': cmd_id, 'cmd': 'update_settings', 'args': settings}) payload = json.dumps({'cmd_id': cmd_id, 'cmd': 'update_settings', 'kwargs': {'settings' : settings}})
print(f'Update settings setup message: {payload} to {publisher_config["topic"]} with config {publisher_config}') print(f'Update settings setup message: {payload} to {publisher_config["topic"]} with config {publisher_config}')
publish.single(payload=payload, **publisher_config) publish.single(payload=payload, **publisher_config)
sequence = [[1, 2, 3, 4]] sequence = [[1, 2, 3, 4]]
cmd_id = uuid.uuid4().hex cmd_id = uuid.uuid4().hex
payload = json.dumps({'cmd_id': cmd_id, 'cmd': 'set_sequence', 'args': sequence}) payload = json.dumps({'cmd_id': cmd_id, 'cmd': 'set_sequence', 'kwargs': {'sequence': sequence}})
print(f'Set sequence message: {payload} to {publisher_config["topic"]} with config {publisher_config}') print(f'Set sequence message: {payload} to {publisher_config["topic"]} with config {publisher_config}')
publish.single(payload=payload, **publisher_config) publish.single(payload=payload, **publisher_config)
cmd_id = uuid.uuid4().hex cmd_id = uuid.uuid4().hex
...@@ -38,7 +38,7 @@ publish.single(payload=payload, **publisher_config) ...@@ -38,7 +38,7 @@ publish.single(payload=payload, **publisher_config)
for i in range(4): for i in range(4):
cmd_id = uuid.uuid4().hex cmd_id = uuid.uuid4().hex
payload = json.dumps({'cmd_id': cmd_id, 'cmd': 'start'}) payload = json.dumps({'cmd_id': cmd_id, 'cmd': 'run_sequence_async'})
print(f'Publishing message {i}: {payload} to {publisher_config["topic"]} with config {publisher_config}') print(f'Publishing message {i}: {payload} to {publisher_config["topic"]} with config {publisher_config}')
publish.single(payload=payload, **publisher_config) publish.single(payload=payload, **publisher_config)
time.sleep(1) time.sleep(1)
......
import paho.mqtt.client as mqtt
from config import MQTT_CONTROL_CONFIG, CONTROL_CONFIG, OHMPI_CONFIG
import time
from queue import Queue
import zmq
ctrl_queue = Queue()
def on_message(client, userdata, message):
global socket
# Send the command
print(f'Sending command {message.payload.decode("utf-8")}')
socket.send(message.payload)
# Get the reply
reply = socket.recv()
print(f'Received reply {message.payload.decode("utf-8")}: {reply}')
mqtt_client = mqtt.Client(f'ohmpi_{OHMPI_CONFIG["id"]}_listener', clean_session=False) # create new instance
print('connecting command listener to broker')
trials = 0
trials_max = 10
broker_connected = False
while trials < trials_max:
try:
mqtt_client.username_pw_set(MQTT_CONTROL_CONFIG['auth'].get('username'),
MQTT_CONTROL_CONFIG['auth']['password'])
mqtt_client.connect(MQTT_CONTROL_CONFIG['hostname'])
trials = trials_max
broker_connected = True
except:
print('trying again...')
time.sleep(2)
trials += 1
if broker_connected:
print('Subscribing to topic', MQTT_CONTROL_CONFIG['ctrl_topic'])
mqtt_client.subscribe(MQTT_CONTROL_CONFIG['ctrl_topic'], MQTT_CONTROL_CONFIG['qos'])
mqtt_client.on_message = on_message
mqtt_client.loop_start()
context = zmq.Context()
# Socket to talk to server
print("Connecting to ohmpi control server")
socket = context.socket(zmq.REQ)
socket.connect(f'tcp://localhost:{CONTROL_CONFIG["tcp_port"]}')
while True:
time.sleep(.1)
else:
print("Unable to connect to control broker")
exit(1)
...@@ -15,7 +15,6 @@ from copy import deepcopy ...@@ -15,7 +15,6 @@ from copy import deepcopy
import numpy as np import numpy as np
import csv import csv
import time import time
from io import StringIO
from datetime import datetime from datetime import datetime
from termcolor import colored from termcolor import colored
import threading import threading
...@@ -35,7 +34,8 @@ try: ...@@ -35,7 +34,8 @@ try:
import digitalio # noqa import digitalio # noqa
from digitalio import Direction # noqa from digitalio import Direction # noqa
from gpiozero import CPUTemperature # noqa from gpiozero import CPUTemperature # noqa
import minimalmodbus # noqa import minimalmodbus # noqa
arm64_imports = True arm64_imports = True
except ImportError as error: except ImportError as error:
if EXEC_LOGGING_CONFIG['logging_level'] == DEBUG: if EXEC_LOGGING_CONFIG['logging_level'] == DEBUG:
...@@ -130,13 +130,13 @@ class OhmPi(object): ...@@ -130,13 +130,13 @@ class OhmPi(object):
# current injection module # current injection module
if self.idps: if self.idps:
self.DPS = minimalmodbus.Instrument(port='/dev/ttyUSB0', slaveaddress=1) # port name, address (decimal) self.DPS = minimalmodbus.Instrument(port='/dev/ttyUSB0', slaveaddress=1) # port name, address (decimal)
self.DPS.serial.baudrate = 9600 # Baud rate 9600 as listed in doc self.DPS.serial.baudrate = 9600 # Baud rate 9600 as listed in doc
self.DPS.serial.bytesize = 8 # self.DPS.serial.bytesize = 8 #
self.DPS.serial.timeout = 1 # greater than 0.5 for it to work self.DPS.serial.timeout = 1 # greater than 0.5 for it to work
self.DPS.debug = False # self.DPS.debug = False #
self.DPS.serial.parity = 'N' # No parity self.DPS.serial.parity = 'N' # No parity
self.DPS.mode = minimalmodbus.MODE_RTU # RTU mode self.DPS.mode = minimalmodbus.MODE_RTU # RTU mode
self.DPS.write_register(0x0001, 40, 0) # max current allowed (36 mA for relays) self.DPS.write_register(0x0001, 40, 0) # max current allowed (36 mA for relays)
# (last number) 0 is for mA, 3 is for A # (last number) 0 is for mA, 3 is for A
# injection courant and measure (TODO check if it works, otherwise back in run_measurement()) # injection courant and measure (TODO check if it works, otherwise back in run_measurement())
...@@ -157,7 +157,7 @@ class OhmPi(object): ...@@ -157,7 +157,7 @@ class OhmPi(object):
f" on {MQTT_CONTROL_CONFIG['hostname']} broker") f" on {MQTT_CONTROL_CONFIG['hostname']} broker")
def connect_mqtt() -> mqtt_client: def connect_mqtt() -> mqtt_client:
def on_connect(client, userdata, flags, rc): def on_connect(mqttclient, userdata, flags, rc):
if rc == 0: if rc == 0:
self.exec_logger.debug(f"Successfully connected to control broker:" self.exec_logger.debug(f"Successfully connected to control broker:"
f" {MQTT_CONTROL_CONFIG['hostname']}") f" {MQTT_CONTROL_CONFIG['hostname']}")
...@@ -170,6 +170,7 @@ class OhmPi(object): ...@@ -170,6 +170,7 @@ class OhmPi(object):
client.on_connect = on_connect client.on_connect = on_connect
client.connect(MQTT_CONTROL_CONFIG['hostname'], MQTT_CONTROL_CONFIG['port']) client.connect(MQTT_CONTROL_CONFIG['hostname'], MQTT_CONTROL_CONFIG['port'])
return client return client
try: try:
self.exec_logger.debug(f"Connecting to control broker: {MQTT_CONTROL_CONFIG['hostname']}") self.exec_logger.debug(f"Connecting to control broker: {MQTT_CONTROL_CONFIG['hostname']}")
self.controller = connect_mqtt() self.controller = connect_mqtt()
...@@ -209,6 +210,7 @@ class OhmPi(object): ...@@ -209,6 +210,7 @@ class OhmPi(object):
Parameters Parameters
---------- ----------
cmd_id
filename : str filename : str
filename to save the last measurement dataframe filename to save the last measurement dataframe
last_measurement : dict last_measurement : dict
...@@ -305,8 +307,8 @@ class OhmPi(object): ...@@ -305,8 +307,8 @@ class OhmPi(object):
time.sleep(best_tx_injtime) # inject for given tx time time.sleep(best_tx_injtime) # inject for given tx time
# autogain # autogain
self.ads_current = ads.ADS1115(self.i2c, gain=2/3, data_rate=860, address=self.ads_current_address) self.ads_current = ads.ADS1115(self.i2c, gain=2 / 3, data_rate=860, address=self.ads_current_address)
self.ads_voltage = ads.ADS1115(self.i2c, gain=2/3, data_rate=860, address=self.ads_voltage_address) self.ads_voltage = ads.ADS1115(self.i2c, gain=2 / 3, data_rate=860, address=self.ads_voltage_address)
# print('current P0', AnalogIn(self.ads_current, ads.P0).voltage) # print('current P0', AnalogIn(self.ads_current, ads.P0).voltage)
# print('voltage P0', AnalogIn(self.ads_voltage, ads.P0).voltage) # print('voltage P0', AnalogIn(self.ads_voltage, ads.P0).voltage)
# print('voltage P2', AnalogIn(self.ads_voltage, ads.P2).voltage) # print('voltage P2', AnalogIn(self.ads_voltage, ads.P2).voltage)
...@@ -320,8 +322,8 @@ class OhmPi(object): ...@@ -320,8 +322,8 @@ class OhmPi(object):
# we measure the voltage on both A0 and A2 to guess the polarity # we measure the voltage on both A0 and A2 to guess the polarity
I = AnalogIn(self.ads_current, ads.P0).voltage * 1000. / 50 / self.r_shunt # noqa measure current I = AnalogIn(self.ads_current, ads.P0).voltage * 1000. / 50 / self.r_shunt # noqa measure current
U0 = AnalogIn(self.ads_voltage, ads.P0).voltage * 1000. # measure voltage U0 = AnalogIn(self.ads_voltage, ads.P0).voltage * 1000. # noqa measure voltage
U2 = AnalogIn(self.ads_voltage, ads.P2).voltage * 1000. U2 = AnalogIn(self.ads_voltage, ads.P2).voltage * 1000. # noqa
# print('I (mV)', I*50*self.r_shunt) # print('I (mV)', I*50*self.r_shunt)
# print('I (mA)', I) # print('I (mA)', I)
# print('U0 (mV)', U0) # print('U0 (mV)', U0)
...@@ -337,7 +339,7 @@ class OhmPi(object): ...@@ -337,7 +339,7 @@ class OhmPi(object):
# compute constant # compute constant
c = vmn / I c = vmn / I
Rab = (volt * 1000.) / I Rab = (volt * 1000.) / I # noqa
self.exec_logger.debug(f'Rab = {Rab:.2f} Ohms') self.exec_logger.debug(f'Rab = {Rab:.2f} Ohms')
...@@ -444,6 +446,7 @@ class OhmPi(object): ...@@ -444,6 +446,7 @@ class OhmPi(object):
Parameters Parameters
---------- ----------
cmd_id
filename : str filename : str
Path of the .csv or .txt file with A, B, M and N electrodes. Path of the .csv or .txt file with A, B, M and N electrodes.
Electrode index start at 1. Electrode index start at 1.
...@@ -546,8 +549,6 @@ class OhmPi(object): ...@@ -546,8 +549,6 @@ class OhmPi(object):
except Exception as e: except Exception as e:
self.exec_logger.error( self.exec_logger.error(
f"Unable to execute {cmd}({str(kwargs) if kwargs is not None else ''}): {e}") f"Unable to execute {cmd}({str(kwargs) if kwargs is not None else ''}): {e}")
# f"Unable to execute {cmd}({str(args) + ', ' if args is not None else ''}"
# f"{str(kwargs) if kwargs is not None else ''}): {e}")
status = False status = False
except Exception as e: except Exception as e:
self.exec_logger.warning(f'Unable to decode command {message}: {e}') self.exec_logger.warning(f'Unable to decode command {message}: {e}')
...@@ -699,7 +700,7 @@ class OhmPi(object): ...@@ -699,7 +700,7 @@ class OhmPi(object):
self.pin1.value = False self.pin1.value = False
# one stack = 2 half-cycles (one positive, one negative) # one stack = 2 half-cycles (one positive, one negative)
pinMN = 0 if polarity > 0 else 2 pinMN = 0 if polarity > 0 else 2 # noqa
# sampling for each stack at the end of the injection # sampling for each stack at the end of the injection
sampling_interval = 10 # ms sampling_interval = 10 # ms
...@@ -752,7 +753,7 @@ class OhmPi(object): ...@@ -752,7 +753,7 @@ class OhmPi(object):
end_delay = time.time() end_delay = time.time()
# truncate the meas array if we didn't fill the last samples # truncate the meas array if we didn't fill the last samples
meas = meas[:k+1] meas = meas[:k + 1]
# measurement of current i and voltage u during off time # measurement of current i and voltage u during off time
measpp = np.zeros((meas.shape[0], 3)) * np.nan measpp = np.zeros((meas.shape[0], 3)) * np.nan
...@@ -779,13 +780,13 @@ class OhmPi(object): ...@@ -779,13 +780,13 @@ class OhmPi(object):
end_delay = time.time() end_delay = time.time()
# truncate the meas array if we didn't fill the last samples # truncate the meas array if we didn't fill the last samples
measpp = measpp[:k+1] measpp = measpp[:k + 1]
# we alternate on which ADS1115 pin we measure because of sign of voltage # we alternate on which ADS1115 pin we measure because of sign of voltage
if pinMN == 0: if pinMN == 0:
pinMN = 2 pinMN = 2 # noqa
else: else:
pinMN = 0 pinMN = 0 # noqa
# store data for full wave form # store data for full wave form
fulldata.append(meas) fulldata.append(meas)
...@@ -799,8 +800,8 @@ class OhmPi(object): ...@@ -799,8 +800,8 @@ class OhmPi(object):
# take average from the samples per stack, then sum them all # take average from the samples per stack, then sum them all
# average for the last third of the stacked values # average for the last third of the stacked values
# is done outside the loop # is done outside the loop
sum_i = sum_i + (np.mean(meas[-int(meas.shape[0]//3):, 0])) sum_i = sum_i + (np.mean(meas[-int(meas.shape[0] // 3):, 0]))
vmn1 = np.mean(meas[-int(meas.shape[0]//3), 1]) vmn1 = np.mean(meas[-int(meas.shape[0] // 3), 1])
if (n % 2) == 0: if (n % 2) == 0:
sum_vmn = sum_vmn - vmn1 sum_vmn = sum_vmn - vmn1
sum_ps = sum_ps + vmn1 sum_ps = sum_ps + vmn1
...@@ -913,121 +914,7 @@ class OhmPi(object): ...@@ -913,121 +914,7 @@ class OhmPi(object):
if nb_meas > 1: if nb_meas > 1:
time.sleep(dt) # waiting for next measurement (time-lapse) time.sleep(dt) # waiting for next measurement (time-lapse)
self.status = 'idle' self.status = 'idle'
self.thread = threading.Thread(target=func)
self.thread.start()
def run_sequence(self, cmd_id=None, **kwargs):
"""Runs sequence synchronously (=blocking on main thread).
Additional arguments are passed to run_measurement().
"""
self.status = 'running'
self.exec_logger.debug(f'Status: {self.status}')
self.exec_logger.debug(f'Measuring sequence: {self.sequence}')
t0 = time.time()
# create filename with timestamp
filename = self.settings["export_path"].replace('.csv',
f'_{datetime.now().strftime("%Y%m%dT%H%M%S")}.csv')
self.exec_logger.debug(f'Saving to {filename}')
# make sure all multiplexer are off
self.reset_mux()
# measure all quadrupole of the sequence
if self.sequence is None:
n = 1
else:
n = self.sequence.shape[0]
for i in range(0, n):
if self.sequence is None:
quad = np.array([0, 0, 0, 0])
else:
quad = self.sequence[i, :] # quadrupole
if self.status == 'stopping':
break
# call the switch_mux function to switch to the right electrodes
self.switch_mux_on(quad)
# run a measurement
if self.on_pi:
acquired_data = self.run_measurement(quad, **kwargs)
else: # for testing, generate random data
acquired_data = {
'A': [quad[0]], 'B': [quad[1]], 'M': [quad[2]], 'N': [quad[3]],
'R [ohm]': np.abs(np.random.randn(1))
}
# switch mux off
self.switch_mux_off(quad)
# add command_id in dataset
acquired_data.update({'cmd_id': cmd_id})
# log data to the data logger
# self.data_logger.info(f'{acquired_data}')
# save data and print in a text file
self.append_and_save(filename, acquired_data)
self.exec_logger.debug(f'quadrupole {i + 1:d}/{n:d}')
self.status = 'idle'
def run_sequence_async(self, cmd_id=None, **kwargs):
"""Runs the sequence in a separate thread. Can be stopped by 'OhmPi.interrupt()'.
Additional arguments are passed to run_measurement().
Parameters
----------
cmd_id:
"""
def func():
self.run_sequence(**kwargs)
self.thread = threading.Thread(target=func)
self.thread.start()
self.status = 'idle'
def run_multiple_sequences(self, cmd_id=None, sequence_delay=None, nb_meas=None, **kwargs):
"""Runs multiple sequences in a separate thread for monitoring mode.
Can be stopped by 'OhmPi.interrupt()'.
Additional arguments are passed to run_measurement().
Parameters
----------
cmd_id :
sequence_delay : int, optional
Number of seconds at which the sequence must be started from each others.
nb_meas : int, optional
Number of time the sequence must be repeated.
kwargs : dict, optional
See help(k.run_measurement) for more info.
"""
# self.run = True
if sequence_delay is None:
sequence_delay = self.settings['sequence_delay']
sequence_delay = int(sequence_delay)
if nb_meas is None:
nb_meas = self.settings['nb_meas']
self.status = 'running'
self.exec_logger.debug(f'Status: {self.status}')
self.exec_logger.debug(f'Measuring sequence: {self.sequence}')
def func():
for g in range(0, nb_meas): # for time-lapse monitoring
if self.status == 'stopping':
self.exec_logger.warning('Data acquisition interrupted')
break
t0 = time.time()
self.run_sequence(**kwargs)
# sleeping time between sequence
dt = sequence_delay - (time.time() - t0)
if dt < 0:
dt = 0
if nb_meas > 1:
time.sleep(dt) # waiting for next measurement (time-lapse)
self.status = 'idle'
self.thread = threading.Thread(target=func) self.thread = threading.Thread(target=func)
self.thread.start() self.thread.start()
...@@ -1079,7 +966,7 @@ class OhmPi(object): ...@@ -1079,7 +966,7 @@ class OhmPi(object):
# add command_id in dataset # add command_id in dataset
acquired_data.update({'cmd_id': cmd_id}) acquired_data.update({'cmd_id': cmd_id})
# log data to the data logger # log data to the data logger
# self.data_logger.info(f'{acquired_data}') # already in run_measurement() # self.data_logger.info(f'{acquired_data}')
# save data and print in a text file # save data and print in a text file
self.append_and_save(filename, acquired_data) self.append_and_save(filename, acquired_data)
self.exec_logger.debug(f'quadrupole {i + 1:d}/{n:d}') self.exec_logger.debug(f'quadrupole {i + 1:d}/{n:d}')
...@@ -1149,8 +1036,8 @@ class OhmPi(object): ...@@ -1149,8 +1036,8 @@ class OhmPi(object):
# print(str(quad) + '> I: {:>10.3f} mA, V: {:>10.3f} mV, R: {:>10.3f} kOhm'.format( # print(str(quad) + '> I: {:>10.3f} mA, V: {:>10.3f} mV, R: {:>10.3f} kOhm'.format(
# current, voltage, resist)) # current, voltage, resist))
msg = f'Contact resistance {str(quad):s}: I: {current * 1000.:>10.3f} mA, ' \ msg = f'Contact resistance {str(quad):s}: I: {current * 1000.:>10.3f} mA, ' \
f'V: {voltage :>10.3f} mV, ' \ f'V: {voltage :>10.3f} mV, ' \
f'R: {resist :>10.3f} kOhm' f'R: {resist :>10.3f} kOhm'
self.exec_logger.debug(msg) self.exec_logger.debug(msg)
...@@ -1171,95 +1058,11 @@ class OhmPi(object): ...@@ -1171,95 +1058,11 @@ class OhmPi(object):
else: else:
pass pass
self.status = 'idle' self.status = 'idle'
# #
# # TODO if interrupted, we would need to restore the values # # TODO if interrupted, we would need to restore the values
# # TODO or we offer the possibility in 'run_measurement' to have rs_check each time? # # TODO or we offer the possibility in 'run_measurement' to have rs_check each time?
def set_sequence(self, sequence=None):
try:
self.sequence = np.loadtxt(StringIO(sequence)).astype('uint32')
status = True
except Exception as e:
self.exec_logger.warning(f'Unable to set sequence: {e}')
status = False
def stop(self):
warnings.warn('This function is deprecated. Use interrupt instead.', DeprecationWarning)
self.interrupt()
def _switch_mux(self, electrode_nr, state, role):
"""Selects the right channel for the multiplexer cascade for a given electrode.
Parameters
----------
electrode_nr : int
Electrode index to be switched on or off.
state : str
Either 'on' or 'off'.
role : str
Either 'A', 'B', 'M' or 'N', so we can assign it to a MUX board.
"""
if not self.use_mux or not self.on_pi:
if not self.on_pi:
self.exec_logger.warning('Cannot reset mux while in simulation mode...')
else:
self.exec_logger.warning('You cannot use the multiplexer because use_mux is set to False.'
' Set use_mux to True to use the multiplexer...')
elif self.sequence is None:
self.exec_logger.warning('Unable to switch MUX without a sequence')
else:
# choose with MUX board
tca = adafruit_tca9548a.TCA9548A(self.i2c, self.board_addresses[role])
# find I2C address of the electrode and corresponding relay
# considering that one MCP23017 can cover 16 electrodes
i2c_address = 7 - (electrode_nr - 1) // 16 # quotient without rest of the division
relay_nr = electrode_nr - (electrode_nr // 16) * 16 + 1
if i2c_address is not None:
# select the MCP23017 of the selected MUX board
mcp2 = MCP23017(tca[i2c_address])
mcp2.get_pin(relay_nr - 1).direction = digitalio.Direction.OUTPUT
if state == 'on':
mcp2.get_pin(relay_nr - 1).value = True
else:
mcp2.get_pin(relay_nr - 1).value = False
self.exec_logger.debug(f'Switching relay {relay_nr} '
f'({str(hex(self.board_addresses[role]))}) {state} for electrode {electrode_nr}')
else:
self.exec_logger.warning(f'Unable to address electrode nr {electrode_nr}')
def switch_mux_on(self, quadrupole):
"""Switches on multiplexer relays for given quadrupole.
Parameters
----------
quadrupole : list of 4 int
List of 4 integers representing the electrode numbers.
"""
roles = ['A', 'B', 'M', 'N']
# another check to be sure A != B
if quadrupole[0] != quadrupole[1]:
for i in range(0, 4):
if quadrupole[i] > 0:
self._switch_mux(quadrupole[i], 'on', roles[i])
else:
self.exec_logger.error('Not switching MUX : A == B -> short circuit risk detected!')
def switch_mux_off(self, quadrupole):
"""Switches off multiplexer relays for given quadrupole.
Parameters
----------
quadrupole : list of 4 int
List of 4 integers representing the electrode numbers.
"""
roles = ['A', 'B', 'M', 'N']
for i in range(0, 4):
if quadrupole[i] > 0:
self._switch_mux(quadrupole[i], 'off', roles[i])
def set_sequence(self, sequence=None, cmd_id=None): def set_sequence(self, sequence=None, cmd_id=None):
try: try:
self.sequence = np.array(sequence).astype(int) self.sequence = np.array(sequence).astype(int)
...@@ -1269,9 +1072,9 @@ class OhmPi(object): ...@@ -1269,9 +1072,9 @@ class OhmPi(object):
self.exec_logger.warning(f'Unable to set sequence: {e}') self.exec_logger.warning(f'Unable to set sequence: {e}')
status = False status = False
def stop(self, cmd_id=None): def stop(self, **kwargs):
warnings.warn('This function is deprecated. Use interrupt instead.', DeprecationWarning) warnings.warn('This function is deprecated. Use interrupt instead.', DeprecationWarning)
self.interrupt() self.interrupt(**kwargs)
def _switch_mux(self, electrode_nr, state, role): def _switch_mux(self, electrode_nr, state, role):
"""Selects the right channel for the multiplexer cascade for a given electrode. """Selects the right channel for the multiplexer cascade for a given electrode.
...@@ -1322,6 +1125,7 @@ class OhmPi(object): ...@@ -1322,6 +1125,7 @@ class OhmPi(object):
Parameters Parameters
---------- ----------
cmd_id
quadrupole : list of 4 int quadrupole : list of 4 int
List of 4 integers representing the electrode numbers. List of 4 integers representing the electrode numbers.
""" """
...@@ -1339,6 +1143,7 @@ class OhmPi(object): ...@@ -1339,6 +1143,7 @@ class OhmPi(object):
Parameters Parameters
---------- ----------
cmd_id
quadrupole : list of 4 int quadrupole : list of 4 int
List of 4 integers representing the electrode numbers. List of 4 integers representing the electrode numbers.
""" """
...@@ -1347,65 +1152,6 @@ class OhmPi(object): ...@@ -1347,65 +1152,6 @@ class OhmPi(object):
if quadrupole[i] > 0: if quadrupole[i] > 0:
self._switch_mux(quadrupole[i], 'off', roles[i]) self._switch_mux(quadrupole[i], 'off', roles[i])
def reset_mux(self):
"""Switches off all multiplexer relays."""
if self.on_pi and self.use_mux:
roles = ['A', 'B', 'M', 'N']
for i in range(0, 4):
for j in range(1, self.max_elec + 1):
self._switch_mux(j, 'off', roles[i])
self.exec_logger.debug('All MUX switched off.')
elif not self.on_pi:
self.exec_logger.warning('Cannot reset mux while in simulation mode...')
else:
self.exec_logger.warning('You cannot use the multiplexer because use_mux is set to False.'
' Set use_mux to True to use the multiplexer...')
def _update_acquisition_settings(self, config):
warnings.warn('This function is deprecated, use update_settings() instead.', DeprecationWarning)
self.update_settings(config)
def update_settings(self, config):
"""Updates acquisition settings from a json file or dictionary.
Parameters can be:
- nb_electrodes (number of electrode used, if 4, no MUX needed)
- injection_duration (in seconds)
- nb_meas (total number of times the sequence will be run)
- sequence_delay (delay in second between each sequence run)
- nb_stack (number of stack for each quadrupole measurement)
- export_path (path where to export the data, timestamp will be added to filename)
Parameters
----------
config : str, dict
Path to the .json settings file or dictionary of settings.
"""
status = False
if config is not None:
try:
if isinstance(config, dict):
self.settings.update(config)
else:
with open(config) as json_file:
dic = json.load(json_file)
self.settings.update(dic)
self.exec_logger.debug('Acquisition parameters updated: ' + str(self.settings))
status = True
except Exception as e:
self.exec_logger.warning('Unable to update settings.')
status = False
else:
self.exec_logger.warning('Settings are missing...')
return status
# Properties
@property
def sequence(self):
"""Gets sequence"""
if self._sequence is not None:
assert isinstance(self._sequence, np.ndarray)
return self._sequence
def reset_mux(self, cmd_id=None): def reset_mux(self, cmd_id=None):
"""Switches off all multiplexer relays.""" """Switches off all multiplexer relays."""
if self.on_pi and self.use_mux: if self.on_pi and self.use_mux:
...@@ -1422,9 +1168,9 @@ class OhmPi(object): ...@@ -1422,9 +1168,9 @@ class OhmPi(object):
def _update_acquisition_settings(self, config): def _update_acquisition_settings(self, config):
warnings.warn('This function is deprecated, use update_settings() instead.', DeprecationWarning) warnings.warn('This function is deprecated, use update_settings() instead.', DeprecationWarning)
self.update_settings(config) self.update_settings(settings=config)
def update_settings(self, config:str, cmd_id=None): def update_settings(self, settings: str, cmd_id=None):
"""Updates acquisition settings from a json file or dictionary. """Updates acquisition settings from a json file or dictionary.
Parameters can be: Parameters can be:
- nb_electrodes (number of electrode used, if 4, no MUX needed) - nb_electrodes (number of electrode used, if 4, no MUX needed)
...@@ -1436,21 +1182,22 @@ class OhmPi(object): ...@@ -1436,21 +1182,22 @@ class OhmPi(object):
Parameters Parameters
---------- ----------
config : str, dict cmd_id
settings : str, dict
Path to the .json settings file or dictionary of settings. Path to the .json settings file or dictionary of settings.
""" """
status = False status = False
if config is not None: if settings is not None:
try: try:
if isinstance(config, dict): if isinstance(settings, dict):
self.settings.update(config) self.settings.update(settings)
else: else:
with open(config) as json_file: with open(settings) as json_file:
dic = json.load(json_file) dic = json.load(json_file)
self.settings.update(dic) self.settings.update(dic)
self.exec_logger.debug('Acquisition parameters updated: ' + str(self.settings)) self.exec_logger.debug('Acquisition parameters updated: ' + str(self.settings))
status = True status = True
except Exception as e: except Exception as e: # noqa
self.exec_logger.warning('Unable to update settings.') self.exec_logger.warning('Unable to update settings.')
status = False status = False
else: else:
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment