From a2491be05d4423a7d6ee560eef01905eebae4973 Mon Sep 17 00:00:00 2001 From: su530201 <olivier.kaufmann@umons.ac.be> Date: Wed, 16 Feb 2022 19:58:29 +0100 Subject: [PATCH] Adds MQTT code + settings and local logging --- Ohmpi_4elec_mqtt.py | 281 +++++++++ Proposition MQTT interface.pdf | Bin 0 -> 47862 bytes basic_mqtt_ohmpi_console.ipynb | 194 ++++++ basic_mqtt_ohmpi_controller.ipynb | 728 ++++++++++++++++++++++ compressed_sized_timed_rotating_logger.py | 58 ++ logging_setup.py | 83 +++ ohmpi_param.json | 4 +- settings.py | 40 ++ 8 files changed, 1386 insertions(+), 2 deletions(-) create mode 100644 Ohmpi_4elec_mqtt.py create mode 100644 Proposition MQTT interface.pdf create mode 100644 basic_mqtt_ohmpi_console.ipynb create mode 100644 basic_mqtt_ohmpi_controller.ipynb create mode 100644 compressed_sized_timed_rotating_logger.py create mode 100644 logging_setup.py create mode 100644 settings.py diff --git a/Ohmpi_4elec_mqtt.py b/Ohmpi_4elec_mqtt.py new file mode 100644 index 00000000..74350153 --- /dev/null +++ b/Ohmpi_4elec_mqtt.py @@ -0,0 +1,281 @@ +""" +created on december, 2021 +Update january 2022 +Ohmpi_4elec.py is a program to control a low-cost and open hardware resistivity meter OhmPi that has been developed + by Rémi CLEMENT(INRAE),Vivien DUBOIS(INRAE),Hélène GUYARD(IGE), Nicolas FORQUET (INRAE), + Oliver KAUFMANN (UMONS) and Yannick FARGIER (IFSTTAR). +""" +from settings import OHMPI_CONFIG +try: + import board, busio + import adafruit_tca9548a + import adafruit_ads1x15.ads1115 as ADS + from adafruit_ads1x15.analog_in import AnalogIn + from adafruit_mcp230xx.mcp23008 import MCP23008 + from adafruit_mcp230xx.mcp23017 import MCP23017 + import digitalio + from digitalio import Direction + from gpiozero import CPUTemperature +except: + pass + +from pandas import DataFrame +from datetime import datetime +import time +import numpy as np +import sys +import json +# import glob +from os import path, statvfs +from threading import Thread +from logging_setup import setup_loggers +from mqtt_setup import mqtt_client_setup + +# Initialization +version = "1.01" + +print('\033[1m'+'\033[31m'+' ________________________________') +print('| _ | | | || \/ || ___ \_ _|') +print('| | | | |_| || . . || |_/ / | |' ) +print('| | | | _ || |\/| || __/ | |') +print('\ \_/ / | | || | | || | _| |_') +print(' \___/\_| |_/\_| |_/\_| \___/ ') +print('\033[0m') +print('OhmPi 4 elec MQTT start' ) +print(f'Vers: {version}') + +msg_logger, msg_log_filename, data_logger, data_log_filename, logging_level = setup_loggers() +mqtt_client, measurement_topic = mqtt_client_setup() + +msg_logger.info(f'publishing mqtt to topic {measurement_topic}') + +# Remaining initialization +status = True + +""" +hardware parameters +""" +print(f'\033[1m \033[31m The maximum current cannot be higher than {OHMPI_CONFIG["Imax"]} mA \033[0m') +# offset_p2= 0 +# offset_p3= 0 +integer = 2 # Max value 10 TODO: explain this +nb_elec = 4 # TODO: Improve this +meas = np.zeros((3, integer)) + +""" +import parameters +""" + +with open('ohmpi_param.json') as json_file: + pardict = json.load(json_file) + + +i2c = busio.I2C(board.SCL, board.SDA) #activation du protocle I2C +mcp = MCP23008(i2c, address=0x20) #connexion I2C MCP23008, injection de courant +ads_current = ADS.ADS1115(i2c, gain=2/3,data_rate=860, address=0X48)# connexion ADS1115, pour la mesure de courant +ads_voltage = ADS.ADS1115(i2c, gain=2/3,data_rate=860, address=0X49)# connexion ADS1115, pour la mesure de voltage + +#initialisation des voies pour la polarité +pin0 = mcp.get_pin(0) +pin0.direction = Direction.OUTPUT +pin1 = mcp.get_pin(1) +pin1.direction = Direction.OUTPUT +pin0.value = False +pin1.value = False + + +def switch_mux(electrode_nr, state, role): + """select the right channel for the multiplexer cascade for a given electrode""" + board_address = {'A': 0x76, 'B': 0X71, 'M': 0x74, 'N': 0x70} + + tca = adafruit_tca9548a.TCA9548A(i2c, board_address[role]) # choose MUX A B M or N + i2c_address = None + if electrode_nr < 17: + i2c_address = 7 + relay_nr = electrode_nr + elif 16 < electrode_nr < 33: + i2c_address = 6 + relay_nr = electrode_nr - 16 + elif 32 < electrode_nr < 49: + i2c_address = 5 + relay_nr = electrode_nr - 32 + elif 48 < electrode_nr < 65: + i2c_address = 4 + relay_nr = electrode_nr - 48 + + if i2c_address is not None: + 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 + msg_logger.debug(f'Switching relay {relay_nr} {state} for electrode {electrode_nr}') + else: + msg_logger.warn(f'Unable to address electrode nr {electrode_nr}') + + +def switch_mux_on(quadrupole): + """switch on multiplexer relays for quadrupole""" + roles = ['A', 'B', 'M', 'N'] + for i in range(0, 4): + switch_mux(quadrupole[i], 'on', roles[i]) + + +def switch_mux_off(quadrupole): + """switch off multiplexer relays for quadrupole""" + roles = ['A', 'B', 'M', 'N'] + for i in range(0, 4): + switch_mux(quadrupole[i], 'off', roles[i]) + + +def reset_mux(): + """switch off all multiplexer relays""" + global nb_elec + roles = ['A', 'B', 'M', 'N'] + for i in range(0, 4): + for j in range(1, nb_elec + 1): + switch_mux(j, 'off', roles[i]) + +# function to find rows with identical values in different columns +def find_identical_in_line(array_object): + output = [] + if array_object.ndim == 1: + temp = np.zeros(4) + for i in range(len(array_object)): + temp[i] = np.count_nonzero(array_object == array_object[i]) + if any(temp > 1): + output.append(0) + else: + for i in range(len(array_object[:,1])): + temp = np.zeros(len(array_object[1,:])) + for j in range(len(array_object[1,:])): + temp[j] = np.count_nonzero(array_object[i,:] == array_object[i,j]) + if any(temp > 1): + output.append(i) + return output + + +def read_quad(filename, nb_elec): + """read quadripole file and apply tests""" + output = np.loadtxt(filename, delimiter=" ",dtype=int) # load quadripole file + # locate lines where the electrode index exceeds the maximum number of electrodes + test_index_elec = np.array(np.where(output > nb_elec)) + # locate lines where an electrode is referred twice + test_same_elec = find_identical_in_line(output) + # if statement with exit cases (rajouter un else if pour le deuxième cas du ticket #2) + if test_index_elec.size != 0: + for i in range(len(test_index_elec[0,:])): + print("Error: An electrode index at line "+ str(test_index_elec[0,i]+1)+" exceeds the maximum number of electrodes") + sys.exit(1) + elif len(test_same_elec) != 0: + for i in range(len(test_same_elec)): + print("Error: An electrode index is used twice at line " + str(test_same_elec[i]+1)) + sys.exit(1) + else: + return output + + +def run_measurement(nb_stack, injection_deltat, r_shunt, coefp2, coefp3): + start_time=time.time() + # inner variable initialization + injection_current=0 + sum_vmn=0 + sum_ps=0 + # injection courant and measure + mcp = MCP23008(i2c, address=0x20) + pin0 = mcp.get_pin(0) + pin0.direction = Direction.OUTPUT + pin1 = mcp.get_pin(1) + pin1.direction = Direction.OUTPUT + pin0.value = False + pin1.value = False + for n in range(0, 3+2*nb_stack-1): + # current injection + if (n % 2) == 0: + pin1.value = True + pin0.value = False # current injection polarity nr1 + else: + pin0.value = True + pin1.value = False # current injection nr2 + start_delay=time.time() # stating measurement time + time.sleep(injection_deltat) # delay depending on current injection duration + + # mesureament of i and u + for k in range(0, integer): + meas[0,k] = (AnalogIn(ads_current,ADS.P0).voltage*1000)/(50*r_shunt) # reading current value on ADS channel A0 + meas[1,k] = AnalogIn(ads_voltage,ADS.P0).voltage*coefp2*1000 + meas[2,k] = AnalogIn(ads_voltage,ADS.P1).voltage*coefp3*1000 # reading voltage value on ADS channel A2 + # stop current injection + pin1.value = False + pin0.value = False + end_delay = time.time() + injection_current = injection_current + (np.mean(meas[0, :])) + vmn1 = ((np.mean(meas[1, :]))-(np.mean(meas[2, :]))) + if (n % 2) == 0: + sum_vmn = sum_vmn - vmn1 + sum_ps = sum_ps + vmn1 + else: + sum_vmn = sum_vmn + vmn1 + sum_ps = sum_ps + vmn1 + + cpu = CPUTemperature() + + end_calc = time.time() + time.sleep(2*(end_delay-start_delay)-(end_calc-start_delay)) + #end_sleep2=time.time() + #print(['sleep=',((end_sleep2-start_delay))]) + + #print(['true delta=',((end_delay-start_delay)-injection_deltat)]) + #print(['time stop=',((2*(end_delay-start_delay)-(end_calc-start_delay)))]) + # return averaged values +# cpu= CPUTemperature() + output = DataFrame({ + "time": [datetime.now()], + "A": [(1)], + "B": [(2)], + "M": [(3)], + "N": [(4)], + "inj time [ms]": (end_delay - start_delay) * 1000, + "Vmn [mV]": [(sum_vmn / (3 + 2 * nb_stack - 1))], + "I [mA]": [(injection_current / (3 + 2 * nb_stack - 1))], + "R [ohm]": [(sum_vmn / (3 + 2 * nb_stack - 1) / (injection_current / (3 + 2 * nb_stack - 1)))], + "Ps [mV]": [(sum_ps / (3 + 2 * nb_stack - 1))], + "nbStack": [nb_stack], + "CPU temp [°C]": [cpu.temperature], + "Time [s]": [(-start_time + time.time())], + "Integer [-]": [integer] + + + + # Dead time equivalent to the duration of the current injection pulse + }) + output = output.round(2) + print(output.to_string()) + time.sleep(1) + return output + + +def append_and_save(data_path, last_measurement): + """Save data""" + if path.isfile(data_path): + # Load data file and append data to it + with open(data_path, 'a') as f: + last_measurement.to_csv(f, header=False) + else: + # create data file and add headers + with open(data_path, 'a') as f: + last_measurement.to_csv(f, header=True) + + +""" +Main loop +""" +for g in range(0, pardict.get("nbr_meas")): # for time-lapse monitoring + current_measurement = run_measurement(pardict.get("stack"), pardict.get("injection_duration"), + OHMPI_CONFIG['R_shunt'], OHMPI_CONFIG['coef_p2'], OHMPI_CONFIG['coef_p3']) + append_and_save(pardict.get("export_path"), current_measurement) + msg = f'Resitivity: {current_measurement.iloc[-1]["R [ohm]"]:.2f} ohm' + msg_logger.info(msg) + mqtt_client.publish(measurement_topic, msg) + time.sleep(pardict.get("sequence_delay")) # waiting next measurement (time-lapse) diff --git a/Proposition MQTT interface.pdf b/Proposition MQTT interface.pdf new file mode 100644 index 0000000000000000000000000000000000000000..96a92bca0e11dc1fe46c40cf61f8818744b6ba1f GIT binary patch literal 47862 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 W9b5aK+YYu5SUFIssD!0OQ2!sgQ*D+2 literal 0 HcmV?d00001 diff --git a/basic_mqtt_ohmpi_console.ipynb b/basic_mqtt_ohmpi_console.ipynb new file mode 100644 index 00000000..482796e8 --- /dev/null +++ b/basic_mqtt_ohmpi_console.ipynb @@ -0,0 +1,194 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "606c11c0-3a80-4138-ac50-1da0bd01bef8", + "metadata": {}, + "source": [ + "# A small code to test MQTT interface for ohmpi" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "daf2041b-1df9-42de-a385-f450a826c96f", + "metadata": {}, + "outputs": [], + "source": [ + "import paho.mqtt.client as mqtt\n", + "import time" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "14c42035", + "metadata": {}, + "outputs": [], + "source": [ + "client_id = 'ohmpi_console_sn_0001'\n", + "measurements_topic = 'measurements_ohmpi_sn_0001'" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "391c6373-f7db-485e-b3dd-b1e04a37473a", + "metadata": {}, + "outputs": [], + "source": [ + "broker_address=\"mg3d-dev.umons.ac.be\"" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "8fc857ba-bbcf-4f99-a30f-84fd14ddb2d0", + "metadata": {}, + "outputs": [], + "source": [ + "client = mqtt.Client(client_id, protocol=4) #create new instance" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "24926751-62c6-4833-8d68-99279192d4e0", + "metadata": {}, + "outputs": [], + "source": [ + "def on_message(client, userdata, message):\n", + " m = str(message.payload.decode(\"utf-8\"))\n", + " print(f'message received {m}')\n", + " print(f'topic: {message.topic}')\n", + " print(f'qos: {message.qos}')\n", + " print(f'retain flag: {message.retain}')" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "06e424ff-cd1d-4756-bf53-cfbca4628e73", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "0" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "client.connect(broker_address) #connect to broker" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "f0b06a71-b0bf-4551-a044-94b1100a3a5d", + "metadata": {}, + "outputs": [], + "source": [ + "client.on_message = on_message\n", + "client.loop_start()" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "8e168d2d-25fa-49ac-9d03-dd0da5e61841", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Subscribing to topic measurements_ohmpi_sn_0001\n" + ] + }, + { + "data": { + "text/plain": [ + "(0, 1)" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "print(\"Subscribing to topic\", measurements_topic)\n", + "client.subscribe(measurements_topic)" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "eaa7d034-5383-4ece-a824-f763ce214760", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "message received Resitivity: 215.22 ohm\n", + "topic: measurements_ohmpi_sn_0001\n", + "qos: 0\n", + "retain flag: 0\n", + "message received Resitivity: 214.94 ohm\n", + "topic: measurements_ohmpi_sn_0001\n", + "qos: 0\n", + "retain flag: 0\n" + ] + } + ], + "source": [ + "time.sleep(60)" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "9bd768aa-c4d3-429e-b667-6e981bd28353", + "metadata": {}, + "outputs": [], + "source": [ + "client.loop_stop()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f9a0333a-c1ec-42a8-bd24-3dc466c51bb4", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.10" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/basic_mqtt_ohmpi_controller.ipynb b/basic_mqtt_ohmpi_controller.ipynb new file mode 100644 index 00000000..b9c425cf --- /dev/null +++ b/basic_mqtt_ohmpi_controller.ipynb @@ -0,0 +1,728 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "9ec039c2-8dc0-42ab-ad96-356e54a6c228", + "metadata": {}, + "source": [ + "# A small code to test MQTT ohmpi controller" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "daf2041b-1df9-42de-a385-f450a826c96f", + "metadata": {}, + "outputs": [], + "source": [ + "import paho.mqtt.client as mqtt\n", + "import pandas as pd\n", + "import time" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "013806a6", + "metadata": {}, + "outputs": [], + "source": [ + "sequence = './ABMN.txt'\n", + "broker_address = \"mg3d-dev.umons.ac.be\"\n", + "client_id = \"ohmpi-controller_01\"\n", + "control_topic = 'cmd_ohmpi_sn_0001'\n", + "measurements_topic = 'measurements_ohmpi_sn_0001'" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "c07183bf", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "<div>\n", + "<style scoped>\n", + " .dataframe tbody tr th:only-of-type {\n", + " vertical-align: middle;\n", + " }\n", + "\n", + " .dataframe tbody tr th {\n", + " vertical-align: top;\n", + " }\n", + "\n", + " .dataframe thead th {\n", + " text-align: right;\n", + " }\n", + "</style>\n", + "<table border=\"1\" class=\"dataframe\">\n", + " <thead>\n", + " <tr style=\"text-align: right;\">\n", + " <th></th>\n", + " <th>array</th>\n", + " </tr>\n", + " </thead>\n", + " <tbody>\n", + " <tr>\n", + " <th>0</th>\n", + " <td>1 4 2 3</td>\n", + " </tr>\n", + " <tr>\n", + " <th>1</th>\n", + " <td>2 5 3 4</td>\n", + " </tr>\n", + " <tr>\n", + " <th>2</th>\n", + " <td>3 6 4 5</td>\n", + " </tr>\n", + " <tr>\n", + " <th>3</th>\n", + " <td>4 7 5 6</td>\n", + " </tr>\n", + " <tr>\n", + " <th>4</th>\n", + " <td>5 8 6 7</td>\n", + " </tr>\n", + " <tr>\n", + " <th>...</th>\n", + " <td>...</td>\n", + " </tr>\n", + " <tr>\n", + " <th>150</th>\n", + " <td>3 30 12 21</td>\n", + " </tr>\n", + " <tr>\n", + " <th>151</th>\n", + " <td>4 31 13 22</td>\n", + " </tr>\n", + " <tr>\n", + " <th>152</th>\n", + " <td>5 32 14 23</td>\n", + " </tr>\n", + " <tr>\n", + " <th>153</th>\n", + " <td>1 31 11 21</td>\n", + " </tr>\n", + " <tr>\n", + " <th>154</th>\n", + " <td>2 32 12 22</td>\n", + " </tr>\n", + " </tbody>\n", + "</table>\n", + "<p>155 rows × 1 columns</p>\n", + "</div>" + ], + "text/plain": [ + " array\n", + "0 1 4 2 3\n", + "1 2 5 3 4\n", + "2 3 6 4 5\n", + "3 4 7 5 6\n", + "4 5 8 6 7\n", + ".. ...\n", + "150 3 30 12 21\n", + "151 4 31 13 22\n", + "152 5 32 14 23\n", + "153 1 31 11 21\n", + "154 2 32 12 22\n", + "\n", + "[155 rows x 1 columns]" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df = pd.read_csv(sequence, header=None, names=['array'])\n", + "df" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "8fc857ba-bbcf-4f99-a30f-84fd14ddb2d0", + "metadata": {}, + "outputs": [], + "source": [ + "client = mqtt.Client(client_id, protocol=4) #create new instance" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "c27d85eb", + "metadata": {}, + "outputs": [], + "source": [ + "def on_message(client, userdata, message):\n", + " m = str(message.payload.decode(\"utf-8\"))\n", + " print(f'message received {m}')\n", + " print(f'topic: {message.topic}')\n", + " print(f'qos: {message.qos}')\n", + " print(f'retain flag: {message.retain}')" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "06e424ff-cd1d-4756-bf53-cfbca4628e73", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "0" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "client.connect(broker_address) #connect to broker" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "819fb8f9", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(0, 1)" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "client.on_message = on_message\n", + "client.loop_start()\n", + "client.subscribe(measurements_topic)" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "519791a6", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "publishing 21:55:13 : measure resistivity 0 with array 1 4 2 3\n", + "(0, 2)\n", + "publishing 21:55:14 : measure resistivity 1 with array 2 5 3 4\n", + "(0, 3)\n", + "publishing 21:55:15 : measure resistivity 2 with array 3 6 4 5\n", + "(0, 4)\n", + "publishing 21:55:16 : measure resistivity 3 with array 4 7 5 6\n", + "(0, 5)\n", + "publishing 21:55:17 : measure resistivity 4 with array 5 8 6 7\n", + "(0, 6)\n", + "publishing 21:55:18 : measure resistivity 5 with array 6 9 7 8\n", + "(0, 7)\n", + "publishing 21:55:19 : measure resistivity 6 with array 7 10 8 9\n", + "(0, 8)\n", + "publishing 21:55:20 : measure resistivity 7 with array 8 11 9 10\n", + "(0, 9)\n", + "publishing 21:55:21 : measure resistivity 8 with array 9 12 10 11\n", + "(0, 10)\n", + "publishing 21:55:22 : measure resistivity 9 with array 10 13 11 12\n", + "(0, 11)\n", + "publishing 21:55:23 : measure resistivity 10 with array 11 14 12 13\n", + "(0, 12)\n", + "publishing 21:55:24 : measure resistivity 11 with array 12 15 13 14\n", + "(0, 13)\n", + "publishing 21:55:25 : measure resistivity 12 with array 13 16 14 15\n", + "(0, 14)\n", + "publishing 21:55:26 : measure resistivity 13 with array 14 17 15 16\n", + "(0, 15)\n", + "publishing 21:55:27 : measure resistivity 14 with array 15 18 16 17\n", + "(0, 16)\n", + "publishing 21:55:28 : measure resistivity 15 with array 16 19 17 18\n", + "(0, 17)\n", + "publishing 21:55:29 : measure resistivity 16 with array 17 20 18 19\n", + "(0, 18)\n", + "publishing 21:55:30 : measure resistivity 17 with array 18 21 19 20\n", + "(0, 19)\n", + "publishing 21:55:31 : measure resistivity 18 with array 19 22 20 21\n", + "(0, 20)\n", + "publishing 21:55:32 : measure resistivity 19 with array 20 23 21 22\n", + "(0, 21)\n", + "publishing 21:55:33 : measure resistivity 20 with array 21 24 22 23\n", + "(0, 22)\n", + "publishing 21:55:34 : measure resistivity 21 with array 22 25 23 24\n", + "(0, 23)\n", + "publishing 21:55:35 : measure resistivity 22 with array 23 26 24 25\n", + "(0, 24)\n", + "publishing 21:55:36 : measure resistivity 23 with array 24 27 25 26\n", + "(0, 25)\n", + "publishing 21:55:37 : measure resistivity 24 with array 25 28 26 27\n", + "(0, 26)\n", + "publishing 21:55:38 : measure resistivity 25 with array 26 29 27 28\n", + "(0, 27)\n", + "publishing 21:55:39 : measure resistivity 26 with array 27 30 28 29\n", + "(0, 28)\n", + "publishing 21:55:40 : measure resistivity 27 with array 28 31 29 30\n", + "(0, 29)\n", + "publishing 21:55:41 : measure resistivity 28 with array 29 32 30 31\n", + "(0, 30)\n", + "publishing 21:55:42 : measure resistivity 29 with array 1 7 3 5\n", + "(0, 31)\n", + "publishing 21:55:43 : measure resistivity 30 with array 2 8 4 6\n", + "(0, 32)\n", + "publishing 21:55:44 : measure resistivity 31 with array 3 9 5 7\n", + "(0, 33)\n", + "publishing 21:55:45 : measure resistivity 32 with array 4 10 6 8\n", + "(0, 34)\n", + "publishing 21:55:46 : measure resistivity 33 with array 5 11 7 9\n", + "(0, 35)\n", + "publishing 21:55:47 : measure resistivity 34 with array 6 12 8 10\n", + "(0, 36)\n", + "message received TEST 45 ohm.m\n", + "topic: measurements_ohmpi_sn_0001\n", + "qos: 0\n", + "retain flag: 0\n", + "publishing 21:55:48 : measure resistivity 35 with array 7 13 9 11\n", + "(0, 37)\n", + "message received 21:55:48 : measure resistivity 35 with array 7 13 9 11 45 ohm.m\n", + "topic: measurements_ohmpi_sn_0001\n", + "qos: 0\n", + "retain flag: 0\n", + "publishing 21:55:49 : measure resistivity 36 with array 8 14 10 12\n", + "(0, 38)\n", + "message received 21:55:49 : measure resistivity 36 with array 8 14 10 12 45 ohm.m\n", + "topic: measurements_ohmpi_sn_0001\n", + "qos: 0\n", + "retain flag: 0\n", + "publishing 21:55:50 : measure resistivity 37 with array 9 15 11 13\n", + "(0, 39)\n", + "message received 21:55:50 : measure resistivity 37 with array 9 15 11 13 45 ohm.m\n", + "topic: measurements_ohmpi_sn_0001\n", + "qos: 0\n", + "retain flag: 0\n", + "publishing 21:55:51 : measure resistivity 38 with array 10 16 12 14\n", + "(0, 40)\n", + "message received 21:55:51 : measure resistivity 38 with array 10 16 12 14 45 ohm.m\n", + "topic: measurements_ohmpi_sn_0001\n", + "qos: 0\n", + "retain flag: 0\n", + "publishing 21:55:52 : measure resistivity 39 with array 11 17 13 15\n", + "(0, 41)\n", + "message received 21:55:52 : measure resistivity 39 with array 11 17 13 15 45 ohm.m\n", + "topic: measurements_ohmpi_sn_0001\n", + "qos: 0\n", + "retain flag: 0\n", + "publishing 21:55:53 : measure resistivity 40 with array 12 18 14 16\n", + "(0, 42)\n", + "message received 21:55:53 : measure resistivity 40 with array 12 18 14 16 45 ohm.m\n", + "topic: measurements_ohmpi_sn_0001\n", + "qos: 0\n", + "retain flag: 0\n", + "publishing 21:55:54 : measure resistivity 41 with array 13 19 15 17\n", + "(0, 43)\n", + "message received 21:55:54 : measure resistivity 41 with array 13 19 15 17 45 ohm.m\n", + "topic: measurements_ohmpi_sn_0001\n", + "qos: 0\n", + "retain flag: 0\n", + "publishing 21:55:55 : measure resistivity 42 with array 14 20 16 18\n", + "(0, 44)\n", + "message received 21:55:55 : measure resistivity 42 with array 14 20 16 18 45 ohm.m\n", + "topic: measurements_ohmpi_sn_0001\n", + "qos: 0\n", + "retain flag: 0\n", + "publishing 21:55:56 : measure resistivity 43 with array 15 21 17 19\n", + "(0, 45)\n", + "message received 21:55:56 : measure resistivity 43 with array 15 21 17 19 45 ohm.m\n", + "topic: measurements_ohmpi_sn_0001\n", + "qos: 0\n", + "retain flag: 0\n", + "publishing 21:55:57 : measure resistivity 44 with array 16 22 18 20\n", + "(0, 46)\n", + "message received 21:55:57 : measure resistivity 44 with array 16 22 18 20 45 ohm.m\n", + "topic: measurements_ohmpi_sn_0001\n", + "qos: 0\n", + "retain flag: 0\n", + "publishing 21:55:58 : measure resistivity 45 with array 17 23 19 21\n", + "(0, 47)\n", + "message received 21:55:58 : measure resistivity 45 with array 17 23 19 21 45 ohm.m\n", + "topic: measurements_ohmpi_sn_0001\n", + "qos: 0\n", + "retain flag: 0\n", + "publishing 21:55:59 : measure resistivity 46 with array 18 24 20 22\n", + "(0, 48)\n", + "message received 21:55:59 : measure resistivity 46 with array 18 24 20 22 45 ohm.m\n", + "topic: measurements_ohmpi_sn_0001\n", + "qos: 0\n", + "retain flag: 0\n", + "publishing 21:56:00 : measure resistivity 47 with array 19 25 21 23\n", + "(0, 49)\n", + "message received 21:56:00 : measure resistivity 47 with array 19 25 21 23 45 ohm.m\n", + "topic: measurements_ohmpi_sn_0001\n", + "qos: 0\n", + "retain flag: 0\n", + "publishing 21:56:01 : measure resistivity 48 with array 20 26 22 24\n", + "(0, 50)\n", + "message received 21:56:01 : measure resistivity 48 with array 20 26 22 24 45 ohm.m\n", + "topic: measurements_ohmpi_sn_0001\n", + "qos: 0\n", + "retain flag: 0\n", + "publishing 21:56:02 : measure resistivity 49 with array 21 27 23 25\n", + "(0, 51)\n", + "message received 21:56:02 : measure resistivity 49 with array 21 27 23 25 45 ohm.m\n", + "topic: measurements_ohmpi_sn_0001\n", + "qos: 0\n", + "retain flag: 0\n", + "publishing 21:56:03 : measure resistivity 50 with array 22 28 24 26\n", + "(0, 52)\n", + "message received 21:56:03 : measure resistivity 50 with array 22 28 24 26 45 ohm.m\n", + "topic: measurements_ohmpi_sn_0001\n", + "qos: 0\n", + "retain flag: 0\n", + "publishing 21:56:04 : measure resistivity 51 with array 23 29 25 27\n", + "(0, 53)\n", + "message received 21:56:04 : measure resistivity 51 with array 23 29 25 27 45 ohm.m\n", + "topic: measurements_ohmpi_sn_0001\n", + "qos: 0\n", + "retain flag: 0\n", + "publishing 21:56:05 : measure resistivity 52 with array 24 30 26 28\n", + "(0, 54)\n", + "message received 21:56:05 : measure resistivity 52 with array 24 30 26 28 45 ohm.m\n", + "topic: measurements_ohmpi_sn_0001\n", + "qos: 0\n", + "retain flag: 0\n", + "publishing 21:56:06 : measure resistivity 53 with array 25 31 27 29\n", + "(0, 55)\n", + "message received 21:56:06 : measure resistivity 53 with array 25 31 27 29 45 ohm.m\n", + "topic: measurements_ohmpi_sn_0001\n", + "qos: 0\n", + "retain flag: 0\n", + "publishing 21:56:07 : measure resistivity 54 with array 26 32 28 30\n", + "(0, 56)\n", + "message received 21:56:07 : measure resistivity 54 with array 26 32 28 30 45 ohm.m\n", + "topic: measurements_ohmpi_sn_0001\n", + "qos: 0\n", + "retain flag: 0\n", + "publishing 21:56:08 : measure resistivity 55 with array 1 10 4 7\n", + "(0, 57)\n", + "message received 21:56:08 : measure resistivity 55 with array 1 10 4 7 45 ohm.m\n", + "topic: measurements_ohmpi_sn_0001\n", + "qos: 0\n", + "retain flag: 0\n", + "publishing 21:56:09 : measure resistivity 56 with array 2 11 5 8\n", + "(0, 58)\n", + "message received 21:56:09 : measure resistivity 56 with array 2 11 5 8 45 ohm.m\n", + "topic: measurements_ohmpi_sn_0001\n", + "qos: 0\n", + "retain flag: 0\n", + "publishing 21:56:10 : measure resistivity 57 with array 3 12 6 9\n", + "(0, 59)\n", + "message received 21:56:10 : measure resistivity 57 with array 3 12 6 9 45 ohm.m\n", + "topic: measurements_ohmpi_sn_0001\n", + "qos: 0\n", + "retain flag: 0\n", + "publishing 21:56:11 : measure resistivity 58 with array 4 13 7 10\n", + "(0, 60)\n", + "message received 21:56:11 : measure resistivity 58 with array 4 13 7 10 45 ohm.m\n", + "topic: measurements_ohmpi_sn_0001\n", + "qos: 0\n", + "retain flag: 0\n", + "publishing 21:56:12 : measure resistivity 59 with array 5 14 8 11\n", + "(0, 61)\n", + "message received 21:56:12 : measure resistivity 59 with array 5 14 8 11 45 ohm.m\n", + "topic: measurements_ohmpi_sn_0001\n", + "qos: 0\n", + "retain flag: 0\n", + "publishing 21:56:13 : measure resistivity 60 with array 6 15 9 12\n", + "(0, 62)\n", + "message received 21:56:13 : measure resistivity 60 with array 6 15 9 12 45 ohm.m\n", + "topic: measurements_ohmpi_sn_0001\n", + "qos: 0\n", + "retain flag: 0\n", + "publishing 21:56:14 : measure resistivity 61 with array 7 16 10 13\n", + "(0, 63)\n", + "message received 21:56:14 : measure resistivity 61 with array 7 16 10 13 45 ohm.m\n", + "topic: measurements_ohmpi_sn_0001\n", + "qos: 0\n", + "retain flag: 0\n", + "publishing 21:56:15 : measure resistivity 62 with array 8 17 11 14\n", + "(0, 64)\n", + "message received 21:56:15 : measure resistivity 62 with array 8 17 11 14 45 ohm.m\n", + "topic: measurements_ohmpi_sn_0001\n", + "qos: 0\n", + "retain flag: 0\n", + "publishing 21:56:16 : measure resistivity 63 with array 9 18 12 15\n", + "(0, 65)\n", + "message received 21:56:16 : measure resistivity 63 with array 9 18 12 15 45 ohm.m\n", + "topic: measurements_ohmpi_sn_0001\n", + "qos: 0\n", + "retain flag: 0\n", + "publishing 21:56:17 : measure resistivity 64 with array 10 19 13 16\n", + "(0, 66)\n", + "message received 21:56:17 : measure resistivity 64 with array 10 19 13 16 45 ohm.m\n", + "topic: measurements_ohmpi_sn_0001\n", + "qos: 0\n", + "retain flag: 0\n", + "publishing 21:56:18 : measure resistivity 65 with array 11 20 14 17\n", + "(0, 67)\n", + "message received 21:56:18 : measure resistivity 65 with array 11 20 14 17 45 ohm.m\n", + "topic: measurements_ohmpi_sn_0001\n", + "qos: 0\n", + "retain flag: 0\n", + "publishing 21:56:19 : measure resistivity 66 with array 12 21 15 18\n", + "(0, 68)\n", + "message received 21:56:19 : measure resistivity 66 with array 12 21 15 18 45 ohm.m\n", + "topic: measurements_ohmpi_sn_0001\n", + "qos: 0\n", + "retain flag: 0\n", + "publishing 21:56:20 : measure resistivity 67 with array 13 22 16 19\n", + "(0, 69)\n", + "message received 21:56:20 : measure resistivity 67 with array 13 22 16 19 45 ohm.m\n", + "topic: measurements_ohmpi_sn_0001\n", + "qos: 0\n", + "retain flag: 0\n", + "publishing 21:56:22 : measure resistivity 68 with array 14 23 17 20\n", + "(0, 70)\n", + "message received 21:56:22 : measure resistivity 68 with array 14 23 17 20 45 ohm.m\n", + "topic: measurements_ohmpi_sn_0001\n", + "qos: 0\n", + "retain flag: 0\n", + "publishing 21:56:23 : measure resistivity 69 with array 15 24 18 21\n", + "(0, 71)\n", + "message received 21:56:23 : measure resistivity 69 with array 15 24 18 21 45 ohm.m\n", + "topic: measurements_ohmpi_sn_0001\n", + "qos: 0\n", + "retain flag: 0\n", + "publishing 21:56:24 : measure resistivity 70 with array 16 25 19 22\n", + "(0, 72)\n", + "message received 21:56:24 : measure resistivity 70 with array 16 25 19 22 45 ohm.m\n", + "topic: measurements_ohmpi_sn_0001\n", + "qos: 0\n", + "retain flag: 0\n", + "publishing 21:56:25 : measure resistivity 71 with array 17 26 20 23\n", + "(0, 73)\n", + "message received 21:56:25 : measure resistivity 71 with array 17 26 20 23 45 ohm.m\n", + "topic: measurements_ohmpi_sn_0001\n", + "qos: 0\n", + "retain flag: 0\n", + "publishing 21:56:26 : measure resistivity 72 with array 18 27 21 24\n", + "(0, 74)\n", + "message received 21:56:26 : measure resistivity 72 with array 18 27 21 24 45 ohm.m\n", + "topic: measurements_ohmpi_sn_0001\n", + "qos: 0\n", + "retain flag: 0\n", + "publishing 21:56:27 : measure resistivity 73 with array 19 28 22 25\n", + "(0, 75)\n", + "message received 21:56:27 : measure resistivity 73 with array 19 28 22 25 45 ohm.m\n", + "topic: measurements_ohmpi_sn_0001\n", + "qos: 0\n", + "retain flag: 0\n", + "publishing 21:56:28 : measure resistivity 74 with array 20 29 23 26\n", + "(0, 76)\n", + "publishing 21:56:29 : measure resistivity 75 with array 21 30 24 27\n", + "(0, 77)\n", + "publishing 21:56:30 : measure resistivity 76 with array 22 31 25 28\n", + "(0, 78)\n", + "publishing 21:56:31 : measure resistivity 77 with array 23 32 26 29\n", + "(0, 79)\n", + "publishing 21:56:32 : measure resistivity 78 with array 1 13 5 9\n", + "(0, 80)\n", + "publishing 21:56:33 : measure resistivity 79 with array 2 14 6 10\n", + "(0, 81)\n", + "publishing 21:56:34 : measure resistivity 80 with array 3 15 7 11\n", + "(0, 82)\n", + "publishing 21:56:35 : measure resistivity 81 with array 4 16 8 12\n", + "(0, 83)\n", + "publishing 21:56:36 : measure resistivity 82 with array 5 17 9 13\n", + "(0, 84)\n", + "publishing 21:56:37 : measure resistivity 83 with array 6 18 10 14\n", + "(0, 85)\n", + "publishing 21:56:38 : measure resistivity 84 with array 7 19 11 15\n", + "(0, 86)\n", + "publishing 21:56:39 : measure resistivity 85 with array 8 20 12 16\n", + "(0, 87)\n", + "publishing 21:56:40 : measure resistivity 86 with array 9 21 13 17\n", + "(0, 88)\n", + "publishing 21:56:41 : measure resistivity 87 with array 10 22 14 18\n", + "(0, 89)\n", + "publishing 21:56:42 : measure resistivity 88 with array 11 23 15 19\n", + "(0, 90)\n", + "publishing 21:56:43 : measure resistivity 89 with array 12 24 16 20\n", + "(0, 91)\n", + "publishing 21:56:44 : measure resistivity 90 with array 13 25 17 21\n", + "(0, 92)\n", + "publishing 21:56:45 : measure resistivity 91 with array 14 26 18 22\n", + "(0, 93)\n", + "publishing 21:56:46 : measure resistivity 92 with array 15 27 19 23\n", + "(0, 94)\n", + "publishing 21:56:47 : measure resistivity 93 with array 16 28 20 24\n", + "(0, 95)\n", + "publishing 21:56:48 : measure resistivity 94 with array 17 29 21 25\n", + "(0, 96)\n", + "publishing 21:56:49 : measure resistivity 95 with array 18 30 22 26\n", + "(0, 97)\n", + "publishing 21:56:50 : measure resistivity 96 with array 19 31 23 27\n", + "(0, 98)\n", + "publishing 21:56:51 : measure resistivity 97 with array 20 32 24 28\n", + "(0, 99)\n", + "publishing 21:56:52 : measure resistivity 98 with array 1 16 6 11\n", + "(0, 100)\n", + "publishing 21:56:53 : measure resistivity 99 with array 2 17 7 12\n", + "(0, 101)\n", + "publishing 21:56:54 : measure resistivity 100 with array 3 18 8 13\n", + "(0, 102)\n", + "publishing 21:56:55 : measure resistivity 101 with array 4 19 9 14\n", + "(0, 103)\n", + "publishing 21:56:56 : measure resistivity 102 with array 5 20 10 15\n", + "(0, 104)\n", + "publishing 21:56:57 : measure resistivity 103 with array 6 21 11 16\n", + "(0, 105)\n", + "publishing 21:56:58 : measure resistivity 104 with array 7 22 12 17\n", + "(0, 106)\n", + "publishing 21:56:59 : measure resistivity 105 with array 8 23 13 18\n", + "(0, 107)\n", + "publishing 21:57:00 : measure resistivity 106 with array 9 24 14 19\n", + "(0, 108)\n", + "publishing 21:57:01 : measure resistivity 107 with array 10 25 15 20\n", + "(0, 109)\n", + "publishing 21:57:02 : measure resistivity 108 with array 11 26 16 21\n", + "(0, 110)\n", + "publishing 21:57:03 : measure resistivity 109 with array 12 27 17 22\n", + "(0, 111)\n", + "publishing 21:57:04 : measure resistivity 110 with array 13 28 18 23\n", + "(0, 112)\n", + "publishing 21:57:05 : measure resistivity 111 with array 14 29 19 24\n", + "(0, 113)\n", + "publishing 21:57:06 : measure resistivity 112 with array 15 30 20 25\n", + "(0, 114)\n", + "publishing 21:57:07 : measure resistivity 113 with array 16 31 21 26\n", + "(0, 115)\n", + "publishing 21:57:08 : measure resistivity 114 with array 17 32 22 27\n", + "(0, 116)\n", + "publishing 21:57:09 : measure resistivity 115 with array 1 19 7 13\n", + "(0, 117)\n", + "publishing 21:57:10 : measure resistivity 116 with array 2 20 8 14\n", + "(0, 118)\n", + "publishing 21:57:11 : measure resistivity 117 with array 3 21 9 15\n", + "(0, 119)\n", + "publishing 21:57:12 : measure resistivity 118 with array 4 22 10 16\n", + "(0, 120)\n", + "publishing 21:57:13 : measure resistivity 119 with array 5 23 11 17\n", + "(0, 121)\n", + "publishing 21:57:14 : measure resistivity 120 with array 6 24 12 18\n", + "(0, 122)\n", + "publishing 21:57:15 : measure resistivity 121 with array 7 25 13 19\n", + "(0, 123)\n", + "publishing 21:57:16 : measure resistivity 122 with array 8 26 14 20\n", + "(0, 124)\n", + "publishing 21:57:17 : measure resistivity 123 with array 9 27 15 21\n", + "(0, 125)\n", + "publishing 21:57:18 : measure resistivity 124 with array 10 28 16 22\n", + "(0, 126)\n", + "publishing 21:57:19 : measure resistivity 125 with array 11 29 17 23\n", + "(0, 127)\n" + ] + }, + { + "ename": "KeyboardInterrupt", + "evalue": "", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mKeyboardInterrupt\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m/tmp/ipykernel_156611/4011469085.py\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0mpub\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mclient\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mpublish\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mcontrol_topic\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mcommand\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 6\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mpub\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 7\u001b[0;31m \u001b[0mtime\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msleep\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;31mKeyboardInterrupt\u001b[0m: " + ] + } + ], + "source": [ + "for idx, row in df.iterrows():\n", + " t = time.strftime(\"%H:%M:%S\", time.localtime())\n", + " command = f'{t} : measure resistivity {idx} with array {row[\"array\"]}'\n", + " print(f'publishing {command}')\n", + " pub = client.publish(control_topic, command)\n", + " print(pub)\n", + " time.sleep(1)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b6ae4967", + "metadata": {}, + "outputs": [], + "source": [ + "client.loop_stop()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1517fe4d-b9b3-43b5-a72c-ed691f8f71e2", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "message received Resitivity: 215.17 ohm\n", + "topic: measurements_ohmpi_sn_0001\n", + "qos: 0\n", + "retain flag: 0\n", + "message received Resitivity: 214.98 ohm\n", + "topic: measurements_ohmpi_sn_0001\n", + "qos: 0\n", + "retain flag: 0\n", + "message received Resitivity: 215.12 ohm\n", + "topic: measurements_ohmpi_sn_0001\n", + "qos: 0\n", + "retain flag: 0\n" + ] + } + ], + "source": [ + "client.reinitialise()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cf1f18b7-c712-43f6-957e-aae872881915", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.10" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/compressed_sized_timed_rotating_logger.py b/compressed_sized_timed_rotating_logger.py new file mode 100644 index 00000000..cf8ca6cd --- /dev/null +++ b/compressed_sized_timed_rotating_logger.py @@ -0,0 +1,58 @@ +import time +import os +import logging.handlers as handlers +import zipfile + + +class CompressedSizedTimedRotatingFileHandler(handlers.TimedRotatingFileHandler): + """ + Handler for logging to a set of files, which switches from one file + to the next when the current file reaches a certain size, or at certain + timed intervals + """ + def __init__(self, filename, max_bytes=0, backup_count=0, encoding=None, + delay=0, when='h', interval=1, utc=False, zip_mode=zipfile.ZIP_DEFLATED): + handlers.TimedRotatingFileHandler.__init__(self, filename=filename, when=when, interval=interval, utc=utc, + backupCount=backup_count, encoding=encoding, delay=delay) + self.maxBytes = max_bytes + self.zip_mode = zip_mode + + def shouldRollover(self, record): + """ + Determine if rollover should occur. + Basically, see if the supplied record would cause the file to exceed + the size limit we have. + """ + if self.stream is None: # delay was set... + self.stream = self._open() + if self.maxBytes > 0: # are we rolling over? + msg = "%s\n" % self.format(record) + self.stream.seek(0, 2) # due to non-posix-compliant Windows feature + if self.stream.tell() + len(msg) >= self.maxBytes: + return True + t = int(time.time()) + if t >= self.rolloverAt: + return True + return False + + def find_last_rotated_file(self): + dir_name, base_name = os.path.split(self.baseFilename) + file_names = os.listdir(dir_name) + result = [] + prefix = f'{base_name}.2' # we want to find a rotated file with eg filename.2017-12-12... name + for file_name in file_names: + if file_name.startswith(prefix) and not file_name.endswith('.zip'): + result.append(file_name) + result.sort() + return os.path.join(dir_name, result[0]) + + def doRollover(self): + super(CompressedSizedTimedRotatingFileHandler, self).doRollover() + + dfn = self.find_last_rotated_file() + dfn_zipped = f'{dfn}.zip' + if os.path.exists(dfn_zipped): + os.remove(dfn_zipped) + with zipfile.ZipFile(dfn_zipped, 'w', self.zip_mode) as f: + f.write(dfn) + os.remove(dfn) \ No newline at end of file diff --git a/logging_setup.py b/logging_setup.py new file mode 100644 index 00000000..08b0d617 --- /dev/null +++ b/logging_setup.py @@ -0,0 +1,83 @@ +from settings import LOGGING_CONFIG, DATA_LOGGING_CONFIG +from os import path, mkdir, statvfs +from time import gmtime +import logging +from compressed_sized_timed_rotating_logger import CompressedSizedTimedRotatingFileHandler + + +def setup_loggers(): + # Message logging setup + log_path = path.join(path.dirname(__file__), 'logs') + if not path.isdir(log_path): + mkdir(log_path) + msg_log_filename = path.join(log_path, 'msg_log') + msg_logger = logging.getLogger('msg_logger') + + # Data logging setup + base_path = path.dirname(__file__) + data_path = path.join(base_path, 'data') + if not path.isdir(data_path): + mkdir(data_path) + data_log_filename = path.join(data_path, 'data_log') + data_logger = logging.getLogger('data_logger') + + # Debug and logging + debug = LOGGING_CONFIG['debug_mode'] + if debug: + logging_level = logging.DEBUG + else: + logging_level = logging.INFO + + # Set message logging format and level + log_format = '%(asctime)-15s | %(process)d | %(levelname)s: %(message)s' + logging_to_console = LOGGING_CONFIG['logging_to_console'] + msg_handler = CompressedSizedTimedRotatingFileHandler(msg_log_filename, max_bytes=LOGGING_CONFIG['max_bytes'], + backup_count=LOGGING_CONFIG['backup_count'], + when=LOGGING_CONFIG['when'], + interval=LOGGING_CONFIG['interval']) + msg_formatter = logging.Formatter(log_format) + msg_formatter.converter = gmtime + msg_formatter.datefmt = '%Y/%m/%d %H:%M:%S UTC' + msg_handler.setFormatter(msg_formatter) + msg_logger.addHandler(msg_handler) + msg_logger.setLevel(logging_level) + + if logging_to_console: + msg_logger.addHandler(logging.StreamHandler()) + + # Set data logging level and handler + data_logger.setLevel(logging.INFO) + data_handler = CompressedSizedTimedRotatingFileHandler(data_log_filename, + max_bytes=DATA_LOGGING_CONFIG['max_bytes'], + backup_count=DATA_LOGGING_CONFIG['backup_count'], + when=DATA_LOGGING_CONFIG['when'], + interval=DATA_LOGGING_CONFIG['interval']) + data_logger.addHandler(data_handler) + + if not init_logging(msg_logger, logging_level, log_path, data_log_filename): + print('ERROR: Could not initialize logging!') + return msg_logger, msg_log_filename, data_logger, data_log_filename, logging_level + + +def init_logging(msg_logger, logging_level, log_path, data_log_filename): + """ This is the init sequence for the logging system """ + + init_logging_status = True + msg_logger.info('') + msg_logger.info('****************************') + msg_logger.info('*** NEW SESSION STARTING ***') + msg_logger.info('****************************') + msg_logger.info('') + msg_logger.info('Logging level: %s' % logging_level) + try: + st = statvfs('.') + available_space = st.f_bavail * st.f_frsize / 1024 / 1024 + msg_logger.info(f'Remaining disk space : {available_space:.1f} MB') + except Exception as e: + msg_logger.debug('Unable to get remaining disk space: {e}') + msg_logger.info('Saving data log to ' + data_log_filename) + msg_logger.info('OhmPi settings:') + # TODO Add OhmPi settings + msg_logger.info('') + msg_logger.info(f'init_logging_status: {init_logging_status}') + return init_logging_status diff --git a/ohmpi_param.json b/ohmpi_param.json index 018dd93f..cda9a166 100644 --- a/ohmpi_param.json +++ b/ohmpi_param.json @@ -1,8 +1,8 @@ { "nb_electrodes": 64, - "injection_duration":0.2, + "injection_duration": 4, "nbr_meas": 100, "sequence_delay": 1, "stack": 1, - "export_path": "/home/pi/Documents/OhmPi/measurement.csv" + "export_path": "./measurement.csv" } diff --git a/settings.py b/settings.py new file mode 100644 index 00000000..02737c1d --- /dev/null +++ b/settings.py @@ -0,0 +1,40 @@ +# OhmPi configuration +OHMPI_CONFIG = { + 'id': '0001', # Unique identifier of the OhmPi board (string) + 'R_shunt': 2, # Shunt resistance in Ohms + 'Imax': 4800/50/2, # Maximum current + 'coef_p2': 2.50, # slope for current conversion for ADS.P2, measurement in V/V + 'coef_p3': 2.50, # slope for current conversion for ADS.P3, measurement in V/V + 'offset_p2': 0, + 'offset_p3': 0, + 'integer': 2, # Max value 10 WHAT IS THIS? + 'version': 2 +} + +# local messages logging configuration +LOGGING_CONFIG = { + 'debug_mode': False, + 'logging_to_console': False, + 'file_name': 'ohmpi_log', + 'max_bytes': 262144, + 'backup_count': 30, + 'when': 'd', + 'interval': 1 +} + +# local data logging configuration +DATA_LOGGING_CONFIG = { + 'file_name': 'data_log', + 'max_bytes': 16777216, + 'backup_count': 1024, + 'when': 'd', + 'interval': 1 +} + +# MQTT configuration parameters +MQTT_CONFIG = { + 'mqtt_broker': 'mg3d-dev.umons.ac.be', + 'client_id': f'ohmpi_sn_{OHMPI_CONFIG["id"]}', + 'control_topic': f'cmd_ohmpi_sn_{OHMPI_CONFIG["id"]}', + 'measurements_topic': f'measurements_ohmpi_sn_{OHMPI_CONFIG["id"]}' +} -- GitLab