Example #1
0
def send_and_sleep(output, sleep_time):
    """
    Sets output value and waits for a given amount of time.

    INPUT:
        output (0|1) : value to set as output for the pin
        sleep_time (Float) : amount of seconds to wait

    RETURNS:
        (int) 0 if everything went well, negative number if an error occured.
    """

    try:

        # Sets output value
        GPIO.output(pin_gpio_id, output)

        # Waits for given amount of time
        sleep(sleep_time)

    except IOError:

        # Failed to execute, so return failure status
        general_utils.log_error(-422, pin_gpio_id)
        ##########
        return -1
        ##########

    # Everything went smoothly.
    #########
    return 0
Example #2
0
def get_measurements(address, _):
    """
    Measures and returns all available measurements for the room as a dictionnary.

    INPUT:
        address (None) address of sensehat (to ignore)
        temperature_correction (float, unused) correction to apply to measurement value, to account 
            for external effects

    RETURNS:
        (Dict) dictionnary as {'luminosity': value1}
    """

    # Initializes dictionnary of measurements collected
    all_values = {
        'luminosity': None
    }

    try:

        if tsl2561 is not None:

            # Gets all measurements from Sensehat, and applies correction
            tsl2561_sensor = tsl2561.TSL2561(address)
            all_values['luminosity'] = tsl2561_sensor.lux()

    except (IOError, Exception) as e:

        # Something went wrong when retrieving the values. Log error.
        # NOTE : Happened once with Exception 'Sensor is saturated'.
        general_utils.log_error(-409, 'TSL2561', str(e))

    ##################
    return all_values
Example #3
0
def average_sensor_measures(sensor_dictionnary_object):
    """
    Averages successive sample values into on intermediary average, smoothes it using previous 
    averages, then outputs it

    INPUT
        sensor_dictionnary_object {Dict} information about a sensor, as shown in global variables
    """

    # Applies the process to all types of measurements made
    for measurement in sensor_dictionnary_object['samples_to_average'].keys():

        # One of the measure could never be collected.
        if len(sensor_dictionnary_object) == 0:

            details = '(%s, %s)' % (sensor_dictionnary_object['name'], measurement)
            general_utils.log_error(-424, details)
            continue

        # Gets all samples to create average (before smoothing)
        latest_data_samples = sensor_dictionnary_object['samples_to_average'][measurement]

        # Computes average (before smoothing)
        sample_average = 1.0 * sum(latest_data_samples) / len(latest_data_samples)

        # Deletes samples used (will not be used later)
        sensor_dictionnary_object['samples_to_average'][measurement] = []

        # Adds newly computed average to list of averages
        sensor_dictionnary_object['n_last_averages'][measurement].append(sample_average)

        ############
        # Smoothing
        ############

        # Gets sequence of last averages computed
        n_last_averages = sensor_dictionnary_object['n_last_averages'][measurement]

        # Smoothes these average by computing their own average (discarding None)
        smoothed_average = average_ignore_none(n_last_averages)

        # Updates smoothed_average value in sensor info.
        sensor_dictionnary_object['smoothed_average'][measurement] = smoothed_average

        #########
        # Output
        #########

        print("Average %s : %2.2f" % (measurement, sample_average))
        print("Smoothed Average %s : %2.2f" % (measurement, smoothed_average))

    # Writes all new measures into appropriate files once all computations are over
    output_data(sensor_dictionnary_object)

    #######
    return
Example #4
0
    def get_response(self, instruction_name, message_to_process):
        """
        Executes appropriate instruction, gets an answer, and returns it

        INPUT:
            instruction (str) : the instruction to execute
            message_to_process (lxml.etree): message received through RabbitMQ

        OUTPUT:
            (lxml.etree) response to send RabbitMQ server (before string conversion)
        """

        response_to_send = self.make_base_response()
        finished_processing = False

        # Update configuration and heartbeat are special. They exist for all workers.
        # Sends an acitivity signal with worker ID
        if instruction_name == 'heartbeat':

            response_to_send.set('status', '0')
            finished_processing = True

        # Creates the worker-specific configuration
        if instruction_name == 'update':

            response_to_send = self.update_config(message_to_process,
                                                  response_to_send)
            finished_processing = True

        # Received a non-default instruction although current worker config is default.
        # Instruction cannot be processed => skip it and return empty answer
        if not finished_processing and config_general.is_default:

            general_utils.log_error(-303)
            finished_processing = True

        # If instruction was not default, look at configuration folder for function to use
        if not finished_processing:

            # Get appropriate module from the configuration folder to compute a response
            appropriate_module = config_general.instruction_to_module.get(
                instruction_name, None)

            # Calls module execute function and returns its output (should be the response to send)
            if appropriate_module is not None:

                response_to_send = appropriate_module.execute(
                    self, message_to_process, response_to_send)

            else:

                # No appropriate module could be found, so impossible to process,
                general_utils.log_error(-304)

        ########################
        return response_to_send
def send_signal(remote_button, gpio_pin=21):
    """
    Sends infrared signal to air conditioning system with given options.

    INPUT:
        remote_button (str)
    """

    data_bytes = all_codes.get(remote_button, None)

    if data_bytes is None:

        details = '%s' % (remote_button,)
        ############################################################
        return general_utils.log_error(-505, error_details=details)
        ############################################################

    # Uses remote specific data and data_bytes information to get all sub-signals to create, and
    # order in which to send them to get the full signal to send.
    all_wave_lengths, wave_order = \
        signal_sender.convert_bits_to_length(data_bytes, one_bit, zero_bit, header_signal,
                                             repeat_signal, trail_signal, n_repeat)

    # Creates pigpio interface to send infrared signal
    ir = signal_sender.PigpioInterface(gpio_pin, 38000, 0.5)

    send_status = ir.send_code(all_wave_lengths, wave_order)

    ###################
    return send_status
Example #6
0
def initialize_data_files():
    """
    Initializes all required folders to export measurements from sensor.
    
    OUTPUT
        (int) 0 if no fatal problem while initializing output folders, negative integer otherwise
    """

    # For each i2c sensor registered
    for sensor in all_sensors:

        output_directory = sensor['output_directory']

        # Creates necessary directories if they did not exist already
        if not os.path.exists(output_directory):

            try:

                # Creates directories recursively to reach desired address
                os.makedirs(output_directory)

            except OSError as e:

                # Could not create all directories, so stop code execution
                ##############################################################
                return general_utils.log_error(-410, output_directory, str(e))
                ##############################################################

    #########
    return 0
Example #7
0
    def update_config(self, message_as_xml, worker_base_response):
        """
        Updates configuration folder when Master Controller calls for an update. This overwrites 
        whole config folder.

        INPUT:
            body (str) : workerConfiguration folder content in a xml-format

        OUTPUT:
            config_update_status (str) : config update status report in form <worker id=... 
                status=...>
        """

        # Starts update status to Failure (changed to Success only at end, if everything succeeded)
        configuration_updated_status = worker_base_response

        self.restart_flag = True

        # Message could be interpreted as a XML tree, so delete configuration folder and replace it
        shutil.rmtree(message_as_xml.get('to_delete'), True)

        # Creates config folder. If error occurs, delete whole folder to avoid partial config
        try:

            for directory_to_create in message_as_xml.iter('dir'):

                parent_directory_url = directory_to_create.get('parent')

                if parent_directory_url == '':

                    os.mkdir(directory_to_create.get('name'))

                else:

                    os.mkdir(parent_directory_url + '/' +
                             directory_to_create.get('name'))

            for file_to_create in message_as_xml.iter('file'):

                file_url = file_to_create.get(
                    'parent') + '/' + file_to_create.get('name')
                with open(file_url, 'w') as file_object:

                    if file_to_create.text is not None:

                        file_object.write(file_to_create.text)

            # Update succeded, so set request status to Success
            configuration_updated_status.set('status', '0')

        # Error occured while adding files / directories in the configuration folder.
        except OSError as e:

            self.error_status = general_utils.log_error(-306,
                                                        python_message=str(e))
            general_utils.log_message('Aborting configuration update.')

        ####################################
        return configuration_updated_status
Example #8
0
def read_sensor_values():
    """
    Collects sample from all registered sensors.
    """

    # For each sensor registered
    for sensor_object in all_sensors:

        sensor_name = sensor_object['name']
        sensor_address = sensor_object['address']

        try:

            # Fetch sensor-relevant module and calls its get_measurements function.
            appropriate_driver = sensor_to_driver.get(sensor_object['type'])
            all_values = appropriate_driver.get_measurements(sensor_address,
                                                             sensor_object['correction'])

            # Ignore value if sensor is in a warm-up phase.
            current_time = time.time()
            in_warmup = current_time < sensor_object['last_failed_measure_time'] + \
                sensor_object['warmup']

            # Only adds if value must not be filtered
            if not in_warmup:

                # Adds all measurement collected by sensor. (Temperature, Humidity, Pressure, ...)
                for measurement in all_values.keys():

                    # Only adds measure if it succeedeed
                    if all_values[measurement] is not None:

                        sensor_object['samples_to_average'][measurement].append(
                            all_values[measurement])

        except (IOError, ImportError) as e:

            # Measuring sensor failed => Assume disconnection, so warm-up must take place again
            sensor_object['last_failed_measure_time'] = time.time()

            details = '(' + sensor_name + ', ' + hex(sensor_address) + ')'
            general_utils.log_error(-409, details, str(e))

    #######
    return
Example #9
0
def output_measures_to_web():
    """
    Exports all smoothes averages collected from each sensor to thingspeak website.
    """

    all_fields_as_string = ''  # String containing all measure values (arguments for request)
    field_index = 1  # Current index of argument to send

    # Goes through each measurement or each sensor to construct the string
    for sensor_object in all_sensors:

        for measurement in sensor_object['smoothed_average'].keys():

            # Augments string with '&fieldX=YY.YYY', with X index and YY.YYY values. (ignore None)
            try:

                measurement_value = float(sensor_object['smoothed_average'][measurement])
                all_fields_as_string += '&field%d=%0.3f' % (field_index, measurement_value)

            except TypeError:

                # None Value, so could not be converted to float => ignore and skip to next measure
                pass

            # Increments field_index for following arguments
            field_index += 1

    # Constructs URL to use to post data.
    update_url = thingspeak_url + all_fields_as_string

    try:

        # GET request for url to update data.
        request.urlopen(update_url)

    except (request.URLError, http.client.BadStatusLine):

        # Failed to send request to update data. Log error but continue
        general_utils.log_error(-419, update_url)

    #######
    return
def main():
    """
    Starts Heartbeat monitoring program
    """

    try:
        class_name = 'HeartbeatMonitor'
        general_utils.get_welcome_end_message(class_name, True)

        # Creates the instance for a master controller and sets it to GUI mode.
        # Sets no_block argument to prevent master from waiting for user command line inputs
        rabbit_master = master.main(['-no_block'])

        # Creates GUI Object, to update GUI window
        monitor = HeartbeatMonitoring(rabbit_master)

        # Reads config file to get relevant parameters (workers to monitor, monitoring interval)
        monitor.read_configuration(monitoring_config_url)

        # Only starts monitoring if mails can be sent.
        if monitor.mail_sender.test_credentials(
        ) and monitor.report_mail_destination is not None:

            # Starts monitoring
            while True:

                monitor.monitor_activity()

    except Exception as e:

        # Unforeseen exception occured. Log it for analysis, with full traceback about it
        full_error_message = traceback.format_exc()
        general_utils.log_error(-999,
                                error_details=e,
                                python_message=full_error_message)

    # Exits code
    general_utils.get_welcome_end_message('HeartbeatMonitor', False)

    #######
    return
Example #11
0
def execute(_, instruction_as_xml, worker_base_response):
    """
    Processes ssh instruction

    INPUT
         worker_instance (Worker) worker instance
         instruction_as_xml (lxml.etree) message to process
         worker_base_response (lxml.etree) base of worker response on which to build

    OUTPUT
         (lxml.etree) worker response, with status report on instruction execution
    """

    # sudo /etc/init.d/ssh stop
    # sudo update-rc.d ssh disable

    # Creates base response to be completed in instruction
    ssh_response = worker_base_response

    # Either 'Start' or 'Stop'
    command_to_send = instruction_as_xml.get('command')

    command_status = 0
    if command_to_send in ['Start', 'Stop']:

        is_sshd_alive = test_sshd_alive()
        if command_to_send is 'Start':

            if not is_sshd_alive:

                subprocess.call(
                    '/etc/init.d/ssh start && update-rc.d ssh disable')

        else:

            if is_sshd_alive:

                subprocess.call(
                    '/etc/init.d/ssh stop && update-rc.d ssh enable')

    else:

        command_status = general_utils.log_error(-307,
                                                 'Accepted are {Start, Stop}')

    # Parsing was successfull
    ssh_response.set('status', str(command_status))

    ####################
    return ssh_response
Example #12
0
    def parse_arguments(self, command_line_arguments):
        """
        Parse arguments from the command line to detect the worker id to use

        INPUT:
            command_line_args (str list)
        """

        # Creates a parser and sets its accepted arguments.
        argument_parser = argparse.ArgumentParser()
        argument_parser.add_argument('workerID',
                                     help='ID of the worker to create.',
                                     nargs='?')

        # Parses the arguments. Currently just the first positional argument
        print('Reading arguments..'),
        parsed_arguments, to_ignore = argument_parser.parse_known_args(
            command_line_arguments)
        print('Done.')

        # If unhandled arguments exist, ignore them but print warning.
        if len(to_ignore) > 0:

            general_utils.log_error(-4, error_details=str(to_ignore))

        # Either assigns the given workerID, or stops because no workerID were given.
        if parsed_arguments.workerID is None:

            self.error_status = general_utils.log_error(-15)

        else:

            self.worker_id = parsed_arguments.workerID

        ######
        return
Example #13
0
def get_measurements(address, temperature_correction):
    """
    Measures and returns all available measurements for the room as a dictionnary.

    INPUT:
        address (None) address of sensehat (to ignore)
        temperature_correction (float) correction to apply to measurement value, to account for 
            external effects.

    RETURNS:
        (Dict) dictionnary as {'temperature': value1, 'humidity': value2, 'pressure': value3}
    """

    # Removes address since it is irrelevant
    del address

    # Initializes dictionnary of measurements collected
    all_values = {'temperature': None, 'humidity': None, 'pressure': None}

    if SenseHat is not None:

        try:
            # Gets all measurements from Sensehat, and applies correction
            sense = SenseHat()
            all_values['temperature'] = sense.get_temperature(
            ) + temperature_correction
            all_values['humidity'] = sense.get_humidity()
            all_values['pressure'] = sense.get_pressure()

        except Exception as e:

            # Something went wrong when retrieving the values. Log error.
            general_utils.log_error(-409, 'Sensehat', str(e))

    ##################
    return all_values
Example #14
0
def read_temperature(address):

    # Initializes temperature
    temperature = None

    # Address of the file containing the measure
    url_file = '/sys/devices/w1_bus_master1/' + str(address) + '/w1_slave'

    if os.path.isfile(url_file):

        # Array to contain content of slave_file
        slave_info = []

        # Gets name of all slaves (names are used later, to open this file as short as possible)
        with open(url_file, 'r') as slave_file:

            # Slave file has 2 lines : crc/detect info, and value info.
            for line in slave_file:

                # Strips linebreak
                slave_info.append(line[:-1])

            # First line contain crc/detect info => metadata. Second line contains value at the end
            meta = slave_info[0]
            value = slave_info[1]

            # Only process the value if the metadata line says the measure is ok
            if not meta.startswith(
                    '00 00 00 00 00 00 00 00 00') and not meta.endswith('NO'):

                try:

                    # Read temperature (written as 28000 for 28C) and adds correction
                    temperature = float(value.split('t=')[1]) / 1000

                except ValueError as e:

                    details = '(' + address + '). Could not parse temperature.'
                    general_utils.log_error(-409, details, str(e))

            else:

                details = '(' + address + '). Bad content in file.'
                general_utils.log_error(-409, details)

    else:

        details = '(' + address + '). Slave file does not exist.'
        general_utils.log_error(-409, details)

    ##################
    return temperature
Example #15
0
def send_signal(is_turned_on, mode, temperature, wind_speed, wind_direction, gpio_pin=21):
    """
    Sends infrared signal to air conditioning system with given options.

    INPUT:
        is_turned_on {'on', 'off'} whether aircon is on or off
        mode {'heater', 'cold', 'dry'} which mode the aircon is running in
        temperature {int} temperature target
        wind_speed {'auto', 'low', 'middle', 'high'}
        wind_direction {'auto', 'lowest', 'low', 'middle', 'high', 'highest', 'loop'}
    """

    # Converts all options desired to the data bytes to send, in byte (8 bit-string) array form
    data_bytes, details = convert_info_to_bits(is_turned_on, mode, temperature, wind_speed,
                                               wind_direction)

    if data_bytes is None:

        ############################################################
        return general_utils.log_error(-505, error_details=details)
        ############################################################

    data_bytes = ''.join(data_bytes)

    # Uses remote specific data and data_bytes information to get all sub-signals to create, and
    # order in which to send them to get the full signal to send.
    all_wave_lengths, wave_order = \
        signal_sender.convert_bits_to_length(data_bytes, one_bit, zero_bit, header_signal,
                                             repeat_signal, trail_signal, n_repeat)

    if all_wave_lengths is None:

        # Function failed, so wave_order contains failure error code
        ##################
        return wave_order
        ##################

    # Creates pigpio interface to send infrared signal
    ir = signal_sender.PigpioInterface(gpio_pin, 38000, 0.5)

    send_status = ir.send_code(all_wave_lengths, wave_order)

    ###################
    return send_status
Example #16
0
    def apply_reboot(self, pika_method):
        """
        Processes how worker should reboot and terminates/closes relevant processes

        INPUT:
            pika_method (pika object) : !!!!NO IDEA!!!!
        """

        # Stops queue consumption (avoids messages trapped in  restarting worker)
        # Include a message acknowledgement
        self.pika_connector.stop_consume(pika_method)

        # Forces os reboot in worker.
        os.system('systemctl reboot -i')

        # NOTE : If this line is reached, the reboot failed.
        self.error_status = general_utils.log_error(-997)

        ######
        return
Example #17
0
def execute(_, instruction_as_xml, worker_base_response):
    """
    Processes space instruction

    INPUT
         _ (Worker) worker instance
         instruction_as_xml (lxml.etree) message to process
         worker_base_response (lxml.etree) base of worker response on which to build

    OUTPUT
         (lxml.etree) worker response, with info about sensor on the one-wire bus
    """

    del instruction_as_xml

    space_response = worker_base_response

    try:

        space_info = os.statvfs('/')
        free_space = space_info.f_bavail * space_info.f_frsize
        total_space = space_info.f_blocks * space_info.f_frsize
        used_space = (space_info.f_blocks -
                      space_info.f_bfree) * space_info.f_frsize

        space_response.set('total', convert_magnitude(total_space))
        space_response.set('used', convert_magnitude(used_space))
        space_response.set('free', convert_magnitude(free_space))

        status_code = 0

    except OSError as exception:

        # Failed to call function because number of arguments did not match
        status_code = general_utils.log_error(-601, error_details=exception)

    space_response.set('status', str(status_code))

    ######################
    return space_response
Example #18
0
def read_configuration(config_filename):
    """
    Reads configuration file containing all sensor/heater information and updates internal 
    parameters accordingly.
    Parameters include:
        General params (time between measurements, number of measurements for average, smoothing)
        Sensor params (location, output, correction, ...)

    INPUT
        config_filename (str) name of configuration file
        
    OUTPUT:
        error_code (int) 0 if no fatal error while reading config file, negative integer otherwise
    """

    # Deletes example sensor from code (in global variable)
    all_sensors.pop()

    ########################################
    # Creates configuration parser + parses
    ########################################
    parsed_config = configparser.RawConfigParser()
    if not os.path.isfile(config_filename):

        #####################################################
        return general_utils.log_error(-401, config_filename)
        #####################################################

    else:

        parsed_config.read(config_filename)

    ############################################################
    # Gets general parameters for sample collection / averaging
    ############################################################
    if parsed_config.has_section('General'):

        ##################
        # Sample Interval
        ##################
        if parsed_config.has_option('General', 'sample_interval'):

            try:

                # Reads value from config file.
                candidate_sample_interval = parsed_config.getfloat('General', 'sample_interval')

                # Tests if value in configuration is valid (positive number)
                if candidate_sample_interval > 0.0:

                    # Valid value : update internal parameters
                    global sample_interval
                    sample_interval = candidate_sample_interval

                else:

                    # Value was number, but not positive
                    value_from_config = parsed_config.get('General', 'sample_interval')
                    details = 'sample_interval must be positive (%s).' % (value_from_config,)
                    general_utils.log_error(-412, details)

            except ValueError as e:

                # Value was not a number
                value_from_config = parsed_config.get('General', 'sample_interval')
                details = 'sample_interval must be a number (%s).' % (value_from_config,)
                general_utils.log_error(-412, details, str(e))

        else:

            # Configuration file did not have parameter
            general_utils.log_error(-412, 'sample_interval')

        ###################
        # N Sample For Avg
        ###################
        if parsed_config.has_option('General', 'n_sample_for_average'):

            try:

                # Reads value from config file.
                candidate_n_sample_for_average = parsed_config.getint('General', 
                                                                      'n_sample_for_average')

                # Tests if value in configuration is valid (positive integer)
                if candidate_n_sample_for_average > 0:

                    global n_sample_for_average
                    n_sample_for_average = candidate_n_sample_for_average

                else:

                    # Value was integer, but not positive
                    value_from_config = parsed_config.get('General', 'n_sample_for_average')
                    details = 'n_sample_for_average not positive int (%s).' % (value_from_config,)
                    general_utils.log_error(-412, details)

            except ValueError as e:

                # Value was not an integer
                value_from_config = parsed_config.get('General', 'n_sample_for_average')
                details = 'n_sample_for_average not int (%s).' % (value_from_config,)
                general_utils.log_error(-412, details, str(e))

        else:

            # Configuration file did not have parameter
            general_utils.log_error(-412, 'n_sample_for_average')

        ###################
        # N Avg For Smooth
        ###################
        if parsed_config.has_option('General', 'n_average_for_smooth'):

            try:

                # Reads value from config file
                candidate_n_average_for_smooth = parsed_config.getint('General', 
                                                                      'n_average_for_smooth')

                # Tests if value in configuration is valid (positive integer)
                if candidate_n_average_for_smooth > 0:

                    global n_average_for_smooth
                    n_average_for_smooth = candidate_n_average_for_smooth

                else:

                    # Value was integer, but not positive
                    value_from_config = parsed_config.get('General', 'n_average_for_smooth')
                    details = 'n_average_for_smooth not positive int (%s).' % (value_from_config,)
                    general_utils.log_error(-412, details)

            except ValueError as e:

                # Value was not an integer
                value_from_config = parsed_config.get('General', 'n_average_for_smooth')
                details = 'n_average_for_smooth not int (%s).' % (value_from_config,)
                general_utils.log_error(-412, details, str(e))

        else:

            # Configuration file did not have parameter
            general_utils.log_error(-412, 'n_average_for_smooth')

        ################################
        # Thingspeak API URL (Optional)
        ################################
        if parsed_config.has_option('General', 'thingspeak_key'):

            # Configuration file only contains API key, so after parsing, merged it with base url.
            global thingspeak_url
            thingspeak_key = parsed_config.get('General', 'thingspeak_key')
            thingspeak_url = 'https://api.thingspeak.com/update?api_key=%s' % thingspeak_key

    ######################################
    # Gets all supported sensors to query
    ######################################
    sensor_index = 0
    while True:

        sensor_name = 'Sensor' + str(sensor_index)

        # Check if sensor exist. Assumes config file has sensor listed as 'Sensor17, 'Sensor2', ...
        if parsed_config.has_section(sensor_name):

            # Check whether the sensor has the fundamental characteristics (type and address)
            if parsed_config.has_option(sensor_name, 'type'):

                # Checks if sensor is a supported i2c or 1wire sensor
                sensor_type = parsed_config.get(sensor_name, 'type')
                if sensor_type in sensor_to_driver.keys():

                    # If supported sensor, parses its information and adds it to sensor list
                    parse_sensor_config(parsed_config, sensor_name, sensor_type)

                else:

                    # Saw an unidentified sensor, print an error but keep parsing
                    details = '(%s, %s)' % (sensor_name, sensor_type)
                    general_utils.log_error(-402, details)

            else:

                # Saw a sensor without required info, log error but keep parsing
                general_utils.log_error(-403, sensor_name)

            # Sensor processing finished. Moves on to the next sensor
            sensor_index += 1

        else:

            # If the sensor did not exist, stop searching.
            break

    # No sensors could be detected if config file
    if sensor_index == 0:

        #####################################################
        return general_utils.log_error(-415, config_filename)
        #####################################################

    # No VALID sensor could be detected in config file
    if len(all_sensors) == 0:

        ####################################
        return general_utils.log_error(-423)
        ####################################

    ########
    return 0
Example #19
0
from global_libraries import general_utils

#########################
# Import global packages
#########################

try:

    from sense_hat import SenseHat  # Controls sensehat part on Raspberry

except ImportError as ex:

    # If package not installed, log error and cancel tsl2561 object
    SenseHat = None
    error_details = 'Is sensehat package installed (pip)?'
    general_utils.log_error(-425, error_details, ex)

__author__ = 'Baland Adrien'


####################################################################################################
# FUNCTION (is_valid_address)
####################################################################################################
# Revision History:
#   2016-11-04 AB - Function Created
####################################################################################################
def is_valid_address(address):
    """
    Tests whether provided address is a valid Sensehat address

    INPUT:
Example #20
0
def get_measurements(address, temperature_correction):
    """
    Measures and returns all available measurements for the room as a dictionnary.

    INPUT:
        address (None) address of DHT11 sensor (GPIO pin)
        temperature_correction (float) correction to apply to measurement value, to account for 
            external effects.


    RETURNS:
        (Dict) dictionnary as {'temperature': value}
    """

    global pin_gpio_id
    global no_data_bit_error_last_time

    max_number_retry = 5  # Times script will try to get data from sensor in case of failure

    all_values = {
        'temperature': None,
        'humidity': None
    }

    # Case-handler if the Rpi.GPIO could not be loaded
    if GPIO is None:

        ##################
        return all_values
        ##################

    # Sets the pin to use internally.
    pin_gpio_id = address

    # Sets appropriate mode for GPIO pins
    GPIO.setmode(GPIO.BCM)

    index_retry = 1  # Current trial for sensor info.
    while True:

        # Sets pins for output (to send start signal)
        GPIO.setup(address, GPIO.OUT)

        # Set pin high for 500ms (makes sure everything is freed)
        send_and_sleep(GPIO.HIGH, 0.5)

        # MCU sends start signal and pull down voltage for at least 18milliseconds (initial phase)
        send_and_sleep(GPIO.LOW, 0.020)

        # Change to input using pull up (prepares for response from sensor)
        GPIO.setup(address, GPIO.IN, GPIO.PUD_UP)

        # Collect data into an array
        all_voltage_measurements = collect_pin_values()

        # parse lengths of all data pull up periods
        high_voltage_counts = get_high_voltage_counts(all_voltage_measurements)

        # Tests if exactly 40-bits of data parsed (otherwise, no point in trying to convert them)
        if len(high_voltage_counts) == 40:

            # Get all 40 data bits value (0 or 1)
            all_data_bits = get_data_bits(high_voltage_counts)

            # Aggregate all 40 parsed data bits into 5 data bytes.
            all_data_bytes = convert_data_bits(all_data_bits)

            # Tests whether the checksum in data matches self-calculated checksum.
            if all_data_bytes[4] == compute_checksum(all_data_bytes):

                # Checksum success. Temperature and Humidity computed.
                # DHT11 Does not handle decimal parts, so only first and third bytes are relevant.
                all_values['temperature'] = all_data_bytes[2] + temperature_correction
                all_values['humidity'] = all_data_bytes[0]

                # Success. Can leave the loop
                break

            else:

                # Log errors if checksum test failed (only log error after n failures)
                if index_retry == max_number_retry:

                    general_utils.log_error(-409, 'Checksum did not match in DHT.')
                    break

        else:

            # Log errors if failed to get 40 data bits from sensor (only log error after n failures)
            if index_retry == max_number_retry:

                # Initializes the last time an error has been logged for that particular address
                if pin_gpio_id not in no_data_bit_error_last_time.keys():

                    no_data_bit_error_last_time[pin_gpio_id] = 0

                # Checks if error should be logged
                if now() > no_data_bit_error_last_time[pin_gpio_id] + no_data_bit_error_spacing:

                    error_details = 'Address %d.' % (pin_gpio_id,)
                    general_utils.log_error(-409, 'Not 40 data bits for DHT11', error_details)
                    no_data_bit_error_last_time = now()
                    break

        # Failed to get measures in current trial, increment counter and try again
        index_retry += 1

    ##################
    return all_values
Example #21
0
        # Remove data that has become to old to be used in smoothing
        remove_obsolete_data()

        print("==========================")
        print("=== %s ===" % convert_localtime_to_string(sample_collection_half_time))
        print("==========================")

        # Processes new samples
        post_collection_actions()

        if thingspeak_url is not None:

            output_measures_to_web()

###########
# END main
###########


if __name__ == "__main__":

    # The code will stops but will log the error message to know what went wrong
    try:

        main()

    except Exception as unhandled_exception:

        error_details = traceback.format_exc(limit=None)
        general_utils.log_error(-999, error_details, str(unhandled_exception))
Example #22
0
def collect_pin_values():
    """
    Collect data from DHT11 sensor. Measures as often as possible state (LOW/HIGH) of pin to measure
    The following starting sequence (HIGH, LOW, HIGH) is ignored, as it precedes sensor data 
    response (announces that response will follow).
    Collections stop when measurements stay to HIGH for a long enough time.

    INPUT:

    RETURNS:
        (int[]) array of measurements (HIGH=1, LOW=0) for the given pin.
    """

    # Case-handler if the Rpi.GPIO could not be loaded
    if GPIO is None:

        ##########
        return []
        ##########

    n_same_value_break = 200  # number of successive time same data is seen until collection stops
    n_same_value_count = 0  # Number of successive times the same value was observed
    last_value = -1  # Last measured value
    all_values_collected = []  # All measures collected

    #######################################
    # Skips introduction signal (not data)
    #######################################

    # Wait for DHT to pull pin low => Skips all high
    try:
        while GPIO.input(pin_gpio_id) and n_same_value_count < n_same_value_break:
            n_same_value_count += 1

        # Wait for DHT to pull pin HIGH (next LOW will signal start of data bits) => Skips all low
        n_same_value_count = 0
        while not GPIO.input(pin_gpio_id) and n_same_value_count < n_same_value_break:
            n_same_value_count += 1

        # Wait for DHT to pull pin LOW (start of data bits) => Skips all HIGH
        n_same_value_count = 0
        while GPIO.input(pin_gpio_id) and n_same_value_count < n_same_value_break:
            n_same_value_count += 1

    except IOError:

        # Algorithm failed to read GPIO pin, so stop functions and prints error
        general_utils.log_error(-421, pin_gpio_id)
        ############################
        return all_values_collected
        ############################

    ####################
    # Starts collection
    ####################

    # While measurements are not stable (max_unchanged_count times the same value), reads value and
    # appends it
    n_same_value_count = 0
    while True:

        try:
            # Get current value and appends it
            current_value = GPIO.input(pin_gpio_id)
            all_values_collected.append(current_value)

        except IOError:

            # Algorithm failed to read GPIO pin, so stop functions and prints error
            general_utils.log_error(-421, pin_gpio_id)
            #################
            return all_values_collected
            #################

        # Checks if state changes or stayed the same
        if last_value != current_value:

            # Change occured => Reset counter
            n_same_value_count = 0
            last_value = current_value

        else:

            # Increment counters and tests if stabilized
            n_same_value_count += 1
            if n_same_value_count > n_same_value_break:

                break

    ############################
    return all_values_collected
Example #23
0
def parse_sensor_config(parsed_config, sensor_name, sensor_type):
    """
    Parses information for a given sensor from the configuration file.
    
    INPUT
        parsed_config (ConfigParser object) : configuration parsed by ConfigParser
        sensor_name (str) : name of the sensor to parse (SensorX with X integer)
        sensor_type (str) : type of the sensor to parse
        
    OUTPUT
        (int) 0 if sensor was parsed normally, negative number if parsing reached fatal error
    """

    # Creates default parameters in case an option is missing.
    temperature_correction = 0.0  # Correction to apply on temperature measurements (calibration).
    sensor_warmup = 0.0  # Time necessary for sensor to warmup (values are ignored during warmup)
    output_directory = '/home/pi/'  # Where to write measurements : Base directory
    sensor_location = sensor_name  # Where to write measurement : Subdirectory

    #################
    # Sensor address
    #################
    #  If sensor is a sensehat, address does not exist. Otherwise, address required.
    if sensor_type == 'Sensehat':

        sensor_address = None

    # Otherwise, address is a required parameter
    elif not parsed_config.has_option(sensor_name, 'address'):

        # No address could be found, log an error and stop parsing for that sensor.
        #################################################
        return general_utils.log_error(-403, sensor_name)
        #################################################

    else:

        # Parse the address as a string (checked later)
        sensor_address = parsed_config.get(sensor_name, 'address')

    # Tests if address provided is valid using relevant drivers and converts to appropriate format
    valid_address, converted_address = sensor_to_driver.get(sensor_type)\
        .is_valid_address(sensor_address)

    # Reports problem with address in case it is invalid
    if not valid_address:

        # Stops execution since it will be impossible to get sensor values
        details = '(' + sensor_name + ', ' + sensor_address + ')'
        #############################################
        return general_utils.log_error(-404, details)
        #############################################

    ####################################
    # Temperature correction (optional)
    ####################################
    if parsed_config.has_option(sensor_name, 'correction'):

        try:

            # Gets temperature correction to apply (integer of float)
            temperature_correction = parsed_config.getfloat(sensor_name, 'correction')

        except ValueError as e:

            # Correction parameter value could not be interpreted, log error but uses 0.0 default.
            details = '(%s, %s)' % (sensor_name, parsed_config.get(sensor_name, 'correction'))
            general_utils.log_error(-405, details, e)

    ################################
    # Sensor warmup time (optional)
    if parsed_config.has_option(sensor_name, 'warmup'):

        try:

            # Gets time required for sensor to warmup (positive integer or float)
            candidate_sensor_warmup = parsed_config.getfloat(sensor_name, 'warmup')

            if candidate_sensor_warmup >= 0.0:

                # Sensor warmup inside configuration is valid : update parameters
                sensor_warmup = candidate_sensor_warmup

            else:

                # Warmup parameter value was negative, log error and uses 0.0 default.
                details = '(%s, %s)' % (sensor_name, parsed_config.get(sensor_name, 'warmup'))
                general_utils.log_error(-420, details)

        except ValueError as e:

            # Correction parameter value could not be interpreted, log error but uses 0.0 default.
            details = '(' + sensor_name + ', ' + parsed_config.get(sensor_name, 'warmup') + ')'
            general_utils.log_error(-420, details, e)

    # Output directory (to be combined later with location).
    if parsed_config.has_option(sensor_name, 'output_directory'):

        output_directory = parsed_config.get(sensor_name, 'output_directory')

    else:

        # Could not parse output directories, so log error but uses default.
        general_utils.log_error(-406, sensor_name)

    ##################
    # Sensor location
    if parsed_config.has_option(sensor_name, 'location'):

        sensor_location = parsed_config.get(sensor_name, 'location')

    else:

        # Could not parse location (also used for output directory), so uses sensor name as default.
        general_utils.log_error(-407, sensor_name)

    ############################
    #  Parsing done : now apply
    # Combines output_directory and sensor_location to get actual directory where output is made
    if output_directory.endswith('/'):

        # Output_directory already ended with '/' => append sensor_location and '/'
        output_directory += sensor_location + '/'

    else:

        # Output_directory did not end with '/' => append '/', sensor_location and '/' again.
        output_directory += '/' + sensor_location + '/'

    ######################################
    # Creates corresponding sensor object
    ######################################

    # Only adds sensor if its unique identifiers (output_directory) have not been added before
    if output_directory not in list_all_output_directories:

        # Gets list of all different measures sensor can colllect, to initialze dictionnaries
        all_measurement_types = sensor_to_driver.get(sensor_type).get_measurement_types()

        # Initializes list for collected samples (before average)
        sample_to_average_init = {measurement: [] for measurement in all_measurement_types}

        # Initializes list for computed average (before smoothing). This starts an array with n None
        #  entries for eachmeasurement type
        n_last_averages_init = {measurement: [None for _ in range(n_average_for_smooth)] for 
                                measurement in all_measurement_types}

        # Initializes list for computed smoothed average
        smoothed_average = {measurement: 0.0 for measurement in all_measurement_types}

        sensor = {
            # Base information
            'name': sensor_location,
            'type': sensor_type,
            'address': converted_address,
            'correction': temperature_correction,
            'warmup': sensor_warmup,
            'output_directory': output_directory,
            # Last time a BME280 measurement failed. Used to incorporate warm-up time.
            'last_failed_measure_time': time.time(),
            # Samples to use for averaging
            'samples_to_average': sample_to_average_init,
            # Averages to use for smoothing
            'n_last_averages': n_last_averages_init,
            # Latest value outputed
            'smoothed_average': smoothed_average
        }

        all_sensors.append(sensor)

    else:

        # Redundancy in information detected, log error and DO NOT adds sensor
        details = '(%s, %s)' % (sensor_location, output_directory)
        general_utils.log_error(-414, details)

    #########
    return 0
Example #24
0
    # Links the worker to all relevant queues
    rabbit_worker_instance.link_queue_to_worker()
    general_utils.test_fatal_error(rabbit_worker_instance, script_class_name)

    # Makes the worker start waiting for messages
    rabbit_worker_instance.pika_connector.start_consume()

    # If we reach this end, Worker stopped consuming. Either worker stopped, or an error occured
    general_utils.test_fatal_error(rabbit_worker_instance, script_class_name)

    general_utils.get_welcome_end_message(script_class_name, False)
    exit(0)


####################################################################################################
# END main
####################################################################################################

if __name__ == "__main__":

    try:

        main(sys.argv[1:])

    except Exception as unhandled_exception:

        # Unforeseen exception occured. Log it for analysis, with all info about where it happened
        full_error_message = traceback.format_exc()
        general_utils.log_error(-999, unhandled_exception, full_error_message)
Example #25
0
    def process_order(self, _, pika_method, in_properties, message_received):
        """
        Defines how to process order from the master program.
        If request has timeout and the reception occurs too late : acknowledges messages and stops.
        Calls appropriate instruction, sends an answer (optional + timeoutcheck) and acknowledges 
        message afterwards
        The whole code might be restarted if the instruction processed required it

        INPUT
             channel (pika object) pika channel object. UNUSED because in Worker.pika_connector
             pika_method (pika object) !!!!NO IDEA!!!!
             in_properties (pika Properties) additional properties about the message received
             message_received (str) message content
        """

        try:

            message_as_xml = general_utils.convert_message_to_xml(
                message_received)

            if message_as_xml is None or self.must_be_filtered(message_as_xml):

                self.pika_connector.acknowledge_message(pika_method)

                #######
                return
                #######

            instruction_name = message_as_xml.get('type')
            general_utils.log_message('Received %s request.' %
                                      (str(instruction_name), ))

            # Apply the instruction to the message
            response_to_send = self.get_response(instruction_name,
                                                 message_as_xml)
            response_to_send = etree.tostring(response_to_send)

            # Sends the response or not depending on current time and timeout
            self.send_response(in_properties, response_to_send)

        except KeyError as e:

            general_utils.log_error(-302, python_message=str(e))
            self.pika_connector.acknowledge_message(pika_method)

            #######
            return
            #######

        # Failed to send response => error with RabbitMQ connection => Cannot acknowledge
        if self.error_status != 0:

            #######
            return
            #######

        # Acknowledges message and goes back to listening if no reboot required, restart otherwise.
        if self.restart_flag:

            self.apply_reboot(pika_method)

        else:
            self.pika_connector.acknowledge_message(pika_method)

        #######
        return
    def read_configuration(self, configuration_filename):
        """
        Reads configuration file containing all HeartbeatMonitor information and updates internal
        parameters accordingly
        Parameters include:
            All requirement time parameters to monitor heartbeat
            Instructions that can be sent to worker

        INPUT:
            configuration_filename (str) name of configuration file

        OUTPUT:
            error_code (int) 0 if no fatal error occured, negative integer otherwise
        """

        # List of all parameters which must be present in configuration file, and their type.
        # (1) Time interval (s) between two activity checks (integer)
        # (2) List of workers to watch (comma separated, spaces not supported in names)
        all_required_parameters = [['request_interval', int],
                                   ['mail_min_delay', int],
                                   [
                                       'all_workers',
                                       lambda x: x.replace(' ', '').split(',')
                                   ], ['report_mail_destination', str]]

        # Dictionnary to hold the parsed information from configuration.
        parsed_parameters = {}

        # Checks that the configuration file does exist
        if not os.path.isfile(configuration_filename):

            ############################################################
            return general_utils.log_error(-401, configuration_filename)
            ############################################################

        # Creates configuration parser + parses
        parsed_configuration = configparser.RawConfigParser()
        parsed_configuration.read(configuration_filename)

        ###################################################
        # Checks if config parsing has required parameters
        ###################################################
        # Checks existence of general section
        if not parsed_configuration.has_section('General'):

            # Configuration file was missing required section, so stop execution
            ###############################################
            return general_utils.log_error(-419, 'General')
            ###############################################

        # Checks existence of all required (General, X) options.
        for required_parameter in all_required_parameters:

            if not parsed_configuration.has_option('General',
                                                   required_parameter[0]):

                # Configuration file was missing required section, so stop execution
                ###########################################################
                return general_utils.log_error(-412, required_parameter[0])
                ###########################################################

            try:

                # Parse value as string, converts it to appropriate type, and puts it in dictionnary
                parsed_value = parsed_configuration.get(
                    'General', required_parameter[0])
                parsed_parameters[required_parameter[0]] = required_parameter[
                    1](parsed_value)

            except ValueError as e:

                # Value did not match required type
                value_from_config = parsed_configuration.get(
                    'General', required_parameter)
                details = '(%s, %s)' % (required_parameter, value_from_config)
                #####################################################
                return general_utils.log_error(-412, details, str(e))
                #####################################################

        # Parsing finished successfully. Assigns values.
        self.request_interval = parsed_parameters['request_interval']
        self.report_mail_destination = parsed_parameters[
            'report_mail_destination']
        self.mail_min_delay = parsed_parameters['mail_min_delay']
        for worker_id in parsed_parameters['all_workers']:
            self.activity_checklist[worker_id] = False
            self.last_mail_sent[worker_id] = -1

        #########
        return 0
def execute(worker_instance, instruction_as_xml, worker_base_response):
    """
    Processes infrared remote instruction

    INPUT
         worker_instance (Worker) worker instance
         instruction_as_xml (lxml.etree) message to process
         worker_base_response (lxml.etree) base of worker response on which to build

    OUTPUT
         (lxml.etree) worker response
    """

    # Creates base response to be completed in instruction
    remote_control_response = worker_base_response

    # Gets name of remote to simulate.
    remote_to_use = instruction_as_xml.get('remote')

    # Retrieves appropriate driver using remote name.
    remote_info = remote_to_info.get(remote_to_use, None)
    if remote_info is not None:

        # Checks if remote is handled by this worker or another. Stops if another.
        if worker_instance.worker_id != remote_info[1]:

            status_code = 0

        else:

            # Retrieves the "button" to simulate (button name or full configuration information).
            #   Example: for tv : 'Power', 'Mute', ....  for aircon : 'on,heat,25,strong,highest'
            configuration_to_send = instruction_as_xml.get('config')
            try:

                # Split the comma-separated info (only puts in array if button name)
                configuration_splitted = configuration_to_send.split(',')

                # Logs remote command to send before sending it, for history and bug tracking
                general_utils.log_message('Request for remote %s : %s' % (remote_to_use,
                                                                          configuration_to_send))

                # Sends all element in array as argument (button name or all config parameter)
                status_code = remote_info[0].send_signal(*configuration_splitted)

            except TypeError:

                # Failed to call function because number of arguments did not match
                details = '(%s, %s)' % (remote_to_use, configuration_to_send)
                status_code = general_utils.log_error(-502, error_details=details)

    else:

        # Remote name did not exist in available remotes.
        status_code = general_utils.log_error(-501, error_details=remote_to_use)

    # Puts status code in response.
    remote_control_response.set('status', str(status_code))

    ###############################
    return remote_control_response