Пример #1
0
 def __init__(self, configpath, datapath, context):
     self.configpath = configpath
     self.datapath = datapath
     self.config = ConfigParser.SafeConfigParser()
     
     self.disp = DisplayController()
     
     #paths are absolute
     if not isfile(self.configpath):
         raise SensorConfigException(self.configpath, 'File Not Found')
     if not isdir(self.datapath):
         raise SensorConfigException(self.datapath, 'Directory Not Found')
     try:
         self.config.readfp(open(join(context['config'],'sensor.default.cfg')))
     except (ConfigParser.Error, IOError, ValueError, TypeError):
         logger.exception("Unable to load default sensor configuration")
     try:
         self.config.read(configpath)
     except (ConfigParser.Error, IOError):
         logger.exception("Unable to sensor config from %s" % self.configpath)
     if not self.config.has_option('DEFAULT', 'name'):
         logger.warn("Config file has no name for sensor, defaulting to filename")
         self.config.set('DEFAULT', 'name', splitext(split(self.configpath)[1])[0])
     logger.info("Configuration for sensor %s loaded" % self.config.get('DEFAULT', 'name'))
     return
Пример #2
0
 def __init__(self, cfg):
     config = ConfigParser()
     try:
         config.read(cfg)
     except:
         self.logger.exception("Unable to read config file %s." % cfg)
     
     # try to open the databases
     self.logger.info("Opening databases.")
     try:
         DBSetup.check_databases()
     except (sqlite3.Error, IOError, OSError):
         self.logger.exception("Databases could not be opened.")
     
     self.disp = DisplayController()
     try:
         ## removes all modules, shutsdown hub
         Beagleboard.goto_known_state()
     except (OSError, IOError):
         self.logger.exception("Unable to reset board to default setting.")
Пример #3
0
class Harvester( object ):
    """Harvest data from a modbus device"""
    def __init__(self, configpath, datapath, context):
        self.configpath = configpath
        self.datapath = datapath
        self.config = ConfigParser.SafeConfigParser()
        
        self.disp = DisplayController()
        
        #paths are absolute
        if not isfile(self.configpath):
            raise SensorConfigException(self.configpath, 'File Not Found')
        if not isdir(self.datapath):
            raise SensorConfigException(self.datapath, 'Directory Not Found')
        try:
            self.config.readfp(open(join(context['config'],'sensor.default.cfg')))
        except (ConfigParser.Error, IOError, ValueError, TypeError):
            logger.exception("Unable to load default sensor configuration")
        try:
            self.config.read(configpath)
        except (ConfigParser.Error, IOError):
            logger.exception("Unable to sensor config from %s" % self.configpath)
        if not self.config.has_option('DEFAULT', 'name'):
            logger.warn("Config file has no name for sensor, defaulting to filename")
            self.config.set('DEFAULT', 'name', splitext(split(self.configpath)[1])[0])
        logger.info("Configuration for sensor %s loaded" % self.config.get('DEFAULT', 'name'))
        return
    
    def _open_binaryfile(self, sect):
        binpath = join(self.datapath, self.config.get('DEFAULT', 'name'))
        binpath += '-' + sect.lower() + '.data'
        logger.info("Opening binary file %s for append." % binpath)
        try:
            fh = open(binpath, 'ab')
        except (IOError, OSError):
            logger.exception("Unable to open bin file.")
            fh.close()
            
        return (fh, binpath)
    
    def _append_binaryfile_data(self, sect, bindata):
        fh, binpath = self._open_binaryfile(sect)
        fh.write(bindata)
        fh.close()
        return binpath
    
    def _open_datafile(self):
        self.filepath = join(self.datapath, self.config.get('DEFAULT', 'name'))
        self.filepath += '.csv'
        logger.info("Opening data file %s for append." % self.filepath)
        if exists(self.filepath):
            self.newfile = False
        else:
            self.newfile = True
            try:
                tmpfh = open(self.filepath, 'w')
                tmpfh.write("# Data file %s\r\n" % self.filepath)
                tmpfh.write("# Created on %s by config %s\r\n" % (datetime.now().strftime("%d%b%Y-%H%M%S"), self.configpath))
            except (IOError, OSError):
                logger.exception("Could not header comments to datafile.")
            finally:
                tmpfh.close()
        try:
            fh = open(self.filepath, 'ab')
        except (IOError, OSError):
            logger.exception("Unable to open datafile for append. (%s)." % self.filepath)
        if not fh:
            (fh, path) = tempfile.mkstemp()
            logger.critical("No datafile, creating temp %s" % path)
            self.filepath = path
        self.fh = fh
        return fh
    
    def _close_datafile(self, filehandle):
        filehandle.close()
    
    def _csv_get_columns(self):
        fields = ['date']
        for section in self.config.sections():
            if self.config.has_option(section, 'map_col'):
                if self.config.getboolean(section,'map_col'):
                    if self.config.has_option(section, 'col_name'):
                        fields.append(self.config.get(section, 'col_name'))
                    else:
                        fields.append(str(section))
                    logger.debug('Mapping config section to column %s' % section)
        logger.info('Mapped %d columns.' % len(fields))
        return fields
    def _get_sampling_data(self, sect):
        # sampling interval
        if self.config.has_option(sect, 'samp_interval'):
            try:
                samp_i = self.config.getfloat(sect, 'samp_interval')
            except (ConfigParser.Error, TypeError):
                samp_i = 2e-5
                logger.exception("samp_interval has bad value.")
        else:
            samp_i = 2e-5
        
        # sampling count
        if self.config.has_option(sect, 'samp_count'):
            try:
                samp_c = self.config.getint(sect, 'samp_count')
            except (ConfigParser.Error, TypeError):
                samp_c = 20
                logger.exception("samp_count has bad value.")
        else:
            samp_c = 20
            
        return (samp_c, samp_i)
        
    def _process_config(self):
        sects = self.config.sections()
        sects.sort()
        datarow = {'date': datetime.utcnow().strftime("%d-%m-%YT%H-%M-%S")}
        files = []
        
        if self.config.has_option('DEFAULT', 'port_num'):
            try:
                port_num = self.config.getint('DEFAULT', 'port_num')
            except ConfigParser.Error:
                port_num = 0
        else:
            port_num = 3
        
        if port_num > 3:
            port_num = 0
        
        logger.info("Powering datalogger port %d." % port_num)
        self.disp.switch_port_power(port_num, True)
        time.sleep(0.2)
        
        for sect in sects:
            logger.info("Entering config section %s." % sect)
            if self.config.has_option(sect, 'col_name'):
                key = self.config.get(sect, 'col_name')
            else:
                key = str(sect)
            
            output_value = self._process_config_section_mb(sect)
            if self.config.has_option(sect, 'operation'):
                op = self.config.get(sect, 'operation')
            else:
                op = 'str'
            
            samp_c, samp_i = self._get_sampling_data(sect)
            
            if output_value:
                if op == 'rms':
                    output_value = str(Processor.get_rms(output_value))
                elif op == 'avg':
                    output_value = str(Processor.get_avg(output_value))
                elif op == 'binary':
                    output_value = str(Processor.encode_binary(datetime.utcnow(), output_value, samp_c))
                    files.append(self._append_binaryfile_data(sect, output_value))
                elif op == 'single':
                    output_value = Processor.single_value(output_value)
                else:
                    output_value = str(output_value)
            else:
                output_value = '0'
            
            try:
                datarow[key] = output_value
            except (KeyError, ValueError, TypeError):
                logger.exception("Could not process config section %s." % sect)
            if datarow[key] == False:
                logger.warn("Error potentially occured in getting data for %s." % sect)
            logger.info("Leaving config section %s." % sect)
            
            # sleep if the operation told us to wait
            if self.config.has_option(sect, 'wait'):
                try:
                    wait = self.config.getfloat(sect, 'wait')
                except ConfigParser.Error:
                    wait = 5.0
                logger.info("Sleeping for %.2f seconds." % wait)
                time.sleep(wait)
        
        logger.info("Removing power from datalogger port %d.", port_num)
        self.disp.switch_port_power(port_num, False)
        
        logger.info("Making data entry in csv file.")
        try:
            fh = self._open_datafile()
            cols = self._csv_get_columns()
            logger.debug("Columns: %s." % str(cols))
            logger.debug("Data: %s." % str(datarow))
            csvw = csv.DictWriter(fh, cols, extrasaction='ignore')
            if self.newfile:
                header = {}
                for col in cols:
                    header[col] = col
                csvw.writerow(header)
            csvw.writerow(datarow)
        except:
            logger.exception("Could not write data row to csv file.")
        finally:
            fh.close()
        files.append(self.filepath)
        return files
    
    def harvest(self):
        try:
            return self._process_config()
        except:
            logger.exception("Unable to process %s." % self.configpath)
            return False
        
    def _process_config_section_mb(self, section):
        # use the config section to open a modbus handle
        logger.info("Processing config section %s." % str(section))
        try:
            portname = self.config.get(section,'serialport')
            logger.warn("%s" % portname)
            time.sleep(20)
            speed = self.config.getint(section, 'serialbaud')
            if self.config.has_option(section, 'serialtimeout'):
                timeout = self.config.getint(section, 'serialtimeout')
            else:
                timeout = 5.0
        except (IOError, ConfigParser.Error):
            logger.warning("Error config file %s, section %s." % (self.configpath, section))
            # fallback on defaults
            portname = '/dev/ttyS1'
            speed = 9600
            timeout = 5.0
        
        self.disp.control_led('mbfrm', True)
        self.disp.control_led('mberr', False)
        
        try:
            ser = serial.Serial(portname, speed, timeout=timeout)
        except (OSError, IOError, serial.SerialException):
            logger.exception("Unable to open serial port %s@%d." % (portname, speed))
            ser.close()
            self.disp.control_led('mbfrm', False)
            self.disp.control_led('mberr', True)
            return False
        
        logger.debug("Took control of serial port %s, speed %d, timeout %d" % (portname, speed, timeout))
        
        # load modbus specific information
        try:
            # function code : 0x03 READ HOLDING, 0x04 READ INPUT, 0x06 WRIE SINGLE
            mb_func = int(self.config.get(section, 'mb_func'), 0)
            mb_start = int(self.config.get(section, 'mb_start'), 0)
            mb_address = int(self.config.get(section, 'mb_addr'), 0)
            # if a read is specified, we add a count
            if self.config.has_option(section, 'mb_count'):
                mb_count = int(self.config.get(section, 'mb_count'), 0)
            else:
                mb_count = 0
            # if a write is specified, we append output_value
            if self.config.has_option(section, 'mb_write'):
                mb_write = int(self.config.get(section, 'mb_write'), 0)
            else:
                mb_write = 0
        except ConfigParser.Error:
            logger.exception("Unable to get data for modbus transaction.")
            ser.close()
            self.disp.control_led('mbfrm', False)
            self.disp.control_led('mberr', True)
            return False
        
        # Create the Modbus Master
        master = modbus_rtu.RtuMaster(ser)
        #master.set_verbose(True)
        master.set_timeout(timeout)
        logger.debug("Function to execute: a:0x%02x, f:0x%02x, s:0x%04x, c:0x%04x, w:0x%04x." % (mb_address, mb_func, mb_start, mb_count, mb_write))
        logger.info("Running modbus function 0x%02x on device 0x%02x" % (mb_func, mb_address))
        try:
            if ((mb_func == cst.READ_HOLDING_REGISTERS) or (mb_func == cst.READ_INPUT_REGISTERS)):
                data = master.execute(mb_address, mb_func, mb_start, mb_count)
                logger.debug('Read Op: return data for 0x%02x fn(0x%02x) is %s.' % (mb_address, mb_func, hexbyte.Int16ToHex(list(data))))
            elif mb_func == cst.WRITE_SINGLE_REGISTER:
                data = master.execute(mb_address, mb_func, mb_start, output_value=mb_write)
                logger.debug('Writing data to 0x%02x: [0x%04x] 0x%02x.' %  (mb_address, mb_start, mb_write))
            else:
                logger.error('Unrecognised function code requested.')
        except (serial.SerialException, ModbusFunctionNotSupportedError,
                ModbusInvalidResponseError, ModbusNotConnectedError,
                ModbusError):
            # exception handler
            self.disp.control_led('mbfrm', False)
            self.disp.control_led('mberr', True)
            logger.exception('Func(0x%02x) on 0x%02x failed.' % (mb_func, mb_address))
            data = False
        time.sleep(0.5)
        ser.close()
        self.disp.control_led('mbfrm', False)
        
        return data
        
class DataLogger(object):
    datadir = '/data'
    logger = logging.getLogger('eko.DataLogger')
    def __init__(self, cfg):
        config = ConfigParser()
        try:
            config.read(cfg)
        except:
            self.logger.exception("Unable to read config file %s." % cfg)
        
        # try to open the databases
        self.logger.info("Opening databases.")
        try:
            DBSetup.check_databases()
        except (sqlite3.Error, IOError, OSError):
            self.logger.exception("Databases could not be opened.")
        
        self.disp = DisplayController()
        try:
            ## removes all modules, shutsdown hub
            #Beagleboard.goto_known_state()
            pass
        except (OSError, IOError):
            self.logger.exception("Unable to reset board to default setting.")
    
    def _try_network(self):
        # try ping first
        #try:
        #    self.logger.info("Trying ping for google.com.")
        #    x = ping.do_one('www.google.com', 10000, 1, 8)
        #except socket.error:
        #    self.logger.exception("Ping failed!")
        #    x = None
        google_req = urllib2.Request('http://www.google.com/index.html')
        try:
            self.logger.info("Trying url fetch on google.com")
            x = urllib2.urlopen(google_req, timeout=60)
        except urllib2.URLError:
            self.logger.exception("URL Error, unable to reach google.com")
            x = None
        return x
    
    def ready_internet(self):
        self.logger.info("Attempting to connect to the internet.")
        ## open a pppd instance
        OSTools.pppd_launch()
        
        ## wait for pppd to settle
        time.sleep(5)
        
        retrycount = 5
        while retrycount > 0:
            ## to see if ppp is up
            if OSTools.pppd_status():
                ## ppp is up, try ping
                x = self._try_network()
                if x is not None:
                    self.disp.control_led('net', True)
                    self.disp.control_led('neterr', False)
                    self.logger.info("Ping success.")
                    return True
                else:
                    self.disp.control_led('neterr', True)
                    self.disp.control_led('net', False)
                self.logger.info("Sleeping for 10s.")
                time.sleep(10)
            else:
                # ppp is not up
                ## check if process is running
                ppp_pid = OSTools.pppd_pid()
                if ppp_pid == 0:
                    ## ppp has quit
                    raise InternetConnectionError('pppd unexpectedly quit!')
                else:
                    ## wait 10 seconds, and retry
                    time.sleep(10)
            retrycount -= 1
            self.logger.info("Rechecking network, remaining attempts %d." % retrycount)
        OSTools.pppd_terminate(OSTools.pppd_pid())
        return False
    
    def stop_internet(self):
        self.logger.info("Dropping net connection.")
        self.disp.control_led('net', False)
        return OSTools.pppd_terminate(OSTools.pppd_pid())
    
    def datalog(self):
        # instantiate a harvest dispatcher
        dispatch = EkoDispatcher()
        dispatch.import_configs()
        self.logger.info("Dispatching all sensor polling operations.")
        dispatch.dispatch_all()
        self.logger.info("All sensors polled.")
        return
    
    def upload_kiosk_messages(self):
        try:
            CMsgs.transmit_clientmessages()
        except:
            self.logger.exception("Unable to transmit client messages to server.")
            return False
        return True
    
    def _download_server_messages(self):
        messages = SMsgs.get_messages()
        return messages
        
    def service_server_messages(self, ignore_sa=False):
        msgs = self._download_server_messages()
        for msg in msgs:
            self.logger.debug("Message %s." % str(msg))
            if 'msg_type' not in msg.keys():
                self.logger.warn("No message type specified.")
                continue
            if 'msg' not in msg.keys():
                self.logger.warn("No message specified.")
                continue
            if msg['msg_type'] == 'STAYALIVE':
                if not ignore_sa:
                    self.stay_alive_routine(msg['msg'])
            elif msg['msg_type'] == 'CMD':
                self._exec_process(msg['msg'])
            elif msg['msg_type'] == 'GIVELOGS':
                res = self.upload_logs()
                CMsgs.add_clientmessage("Logs delivered.", res, "Command parser", datetime.utcnow())
            else:
                self.logger.warn("Unrecogised command.")
        return
        
    def _exec_process(self,message):
        self.logger.info("Executing message from server.")
        self.logger.debug("Command is %s." % message)
        try:
            proc = subprocess.Popen("message", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        except:
            self.logger.exception("Subprocess could not be opened.")
            return False
        now = datetime.utcnow()
        start = datetime.utcnow() + timedelta(seconds=240)
        while now < start:
            now = datetime.now()
            if proc.poll() is not None:
                timeout = 0
                break;
            timeout = 1
        if timeout:
            self.logger.error("Process timed out.")
            return False
        ret_tuple = proc.communicate()
        self.logger.debug("Command returns: %s." % str(ret_tuple))
        CMsgs.add_clientmessage("Command Executed:\n%s" % str(ret_tuple), '', "Command Executer", datetime.utcnow())
        return
    
    def stay_alive_routine(self,message_text):
        self.logger.info("Stay Alive for 30 minutes.")
        now = datetime.utcnow()
        stop = now + timedelta(seconds=1800)
        CMsgs.add_clientmessage("Staying Alive.", '', "Command parser", datetime.utcnow())
        while now < stop:
            now = datetime.utcnow()
            time.sleep(60)
            try:
                self.upload_kiosk_messages()
            except:
                self.logger.exception("An error occured when uploading messages.")
            # ignore another call to stay alive
            try:
                self.service_server_messages(ignore_sa=True)
            except:
                self.logger.exception("An error occured when attempting to fetch new orders.")
        CMsgs.add_clientmessage("Going to  die.", '', "Command parser", datetime.utcnow())
        try:
            self.upload_kiosk_messages()
        except:
            self.logger.exception("An error occured when uploading messages.")
    
    def upload_logs(self):
        upd = Uploader.DataUploader()
        ret = upd.zip_logfiles()
        if not ret:
            self.logger.info("Upload task exited. Error or nothing to sync.")
            return False
        (zipfile, manifest) = ret
        res = upd.upload_file(zipfile, manifest, upload_type="logs")
        if res:
            upd.create_sync_record(zipfile)
        else:
            self.disp.control_led('neterr', True)
    
    def upload_data_messages(self):
        upd = Uploader.DataUploader()
        upd.get_filelist()
        ret = upd.build_zip_file()
        if not ret:
            self.logger.info("Upload task failed!")
            return False
        (zipfile, manifest) = ret
        res = upd.upload_file(zipfile, manifest)
        if res:
            upd.create_sync_record(zipfile)
            upd.update_filelist()
        else:
            self.disp.control_led('neterr', True)
    
    def netsync(self):
        # open a internet connection
        ## power the modem
        self.disp.control_led('all', False)
        
        #try:
        #    x = Beagleboard.turn_on_usbhub()
        #except (OSError, IOError):
        #    self.logger.exception("Error encountered when attempting to turn on USB Hub.")
        #    x = False
        
        ### raise error led if hub power failed
        #if not x:
        #    self.disp.control_led('neterr', True)
        
        ## wait for system to settle
        time.sleep(10)
        
        retrycount = 3
        while retrycount > 0:
            try:
                #res = self.ready_internet()
                res = 'Foo'
            except InternetConnectionError:
                self.logger.exception('Could not dial modem.')
                res = None
            if res is not None:
                break
            self.logger.info("Waiting 30 seconds till next attempt.")
            time.sleep(30)
            retrycount -= 1
            self.logger.info("%d attempts left." % retrycount)
        
        # Assume we have net connectivity by this point.
        
        ## sync time if need be
        os.popen('ntpdate -t 90 0.pool.ntp.org 1.pool.ntp.org 2.pool.ntp.org')
        
        self.disp.control_led('sync', True)
        
        try:
            self.upload_kiosk_messages()
        except:
            self.logger.exception("Unable to upload kiosk messages")
            self.disp.control_led('neterr', True)
        
        
        try:
            self.upload_data_messages()
        except:
            self.logger.exception('Network Synchronisation Failed!')
            self.disp.control_led('neterr', True)
            
            
        try:
            self.service_server_messages()
        except:
            self.logger.exception("Unable to get commands from server.")    
        
        self.disp.control_led('sync', False)
        # terminate network
        #self.stop_internet()
        time.sleep(5)
        
        
        
        ## power off the modem
        #try:
        #    Beagleboard.turn_off_usbhub()
        #except:
        #    self.logger.exception("Error encountered when attempting to power off USB hub.")
    
    def run(self):
        # mark
        starttime = datetime.utcnow()
        nextsync = starttime
        while True:
            try:
                self.datalog()
            except KeyboardInterrupt:
                exit(0)
            except:
                self.logger.exception("Unhandled exception!")
            
            self.logger.info("Data logging operation complete.")
            # next poll is scheduled for time nextpoll. If nextpoll is ahead
            # tell beagle to sleep for 10 mins
            self.logger.info("Sleeping for 600 seconds.")
            try:
                os.popen('echo 120 > /debug/pm_debug/wakeup_timer_seconds')
                os.popen('echo mem > /sys/power/state')
            except (IOError, OSError):
                self.logger.exception("Unable to put system to sleep")
            # wait 60 seconds
            time.sleep(20)
            # check if its time for a netsync
            if datetime.utcnow() > nextsync:
                self.netsync()
                # next sync is in 6 hours
                nextsync = datetime.utcnow() + timedelta(minutes=15)
            td = nextsync - datetime.utcnow()
            self.logger.info("Next internet sync is in %.2f minutes." % (td.seconds/60.0))