class pocket_geiger_DAQ(object):
    def __init__(self, maxdata, n_merge):
        self.time_queue=deque()
        self.n_merge=int(n_merge)
        self.count_list=[]
        self.time_list=[]
        self.maxdata=int(maxdata)
        self.count_queue=deque()
        self.count_error=deque()
        self.merge_test=False
        self.first_data = True
        self.last_time = None
        self.sensor = Sensor()
        print('N MERGE: {}'.format(n_merge) )
        
    def create_file(self):
    	import csv
        global results
        file_time= time.strftime("%Y-%m-%d_%H-%M-%S", time.gmtime())
        id_info = []
        with open ('/home/pi/config/server_config.csv') as f:
        	reader = csv.reader(f)
        	for row in reader:
        		id_info.append(row)
        filename = "/home/pi/data/"+"_".join(row)+"_geiger_counter"+file_time+".csv"
        f = open(filename, "ab+")
        results=csv.writer(open(filename, "ab+"), delimiter = ",")
        metadata = []
        metadata.append("Date and Time")
        metadata.append("Count Rate")
        #metadata.append("UV")
        results.writerow(metadata[:])

    def start(self):
        global results
        date_time = time.time()    

        try:
            count_cpm,count_err = self.sensor.get_cpm(date_time ,date_time+self.n_merge)
            self.merge_test=False
            self.add_data(self.count_queue,self.count_error,self.count_list, count_cpm)
            self.add_time(self.time_queue, self.time_list, date_time)

            if self.merge_test==True:
                self.count_list=[]
                #self.UV_list=[]
                self.time_list=[]
            if self.first_data and len(self.count_queue) != 0:
                for i in range(len(self.count_queue)):
                    data = []
                    data.append(self.time_queue[i])
                    data.append(self.count_queue[i])
                    data.append(self.count_error[i])
                    results.writerow(data)

                self.last_time = data[0]
                self.first_data = False
            elif not self.first_data:
                try:

                    if self.time_queue[-1] != self.last_time:
                        data = []
                        data.append(self.time_queue[-1])
                        data.append(self.count_queue[-1])
                        data.append(self.count_error[-1])
                        results.writerow(data)

                        self.last_time = self.time_queue[-1]

                except IndexError:
                    #print('No new data being written.')
                    pass
                        
        except Exception as e:
            print(e)
            #print("CO2 sensor error\n\n")
            pass


    def plot_pg(self):
        if len(self.time_queue)>0:
            self.update_plot(1,self.time_queue,self.count_queue,self.count_error,"Time","Count Rate (CPM)","Count Rate (CPM) vs. time")    

    

    def add_data(self, queue,queue_error, temp_list, data):
        temp_list.append(data)
        if len(temp_list)>=self.n_merge:
            queue.append(np.mean(np.asarray(temp_list)))
            if queue_error is not None:
                queue_error.append(np.std(np.asarray(temp_list)))
            temp_list = []
        if len(queue)>self.maxdata:
            queue.popleft()

    def update_plot(self,plot_id,xdata,ydata,yerr,xlabel,ylabel,title):
        plt.ion()
        fig = plt.figure(plot_id)
        plt.clf()
        #ax=fig.add_subplot(111)


        gs = GridSpec(6,1)
        ax1 = fig.add_subplot(gs[0,:])
        ax2 = fig.add_subplot(gs[1:5,:])


        ax1.set_axis_off()

        display = int(ydata[-1])
        dose = round(display*0.036,4)
        dose_display = str(dose) + " $\mu$Sv/hr"

        if display <= 150:
            ax1.text(0.1, 1.2,"Counts: "+ str(display), fontsize = 14 , ha = "center", backgroundcolor = "lightgreen")
            ax1.text(0.7, 1.2,"Dose: "+ dose_display, fontsize = 14, ha = "center", backgroundcolor = "lightgreen")

        elif display > 150 and display <= 500:
            ax1.text(0.1, 1.2,"Counts: "+str(display), fontsize = 14, ha = "center", backgroundcolor = "yellow")
            ax1.text(0.7, 1.2,"Dose: "+dose_display, fontsize = 14, ha = "center" , backgroundcolor = "yellow")


        elif display > 500 and display <= 2000:
            ax1.text(0.1, 1.2,"Counts: "+str(display), fontsize = 14, ha = "center", backgroundcolor = "orange")
            ax1.text(0.7, 1.2,"Dose: "+dose_display, fontsize = 14, ha = "center",  backgroundcolor = "orange")


        elif display > 2000:
            ax1.text(0.2, 1.2,"Counts: "+str(display), fontsize = 14, ha = "center" , backgroundcolor = "red")
            ax1.text(0.7, 1.2,"Dose: "+dose_display, fontsize = 14, ha = "center", backgroundcolor = "red")



        ax2.set(xlabel = xlabel, ylabel = ylabel, title = title)

        ax2.plot(xdata,ydata,"r.-")
        ax2.errorbar(xdata, ydata, yerr=yerr, fmt='o')
        #fig.autofmt_xdate()
        plt.setp(ax2.xaxis.get_majorticklabels(), rotation=45)
        fig.show()
        plt.pause(0.0005)    

    def add_time(self, queue, timelist, data):
        timelist.append(data)
        if len(timelist)>=self.n_merge:
            self.merge_test=True
            queue.append(timelist[int((self.n_merge)/2)])
        if len(queue)>self.maxdata:
            queue.popleft()    

    def close(self,plot_id):
         plt.close(plot_id)
class Manager(object):
    """
    Master object for dosimeter operation.

    Initializes other classes, tracks time intervals, and converts the counts
    from Sensor into a CPM to give to the server.

    time_interval is the interval (in seconds) over for which CPM is
    calculated.
    """

    # Note: keep the __init__() keywords identical to the keywords in argparse,
    #   in order to avoid unpacking them individually.
    # The None's are handled differently, depending on whether test mode.
    def __init__(self,
                 network_LED_pin=NETWORK_LED_PIN,
                 counts_LED_pin=COUNTS_LED_PIN,
                 signal_pin=SIGNAL_PIN,
                 noise_pin=NOISE_PIN,
                 sender_mode=DEFAULT_SENDER_MODE,
                 interval=None,
                 config=None,
                 publickey=None,
                 hostname=DEFAULT_HOSTNAME,
                 port=None,
                 verbosity=None,
                 log=False,
                 logfile=None,
                 datalog=None,
                 datalogflag=False,
                 protocol=DEFAULT_PROTOCOL,
                 test=None,
                 ):

        self.quit_after_interval = False

        self.protocol = protocol

        self.datalog = datalog
        self.datalogflag = datalogflag

        self.a_flag()
        self.d_flag()
        self.make_data_log(self.datalog)

        self.handle_input(log, logfile, verbosity,
                          test, interval, config, publickey)

        self.test = test

        # LEDs
        if RPI:
            self.network_LED = LED(network_LED_pin)
            self.counts_LED = LED(counts_LED_pin)
        else:
            self.network_LED = None
            self.counts_LED = None

        # other objects
        self.sensor = Sensor(
            counts_LED=self.counts_LED,
            verbosity=self.v,
            logfile=self.logfile)
        self.data_handler = Data_Handler(
            manager=self,
            verbosity=self.v,
            logfile=self.logfile,
            network_led=self.network_LED)
        self.sender = ServerSender(
            manager=self,
            mode=sender_mode,
            port=port,
            verbosity=self.v,
            logfile=self.logfile)

        self.init_log()
        # DEFAULT_UDP_PORT and DEFAULT_TCP_PORT are assigned in sender
        self.branch = ''

        self.data_handler.backlog_to_queue()

    def init_log(self):
        """
        Post log message to server regarding Manager startup.
        """

        # set working directory
        cwd = os.getcwd()
        os.chdir(GIT_DIRECTORY)

        branch = subprocess.check_output(
            ['git', 'rev-parse', '--abbrev-ref', 'HEAD']).rstrip()
        self.vprint(3, 'Found git branch: {}'.format(branch))
        commit = subprocess.check_output(
            ['git', 'rev-parse', '--short', 'HEAD']).rstrip()
        self.vprint(3, 'Found commit: {}'.format(commit))

        os.chdir(cwd)

        msg_code = BOOT_LOG_CODE
        msg_text = 'Booting on {} at {}'.format(branch, commit)
        self.vprint(1, 'Sending log message: [{}] {}'.format(
            msg_code, msg_text))
        try:
            self.sender.send_log(msg_code, msg_text)
        except (socket.gaierror, socket.error, socket.timeout):
            self.vprint(1, 'Failed to send log message, network error')
            if self.network_LED:
                self.network_LED.start_blink(
                    interval=NETWORK_LED_BLINK_PERIOD_S)
        else:
            self.vprint(2, 'Success sending log message')
            if self.network_LED:
                if self.network_LED.blinker:
                    self.network_LED.stop_blink()
                self.network_LED.on()

    def a_flag(self):
        """
        Checks if the -a from_argparse is called.

        If it is called, sets the path of the data-log to
        DEFAULT_DATALOG.
        """
        if self.datalogflag:
            self.datalog = DEFAULT_DATALOG

    def d_flag(self):
        """
        Checks if the -d from_argparse is called.

        If it is called, sets datalogflag to True.
        """
        if self.datalog:
            self.datalogflag = True

    def make_data_log(self, file):
        if self.datalogflag:
            with open(file, 'a') as f:
                pass

    def handle_input(self,
                     log, logfile, verbosity,
                     test, interval, config, publickey):

        # resolve logging defaults
        if log and logfile is None:
            # use default file if logging is enabled
            logfile = DEFAULT_LOGFILE
        if logfile and not log:
            # enable logging if logfile is specified
            #   (this overrides a log=False input which wouldn't make sense)
            log = True
        if log:
            self.logfile = logfile
        else:
            self.logfile = None

        # set up verbosity
        if verbosity is None:
            if test:
                verbosity = 2
            else:
                verbosity = 1
        self.v = verbosity
        set_verbosity(self, logfile=logfile)

        if log:
            self.vprint(1, '')
            self.vprint(1, 'Writing to logfile at {}'.format(self.logfile))
        self.test = test
        self.running = False

        # resolve defaults that depend on test mode
        if self.test:
            if interval is None:
                self.vprint(
                    2, "No interval given, using default for TEST MODE")
                interval = DEFAULT_INTERVAL_TEST

        else:
            if interval is None:
                self.vprint(
                    2, "No interval given, using default for NORMAL MODE")
                interval = DEFAULT_INTERVAL_NORMAL
            if config is None:
                self.vprint(2, "No config file given, " +
                            "attempting to use default config path")
                config = DEFAULT_CONFIG
            if publickey is None:
                self.vprint(2, "No publickey file given, " +
                            "attempting to use default publickey path")
                publickey = DEFAULT_PUBLICKEY

        self.interval = interval

        if self.datalogflag:
            self.vprint(
                1, 'Writing CPM to data log at {}'.format(self.datalog))

        if config:
            try:
                self.config = Config(config,
                                     verbosity=self.v, logfile=self.logfile)
            except IOError:
                raise IOError(
                    'Unable to open config file {}!'.format(config))
        else:
            self.vprint(
                1, 'WARNING: no config file given. Not posting to server')
            self.config = None

        if publickey:
            try:
                self.publickey = PublicKey(
                    publickey, verbosity=self.v, logfile=self.logfile)
            except IOError:
                raise IOError(
                    'Unable to load publickey file {}!'.format(publickey))
        else:
            self.vprint(
                1, 'WARNING: no public key given. Not posting to server')
            self.publickey = None

        self.aes = None     # gets checked in sender. feature in manager_d3s

    def run(self):
        """
        Start counting time.

        This method does NOT return, so run in a subprocess if you
        want to keep control.

        However, setting self.running = False will stop, as will a
          KeyboardInterrupt.
        """

        this_start, this_end = self.get_interval(time.time())
        self.vprint(
            1, ('Manager is starting to run at {}' +
                ' with intervals of {}s').format(
                datetime_from_epoch(this_start), self.interval))
        self.running = True

        try:
            while self.running:
                self.vprint(3, 'Sleeping at {} until {}'.format(
                    datetime_from_epoch(time.time()),
                    datetime_from_epoch(this_end)))
                try:
                    self.sleep_until(this_end)
                except SleepError:
                    self.vprint(1, 'SleepError: system clock skipped ahead!')
                    # the previous start/end times are meaningless.
                    # There are a couple ways this could be handled.
                    # 1. keep the same start time, but go until time.time()
                    #    - but if there was actually an execution delay,
                    #      the CPM will be too high.
                    # 2. set start time to be time.time() - interval,
                    #    and end time is time.time().
                    #    - but if the system clock was adjusted halfway through
                    #      the interval, the CPM will be too low.
                    # The second one is more acceptable.
                    self.vprint(
                        3, 'former this_start = {}, this_end = {}'.format(
                            datetime_from_epoch(this_start),
                            datetime_from_epoch(this_end)))
                    this_start, this_end = self.get_interval(
                        time.time() - self.interval)

                self.handle_cpm(this_start, this_end)
                if self.quit_after_interval:
                    self.vprint(1, 'Reboot: taking down Manager')
                    self.stop()
                    self.takedown()
                    os.system('sudo {0} {1}'.format(
                        REBOOT_SCRIPT, self.branch))
                this_start, this_end = self.get_interval(this_end)
        except KeyboardInterrupt:
            self.vprint(1, '\nKeyboardInterrupt: stopping Manager run')
            self.stop()
            self.takedown()
        except SystemExit:
            self.vprint(1, '\nSystemExit: taking down Manager')
            self.stop()
            self.takedown()

    def stop(self):
        """Stop counting time."""
        self.running = False

    def sleep_until(self, end_time, retry=True):
        """
        Sleep until the given timestamp.

        Input:

          end_time: number of seconds since epoch, e.g. time.time()
        """

        catching_up_flag = False
        sleeptime = end_time - time.time()
        self.vprint(3, 'Sleeping for {} seconds'.format(sleeptime))
        if sleeptime < 0:
            # can happen if flushing queue to server takes longer than interval
            sleeptime = 0
            catching_up_flag = True
        time.sleep(sleeptime)
        if self.quit_after_interval and retry:
            # SIGQUIT signal somehow interrupts time.sleep
            # which makes the retry argument needed
            self.sleep_until(end_time, retry=False)
        now = time.time()
        self.vprint(
            2, 'sleep_until offset is {} seconds'.format(now - end_time))
        # normally this offset is < 0.1 s
        # although a reboot normally produces an offset of 9.5 s
        #   on the first cycle
        if not catching_up_flag and (now - end_time > 10 or now < end_time):
            # raspberry pi clock reset during this interval
            # normally the first half of the condition triggers it.
            raise SleepError

    def get_interval(self, start_time):
        """
        Return start and end time for interval, based on given start_time.
        """
        end_time = start_time + self.interval
        return start_time, end_time

    def data_log(self, file, **kwargs):
        """
        Writes cpm to data-log.
        """
        time_string = time.strftime("%Y-%m-%d %H:%M:%S")
        cpm, cpm_err = kwargs.get('cpm'), kwargs.get('cpm_err')
        if self.datalogflag:
            with open(file, 'a') as f:
                f.write('{0}, {1}, {2}'.format(time_string, cpm, cpm_err))
                f.write('\n')
                self.vprint(2, 'Writing CPM to data log at {}'.format(file))

    def handle_cpm(self, this_start, this_end):
        """
        Get CPM from sensor, display text, send to server.
        """
        cpm, cpm_err = self.sensor.get_cpm(this_start, this_end)
        counts = int(round(cpm * self.interval / 60))
        self.data_handler.main(
            self.datalog, cpm, cpm_err, this_start, this_end, counts)

    def takedown(self):
        """Delete self and child objects and clean up GPIO nicely."""

        # sensor
        self.sensor.cleanup()
        del(self.sensor)

        try:
            GPIO.cleanup()
        except NameError:
            # not on a Raspberry Pi so no GPIO
            pass

        # send the rest of the queue object to DEFAULT_DATA_BACKLOG_FILE upon
        #   shutdown
        self.data_handler.send_all_to_backlog()

        # self. can I even do this?
        del(self)

    @classmethod
    def from_argparse(cls):
        """
        Initialize a Manager instance using arguments from the command line.

        For usage:
        python manager.py -h
        """

        # Note: keep the keywords identical to the keywords in __init__(),
        #   to avoid individual handling of arguments.
        # The arguments with default=None depend on test state.
        #   They are handled in __init__()
        # Also, LED pin numbers could be added here if you want.

        parser = argparse.ArgumentParser(
            description="Manager for the DoseNet radiation detector")
        # test mode
        parser.add_argument(
            '--test', '-t', action='store_true', default=False,
            help='Start in test mode (no config, 30s intervals)')
        # interval: default depends on whether test mode is enabled
        parser.add_argument(
            '--interval', '-i', type=int, default=None,
            help=('Interval of CPM measurement, in seconds' +
                  ' (default 300 for normal mode)'))
        # verbosity
        parser.add_argument(
            '--verbosity', '-v', type=int, default=None,
            help='Verbosity level (0 to 3) (default 1)')
        parser.add_argument(
            '--log', '-l', action='store_true', default=False,
            help='Enable file logging of all verbose text (default off)')
        parser.add_argument(
            '--logfile', '-f', type=str, default=None,
            help='Specify file for logging (default {})'.format(
                DEFAULT_LOGFILE))
        # config file and public key
        parser.add_argument(
            '--config', '-c', default=None,
            help='Specify a config file (default {})'.format(DEFAULT_CONFIG))
        parser.add_argument(
            '--publickey', '-k', default=None,
            help='Specify a publickey file (default {})'.format(
                DEFAULT_PUBLICKEY))
        # server address and port
        parser.add_argument(
            '--hostname', '-s', default=DEFAULT_HOSTNAME,
            help='Specify a server hostname (default {})'.format(
                DEFAULT_HOSTNAME))
        parser.add_argument(
            '--port', '-p', type=int, default=None,
            help='Specify a port for the server ' +
            '(default {} for UDP, {} for TCP)'.format(
                DEFAULT_UDP_PORT, DEFAULT_TCP_PORT))
        parser.add_argument(
            '--sender-mode', '-m', type=str, default=DEFAULT_SENDER_MODE,
            choices=['udp', 'tcp', 'UDP', 'TCP'],
            help='The network protocol used in sending data ' +
            '(default {})'.format(DEFAULT_SENDER_MODE))

        # datalog
        parser.add_argument(
            '--datalog', '-d', default=None,
            help='Specify a path for the datalog (default {})'.format(
                DEFAULT_DATALOG))
        parser.add_argument(
            '--datalogflag', '-a', action='store_true', default=False,
            help='Enable logging local data (default off)')

        # communication protocal
        parser.add_argument(
            '--protocol', '-r', default=DEFAULT_PROTOCOL,
            help='Specify what communication protocol is to be used ' +
            '(default {})'.format(DEFAULT_PROTOCOL))

        args = parser.parse_args()
        arg_dict = vars(args)
        mgr = Manager(**arg_dict)

        return mgr
Beispiel #3
0
class Manager(object):
    """
    Master object for dosimeter operation.

    Initializes other classes, tracks time intervals, and converts the counts
    from Sensor into a CPM to give to the server.

    time_interval is the interval (in seconds) over for which CPM is
    calculated.
    """

    # Note: keep the __init__() keywords identical to the keywords in argparse,
    #   in order to avoid unpacking them individually.
    # The None's are handled differently, depending on whether test mode.
    def __init__(self,
                 network_LED_pin=NETWORK_LED_PIN,
                 power_LED_pin=POWER_LED_PIN,
                 counts_LED_pin=COUNTS_LED_PIN,
                 signal_pin=SIGNAL_PIN,
                 noise_pin=NOISE_PIN,
                 sender_mode=DEFAULT_SENDER_MODE,
                 interval=None,
                 config=None,
                 publickey=None,
                 hostname=DEFAULT_HOSTNAME,
                 port=None,
                 verbosity=None,
                 log=False,
                 logfile=None,
                 datalog=None,
                 datalogflag=False,
                 protocol=DEFAULT_PROTOCOL,
                 test=None,
                 ):

        self.quit_after_interval = False

        self.protocol = protocol

        self.datalog = datalog
        self.datalogflag = datalogflag

        self.a_flag()
        self.d_flag()
        self.make_data_log(self.datalog)

        self.handle_input(log, logfile, verbosity,
                          test, interval, config, publickey)

        self.test = test

        # LEDs
        if RPI:
            self.power_LED = LED(power_LED_pin)
            self.network_LED = LED(network_LED_pin)
            self.counts_LED = LED(counts_LED_pin)

            self.power_LED.on()
        else:
            self.power_LED = None
            self.network_LED = None
            self.counts_LED = None

        # other objects
        self.sensor = Sensor(
            counts_LED=self.counts_LED,
            verbosity=self.v,
            logfile=self.logfile)
        self.data_handler = Data_Handler(
            manager=self,
            verbosity=self.v,
            logfile=self.logfile,
            network_led=self.network_LED)
        self.sender = ServerSender(
            manager=self,
            mode=sender_mode,
            port=port,
            verbosity=self.v,
            logfile=self.logfile)

        self.init_log()
        # DEFAULT_UDP_PORT and DEFAULT_TCP_PORT are assigned in sender
        self.branch = ''

        self.data_handler.backlog_to_queue()

    def init_log(self):
        """
        Post log message to server regarding Manager startup.
        """

        # set working directory
        cwd = os.getcwd()
        os.chdir(GIT_DIRECTORY)

        branch = subprocess.check_output(
            ['git', 'rev-parse', '--abbrev-ref', 'HEAD']).rstrip()
        self.vprint(3, 'Found git branch: {}'.format(branch))
        commit = subprocess.check_output(
            ['git', 'rev-parse', '--short', 'HEAD']).rstrip()
        self.vprint(3, 'Found commit: {}'.format(commit))

        os.chdir(cwd)

        msg_code = BOOT_LOG_CODE
        msg_text = 'Booting on {} at {}'.format(branch, commit)
        self.vprint(1, 'Sending log message: [{}] {}'.format(
            msg_code, msg_text))
        try:
            self.sender.send_log(msg_code, msg_text)
        except (socket.gaierror, socket.error, socket.timeout):
            self.vprint(1, 'Failed to send log message, network error')
            if self.network_LED:
                self.network_LED.start_blink(
                    interval=NETWORK_LED_BLINK_PERIOD_S)
        else:
            self.vprint(2, 'Success sending log message')
            if self.network_LED:
                if self.network_LED.blinker:
                    self.network_LED.stop_blink()
                self.network_LED.on()

    def a_flag(self):
        """
        Checks if the -a from_argparse is called.

        If it is called, sets the path of the data-log to
        DEFAULT_DATALOG.
        """
        if self.datalogflag:
            self.datalog = DEFAULT_DATALOG

    def d_flag(self):
        """
        Checks if the -d from_argparse is called.

        If it is called, sets datalogflag to True.
        """
        if self.datalog:
            self.datalogflag = True

    def make_data_log(self, file):
        if self.datalogflag:
            with open(file, 'a') as f:
                pass

    def handle_input(self,
                     log, logfile, verbosity,
                     test, interval, config, publickey):

        # resolve logging defaults
        if log and logfile is None:
            # use default file if logging is enabled
            logfile = DEFAULT_LOGFILE
        if logfile and not log:
            # enable logging if logfile is specified
            #   (this overrides a log=False input which wouldn't make sense)
            log = True
        if log:
            self.logfile = logfile
        else:
            self.logfile = None

        # set up verbosity
        if verbosity is None:
            if test:
                verbosity = 2
            else:
                verbosity = 1
        self.v = verbosity
        set_verbosity(self, logfile=logfile)

        if log:
            self.vprint(1, '')
            self.vprint(1, 'Writing to logfile at {}'.format(self.logfile))
        self.test = test
        self.running = False

        # resolve defaults that depend on test mode
        if self.test:
            if interval is None:
                self.vprint(
                    2, "No interval given, using default for TEST MODE")
                interval = DEFAULT_INTERVAL_TEST

        else:
            if interval is None:
                self.vprint(
                    2, "No interval given, using default for NORMAL MODE")
                interval = DEFAULT_INTERVAL_NORMAL
            if config is None:
                self.vprint(2, "No config file given, " +
                            "attempting to use default config path")
                config = DEFAULT_CONFIG
            if publickey is None:
                self.vprint(2, "No publickey file given, " +
                            "attempting to use default publickey path")
                publickey = DEFAULT_PUBLICKEY

        self.interval = interval

        if self.datalogflag:
            self.vprint(
                1, 'Writing CPM to data log at {}'.format(self.datalog))

        if config:
            try:
                self.config = Config(config,
                                     verbosity=self.v, logfile=self.logfile)
            except IOError:
                raise IOError(
                    'Unable to open config file {}!'.format(config))
        else:
            self.vprint(
                1, 'WARNING: no config file given. Not posting to server')
            self.config = None

        if publickey:
            try:
                self.publickey = PublicKey(
                    publickey, verbosity=self.v, logfile=self.logfile)
            except IOError:
                raise IOError(
                    'Unable to load publickey file {}!'.format(publickey))
        else:
            self.vprint(
                1, 'WARNING: no public key given. Not posting to server')
            self.publickey = None

        self.aes = None     # gets checked in sender. feature in manager_d3s

    def run(self):
        """
        Start counting time.

        This method does NOT return, so run in a subprocess if you
        want to keep control.

        However, setting self.running = False will stop, as will a
          KeyboardInterrupt.
        """

        this_start, this_end = self.get_interval(time.time())
        self.vprint(
            1, ('Manager is starting to run at {}' +
                ' with intervals of {}s').format(
                datetime_from_epoch(this_start), self.interval))
        self.running = True

        try:
            while self.running:
                self.vprint(3, 'Sleeping at {} until {}'.format(
                    datetime_from_epoch(time.time()),
                    datetime_from_epoch(this_end)))
                try:
                    self.sleep_until(this_end)
                except SleepError:
                    self.vprint(1, 'SleepError: system clock skipped ahead!')
                    # the previous start/end times are meaningless.
                    # There are a couple ways this could be handled.
                    # 1. keep the same start time, but go until time.time()
                    #    - but if there was actually an execution delay,
                    #      the CPM will be too high.
                    # 2. set start time to be time.time() - interval,
                    #    and end time is time.time().
                    #    - but if the system clock was adjusted halfway through
                    #      the interval, the CPM will be too low.
                    # The second one is more acceptable.
                    self.vprint(
                        3, 'former this_start = {}, this_end = {}'.format(
                            datetime_from_epoch(this_start),
                            datetime_from_epoch(this_end)))
                    this_start, this_end = self.get_interval(
                        time.time() - self.interval)

                self.handle_cpm(this_start, this_end)
                if self.quit_after_interval:
                    self.vprint(1, 'Reboot: taking down Manager')
                    self.stop()
                    self.takedown()
                    os.system('sudo {0} {1}'.format(
                        REBOOT_SCRIPT, self.branch))
                this_start, this_end = self.get_interval(this_end)
        except KeyboardInterrupt:
            self.vprint(1, '\nKeyboardInterrupt: stopping Manager run')
            self.stop()
            self.takedown()
        except SystemExit:
            self.vprint(1, '\nSystemExit: taking down Manager')
            self.stop()
            self.takedown()

    def stop(self):
        """Stop counting time."""
        self.running = False

    def sleep_until(self, end_time, retry=True):
        """
        Sleep until the given timestamp.

        Input:
          end_time: number of seconds since epoch, e.g. time.time()
        """

        catching_up_flag = False
        sleeptime = end_time - time.time()
        self.vprint(3, 'Sleeping for {} seconds'.format(sleeptime))
        if sleeptime < 0:
            # can happen if flushing queue to server takes longer than interval
            sleeptime = 0
            catching_up_flag = True
        time.sleep(sleeptime)
        if self.quit_after_interval and retry:
            # SIGQUIT signal somehow interrupts time.sleep
            # which makes the retry argument needed
            self.sleep_until(end_time, retry=False)
        now = time.time()
        self.vprint(
            2, 'sleep_until offset is {} seconds'.format(now - end_time))
        # normally this offset is < 0.1 s
        # although a reboot normally produces an offset of 9.5 s
        #   on the first cycle
        if not catching_up_flag and (now - end_time > 10 or now < end_time):
            # raspberry pi clock reset during this interval
            # normally the first half of the condition triggers it.
            raise SleepError

    def get_interval(self, start_time):
        """
        Return start and end time for interval, based on given start_time.
        """
        end_time = start_time + self.interval
        return start_time, end_time

    def data_log(self, file, cpm, cpm_err):
        """
        Writes cpm to data-log.
        """
        time_string = time.strftime("%Y-%m-%d %H:%M:%S")
        if self.datalogflag:
            with open(file, 'a') as f:
                f.write('{0}, {1}, {2}'.format(time_string, cpm, cpm_err))
                f.write('\n')
                self.vprint(2, 'Writing CPM to data log at {}'.format(file))

    def handle_cpm(self, this_start, this_end):
        """
        Get CPM from sensor, display text, send to server.
        """
        cpm, cpm_err = self.sensor.get_cpm(this_start, this_end)
        counts = int(round(cpm * self.interval / 60))
        self.data_handler.main(
            self.datalog, cpm, cpm_err, this_start, this_end, counts)

    def takedown(self):
        """Delete self and child objects and clean up GPIO nicely."""

        # sensor
        self.sensor.cleanup()
        del(self.sensor)

        # power LED
        try:
            self.power_LED.off()
        except AttributeError:
            # no LED
            pass
        try:
            GPIO.cleanup()
        except NameError:
            # not on a Raspberry Pi so no GPIO
            pass

        # send the rest of the queue object to DEFAULT_DATA_BACKLOG_FILE upon
        #   shutdown
        self.data_handler.send_all_to_backlog()

        # self. can I even do this?
        del(self)

    @classmethod
    def from_argparse(cls):
        """
        Initialize a Manager instance using arguments from the command line.

        For usage:
        python manager.py -h
        """

        # Note: keep the keywords identical to the keywords in __init__(),
        #   to avoid individual handling of arguments.
        # The arguments with default=None depend on test state.
        #   They are handled in __init__()
        # Also, LED pin numbers could be added here if you want.

        parser = argparse.ArgumentParser(
            description="Manager for the DoseNet radiation detector")
        # test mode
        parser.add_argument(
            '--test', '-t', action='store_true', default=False,
            help='Start in test mode (no config, 30s intervals)')
        # interval: default depends on whether test mode is enabled
        parser.add_argument(
            '--interval', '-i', type=int, default=None,
            help=('Interval of CPM measurement, in seconds' +
                  ' (default 300 for normal mode)'))
        # verbosity
        parser.add_argument(
            '--verbosity', '-v', type=int, default=None,
            help='Verbosity level (0 to 3) (default 1)')
        parser.add_argument(
            '--log', '-l', action='store_true', default=False,
            help='Enable file logging of all verbose text (default off)')
        parser.add_argument(
            '--logfile', '-f', type=str, default=None,
            help='Specify file for logging (default {})'.format(
                DEFAULT_LOGFILE))
        # config file and public key
        parser.add_argument(
            '--config', '-c', default=None,
            help='Specify a config file (default {})'.format(DEFAULT_CONFIG))
        parser.add_argument(
            '--publickey', '-k', default=None,
            help='Specify a publickey file (default {})'.format(
                DEFAULT_PUBLICKEY))
        # server address and port
        parser.add_argument(
            '--hostname', '-s', default=DEFAULT_HOSTNAME,
            help='Specify a server hostname (default {})'.format(
                DEFAULT_HOSTNAME))
        parser.add_argument(
            '--port', '-p', type=int, default=None,
            help='Specify a port for the server ' +
            '(default {} for UDP, {} for TCP)'.format(
                DEFAULT_UDP_PORT, DEFAULT_TCP_PORT))
        parser.add_argument(
            '--sender-mode', '-m', type=str, default=DEFAULT_SENDER_MODE,
            choices=['udp', 'tcp', 'UDP', 'TCP'],
            help='The network protocol used in sending data ' +
            '(default {})'.format(DEFAULT_SENDER_MODE))

        # datalog
        parser.add_argument(
            '--datalog', '-d', default=None,
            help='Specify a path for the datalog (default {})'.format(
                DEFAULT_DATALOG))
        parser.add_argument(
            '--datalogflag', '-a', action='store_true', default=False,
            help='Enable logging local data (default off)')

        # communication protocal
        parser.add_argument(
            '--protocol', '-r', default=DEFAULT_PROTOCOL,
            help='Specify what communication protocol is to be used ' +
            '(default {})'.format(DEFAULT_PROTOCOL))

        args = parser.parse_args()
        arg_dict = vars(args)
        mgr = Manager(**arg_dict)

        return mgr