microPAM utilities#

micoPAM console#

The microPAM software includes a basic menu that can interacted with from the PC, using either a terminal program or the following Python and R scripts.

Python script#

import serial
import serial.tools.list_ports

from datetime import datetime
def PC_time():
    date_time=datetime.now()
    print("Date and time is:", date_time)

def microPAM_showParameters():
    s=serial.tools.list_ports.comports(True)
    print(s[0].device)
    with serial.Serial(s[0].device) as ser:
        ser.reset_input_buffer
        ser.write(b'?p\r')
        for ii in range(5):
            print(ser.readline().decode('utf-8').rstrip())
    print()

def microPAM_syncTime():
    s=serial.tools.list_ports.comports(True)

    date_time=datetime.now()
    dd=date_time.strftime('!d%Y-%m-%d\r')
    tt=date_time.strftime('!t%H:%M:%S\r')

    with serial.Serial(s[0].device) as ser:
        ser.write(dd.encode())
        str=ser.readline().decode('utf-8').rstrip()
        print(str)

    with serial.Serial(s[0].device) as ser:
        ser.write(tt.encode())
        print(ser.readline().decode('utf-8').rstrip())

def microPAM_setParameter(token,value):
    s=serial.tools.list_ports.comports(True)
    with serial.Serial(s[0].device) as ser:
        data="!"+token+str(value)+"\r"
        ser.write(data.encode())
        print(ser.readline().decode('utf-8').rstrip())

def microPAM_readLines(value):
    s=serial.tools.list_ports.comports(True)
    with serial.Serial(s[0].device) as ser:
        for ii in range(value):
            print(ser.readline().decode('utf-8').rstrip())

def microPAM_command(value):
    s=serial.tools.list_ports.comports(True)
    with serial.Serial(s[0].device) as ser:
        ser.write(value.encode())

Example of python based setup of microPAM#

PC_time()
microPAM_connected = 0      # change to 1 if microPAM is connected to USB
if microPAM_connected:
    microPAM_showParameters()
    microPAM_syncTime()
#    microPAM_setParameter("f",44100)
#    microPAM_setParameter("s",12)
#    microPAM_setParameter("c",1)
    microPAM_showParameters()
Date and time is: 2024-10-09 17:49:08.169651

Example of python based monitoring of microPAM#

if microPAM_connected:
    microPAM_command("m")   # switch on monitor
    microPAM_readLines(10)
    microPAM_command("m")   # switch off monitor
if microPAM_connected:
    microPAM_command("s")   # start archiving
    microPAM_readLines(5)
    microPAM_command("e")   # stop archiving

microPAM GUI#

#%%writefile microPAM_GUI.py 
# uncomment prev line to save cell to file
# micoPAM GUI
# use this cell to test and develop GUI
# to compile "pyinstaller microPAM_GUI.py --noconfirm"
# will generate "dist/micoPAM_GUI/microPAM_GUI.exe"
# and  "dist/micoPAM_GUI/_internal" with all required pyd/dll files
#
import tkinter as tk
import time
from datetime import datetime
import serial
import serial.tools.list_ports

class Window(tk.Frame):

    def mEntry(self,txt,x,y,w,dx):
        label = tk.Label(text=txt,font=("Helvetica", 18))
        label.place(x=x-dx,y=y)
        edit = tk.Entry(text="", fg="Black", font=("Helvetica", 18),width=w)
        edit.place(x=x,y=y)
        return edit

    def mgetParam(self,ser,txt):
        ser.write(txt.encode())
        txt=ser.readline().decode('utf-8').rstrip()
        ip=txt.find("=")
        return txt[ip+2:]

    def mputEntry(self,edit,txt):
        edit.delete(0,tk.END)
        edit.insert(0,txt)

    def mUpdate(self,ser,edit,txt):
        ser.write(txt.encode())
        txt=ser.readline().decode('utf-8').rstrip()
        ip=txt.find("=")
        edit.delete(0,tk.END)
        edit.insert(0,txt[ip+2:])

    def mgetEntry(self,ser,str,edit):
        data=str+edit.get()+"\r"
        ser.write(data.encode())

    def ndays(self,d,m,y):
        def lpY(y): return (y%4==0) | ((y%100==0) & (y%400>0))
        dom=[31,28,31,30,31,30,31,31,30,31,30]
        #
        # number of days since 1-1-1970
        y1=y-1970
        days=y1*365
        for ii in range(y1): 
            if lpY(1970+ii): days +=1 
        #
        m -= 1
        for ii in range(m):
            days += dom[ii]
            if ii==1:
                if lpY(y): days += 1
        #
        d -= 1
        days += d
        return days, (days+4)%7 # 1-1-70 was thursday 1-1-24 was monday
    
    def nidays(self,days):
        def lpY(y): return (y%4==0) | ((y%100==0) & (y%400>0))
        dom=[31,28,31,30,31,30,31,31,30,31,30]
        #
        y1=0
        while days>0:
            if lpY(1970+y1): 
                days -=366
            else:
                days -= 365
            y1 +=1
        #
        if y1>0:
            y1 -= 1
        if days<=0:
            if lpY(1970+y1): 
                days += 366
            else:
                days += 365
        #
        days += 1
        ii = 0
        while days >=0:
            if (ii==1) & lpY(1970+y1): days -=1
            days -= dom[ii]
            ii += 1
        ii=ii-1
        days += dom[ii]
        m = ii
        return (1970+y1,m+1,days)

    def __init__(self, master=None):
        tk.Frame.__init__(self, master)        
        self.master = master

        # widget can take all window
        self.pack(fill=tk.BOTH, expand=1)

        label1 = tk.Label(text="PC:",font=("Helvetica", 18))
        label1.place(x=100,y=10)
        self.pcClocklabel = tk.Label(text="", fg="Red", font=("Helvetica", 18))
        self.pcClocklabel.place(x=160,y=10)
        self.update_clock()

        label2 = tk.Label(text="MCU:",font=("Helvetica", 18))
        label2.place(x=80,y=60)
        self.mcuClocklabel = tk.Label(text="", fg="Black", font=("Helvetica", 18))
        self.mcuClocklabel.place(x=160,y=60)

        xo=120
        yo=110
        ii=0
        self.fsamp_edit = self.mEntry("fsamp:",xo,yo+ii*40,10,80); ii+=1
        self.proc_edit  = self.mEntry("proc:",xo,yo+ii*40,5,80); ii+=1
        self.shift_edit = self.mEntry("shift:",xo,yo+ii*40,5,80); ii+=1
        self.again_edit = self.mEntry("again:",xo,yo+ii*40,5,80); ii+=1

        xo=350
        yo=110
        ii=0
        self.t_acq_edit = self.mEntry("t_acq:",xo,yo+ii*40,5,80); ii+=1
        self.t_on_edit  = self.mEntry("t_on:",xo,yo+ii*40,5,80); ii+=1
        self.t_rep_edit = self.mEntry("t_rep:",xo,yo+ii*40,5,80); ii+=1
        ii=0
        xo += 160
        self.h_1_edit = self.mEntry("h_1:",xo,yo+ii*40,5,80); ii+=1
        self.h_2_edit = self.mEntry("h_2:",xo,yo+ii*40,5,80); ii+=1
        self.h_3_edit = self.mEntry("h_3:",xo,yo+ii*40,5,80); ii+=1
        self.h_4_edit = self.mEntry("h_4:",xo,yo+ii*40,5,80); ii+=1
        yo += 40
        self.d_start_edit = self.mEntry("d_start:",xo-320,yo+ii*40,3,90); 
        self.m_start_edit = self.mEntry("m_start:",xo-160,yo+ii*40,3,90); 
        self.y_start_edit = self.mEntry("y_start:",xo,yo+ii*40,5,90); ii+=1
        self.d_on_edit = self.mEntry("d_on:",xo,yo+ii*40,5,80); ii+=1
        self.d_rep_edit = self.mEntry("d_rep:",xo,yo+ii*40,5,80); ii+=1

        # create buttons
        xm=600
        exitButton = tk.Button(self, text="Exit ", command=self.clickExitButton, font=("Helvetica", 18))
        exitButton.place(x=xm, y=10)
        loadButton = tk.Button(self, text="Load", command=self.clickLoadButton, font=("Helvetica", 18))
        loadButton.place(x=xm, y=80)
        saveButton = tk.Button(self, text="Save", command=self.clickSaveButton, font=("Helvetica", 18))
        saveButton.place(x=xm, y=150)
        storeButton = tk.Button(self, text="Store", command=self.clickStoreButton, font=("Helvetica", 18))
        storeButton.place(x=xm, y=220)
        syncButton = tk.Button(self, text="Sync", command=self.clickSyncButton, font=("Helvetica", 18))
        syncButton.place(x=xm, y=310)
        self.storeButton=storeButton
        #
        date_time=datetime.now()
        self.mputEntry(self.d_start_edit,str(date_time.day))
        self.mputEntry(self.m_start_edit,str(date_time.month))
        self.mputEntry(self.y_start_edit,str(date_time.year))

        s=serial.tools.list_ports.comports(True)
        with serial.Serial(s[0].device) as ser:
            ser.reset_input_buffer()
            ser.reset_output_buffer()


    def clickExitButton(self):
        self.master.destroy() 

    def clickLoadButton(self):
        s=serial.tools.list_ports.comports(True)
        if len(s)==0:
            return
        with serial.Serial(s[0].device) as ser:
            ser.reset_input_buffer()
            # stop acquisition
            ser.write(b'e\r')
            txt=ser.readline().decode('utf-8').rstrip()
            # stop monitor
            ser.write(b':m0\r')
            txt=ser.readline().decode('utf-8').rstrip()
            ser.reset_input_buffer()
            #
            # load now data from device
            ser.write(b'?d\r')
            txt1=ser.readline().decode('utf-8').rstrip()
            ser.write(b'?t\r')
            txt2=ser.readline().decode('utf-8').rstrip()
            ip1=txt1.find("=")
            ip2=txt2.find("=")
            self.mcuClocklabel.configure(text=txt1[ip1+2:]+txt2[ip2+1:])
            #
            self.mUpdate(ser,self.t_acq_edit,"?a")
            self.mUpdate(ser,self.t_on_edit, "?o")
            self.mUpdate(ser,self.t_rep_edit,"?r")
            #
            self.mUpdate(ser,self.h_1_edit,"?1")
            self.mUpdate(ser,self.h_2_edit,"?2")
            self.mUpdate(ser,self.h_3_edit,"?3")
            self.mUpdate(ser,self.h_4_edit,"?4")
            #
            self.mUpdate(ser,self.d_on_edit,"?5")
            self.mUpdate(ser,self.d_rep_edit,"?6")
            #
            self.mUpdate(ser,self.fsamp_edit,"?f")
            self.mUpdate(ser,self.proc_edit, "?c")
            self.mUpdate(ser,self.shift_edit,"?s")
            self.mUpdate(ser,self.again_edit,"?g")

            days=int(self.mgetParam(ser,"?0"))
            print('get',days)
            year,month,day=self.nidays(days+20000)
            self.mputEntry(self.d_start_edit,str(day))
            self.mputEntry(self.m_start_edit,str(month))
            self.mputEntry(self.y_start_edit,str(year))
        self.storeButton["state"]=tk.DISABLED

    def clickSaveButton(self):
        dx=self.d_start_edit.get()
        mx=self.m_start_edit.get()
        yx=self.y_start_edit.get()
        days,dow=self.ndays(int(dx),int(mx),int(yx))
        #
        s=serial.tools.list_ports.comports(True)
        with serial.Serial(s[0].device) as ser:
            ser.read_all()
            self.mgetEntry(ser,'!a',self.t_acq_edit)
            self.mgetEntry(ser,'!o',self.t_on_edit)
            self.mgetEntry(ser,'!r',self.t_rep_edit)
            self.mgetEntry(ser,'!1',self.h_1_edit)
            self.mgetEntry(ser,'!2',self.h_2_edit)
            self.mgetEntry(ser,'!3',self.h_3_edit)
            self.mgetEntry(ser,'!4',self.h_4_edit)
            self.mgetEntry(ser,'!5',self.d_on_edit)
            self.mgetEntry(ser,'!6',self.d_rep_edit)
            #
            self.mgetEntry(ser,'!f',self.fsamp_edit)
            self.mgetEntry(ser,'!c',self.proc_edit)
            self.mgetEntry(ser,'!s',self.shift_edit)
            self.mgetEntry(ser,'!g',self.again_edit)
            #
            data="!0"+str(days-20000)+"\r"
            print('put', data)
            ser.write(data.encode())
            #
            ser.read_all()
            with open("config.txt","w") as f:
                f.write("a="+self.t_acq_edit.get()+"; t_acq\n")
                f.write("o="+self.t_on_edit.get()+"; t_on\n")
                f.write("r="+self.t_rep_edit.get()+"; t_rep\n")

                f.write("1="+self.h_1_edit.get()+"; h_1\n")
                f.write("2="+self.h_2_edit.get()+"; h_2\n")
                f.write("3="+self.h_3_edit.get()+"; h_3\n")
                f.write("4="+self.h_4_edit.get()+"; h_4\n")
                f.write("5="+self.d_on_edit.get()+"; d_on\n")
                f.write("6="+self.d_rep_edit.get()+"; d_rep\n")

                f.write("f="+self.fsamp_edit.get()+"; fsamp\n")
                f.write("c="+self.proc_edit.get()+"; proc\n")
                f.write("s="+self.shift_edit.get()+"; shift\n")
                f.write("g="+self.again_edit.get()+"; again\n")

                f.write("0="+str(days-20000)+"; d_0\n")

        self.storeButton["state"]=tk.NORMAL

    def clickStoreButton(self):
        s=serial.tools.list_ports.comports(True)
        with serial.Serial(s[0].device) as ser:
            ser.read_all()
            ser.write("!w1\r".encode())
            time.sleep(0.1)
            txt=ser.readline().decode('utf-8').rstrip()
            #
            ser.write(":w".encode())
            time.sleep(0.1)
            txt=ser.readline().decode('utf-8').rstrip()
            txt=ser.readline().decode('utf-8').rstrip()
            print(txt)

    def clickSyncButton(self):
        s=serial.tools.list_ports.comports(True)
        date_time=datetime.now()
        dd=date_time.strftime("!d%Y-%m-%d\r")
        tt=date_time.strftime("!t%H:%M:%S\r")

        with serial.Serial(s[0].device) as ser:
            ser.write(tt.encode())
        with serial.Serial(s[0].device) as ser:
            ser.write(dd.encode())
        with serial.Serial(s[0].device) as ser:
            ser.write(b':c')

    def update_clock(self):
        now = time.strftime("%Y-%m-%d %H:%M:%S")
        self.pcClocklabel.configure(text=now)
        self.after(1000, self.update_clock)

root = tk.Tk()
app = Window(root)
root.wm_title("MicroPAM V3 (WMXZ)")
root.geometry("700x500")

microPAM_connected = 1      # change to 1 if microPAM is connected to USB
if microPAM_connected:
    root.after(1000, app.update_clock)
    root.mainloop() 
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
Cell In[5], line 300
    297         self.after(1000, self.update_clock)
    299 root = tk.Tk()
--> 300 app = Window(root)
    301 root.wm_title("MicroPAM V3 (WMXZ)")
    302 root.geometry("700x500")

Cell In[5], line 161, in Window.__init__(self, master)
    158 self.mputEntry(self.y_start_edit,str(date_time.year))
    160 s=serial.tools.list_ports.comports(True)
--> 161 with serial.Serial(s[0].device) as ser:
    162     ser.reset_input_buffer()
    163     ser.reset_output_buffer()

IndexError: list index out of range

R scripts#

The following functions allow the interaction with the microPAM using R

library('serial')

listParameters <- function()
{
	ser <- listPorts()
	com <- serialConnection(name="microPAM",port=ser,
			buffering='line',
			translation='cr')
	if(isOpen(com)==FALSE) open(com)

	write.serialConnection(com,"?p\r\n")
	flush(com)
	Sys.sleep(0.1)
	ret<-read.serialConnection(com)
	close(com)
	cat(c(ret,"\n"))
}

syncTime <- function()
{
	ser <- listPorts()
	com <- serialConnection(name="microPAM",port=ser,
			buffering='line',
			translation='cr')
	if(isOpen(com)==FALSE) open(com)

	now=Sys.time()

	dd=format(now,"!d%Y-%m-%d\r\n")
	write.serialConnection(com,dd)
	flush(com)
	Sys.sleep(0.1)

	ret<-read.serialConnection(com)
	cat(ret)

	tt=format(now,"!t%H-%M-%S\r\n")
	write.serialConnection(com,tt)
	flush(com)
	Sys.sleep(0.1)

	ret<-read.serialConnection(com)

	cat(c(ret,"\n"))
	close(com)
}

setParameter <- function(token,value)
{
	ser <- listPorts()
	com <- serialConnection(name="microPAM",port=ser,
			buffering='line',
			translation='cr')
	if(isOpen(com)==FALSE) open(com)

	uu=sprintf("!%s%d\r\n",token,value)
	write.serialConnection(com,uu)
	flush(com)
	Sys.sleep(0.1)

	ret<-read.serialConnection(com)
	cat(c(ret,"\n"))
	close(com)
}

doCommand <- function(token)
{
	ser <- listPorts()
	com <- serialConnection(name="microPAM",port=ser,
			buffering='line',
			translation='cr')
	if(isOpen(com)==FALSE) open(com)

	uu=sprintf("%s\r\n",token)
	write.serialConnection(com,uu)
	flush(com)
	close(com)
}

monitor <- function(num)
{
	ser <- listPorts()
	com <- serialConnection(name="microPAM",port=ser,
			buffering='line',
			translation='lf')
	if(isOpen(com)==FALSE) open(com)

	ret<-read.serialConnection(com)
	for (ii in 1:num)
	{ Sys.sleep(1)
  	  ret<-read.serialConnection(com)
	  cat(c(ret,'\n'))
	}
	close(com)
}

basic audio test (wav file generation) script#

import numpy as np
import wavio
rate = 22050             # samples per second
T = 3                    # sample duration (seconds)
f = 440.0                # sound frequency (Hz)
t = np.linspace(0, T, T*rate, endpoint=False)
sig = np.sin(2 * np.pi * f * t)
wavio.write("sine24.wav", sig, rate, sampwidth=3)   # 24 bit data (sampwidth=3)

Elliptical filters#

Low-pass filter with elliptical (Cauer) filter for very low (< 1kHz) corner frequencies

  • first decimate data to about 2400 Hz

  • estimate biquad filter coefficients with python script

  • apply 3 biquad filter

import numpy as np
from scipy.signal import ellip, sosfreqz
import scipy.signal as sig
import matplotlib.pyplot as plt

Fs = 2400

x = sig.ellip(6, 1, 60, [300 / (Fs / 2)], output='sos')
print(x)
freqs, resps = sig.sosfreqz(x, fs = Fs)

fig = plt.figure()
ax = fig.add_subplot(111)
ax.grid()

ax.plot(freqs, 20 * np.log10(np.abs(resps)))

plt.show()
[[ 0.00454991  0.00480905  0.00454991  1.         -1.54273107  0.64191613]
 [ 1.         -0.63758644  1.          1.         -1.43156463  0.80165859]
 [ 1.         -1.02047284  1.          1.         -1.37624928  0.94360981]]
_images/d7054c76241dbbeed0df9df77e143ca4b0de11bc68afe26f74cffc86695a9d4d.png