Commit 43df7157 authored by remi.clement@inrae.fr's avatar remi.clement@inrae.fr
Browse files

add some doc on usage + all mqtt files from merge

Showing with 411 additions and 0 deletions
+411 -0
Pipfile 0 → 100644
[[source]]
name = "pypi"
url = "https://pypi.org/simple"
verify_ssl = true
[dev-packages]
[packages]
numpy = "*"
# cython = "*"
# pandas = "*"
paho-mqtt = "*"
pytz = "*"
six = "*"
gpiozero = "*"
adafruit-blinka = "*"
adafruit-circuitpython-ads1x15 = "*"
adafruit-circuitpython-tca9548a = "*"
adafruit-circuitpython-mcp230xx = "*"
termcolor ="*"
[requires]
python_version = "3.9"
File added
File added
#!/bin/bash
sudo apt-get install -y libatlas-base-dev
python -m venv ohmpy
source ohmpy/bin/activate || exit 1 # NOTE: Added || exit to avoid installing requirements in system python
export CFLAGS=-fcommon
pip install -r requirements.txt
.. warning::
**Ohmpi is a participative project open to all, it requires skills in electronics and to respect the safety rules. Ohmpi must be assembled in a professional context and by people competent in electronics. The Ohmpi team cannot be held responsible for any material or human damage which would be associated with the use or the assembly of OHMPI. The Ohmpi team cannot be held responsible if the equipment does not work after assembly.**
**Usage** Interfaces and applications
****************************************************
Different interfaces can be used to communicated with the OhmPi.
*** Web interface ***
The raspberrypi of the OhmPi is used as a Wifi Access Point (AP) and run
a small webserver to serve the 'index.html' interface. Using a laptop or
a mobile phone connected to the wifi of the Raspberry Pi, one can see this
interface, upload sequence, change parameters, run sequence and download data.
To configure the Raspberry Pi to act as an access point and run
the webserver automatically on start, see instructions in 'runOnStart.sh'.
Once configure, the webserver should start by itself on start and once
connected to the Pi, the user can go to `10.3.141.1:8080<http://10.3.141.1:8080>`
to access the interface.
TODO add screenshot of interface
*** Python interface***
By importing the `OhmPi` class from the ohmpi.py, one can controle the OhmPi using interactive IPython.
Typically, it involves using the Thonny on the Raspberry Pi or the Terminal. Once can also connect using
ssh and run the Python interface (see PuTTY on Windows or ssh command on macOS/Linux).
To access the Python API, make sure the file ohmpi.py is in the same
directory as where you run the commands/script. The file ohmpi.py can
be found on the OhmPi gitlab repository. We recommend downloading the
entire repository as ohmpi.py import other .py files and default configuration
files (.json and .py).
.. codeblock:: python
:caption: Example of using the Python API to control OhmPi
from ohmpi import OhmPi
k = OhmPi(idps=True) # if V3.0 make sure to set idps to True
# the DPS5005 is used in V3.0 to inject higher voltage
# default parameters are stored in the pardict argument
# they can be manually changed
k.pardict['injection_duration'] = 0.5 # injection time in seconds
k.pardict['nb_stack'] = 1 # one stack is two half-cycles
k.pardict['nbr_meas'] = 1 # number of time the sequence is repeated
# without multiplexer, one can simple measure using
out = k.run_measurement()
# out contains information about the measurement and can be save as
k.append_and_save('out.csv', out)
# custom or adaptative argument (see help of run_measurement())
k.run_measurement(nb_stack=4, # do 4 stacks (8 half-cycles)
injection_duration=2, # inject for 2 seconds
autogain=True, # adapt gain of ADS to get good resolution
strategy='vmin', # inject min voltage for Vab (v3.0)
tx_volt=5) # vab for finding vmin or vab injectected
# if 'strategy' is 'constant'
# if a multiplexer is connected, we can also manually switch it
k.reset_mux() # check that all mux are closed (do this FIRST)
k.switch_mux_on([1, 4, 2, 3])
k.run_measurement()
k.switch_mux_off([1, 4, 2, 3]) # don't forget this! risk of short-circuit
# import a sequence
k.read_quad('sequence.txt') # four columns, no header, space as separator
print(k.sequence) # where the sequence is stored
# rs check
k.rs_check() # run an RS check (check contact resistances) for all
# electrodes of the given sequence
# run a sequence
k.measure() # measure accept same arguments as run_measurement()
# NOTE: this is an asynchronous command that runs in a separate thread
# after executing the command, the prompt will return immediately
# the asynchronous thread can be stopped during execution using
k.stop()
# otherwise, it will exit by itself at the end of the sequence
# if multiple measurement are to be taken, the sequence will be repeated
***MQTT interface***
Interface to communicate with the Pi designed for the Internet of Things (IoT).
from http.server import SimpleHTTPRequestHandler, HTTPServer
import os
import json
import uuid
from config import CONTROL_CONFIG
from termcolor import colored
import threading
import pandas as pd
import shutil
import zmq # to write on TCP
hostName = "0.0.0.0" # for AP mode (not AP-STA)
serverPort = 8080
# https://gist.github.com/MichaelCurrie/19394abc19abd0de4473b595c0e37a3a
tcp_port = CONTROL_CONFIG['tcp_port']
context = zmq.Context()
socket = context.socket(zmq.REQ)
socket.connect(f'tcp://localhost:{CONTROL_CONFIG["tcp_port"]}')
print(colored(f'Sending commands and listening on tcp port {tcp_port}.'))
class MyServer(SimpleHTTPRequestHandler):
# because we use SimpleHTTPRequestHandler, we do not need to implement
# the do_GET() method (if we use the BaseHTTPRequestHandler, we would need to)
# def do_GET(self):
# # normal get for wepages (not so secure!)
# print(self.command)
# print(self.headers)
# print(self.request)
# self.send_response(200)
# self.send_header("Content-type", "text/html")
# self.end_headers()
# with open(os.path.join('.', self.path[1:]), 'r') as f:
# self.wfile.write(bytes(f.read(), "utf-8"))
def do_POST(self):
cmd_id = uuid.uuid4().hex
global socket
# global ohmpiThread, status, run
dic = json.loads(self.rfile.read(int(self.headers['Content-Length'])))
rdic = {} # response dictionnary
if dic['cmd'] == 'start':
#ohmpi.measure()
socket.send_string(json.dumps({
'cmd_id': cmd_id,
'cmd': 'start'
}))
elif dic['cmd'] == 'stop':
# ohmpi.stop()
socket.send_string(json.dumps({
'cmd_id': cmd_id,
'cmd': 'stop'
}))
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.replace('.csv', '') not in dic['surveyNames']
and fname != 'readme.txt'
and '_rs' not in fname):
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':
# ohmpi.stop()
socket.send_string(json.dumps({
'cmd_id': cmd_id,
'cmd': 'update_settings',
'args': dic['config']
}))
cdic = dic['config']
"""
ohmpi.pardict['nb_electrodes'] = int(cdic['nbElectrodes'])
ohmpi.pardict['injection_duration'] = float(cdic['injectionDuration'])
ohmpi.pardict['nbr_meas'] = int(cdic['nbMeasurements'])
ohmpi.pardict['nb_stack'] = int(cdic['nbStack'])
ohmpi.pardict['sequence_delay'] = int(cdic['sequenceDelay'])
if cdic['sequence'] != '':
with open('sequence.txt', 'w') as f:
f.write(cdic['sequence'])
ohmpi.read_quad('sequence.txt')
print('new sequence set.')
print('setConfig', ohmpi.pardict)
"""
elif dic['cmd'] == 'invert':
pass
elif dic['cmd'] == 'getResults':
pass
elif dic['cmd'] == 'rsCheck':
# ohmpi.rs_check()
socket.send_string(json.dumps({
'cmd_id': cmd_id,
'cmd': 'rs_check'
}))
fnames = sorted([fname for fname in os.listdir('data/') if fname[-7:] == '_rs.csv'])
df = pd.read_csv('data/' + fnames[-1])
ddic = {
'AB': (df['A'].astype('str') + '-' + df['B'].astype(str)).tolist(),
'res': df['RS [kOhm]'].tolist()
}
rdic['data'] = ddic
elif dic['cmd'] == 'download':
shutil.make_archive('data', 'zip', 'data')
elif dic['cmd'] == 'shutdown':
print('shutting down...')
os.system('shutdown now -h')
elif dic['cmd'] == 'restart':
print('shutting down...')
os.system('reboot')
else:
# command not found
rdic['response'] = 'command not found'
# rdic['status'] = ohmpi.status
rdic['status'] = 'unknown' # socket_out.
# wait for reply
message = socket.recv()
print('+++////', message)
rdic['data'] = message.decode('utf-8')
"""
while False:
message = socket.recv()
print(f'Received command: {message}')
e = None
try:
decoded_message = json.loads(message.decode('utf-8'))
cmd = decoded_message.pop('cmd', None)
args = decoded_message.pop('args', None)
status = False
e = None
if cmd is not None and cmd_id is decoded_message.pop('cmd_id', None):
print('reply=', decoded_message)
except Exception as e:
print(f'Unable to decode command {message}: {e}')
"""
self.send_response(200)
self.send_header('Content-Type', 'text/json')
self.end_headers()
self.wfile.write(bytes(json.dumps(rdic), 'utf8'))
if __name__ == "__main__":
webServer = HTTPServer((hostName, serverPort), MyServer)
print("Server started http://%s:%s" % (hostName, serverPort))
try:
webServer.serve_forever()
except KeyboardInterrupt:
pass
webServer.server_close()
print("Server stopped.")
import json
import paho.mqtt.client as mqtt
import paho.mqtt.publish as publish
from config import MQTT_CONTROL_CONFIG, OHMPI_CONFIG
import time
import uuid
client = mqtt.Client(f'ohmpi_{OHMPI_CONFIG["id"]}_controller', clean_session=False) # create new instance
print('connecting controller to broker')
client.connect(MQTT_CONTROL_CONFIG['hostname'])
client.loop_start()
publisher_config = MQTT_CONTROL_CONFIG.copy()
publisher_config['topic'] = MQTT_CONTROL_CONFIG['ctrl_topic']
publisher_config.pop('ctrl_topic')
settings = {
'injection_duration': 0.2,
'nbr_meas': 1,
'sequence_delay': 1,
'nb_stack': 1,
'export_path': 'data/measurement.csv'
}
cmd_id = uuid.uuid4().hex
payload = json.dumps({'cmd_id': cmd_id, 'cmd': 'update_settings', 'args': settings})
print(f'Update settings setup message: {payload} to {publisher_config["topic"]} with config {publisher_config}')
publish.single(payload=payload, **publisher_config)
sequence = [[1, 2, 3, 4]]
cmd_id = uuid.uuid4().hex
payload = json.dumps({'cmd_id': cmd_id, 'cmd': 'set_sequence', 'args': sequence})
print(f'Set sequence message: {payload} to {publisher_config["topic"]} with config {publisher_config}')
publish.single(payload=payload, **publisher_config)
cmd_id = uuid.uuid4().hex
payload = json.dumps({'cmd_id': cmd_id, 'cmd': 'rs_check'})
print(f'Run rs_check: {payload} to {publisher_config["topic"]} with config {publisher_config}')
publish.single(payload=payload, **publisher_config)
for i in range(4):
cmd_id = uuid.uuid4().hex
payload = json.dumps({'cmd_id': cmd_id, 'cmd': 'start'})
print(f'Publishing message {i}: {payload} to {publisher_config["topic"]} with config {publisher_config}')
publish.single(payload=payload, **publisher_config)
time.sleep(1)
client.loop_stop()
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 broker")
exit(1)
{
"nb_electrodes": 64,
"injection_duration": 0.2,
"nbr_meas": 1,
"sequence_delay": 1,
"nb_stack": 1,
"export_path": "data/measurement.csv"
}
numpy
paho-mqtt
termcolor
pandas
pyzmq
[{"id":"ea6628fd.4d57e8","type":"tab","label":"Flow 1","disabled":false,"info":""},{"id":"6ae7e77e.04c64","type":"mqtt-broker","name":"ohmpi_mqtt_broker","broker":"mg3d-dev.umons.ac.be","port":"1883","clientid":"","usetls":false,"compatmode":false,"keepalive":"60","cleansession":true,"birthTopic":"","birthQos":"0","birthPayload":"","closeTopic":"","closeQos":"0","closePayload":"","willTopic":"","willQos":"0","willPayload":""},{"id":"64a75353.37700c","type":"ui_group","name":"Messages","tab":"5d888f29.07334","order":1,"disp":true,"width":"16","collapse":true},{"id":"142ad6ae.d55e29","type":"ui_group","name":"Buttons","tab":"5d888f29.07334","order":3,"disp":true,"width":"6","collapse":false},{"id":"5d888f29.07334","type":"ui_tab","name":"Home","icon":"dashboard","disabled":false,"hidden":false},{"id":"ea73b76b.ee0738","type":"ui_base","theme":{"name":"theme-light","lightTheme":{"default":"#0094CE","baseColor":"#0094CE","baseFont":"-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif","edited":false},"darkTheme":{"default":"#097479","baseColor":"#097479","baseFont":"-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif","edited":false},"customTheme":{"name":"Untitled Theme 1","default":"#4B7930","baseColor":"#4B7930","baseFont":"-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif"},"themeState":{"base-color":{"default":"#0094CE","value":"#0094CE","edited":false},"page-titlebar-backgroundColor":{"value":"#0094CE","edited":false},"page-backgroundColor":{"value":"#fafafa","edited":false},"page-sidebar-backgroundColor":{"value":"#ffffff","edited":false},"group-textColor":{"value":"#1bbfff","edited":false},"group-borderColor":{"value":"#ffffff","edited":false},"group-backgroundColor":{"value":"#ffffff","edited":false},"widget-textColor":{"value":"#111111","edited":false},"widget-backgroundColor":{"value":"#0094ce","edited":false},"widget-borderColor":{"value":"#ffffff","edited":false},"base-font":{"value":"-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif"}},"angularTheme":{"primary":"indigo","accents":"blue","warn":"red","background":"grey","palette":"light"}},"site":{"name":"Node-RED Dashboard","hideToolbar":"false","allowSwipe":"false","lockMenu":"false","allowTempTheme":"true","dateFormat":"DD/MM/YYYY","sizes":{"sx":48,"sy":48,"gx":6,"gy":6,"cx":6,"cy":6,"px":0,"py":0}}},{"id":"147e389f.fc5a6f","type":"mqtt in","z":"ea6628fd.4d57e8","name":"","topic":"ohmpi_0001/exec","qos":"2","datatype":"auto","broker":"6ae7e77e.04c64","inputs":0,"x":110,"y":40,"wires":[["ddcc63.c93813a","40efede3.33021c"]]},{"id":"ddcc63.c93813a","type":"debug","z":"ea6628fd.4d57e8","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":390,"y":40,"wires":[]},{"id":"40efede3.33021c","type":"ui_text","z":"ea6628fd.4d57e8","group":"64a75353.37700c","order":0,"width":"0","height":"0","name":"MQTT exec","label":"Execution","format":"{{msg.payload}}","layout":"row-spread","x":390,"y":80,"wires":[]},{"id":"64e06023.868f58","type":"mqtt in","z":"ea6628fd.4d57e8","name":"","topic":"ohmpi_0001/data","qos":"2","datatype":"auto","broker":"6ae7e77e.04c64","inputs":0,"x":100,"y":140,"wires":[["ac400b27.992c78","8d7f5d51.c06c4"]]},{"id":"ac400b27.992c78","type":"debug","z":"ea6628fd.4d57e8","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":390,"y":140,"wires":[]},{"id":"8d7f5d51.c06c4","type":"ui_text","z":"ea6628fd.4d57e8","group":"64a75353.37700c","order":1,"width":0,"height":0,"name":"MQTT Data","label":"Data","format":"{{msg.payload}}","layout":"row-spread","x":390,"y":180,"wires":[]},{"id":"e1cc28ee.d2dd6","type":"mqtt out","z":"ea6628fd.4d57e8","name":"MQTT ctrl","topic":"ohmpi_0001/ctrl","qos":"0","retain":"false","broker":"6ae7e77e.04c64","x":380,"y":340,"wires":[]},{"id":"a144e7f8.416768","type":"ui_button","z":"ea6628fd.4d57e8","name":"","group":"142ad6ae.d55e29","order":0,"width":"2","height":"1","passthru":false,"label":"Start","tooltip":"","color":"","bgcolor":"green","className":"","icon":"","payload":"start","payloadType":"str","topic":"topic","topicType":"msg","x":70,"y":340,"wires":[["e1cc28ee.d2dd6","4a86345b.fb400c"]]},{"id":"abd65786.c433c8","type":"ui_button","z":"ea6628fd.4d57e8","name":"","group":"142ad6ae.d55e29","order":0,"width":"2","height":"1","passthru":false,"label":"Stop","tooltip":"","color":"","bgcolor":"red","icon":"","payload":"stop","payloadType":"str","topic":"topic","topicType":"msg","x":70,"y":400,"wires":[["e1cc28ee.d2dd6","4a86345b.fb400c"]]},{"id":"4a86345b.fb400c","type":"ui_audio","z":"ea6628fd.4d57e8","name":"","group":"142ad6ae.d55e29","voice":"urn:moz-tts:speechd:English%20(Received%20Pronunciation)?en","always":"","x":380,"y":400,"wires":[]},{"id":"398460c6.27a9e8","type":"mqtt in","z":"ea6628fd.4d57e8","name":"","topic":"ohmpi_0001/soh","qos":"2","datatype":"auto","broker":"6ae7e77e.04c64","inputs":0,"x":100,"y":240,"wires":[["21b85b73.b7ef34","498c2864.b36b18"]]},{"id":"21b85b73.b7ef34","type":"debug","z":"ea6628fd.4d57e8","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":390,"y":240,"wires":[]},{"id":"498c2864.b36b18","type":"ui_text","z":"ea6628fd.4d57e8","group":"64a75353.37700c","order":1,"width":0,"height":0,"name":"MQTT SOH","label":"SOH","format":"{{msg.payload}}","layout":"row-spread","x":390,"y":280,"wires":[]},{"id":"3dbcbc0e.3d24ac","type":"ui_form","z":"ea6628fd.4d57e8","name":"Commands","label":"","group":"142ad6ae.d55e29","order":4,"width":0,"height":0,"options":[{"label":"cmd_id","value":"cmd_id","type":"text","required":false,"rows":null},{"label":"cmd","value":"cmd","type":"text","required":true,"rows":null},{"label":"args","value":"args","type":"text","required":false,"rows":null}],"formValue":{"cmd_id":"","cmd":"","args":""},"payload":"","submit":"submit","cancel":"cancel","topic":"topic","topicType":"msg","splitLayout":"","className":"","x":120,"y":640,"wires":[["e1cc28ee.d2dd6","bfc5c3.3e79724"]]},{"id":"bfc5c3.3e79724","type":"ui_text","z":"ea6628fd.4d57e8","group":"64a75353.37700c","order":3,"width":0,"height":0,"name":"","label":"text","format":"{{msg.payload}}","layout":"row-spread","className":"","x":380,"y":640,"wires":[]}]
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