diff --git a/dev/test_mb_2023_4_mux_2023.py b/dev/test_mb_2023_4_mux_2023.py index 59bc6377a5bcf59a70dcdb99595c657af5320239..80ff435b8795ec55cefa96ecccbd3564e43acaf8 100644 --- a/dev/test_mb_2023_4_mux_2023.py +++ b/dev/test_mb_2023_4_mux_2023.py @@ -87,9 +87,9 @@ if within_ohmpi: # k.get_data() k.load_sequence(os.path.join(os.path.dirname(__file__), '../sequences/test_circuit_1423.txt')) k.reset_mux() - k.run_multiple_sequences(sequence_delay=20, nb_meas=3) + # k.run_multiple_sequences(sequence_delay=20, nb_meas=3) # k.run_sequence(injection_duration=0.2) - # k.rs_check(tx_volt=4) + k.rs_check(tx_volt=4) # k.test_mux(mux_id=None, activation_time=0.2) # k._hw.switch_mux([A, B, M, N], state='on') # k._hw.vab_square_wave(12.,1., cycles=2) diff --git a/index.html b/index.html index 65ed3d53cd06e407221ad36d704db82298d43b20..7d7a3a4442b2ee1b2ec006a33980df3f8fc78cb0 100755 --- a/index.html +++ b/index.html @@ -220,8 +220,24 @@ mosquitto_sub -h raspberrypi.local -t ohmpi_0001/ctrl console.log('DATA LOG:', payload) let ddic = JSON.parse(payload.split('INFO:')[1]) - // check cmd_id is any - processMessage(ddic) + // RS check data + if ('rsdata' in ddic) { + rsdata[0]['x'].push(ddic['rsdata']['A']), //, + '-' + ddic['rsdata']['B'], + rsdata[0]['y'].push(ddic['rsdata']['rs']), + Plotly.redraw('rs') + + } else if ('download' in ddic) { + let dwl = document.getElementById('download') + dwl.setAttribute('href', serverUrl + '/data.zip') + dwl.setAttribute('download', 'data.zip') + dwl.click() + } else { // data or results from inversion + processMessage(ddic) + } + + if ('status' in ddic) { + document.getElementById('output').value = ddic['status'] + } // usually these don't have a cmd_id so we are not sure when @@ -488,7 +504,13 @@ mosquitto_sub -h raspberrypi.local -t ohmpi_0001/ctrl let surveySelect = document.getElementById('surveySelect') // bar chart for contact resistance - let rsdata = [] + let rsdata = [{ + 'x': [], + 'y': [], + name: 'RS', + type: 'bar', + } + ] let rslayout = { title: 'Contact resistances', yaxis: { @@ -518,7 +540,12 @@ mosquitto_sub -h raspberrypi.local -t ohmpi_0001/ctrl // clear RS graph function rsClearBtnFunc() { - rsdata = [] + rsdata = [{ + 'x': [], + 'y': [], + name: 'RS', + type: 'bar', + }] Plotly.newPlot('rs', rsdata, rslayout, {responsive: true}) } let rsClearBtn = document.getElementById('rsClearBtn') @@ -539,7 +566,7 @@ mosquitto_sub -h raspberrypi.local -t ohmpi_0001/ctrl //if (('status' in ddic) | ('data' in ddic)) { if (ddic.constructor == Object) { // it's a dictionnary // acquisition related - processData(ddic) + processData(ddic) } else { // inversion related invertedData = ddic @@ -769,11 +796,8 @@ mosquitto_sub -h raspberrypi.local -t ohmpi_0001/ctrl // download data function downloadBtnFunc() { - sendCommand('{"cmd": "download"}', function(x) { - let dwl = document.getElementById('download') - dwl.setAttribute('href', serverUrl + '/data.zip') - dwl.setAttribute('download', 'data.zip') - dwl.click() + sendCommand('{"cmd": "download_data"}', function(x) { + console.log(x) }) } let downloadBtn = document.getElementById('downloadBtn') diff --git a/ohmpi/http_interface.py b/ohmpi/http_interface.py index 7fd04e37d9e8ae509f56a8e85ea043bd6b22ac73..2b7e6a6534495a6757ba03890fa4220f512cde8a 100644 --- a/ohmpi/http_interface.py +++ b/ohmpi/http_interface.py @@ -2,77 +2,74 @@ from http.server import SimpleHTTPRequestHandler, HTTPServer import os import json import uuid -from config import MQTT_CONTROL_CONFIG, OHMPI_CONFIG -from termcolor import colored -import pandas as pd +# from config import MQTT_CONTROL_CONFIG, OHMPI_CONFIG +# from termcolor import colored +# import pandas as pd import shutil import time -import numpy as np +# import numpy as np from io import StringIO import threading -import paho.mqtt.client as mqtt_client -import paho.mqtt.publish as publish +# import paho.mqtt.client as mqtt_client +# import paho.mqtt.publish as publish hostName = "0.0.0.0" # for AP mode (not AP-STA) -serverPort = 8080 +serverPort = 8000 # https://gist.github.com/MichaelCurrie/19394abc19abd0de4473b595c0e37a3a -ctrl_broker = MQTT_CONTROL_CONFIG['hostname'] -publisher_config = MQTT_CONTROL_CONFIG.copy() -publisher_config['topic'] = MQTT_CONTROL_CONFIG['ctrl_topic'] -publisher_config.pop('ctrl_topic') - -print(colored(f"Sending commands control topic {MQTT_CONTROL_CONFIG['ctrl_topic']} on {MQTT_CONTROL_CONFIG['hostname']} broker.")) -cmd_id = None -received = False -rdic = {} - - -# set controller globally as __init__ seem to be called for each request and so we subscribe again each time (=overhead) -controller = mqtt_client.Client(f"ohmpi_{OHMPI_CONFIG['id']}_interface_http", clean_session=False) # create new instance -print(colored(f"Connecting to control topic {MQTT_CONTROL_CONFIG['ctrl_topic']} on {MQTT_CONTROL_CONFIG['hostname']} broker", 'blue')) -trials = 0 -trials_max = 10 -broker_connected = False -while trials < trials_max: - try: - controller.username_pw_set(MQTT_CONTROL_CONFIG['auth'].get('username'), - MQTT_CONTROL_CONFIG['auth']['password']) - controller.connect(MQTT_CONTROL_CONFIG['hostname']) - trials = trials_max - broker_connected = True - except Exception as e: - print(f'Unable to connect control broker: {e}') - print('trying again to connect to control broker...') - time.sleep(2) - trials += 1 -if broker_connected: - print(f"Subscribing to control topic {MQTT_CONTROL_CONFIG['ctrl_topic']}") - controller.subscribe(MQTT_CONTROL_CONFIG['ctrl_topic'], MQTT_CONTROL_CONFIG['qos']) -else: - print(f"Unable to connect to control broker on {MQTT_CONTROL_CONFIG['hostname']}") - controller = None - - -# start a listener for acknowledgement -def _control(): - def on_message(client, userdata, message): - global cmd_id, rdic, received - - command = json.loads(message.payload.decode('utf-8')) - #print('++++', cmd_id, received, command) - if ('reply' in command.keys()) and (command['cmd_id'] == cmd_id): - print(f'Acknowledgement reception of command {command} by OhmPi') - # print('oooooooooook', command['reply']) - received = True - #rdic = command - - controller.on_message = on_message - controller.loop_forever() +# ctrl_broker = MQTT_CONTROL_CONFIG['hostname'] +# publisher_config = MQTT_CONTROL_CONFIG.copy() +# publisher_config['topic'] = MQTT_CONTROL_CONFIG['ctrl_topic'] +# publisher_config.pop('ctrl_topic') + +# print(colored(f"Sending commands control topic {MQTT_CONTROL_CONFIG['ctrl_topic']} on {MQTT_CONTROL_CONFIG['hostname']} broker.")) +# cmd_id = None +# received = False +# rdic = {} + + +# # set controller globally as __init__ seem to be called for each request and so we subscribe again each time (=overhead) +# controller = mqtt_client.Client(f"ohmpi_{OHMPI_CONFIG['id']}_interface_http", clean_session=False) # create new instance +# print(colored(f"Connecting to control topic {MQTT_CONTROL_CONFIG['ctrl_topic']} on {MQTT_CONTROL_CONFIG['hostname']} broker", 'blue')) +# trials = 0 +# trials_max = 10 +# broker_connected = False +# while trials < trials_max: +# try: +# controller.username_pw_set(MQTT_CONTROL_CONFIG['auth'].get('username'), +# MQTT_CONTROL_CONFIG['auth']['password']) +# controller.connect(MQTT_CONTROL_CONFIG['hostname']) +# trials = trials_max +# broker_connected = True +# except Exception as e: +# print(f'Unable to connect control broker: {e}') +# print('trying again to connect to control broker...') +# time.sleep(2) +# trials += 1 +# if broker_connected: +# print(f"Subscribing to control topic {MQTT_CONTROL_CONFIG['ctrl_topic']}") +# controller.subscribe(MQTT_CONTROL_CONFIG['ctrl_topic'], MQTT_CONTROL_CONFIG['qos']) +# else: +# print(f"Unable to connect to control broker on {MQTT_CONTROL_CONFIG['hostname']}") +# controller = None + + +# # start a listener for acknowledgement +# def _control(): +# def on_message(client, userdata, message): +# global cmd_id, rdic, received + +# command = json.loads(message.payload.decode('utf-8')) +# if ('reply' in command.keys()) and (command['cmd_id'] == cmd_id): +# print(f'Acknowledgement reception of command {command} by OhmPi') +# received = True + +# controller.on_message = on_message +# controller.loop_forever() -t = threading.Thread(target=_control) -t.start() +# t = threading.Thread(target=_control) +# t.start() class MyServer(SimpleHTTPRequestHandler): @@ -124,64 +121,64 @@ class MyServer(SimpleHTTPRequestHandler): cmd_id = uuid.uuid4().hex dic = json.loads(self.rfile.read(int(self.headers['Content-Length']))) rdic = {} # response dictionary - if dic['cmd'] == 'run_multiple_sequences': - payload = json.dumps({'cmd_id': cmd_id, 'cmd': 'run_multiple_sequences'}) - publish.single(payload=payload, **publisher_config) - elif dic['cmd'] == 'interrupt': - payload = json.dumps({'cmd_id': cmd_id, 'cmd': 'interrupt'}) - publish.single(payload=payload, **publisher_config) - elif dic['cmd'] == 'getData': - # get all .csv file in data folder - fnames = [fname for fname in os.listdir('data/') if fname[-4:] == '.csv'] - ddic = {} - for fname in fnames: - if ((fname != 'readme.txt') - and ('_rs' not in fname) - and (fname.replace('.csv', '') not in dic['surveyNames'])): - df = pd.read_csv('data/' + fname) - ddic[fname.replace('.csv', '')] = { - 'a': df['A'].tolist(), - 'b': df['B'].tolist(), - 'm': df['M'].tolist(), - 'n': df['N'].tolist(), - 'rho': df['R [ohm]'].tolist(), - } - rdic['data'] = ddic - elif dic['cmd'] == 'removeData': - shutil.rmtree('data') - os.mkdir('data') - elif dic['cmd'] == 'update_settings': - if 'sequence' in dic['config'].keys() and dic['config']['sequence'] is not None: - sequence = dic['config'].pop('sequence', None) - sequence = np.loadtxt(StringIO(sequence)).astype(int).tolist() # list of list - # we pass the sequence as a list of list as this object is easier to parse for the json.loads() - # of ohmpi._process_commands() - payload = json.dumps({'cmd_id': cmd_id, 'cmd': 'set_sequence', 'kwargs': {'sequence': sequence}}) - print('payload ===', payload) - publish.single(payload=payload, **publisher_config) - payload = json.dumps({'cmd_id': cmd_id + '_settings', 'cmd': 'update_settings', 'kwargs': {'config': dic['config']}}) - cdic = dic['config'] - publish.single(payload=payload, **publisher_config) - elif dic['cmd'] == 'invert': - pass - elif dic['cmd'] == 'getResults': - pass - elif dic['cmd'] == 'rsCheck': - payload = json.dumps({'cmd_id': cmd_id, 'cmd': 'rs_check'}) - publish.single(payload=payload, **publisher_config) - - elif dic['cmd'] == 'getRsCheck': - fnames = sorted([fname for fname in os.listdir('data/') if fname[-7:] == '_rs.csv']) - if len(fnames) > 0: - df = pd.read_csv('data/' + fnames[-1]) - ddic = { - 'AB': (df['A'].astype('str') + '-' + df['B'].astype(str)).tolist(), - 'res': df['RS [kOhm]'].tolist() - } - else: - ddic = {} - rdic['data'] = ddic - elif dic['cmd'] == 'download': + # if dic['cmd'] == 'run_multiple_sequences': + # payload = json.dumps({'cmd_id': cmd_id, 'cmd': 'run_multiple_sequences'}) + # publish.single(payload=payload, **publisher_config) + # elif dic['cmd'] == 'interrupt': + # payload = json.dumps({'cmd_id': cmd_id, 'cmd': 'interrupt'}) + # publish.single(payload=payload, **publisher_config) + # elif dic['cmd'] == 'getData': + # # get all .csv file in data folder + # fnames = [fname for fname in os.listdir('data/') if fname[-4:] == '.csv'] + # ddic = {} + # for fname in fnames: + # if ((fname != 'readme.txt') + # and ('_rs' not in fname) + # and (fname.replace('.csv', '') not in dic['surveyNames'])): + # df = pd.read_csv('data/' + fname) + # ddic[fname.replace('.csv', '')] = { + # 'a': df['A'].tolist(), + # 'b': df['B'].tolist(), + # 'm': df['M'].tolist(), + # 'n': df['N'].tolist(), + # 'rho': df['R [ohm]'].tolist(), + # } + # rdic['data'] = ddic + # elif dic['cmd'] == 'removeData': + # shutil.rmtree('data') + # os.mkdir('data') + # elif dic['cmd'] == 'update_settings': + # if 'sequence' in dic['config'].keys() and dic['config']['sequence'] is not None: + # sequence = dic['config'].pop('sequence', None) + # sequence = np.loadtxt(StringIO(sequence)).astype(int).tolist() # list of list + # # we pass the sequence as a list of list as this object is easier to parse for the json.loads() + # # of ohmpi._process_commands() + # payload = json.dumps({'cmd_id': cmd_id, 'cmd': 'set_sequence', 'kwargs': {'sequence': sequence}}) + # print('payload ===', payload) + # publish.single(payload=payload, **publisher_config) + # payload = json.dumps({'cmd_id': cmd_id + '_settings', 'cmd': 'update_settings', 'kwargs': {'config': dic['config']}}) + # cdic = dic['config'] + # publish.single(payload=payload, **publisher_config) + # elif dic['cmd'] == 'invert': + # pass + # elif dic['cmd'] == 'getResults': + # pass + # elif dic['cmd'] == 'rsCheck': + # payload = json.dumps({'cmd_id': cmd_id, 'cmd': 'rs_check'}) + # publish.single(payload=payload, **publisher_config) + + # elif dic['cmd'] == 'getRsCheck': + # fnames = sorted([fname for fname in os.listdir('data/') if fname[-7:] == '_rs.csv']) + # if len(fnames) > 0: + # df = pd.read_csv('data/' + fnames[-1]) + # ddic = { + # 'AB': (df['A'].astype('str') + '-' + df['B'].astype(str)).tolist(), + # 'res': df['RS [kOhm]'].tolist() + # } + # else: + # ddic = {} + # rdic['data'] = ddic + if dic['cmd'] == 'download': shutil.make_archive('data', 'zip', 'data') elif dic['cmd'] == 'shutdown': print('shutting down...') diff --git a/ohmpi/ohmpi.py b/ohmpi/ohmpi.py index d75f076683fafa70aa7e4f7466f13ba783a09c42..5599f10aa2c9378412dd83a66e85ce162171e7a4 100644 --- a/ohmpi/ohmpi.py +++ b/ohmpi/ohmpi.py @@ -15,7 +15,7 @@ from copy import deepcopy import numpy as np import csv import time -from shutil import rmtree +from shutil import rmtree, make_archive from threading import Thread from inspect import getmembers, isfunction from datetime import datetime @@ -435,6 +435,28 @@ class OhmPi(object): else: self.exec_logger.warning('Not on Raspberry Pi, skipping reboot...') + def download_data(self, cmd_id=None): + """Create a zip of the data folder. + """ + datadir = os.path.join(os.path.dirname(__file__), '../data/') + make_archive(datadir, 'zip', 'data') + self.data_logger.info(json.dumps({'download': 'ready'})) + + def shutdown(self, cmd_id=None): + """Shutdown the Raspberry Pi + + Parameters + ---------- + cmd_id : str, optional + Unique command identifier + """ + + if self.on_pi: + self.exec_logger.info(f'Restarting pi following command {cmd_id}...') + os.system('poweroff') + else: + self.exec_logger.warning('Not on Raspberry Pi, skipping shutdown...') + def run_measurement(self, quad=None, nb_stack=None, injection_duration=None, duty_cycle=None, autogain=True, strategy='constant', tx_volt=5., best_tx_injtime=0.1, cmd_id=None, **kwargs): @@ -736,7 +758,9 @@ class OhmPi(object): # -> might be a problem at B (cf what we did with WofE) def rs_check(self, tx_volt=5., cmd_id=None): # TODO: add a default value for rs-check in config.py import it in ohmpi.py and add it in rs_check definition - """Checks contact resistances + """Checks contact resistances. + Strategy: we just open A and B, measure the current and using vAB set or + assumed (12V assumed for battery), we compute Rab. Parameters ---------- @@ -751,7 +775,7 @@ class OhmPi(object): # create custom sequence where MN == AB # we only check the electrodes which are in the sequence (not all might be connected) if self.sequence is None: - quads = np.array([[1, 2, 1, 2]], dtype=np.uint32) + quads = np.array([[1, 2, 0, 0]], dtype=np.uint32) else: elec = np.sort(np.unique(self.sequence.flatten())) # assumed order quads = np.vstack([ @@ -776,27 +800,40 @@ class OhmPi(object): # measure all quad of the RS sequence for i in range(0, quads.shape[0]): quad = quads[i, :] # quadrupole - self.switch_mux_on(quad, bypass_check=True) # put before raising the pins (otherwise conflict i2c) - d = self.run_measurement(quad=quad, nb_stack=1, injection_duration=0.2, tx_volt=tx_volt, autogain=False, - bypass_check=True) + self._hw.switch_mux(electrodes=list(quads[i, :2]), roles=['A', 'B'], state='on') + self._hw._vab_pulse(duration=0.2) + current = self._hw.readings[-1, 3] + voltage = self._hw.tx.pwr.voltage * 1000 + time.sleep(0.2) + + # self.switch_mux_on(quad, bypass_check=True) # put before raising the pins (otherwise conflict i2c) + # d = self.run_measurement(quad=quad, nb_stack=1, injection_duration=0.2, tx_volt=tx_volt, autogain=False, + # bypass_check=True) # if self._hw.tx.voltage_adjustable: # voltage = self._hw.tx.voltage # imposed voltage on dps # else: # voltage = self._hw.rx.voltage - voltage = self._hw.rx.voltage - current = self._hw.tx.current + # voltage = self._hw.rx.voltage + # current = self._hw.tx.current # compute resistance measured (= contact resistance) - resist = abs(voltage / current) / 1000. + resist = abs(voltage / current) / 1000 # kOhm # print(str(quad) + '> I: {:>10.3f} mA, V: {:>10.3f} mV, R: {:>10.3f} kOhm'.format( # current, voltage, resist)) - msg = f'Contact resistance {str(quad):s}: I: {current * 1000.:>10.3f} mA, ' \ - f'V: {voltage :>10.3f} mV, ' \ - f'R: {resist :>10.3f} kOhm' - - self.exec_logger.info(msg) + # msg = f'Contact resistance {str(quad):s}: I: {current :>10.3f} mA, ' \ + # f'V: {voltage :>10.3f} mV, ' \ + # f'R: {resist :>10.3f} kOhm' + # create a message as dictionnary to be used by the html interface + msg = { + 'rsdata': { + 'A': int(quad[0]), + 'B': int(quad[1]), + 'rs': resist, # in kOhm + } + } + self.data_logger.info(json.dumps(msg)) # if contact resistance = 0 -> we have a short circuit!! if resist < 1e-5: @@ -845,7 +882,7 @@ class OhmPi(object): quadrupole : list of 4 int List of 4 integers representing the electrode numbers. bypass_check: bool, optional - Bypasses checks for A==M or A==M or B==M or B==N (i.e. used for rs-check) + Bypasses checks for A==M or A==N or B==M or B==N (i.e. used for rs-check) """ assert len(quadrupole) == 4 if (self._hw.tx.pwr.voltage > self._hw.rx._voltage_max) and bypass_check: diff --git a/run_http_interface.sh b/run_http_interface.sh index 956067d067460056cf3d8ac18b6d519bc7413dfe..f98f082f9b176adf1a1a0afa429146ab9fb33c16 100755 --- a/run_http_interface.sh +++ b/run_http_interface.sh @@ -1,7 +1,7 @@ #!bin/bash -USER="pi" # change if other username +export PYTHONPATH=/home/$USER/OhmPi cd /home/$USER/OhmPi source /home/$USER/OhmPi/ohmpy/bin/activate -python ohmpi.py & # run ohmpi.py to capture the commands -python http_interface.py # run http_interface to serve the web GUI +python dev/start_mqtt_html.py & # run ohmpi.py to capture the commands +python3 -m http.server # run web GUI