Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #!/usr/bin/python
- import sys, os
- import serial
- import time, datetime
- import threading, thread
- import socket
- from collections import deque
- import random
- import pyaudio
- import math
- import struct
- ##############################################################################
- # pyRadMon - logger for Geiger counters #
- # Original Copyright 2013 by station pl_gdn_1 #
- # Copyright 2014 by Auseklis Corporation, Richmond, Virginia, U.S.A. #
- # #
- # This file is part of The PyRadMon Project #
- # https://sourceforge.net/p/pyradmon #
- # #
- # PyRadMon is free software: you can redistribute it and/or modify it under #
- # the terms of the GNU General Public License as published by the Free #
- # Software Foundation, either version 3 of the License, or (at your option) #
- # any later version. #
- # #
- # PyRadMon is distributed in the hope that it will be useful, but WITHOUT #
- # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
- # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
- # more details. #
- # #
- # You should have received a copy of the GNU General Public License #
- # along with PyRadMon. If not, see <http://www.gnu.org/licenses/>. #
- # #
- # @license GPL-3.0+ <http://spdx.org/licenses/GPL-3.0+> #
- ##############################################################################
- # version is a.b.c, change in a or b means new functionality/bugfix, #
- # change in c = bugfix #
- # do not uncomment line below, it's currently used in HTTP headers #
- VERSION="1.1.9"
- # To see your online los, report a bug or request a new feature, please #
- # visit http://www.radmon.org and/or https://sourceforge.net/p/pyradmon #
- ##############################################################################
- ##############################################################################
- # Part 1 - configuration procedures
- #
- # Read configuration from file + constants definition
- ##############################################################################
- class config():
- # used as enums
- UNKNOWN=0
- DEMO=1
- MYGEIGER=2
- GMC=3
- NETIO=4
- AUDIO=5
- def __init__(self):
- # define constants
- self.CONFIGFILE="config.txt"
- self.UNKNOWN=0
- self.DEMO=1
- self.MYGEIGER=2
- self.GMC=3
- self.NETIO=4
- self.AUDIO=5
- self.user="not_set"
- self.password="not_set"
- self.portName=None
- self.portSpeed=2400
- self.timeout=40 # not used for now
- self.protocol=self.UNKNOWN
- self.deviceIndex=0
- def readConfig(self):
- print "Reading configuration:\r\n\t"
- # if file is present then try to read configuration from it
- try:
- f = open(self.CONFIGFILE)
- line=" "
- # analyze file line by line, format is parameter=value
- while (line):
- line=f.readline()
- params=line.split("=")
- if len(params)==2:
- parameter=params[0].strip().lower()
- value=params[1].strip()
- if parameter=="user":
- self.user=value
- print "\tUser name configured\r\n\t"
- elif parameter=="password":
- self.password=value
- print "\tPassword configured\r\n\t"
- elif parameter=="serialport":
- self.portName=value
- print "\tSerial port name configured\r\n\t"
- elif parameter=="speed":
- self.portSpeed=int(value)
- print "\tSerial port speed configured\r\n\t"
- elif parameter=="device":
- self.deviceIndex=int(value)
- print "\tDevice number configured\r\n\t"
- elif parameter=="protocol":
- value=value.lower()
- if value=="mygeiger":
- self.protocol=self.MYGEIGER
- elif value=="demo":
- self.protocol=self.DEMO
- elif value=="gmc":
- self.protocol=self.GMC
- elif value=="netio":
- self.protocol=self.NETIO
- elif value=="audio":
- self.protocol=self.AUDIO
- if self.protocol!=self.UNKNOWN:
- print "\tProtocol configured\r\n\t"
- # end of if
- # end of while
- f.close()
- except Exception as e:
- print "\tFailed to read configuration file:\r\n\t",str(e), "\r\nExiting\r\n"
- exit(1)
- # well done, configuration is ready to use
- print ""
- ################################################################################
- # Part 2 - Geiger counter communication
- #
- # It should be easy to add different protocol by simply
- # creating new class based on baseGeigerCommunication, as it's done in
- # classes Demo and myGeiger
- ################################################################################
- class baseGeigerCommunication(threading.Thread):
- def __init__(self, cfg):
- super(baseGeigerCommunication, self).__init__()
- self.sPortName=cfg.portName
- self.sPortSpeed=cfg.portSpeed
- self.timeout=cfg.timeout
- self.stopwork=0
- self.queue=deque()
- self.queueLock=0
- self.is_running=1
- def run(self):
- try:
- print "Gathering data started => geiger 1\r\n"
- self.serialPort = serial.Serial(self.sPortName, self.sPortSpeed, timeout=1)
- self.serialPort.flushInput()
- self.initCommunication()
- while(self.stopwork==0):
- result=self.getData()
- while (self.queueLock==1):
- print "Geiger communication: quene locked! => geiger 1\r\n"
- time.sleep(0.5)
- self.queueLock=1
- self.queue.append(result)
- self.queueLock=0
- print "Geiger sample => geiger 1:\tCPM =",result[0],"\t",str(result[1])
- print "hereB"
- self.serialPort.close()
- print "Gathering data from Geiger stopped => geiger 1\r\n"
- except serial.SerialException as e:
- print "Problem with serial port => geiger 1:\r\n\t", str(e),"\r\nExiting\r\n"
- self.stop()
- sys.exit(1)
- def initCommunication(self):
- print "Initializing geiger communication => geiger 1\r\n"
- def sendCommand(self, command):
- self.serialPort.flushInput()
- self.serialPort.write(command)
- # assume that device responds within 0.5s
- time.sleep(0.5)
- response=""
- while (self.serialPort.inWaiting()>0 and self.stopwork==0):
- response = response + self.serialPort.read()
- return response
- def getData(self):
- cpm=25
- utcTime=datetime.datetime.utcnow()
- data=[cpm, utcTime]
- return data
- def stop(self):
- self.stopwork=1
- self.queueLock=0
- self.is_running=0
- def getResult(self):
- # check if we have some data in queue
- if len(self.queue)>0:
- # check if it's safe to process queue
- while (self.queueLock==1):
- print "getResult: quene locked! => geiger 1\r\n"
- time.sleep(0.5)
- # put lock so measuring process will not interfere with queue,
- # processing should be fast enought to not break data acquisition from geiger
- self.queueLock=1
- cpm=0
- # now get sum of all CPM's
- for singleData in self.queue:
- cpm=cpm+singleData[0]
- # and divide by number of elements
- # to get mean value, 0.5 is for rounding up/down
- cpm=int( ( float(cpm) / len(self.queue) ) +0.5)
- # report with latest time from quene
- utcTime=self.queue.pop()[1]
- # clear queue and remove lock
- self.queue.clear()
- self.queueLock=0
- data=[cpm, utcTime]
- else:
- # no data in queue, return invalid CPM data and current time
- data=[-1, datetime.datetime.utcnow()]
- return data
- class Demo(baseGeigerCommunication):
- def run(self):
- print "Gathering data started => geiger 1\r\n"
- while(self.stopwork==0):
- result=self.getData()
- while (self.queueLock==1):
- print "Geiger communication: quene locked! => geiger 1\r\n"
- time.sleep(0.5)
- self.queueLock=1
- self.queue.append(result)
- self.queueLock=0
- print "Geiger sample => geiger 1:\t",result,"\r\n"
- outputTxtName = "data-dosi.txt"
- outputFile = open(outputTxtName, "a")
- outputFile.write(str(result[0]) + "," + str(result[1]) + "\n")
- outputFile.close()
- print "Gathering data from Geiger stopped => geiger 1\r\n"
- def getData(self):
- for i in range(0,5):
- time.sleep(1)
- cpm=random.randint(5,40)
- utcTime=datetime.datetime.utcnow()
- data=[cpm, utcTime]
- return data
- class myGeiger(baseGeigerCommunication):
- def getData(self):
- cpm=-1
- try:
- # wait for data
- while (self.serialPort.inWaiting()==0 and self.stopwork==0):
- time.sleep(1)
- time.sleep(0.1) # just to ensure all CPM bytes are in serial port buffer
- # read all available data
- x=""
- while (self.serialPort.inWaiting()>0 and self.stopwork==0):
- x = x + self.serialPort.read()
- if len(x)>0:
- cpm=int(x)
- utcTime=datetime.datetime.utcnow()
- data=[cpm, utcTime]
- return data
- except Exception as e:
- print "\r\nProblem in getData procedure (disconnected USB device?) => geiger 1:\r\n\t",str(e),"\r\nExiting\r\n"
- self.stop()
- sys.exit(1)
- class gmc(baseGeigerCommunication):
- def initCommunication(self):
- print "Initializing GMC protocol communication => geiger 1\r\n"
- # get firmware version
- response=self.sendCommand("<GETVER>>")
- if len(response)>0:
- print "Found GMC-compatible device, version => geiger 1: ", response, "\r\n"
- # get serial number
- # serialnum=self.sendCommand("<GETSERIAL>>")
- # serialnum.int=struct.unpack('!1H', serialnum(7))[0]
- # print "Device Serial Number is: ", serialnum.int
- # disable heartbeat, we will request data from script
- self.sendCommand("<HEARTBEAT0>>")
- print "Please note data will be acquired once per 5 seconds => geiger 1\r\n"
- # update the device time
- unitTime=self.sendCommand("<GETDATETIME>>")
- print "Unit shows time as => geiger 1: ", unitTime, "\r\n"
- # self.sendCommand("<SETDATETIME[" + time.strftime("%y%m%d%H%M%S") + "]>>")
- print "<SETDATETIME[" + time.strftime("%y%m%d%H%M%S") + "]>>"
- else:
- print "No response from device => geiger 1\r\n"
- self.stop()
- sys.exit(1)
- def getData(self):
- cpm=-1
- try:
- # wait, we want sample every 30s
- for i in range(0,3):
- time.sleep(1)
- # send request
- response=self.sendCommand("<GETCPM>>")
- if len(response)==2:
- # convert bytes to 16 bit int
- cpm=ord(response[0])*256+ord(response[1])
- else:
- print "Unknown response to CPM request, device is not GMC-compatible? => geiger 1\r\n"
- self.stop()
- sys.exit(1)
- utcTime=datetime.datetime.utcnow()
- data=[cpm, utcTime]
- return data
- except Exception as e:
- print "\r\nProblem in getData procedure (disconnected USB device?) => geiger 1:\r\n\t",str(e),"\r\nExiting\r\n"
- self.stop()
- sys.exit(1)
- class netio(baseGeigerCommunication):
- def getData(self):
- cpm=-1
- try:
- # we want data only once per 30 seconds, ignore rest
- # it's averaged for 60 seconds by device anyway
- for i in range(0,30):
- time.sleep(1)
- # wait for data, should be already there (from last 30s)
- while (self.serialPort.inWaiting()==0 and self.stopwork==0):
- time.sleep(0.5)
- time.sleep(0.1) # just to ensure all CPM bytes are in serial port buffer
- # read all available data
- # do not stop receiving unless it ends with \r\n
- x=""
- while ( x.endswith("\r\n")==False and self.stopwork==0):
- while ( self.serialPort.inWaiting()>0 and self.stopwork==0 ):
- x = x + self.serialPort.read()
- # if CTRL+C pressed then x can be invalid so check it
- if x.endswith("\r\n"):
- # we want only latest data, ignore older
- tmp=x.splitlines()
- x=tmp[len(tmp)-1]
- cpm=int(x)
- utcTime=datetime.datetime.utcnow()
- data=[cpm, utcTime]
- return data
- except Exception as e:
- print "\r\nProblem in getData procedure (disconnected USB device?) => geiger 1:\r\n\t",str(e),"\r\nExiting\r\n"
- self.stop()
- sys.exit(1)
- def initCommunication(self):
- print "Initializing NetIO => geiger 1\r\n"
- # send "go" to start receiving CPM data
- response=self.sendCommand("go\r\n")
- print "Please note data will be acquired once per 30 seconds => geiger 1\r\n"
- ################################################################################
- # Part 2b - audio geiger handeler
- ################################################################################
- INITIAL_TAP_THRESHOLD = 0.010
- FORMAT = pyaudio.paInt16
- SHORT_NORMALIZE = (1.0/32768.0)
- CHANNELS = 2
- RATE = 44100
- INPUT_BLOCK_TIME = 0.05
- INPUT_FRAMES_PER_BLOCK = int(RATE*INPUT_BLOCK_TIME)
- # if the noise was longer than this many blocks, it's not a 'tap'
- MAX_TAP_BLOCKS = 0.15/INPUT_BLOCK_TIME
- def get_rms( block ):
- # RMS amplitude is defined as the square root of the
- # mean over time of the square of the amplitude.
- # so we need to convert this string of bytes into
- # a string of 16-bit samples...
- # we will get one short out for each
- # two chars in the string.
- count = len(block)/2
- format = "%dh"%(count)
- shorts = struct.unpack( format, block )
- # iterate over the block.
- sum_squares = 0.0
- for sample in shorts:
- # sample is a signed short in +/- 32768.
- # normalize it to 1.0
- n = sample * SHORT_NORMALIZE
- sum_squares += n*n
- return math.sqrt( sum_squares / count )
- class audioCommunication(threading.Thread):
- def __init__(self, cfg):
- super(audioCommunication, self).__init__()
- self.initCommunication()
- self.timeout=cfg.timeout
- self.stopwork=0
- self.queue=deque()
- self.queueLock=0
- self.is_running=1
- self.pa = pyaudio.PyAudio()
- self.device_index = cfg.deviceIndex
- self.tap_threshold = INITIAL_TAP_THRESHOLD
- self.noisycount = 0
- def run(self):
- try:
- print "Gathering data started => geiger 1\r\n"
- while(self.stopwork==0):
- result=self.getData()
- while (self.queueLock==1):
- print "Geiger communication: quene locked! => geiger 1\r\n"
- time.sleep(0.5)
- self.queueLock=1
- self.queue.append(result)
- self.queueLock=0
- print "Geiger sample => geiger 1:\tCPM =",result[0],"\t",str(result[1]),"\r\n"
- print "a"
- print "Gathering data from Geiger stopped => geiger 1\r\n"
- self.pa.terminate()
- except Exception as e:
- print "Problem with audio port => geiger 1:\r\n\t", str(e),"\r\nExiting\r\n"
- self.stop()
- sys.exit(1)
- def initCommunication(self):
- print "Initializing audio communication => geiger 1\r\n"
- def getData(self):
- self.stream = self.pa.open(format = FORMAT, channels = CHANNELS, rate = RATE, input = True, input_device_index = self.device_index, frames_per_buffer = INPUT_FRAMES_PER_BLOCK)
- for i in range(600):
- try:
- block = self.stream.read(INPUT_FRAMES_PER_BLOCK)
- except Exception as ex:
- print "Problem with audio port => geiger 1:\r\n\t", str(ex),"\r\nExiting\r\n"
- self.stream.stop_stream()
- self.stream.close()
- self.stop()
- sys.exit(1)
- amplitude = get_rms( block )
- if amplitude > self.tap_threshold:
- # noisy block
- self.noisycount += 1
- self.stream.stop_stream()
- self.stream.close()
- if self.noisycount >= 0:
- cpm = self.noisycount * ( 60 / 30 )
- self.noisycount = 0
- utcTime=datetime.datetime.utcnow()
- data=[cpm, utcTime]
- return data
- def stop(self):
- self.stopwork=1
- self.queueLock=0
- self.is_running=0
- def getResult(self):
- # check if we have some data in queue
- if len(self.queue)>0:
- # check if it's safe to process queue
- while (self.queueLock==1):
- print "getResult: quene locked! => geiger 1\r\n"
- time.sleep(0.5)
- # put lock so measuring process will not interfere with queue,
- # processing should be fast enought to not break data acquisition from geiger
- self.queueLock=1
- cpm=0
- # now get sum of all CPM's
- for singleData in self.queue:
- cpm=cpm+singleData[0]
- # and divide by number of elements
- # to get mean value, 0.5 is for rounding up/down
- cpm=int( ( float(cpm) / len(self.queue) ) +0.5)
- # report with latest time from quene
- utcTime=self.queue.pop()[1]
- # clear queue and remove lock
- self.queue.clear()
- self.queueLock=0
- data=[cpm, utcTime]
- else:
- # no data in queue, return invalid CPM data and current time
- data=[-1, datetime.datetime.utcnow()]
- return data
- ################################################################################
- # Part 3 - Web server communication
- ################################################################################
- class webCommunication():
- HOST="www.radmon.org"
- #HOST="127.0.0.1" # uncomment this for debug purposes on localhost
- PORT=80
- def __init__(self, mycfg):
- self.user=mycfg.user
- self.password=mycfg.password
- def sendSample(self, sample):
- print "Connecting to server => geiger 1\r\n"
- s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- s.connect((self.HOST, self.PORT))
- BUFFER_SIZE=1024
- sampleCPM=sample[0]
- sampleTime=sample[1]
- # format date and time as required
- dtime=sampleTime.strftime("%Y-%m-%d%%20%H:%M:%S")
- url="GET /radmon.php?user="+self.user+"&password="+self.password+"&function=submit&datetime="+dtime+"&value="+str(sampleCPM)+"&unit=CPM HTTP/1.1"
- request=url+"\r\nHost: www.radmon.org\r\nUser-Agent: pyRadMon "+VERSION+"\r\n\r\n"
- print "Sending average sample => geiger 1: "+str(sampleCPM)+" CPM\r\n"
- #print "\r\n### HTTP Request ###\r\n"+request
- s.send(request)
- data = s.recv(BUFFER_SIZE)
- httpResponse=str(data).splitlines()[0]
- print "Server response => geiger 1: ",httpResponse,"\r\n"
- if "incorrect login" in data.lower():
- print "You are using incorrect user/password combination => geiger 1!\r\n"
- geigerCommunication.stop()
- sys.exit(1)
- #print "\r\n### HTTP Response ###\r\n"+data+"\r\n"
- s.close
- ################################################################################
- # Main code
- ################################################################################
- outputTxtName = "data-dosi.txt"
- outputFile = open(outputTxtName, "a")
- outputFile.write(datetime.datetime.utcnow().strftime("%y-%m-%d-%H:%M:%S") + "\n")
- outputFile.close()
- # main loop is in while loop
- # check if file exists, if not, create one and exit
- if (os.path.isfile("config.txt")==0):
- print "\tNo configuration file, creating default one.\r\n\t"
- try:
- f = open("config.txt", 'w')
- f.write("# Parameter names are not case-sensitive\r\n")
- f.write("# Parameter values are case-sensitive\r\n")
- f.write("user=test_user\r\n")
- f.write("password=test_password\r\n")
- f.write("# Port is usually /dev/ttyUSBx in Linux and COMx in Windows\r\n")
- f.write("serialport=/dev/ttyUSB0\r\n")
- f.write("speed=2400\r\n")
- f.write("# Protocols: demo, mygeiger, gmc, netio, audio\r\n")
- f.write("protocol=demo\r\n")
- f.write("# In case of audio, input the device number here, default is 0.\r\n")
- p = pyaudio.PyAudio()
- info = p.get_host_api_info_by_index(0)
- numdevices = info.get('deviceCount')
- #for each audio device, determine if is an input or an output and add it to the appropriate list and dictionary
- for i in range (0,numdevices):
- if p.get_device_info_by_host_api_device_index(0,i).get('maxInputChannels')>0:
- f.write("# %d - %s \r\n"%(i,p.get_device_info_by_host_api_device_index(0,i).get('name')))
- p.terminate()
- f.write("device=0\r\n")
- f.close()
- print "\tPlease open config.txt file using text editor and update configuration.\r\n"
- exit(1)
- except Exception as e:
- print "\tFailed to create configuration file\r\n\t",str(e)
- exit(1)
- else:
- # create and read configuration data
- cfg=config()
- cfg.readConfig()
- try:
- # create geiger communication object
- if cfg.protocol==config.MYGEIGER:
- print "Using myGeiger protocol for 1 => geiger 1\r\n"
- geigerCommunication=myGeiger(cfg)
- elif cfg.protocol==config.DEMO:
- print "Using Demo mode for 1 => geiger 1\r\n"
- geigerCommunication=Demo(cfg)
- elif cfg.protocol==config.GMC:
- print "Using GMC protocol for 1 => geiger 1\r\n"
- geigerCommunication=gmc(cfg)
- elif cfg.protocol==config.NETIO:
- print "Using NetIO protocol for 1 => geiger 1\r\n"
- geigerCommunication=netio(cfg)
- elif cfg.protocol==config.AUDIO:
- print "Using audio protocol for 1 => geiger 1\r\n"
- geigerCommunication = audioCommunication(cfg)
- else:
- print "Unknown protocol configured, can't run => geiger 1\r\n"
- sys.exit(1)
- # create web server communication object
- webService=webCommunication(cfg)
- # start measuring thread
- geigerCommunication.start()
- # Now send data to web site every 30 seconds
- while(geigerCommunication.is_running==1):
- sample=geigerCommunication.getResult()
- if sample[0]!=-1:
- # sample is valid, CPM !=-1
- print "Average result => geiger 1:\tCPM =",sample[0],"\t",str(sample[1]),"\r\n"
- #outputFile.write(str(sample[0]) + "," + str(sample[1]) + "\n")
- try:
- webService.sendSample(sample)
- except Exception as e:
- print "Error communicating server => geiger 1:\r\n\t", str(e),"\r\n"
- print "Waiting 30 seconds => geiger 1\r\n"
- # actually waiting 30x1 seconds, it's has better response when CTRL+C is used, maybe will be changed in future
- for i in range(0,30):
- time.sleep(1)
- else:
- print "No samples in queue, waiting 5 seconds => geiger 1\r\n"
- for i in range(0,5):
- time.sleep(1)
- except KeyboardInterrupt as e:
- print "\r\nCTRL+C pressed, exiting program\r\n\t", str(e), "\r\n"
- geigerCommunication.stop()
- except Exception as e:
- print "\r\nUnhandled error\r\n\t",str(e),"\r\n"
- geigerCommunication.stop()
- geigerCommunication.stop()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement