Example #1
0
    def __init__(self, logger, data_type, lora):
        """
        Schedules events for calculating averages or getting GPS position and also schedules random LoRa messages for
        each interval
        :param logger: status logger
        :type logger: LoggerFactory object
        :param data_type: "sensors" or "gps"
        :type data_type: str
        :param lora: LoRaWan object, False if lora is not enabled
        :type lora: LoRaWAN object
        """

        #  Arguments
        self.logger = logger
        self.data_type = data_type
        self.lora = lora

        #  Attributes
        if self.data_type == "sensors":
            self.interval_s = int(float(config.get_config("interval")) * 60)
            if self.interval_s < 15 * 60:
                self.logger.warning(
                    "Interval is less than 15 mins - real time transmission is not guaranteed"
                )
        elif self.data_type == "gps":
            self.interval_s = int(
                float(config.get_config("GPS_period")) * 3600)
        self.s_to_next_lora = None
        self.first_alarm = None
        self.periodic_alarm = None
        self.random_alarm = None

        #  Start scheduling events
        self.start_events()
Example #2
0
def setup_new_config(logger):
    """
    Start a WiFi AP that provides a configuration page.
    The device automatically reboots and applies modifications upon successful configuration.
    :param logger: status logger
    :type logger: LoggerFactory
    """

    #  Only one of this thread is allowed to run at a time
    if not wifi_lock.locked():
        with wifi_lock:

            logger.info("New configuration setup started")

            # Config uses LED colours to indicate the state of the connection - lock is necessary to disable error pings
            led_lock.acquire(1)
            unique_id = ubinascii.hexlify(machine.unique_id()).decode()
            # set pycom up as access point
            wlan = WLAN(mode=WLAN.AP,
                        ssid=config.get_config("device_name") + unique_id)
            # Connect to PmSensor using password set by the user
            wlan.init(
                mode=WLAN.AP,
                ssid=config.get_config("device_name") + unique_id,
                auth=(WLAN.WPA2, config.get_config("password")),
                channel=1,
                antenna=WLAN.INT_ANT,
            )
            # Load HTML via entering 192,168.4.10 to browser
            wlan.ifconfig(
                id=1,
                config=("192.168.4.10", "255.255.255.0", "192.168.4.1",
                        "192.168.4.1"),
            )

            logger.info("Access point turned on as {}".format(
                config.get_config("device_name") + unique_id))
            logger.info(
                "Configuration website can be accessed at 192.168.4.10")

            address = socket.getaddrinfo(
                "0.0.0.0", 80)[0][-1]  # Accept stations from all addresses
            sct = socket.socket()  # Create socket for communication
            sct.settimeout(int(
                float(config.get_config("config_timeout")) *
                60))  # session times out after x seconds
            gc.collect(
            )  # frees up unused memory if there was a previous connection
            sct.bind(address)  # Bind address to socket
            sct.listen(1)  # Allow one station to connect to socket

            pycom.rgbled(0x000055)  # Blue LED - waiting for connection

            get_new_config(sct, logger)

            wlan.deinit()  # turn off wifi
            gc.collect()

            logger.info("rebooting...")
            machine.reset()
Example #3
0
def pm_thread(sensor_name, status_logger, pins, serial_id):

    status_logger.debug("Thread {} started".format(sensor_name))

    sensor_logger = SensorLogger(sensor_name=sensor_name, terminal_out=True)

    sensor_type = config.get_config(sensor_name)
    init_time = config.get_config(sensor_name + "_init")

    init_count = 0

    if sensor_type == "PMS5003":

        # initialize sensor
        sensor = Plantower(pins=pins, id=serial_id)

        # warm up time  - readings are not logged
        while init_count < init_time:
            try:
                time.sleep(1)
                sensor.read()
                init_count += 1
            except PlantowerException as e:
                status_logger.exception("Failed to read from sensor PMS5003")
                blink_led((0x550000, 0.4, True))

    elif sensor_type == "SPS030":

        # initialize sensor
        while True:
            try:
                sensor = Sensirion(
                    retries=1, pins=pins,
                    id=serial_id)  # automatically starts measurement
                break
            except SensirionException as e:
                status_logger.exception("Failed to read from sensor SPS030")
                blink_led((0x550000, 0.4, True))
                time.sleep(1)

        # warm up time  - readings are not logged
        while init_count < init_time:
            try:
                time.sleep(1)
                sensor.read()
                init_count += 1
            except SensirionException as e:
                status_logger.exception("Failed to read from sensor SPS030")
                blink_led((0x550000, 0.4, True))

    # start a periodic timer interrupt to poll readings every second
    processing_alarm = Timer.Alarm(process_readings,
                                   arg=(sensor_type, sensor, sensor_logger,
                                        status_logger),
                                   s=1,
                                   periodic=True)
Example #4
0
def get_sensor_averages(logger, lora):
    """
    Gets averages for specific columns of defined sensor data, loads them into a line and appends it to the file which
    the LoRa thread sends data from.
    A sensor is defined if it is ticked in the configuration and has data to be sent (has a current.csv file).
    :type logger: LoggerFactory object (status_logger)
    :param is_def: Stores which sensors are defined in the form "sensor_name" : True/False
    :type is_def: dict
    """

    logger.debug("Calculating averages")

    # get a dictionary of sensors and their status
    sensors = get_sensors()
    fmt = get_format(sensors)
    version = str(config.get_config("version"))
    timestamp = s.csv_timestamp_template.format(
        *time.gmtime())  # get current time in desired format
    minutes = str(minutes_of_the_month())  # get minutes past last midnight

    try:
        sensor_averages = {}
        for sensor_name in [s.TEMP, s.PM1, s.PM2]:
            if sensors[sensor_name]:
                sensor_averages.update(calculate_average(sensor_name, logger))

        # Append averages to the line to be sent over LoRa according to which sensors are defined.
        line_to_log = '{}' + fmt + ',' + version + ',' + minutes
        for sensor_name in [s.TEMP, s.PM1, s.PM2]:
            if sensors[sensor_name]:
                line_to_log += ',' + str(
                    config.get_config(sensor_name + "_id")) + ',' + ','.join(
                        sensor_averages[sensor_name + "_avg"]) + ',' + str(
                            sensor_averages[sensor_name + "_count"])
        line_to_log += '\n'

        # Logs line_to_log to archive and places copies into relevant to_send folders
        log_averages(line_to_log.format(timestamp + ','))
        if config.get_config("LORA") == "ON":
            year_month = timestamp[2:4] + "," + timestamp[5:7] + ','
            lora.lora_buffer.write(line_to_log.format(year_month))

        # If raw data was processed, saved and dumped, processing files can be deleted
        path = s.processing_path
        for sensor_name in [s.TEMP, s.PM1, s.PM2]:
            if sensors[sensor_name]:
                filename = sensor_name + '.csv'
                try:
                    os.remove(path + filename)
                except Exception as e:
                    logger.exception("Failed to remove " + filename + " in " +
                                     path)

    except Exception as e:
        logger.exception("Failed to flash averages")
        blink_led((0x550000, 0.4, True))
Example #5
0
def get_sensor_averages(logger, lora):
    """
    Takes the averages of sensor readings and constructs a line to log to the SD card, terminal and lora buffer
    :param logger: status logger
    :type logger: LoggerFactory object
    :param lora: LoRaWAN object, False if lora is not enabled
    :type lora: LoRaWAN object
    """

    logger.debug("Calculating averages")

    # get a dictionary of sensors and their status
    sensors = get_sensors()
    fmt = get_format(sensors)
    version = str(config.get_config("fmt_version"))
    timestamp = s.csv_timestamp_template.format(
        *time.gmtime())  # get current time in desired format
    minutes = str(minutes_of_the_month())  # get minutes past last midnight

    try:
        sensor_averages = {}
        for sensor_name in [s.TEMP, s.PM1, s.PM2]:
            if sensors[sensor_name]:
                sensor_averages.update(calculate_average(sensor_name, logger))

        # Append averages to the line to be sent over LoRa according to which sensors are defined.
        line_to_log = '{}' + fmt + ',' + version + ',' + minutes
        for sensor_name in [s.TEMP, s.PM1, s.PM2]:
            if sensors[sensor_name]:
                line_to_log += ',' + str(
                    config.get_config(sensor_name + "_id")) + ',' + ','.join(
                        sensor_averages[sensor_name + "_avg"]) + ',' + str(
                            sensor_averages[sensor_name + "_count"])
        line_to_log += '\n'

        # Logs line_to_log to archive and places copies into relevant to_send folders
        log_averages(line_to_log.format(timestamp + ','))
        if lora is not False:
            year_month = timestamp[2:4] + "," + timestamp[5:7] + ','
            lora.lora_buffer.write(line_to_log.format(year_month))

        # If raw data was processed, saved and dumped, processing files can be deleted
        path = s.processing_path
        for sensor_name in [s.TEMP, s.PM1, s.PM2]:
            if sensors[sensor_name]:
                filename = sensor_name + '.csv'
                try:
                    os.remove(path + filename)
                except Exception as e:
                    pass

    except Exception as e:
        logger.exception("Failed to flash averages")
        blink_led((0x550000, 0.4, True))
Example #6
0
def get_random_time():
    """
    Get random number of seconds within interval
    :return: s_to_next_lora
    :rtype: int
    """

    # get random number of seconds within (interval - lora_timeout) and add one so it cannot be zero
    s_to_next_lora = int((machine.rng() / (2**24)) *
                         (int(float(config.get_config("interval")) * 60) -
                          int(config.get_config("lora_timeout")))) + 1
    return s_to_next_lora
Example #7
0
    def get_random_time(self):
        # get random number of seconds within interval, add one so it cannot be zero
        s_to_next_lora = int(
            machine.rng() /
            (2**24) * int(float(config.get_config("interval")) * 60)) + 1

        return s_to_next_lora
Example #8
0
def get_time(rtc, logger):

    if gps_lock.locked():
        logger.debug("Waiting for other gps thread to finish")
    with gps_lock:
        logger.info("Getting UTC datetime via GPS")

        serial, chrono, indicator_led = gps_init(logger)  # initialize serial and timer
        com_counter = int(chrono.read())  # counter for checking whether gps is connected
        message = False  # no message while terminal is disabled (by default)

        while True:
            # data_in = '$GPRMC,085258.000,A,5056.1384,N,00123.1522,W,0.00,159.12,200819,,,A*7E\r\n'
            data_in = (str(serial.readline()))[1:]

            if (int(chrono.read()) - com_counter) >= 10:
                gps_deinit(serial, logger, message, indicator_led)
                logger.error("GPS enabled, but not connected")
                return False

            if data_in[1:4] != "$GP":
                time.sleep(1)
            else:
                for char in data_in:
                    sentence = gps.update(char)
                    if sentence == "GPRMC":
                        com_counter = int(chrono.read())
                        if gps.valid:

                            # Set current time on pycom - convert seconds (timestamp[2]) from float to int
                            datetime = (int('20' + str(gps.date[2])), gps.date[1], gps.date[0], gps.timestamp[0],
                                        gps.timestamp[1], int(gps.timestamp[2]), 0, 0)
                            rtc.init(datetime)

                            # Set current time on RTC module if connected - convert seconds (timestamp[2]) from float to int
                            h_day, h_mnth, h_yr = int(str(gps.date[0]), 16), int(str(gps.date[1]), 16), int(str(gps.date[2]),
                                                                                                            16)
                            h_hr, h_min, h_sec = int(str(gps.timestamp[0]), 16), int(str(gps.timestamp[1]), 16), int(
                                str(int(gps.timestamp[2])), 16)
                            try:
                                clock.set_time(h_yr, h_mnth, h_day, h_hr, h_min, h_sec)
                                message = """GPS UTC datetime successfully updated on pycom board 
                                            GPS UTC datetime successfully updated on RTC module"""
                            except Exception:
                                message = """GPS UTC datetime successfully updated on pycom board 
                                            Failed to set GPS UTC datetime on the RTC module"""

                            gps_deinit(serial, logger, message, indicator_led)
                            return True

            # If timeout elapsed exit function or thread
            if chrono.read() >= int(config.get_config("GPS_timeout")):
                gps_deinit(serial, logger, message, indicator_led)
                logger.error("""GPS timeout
                Check if GPS module is connected
                Place device under clear sky
                Increase GPS timeout in configurations""")
                return False
Example #9
0
    def periodic_event(self, arg):
        if self.data_type == "gps":
            # Get position from gps to be sent over LoRA
            _thread.start_new_thread(GpsSIM28.get_position,
                                     (self.logger, self.lora))

        elif self.data_type == "sensors":
            # Put new averages in lora buffer and also print to terminal.
            get_sensor_averages(logger=self.logger,
                                sensor_loggers=self.sensor_loggers,
                                lora=self.lora)

            if self.lora is not False:  # Schedule LoRa messages if LoRa is enabled
                # if device was last transmitting a day or more ago, reset message_count for the day
                today = time.gmtime()
                date = str(today[0]) + str(today[1]) + str(today[2])
                if self.lora.transmission_date != date:
                    self.lora.message_count = 0
                    self.lora.transmission_date = date
                    config.save_config({
                        "message_count": self.lora.message_count,
                        "transmission_date": date
                    })

                # send 2, 3 or at most 4 messages per interval based on length of interval
                lora_slot = int(
                    float(config.get_config("interval")) *
                    60) // 30  # lora_rate changes for each 30 seconds
                max_lora_slot = self.lora.message_limit // 96  # max number of msg per interval optimized around 15 min
                if max_lora_slot < 2:
                    max_lora_slot = 2
                if lora_slot < 2:
                    raise Exception("Interval has to be at least a minute")
                else:
                    lora_rate = lora_slot
                    if lora_slot > max_lora_slot:
                        lora_rate = max_lora_slot

                waiting = self.lora.lora_buffer.size(
                    lora_rate
                )  # check number of messages in stack (up to max to send)
                remaining = self.lora.message_limit - self.lora.message_count  # check how many more we can send today
                if remaining <= 0:
                    self.logger.info("LoRa message limit reached for the day")
                if remaining - waiting >= 0:
                    count = waiting  # if we have more than we want to send, send them all
                else:
                    count = remaining  # if we have less than we want to send, send up to the limit
                for val in range(
                        count
                ):  # Schedule up to 4 randomly timed messages within interval
                    self.random_alarm = Timer.Alarm(self.random_event,
                                                    s=get_random_time(),
                                                    periodic=False)

        else:
            raise Exception("Non existent data type")
Example #10
0
def get_sensors():

    sensors = {s.TEMP: False, s.PM1: False, s.PM2: False}

    for sensor in sensors:
        if config.get_config(sensor) != "OFF":
            sensors[sensor] = True

    return sensors
Example #11
0
def software_update(logger):

    with wifi_lock:
        try:
            logger.info("Over the Air update started")

            led_lock.acquire(1)  # disable all other indicator LEDs
            pycom.rgbled(0x555500)  # Yellow LED

            # Get credentials from configuration
            ssid = config.get_config("SSID")
            password = config.get_config("wifi_password")
            server_ip = config.get_config("server")
            port = int(config.get_config("port"))

            print(ssid, password, server_ip, port)

            # Perform OTA update
            ota = WiFiOTA(ssid, password, server_ip, port)

            # Turn off WiFi to save power
            w = WLAN()
            w.deinit()

            ota.connect()
            ota.update()

            time.sleep(5)

        except Exception as e:
            logger.exception("Failed to update the device")
            pycom.rgbled(0x550000)
            time.sleep(3)

        finally:
            # Turn off update mode
            config.save_configuration({"update": False})

            pycom.rgbled(0x000000)
            led_lock.release()

            # Reboot the device to apply patches
            machine.reset()
Example #12
0
def get_logging_level():
    logging_lvl = config.get_config("logging_lvl")
    if logging_lvl == "Critical":
        return CRITICAL
    elif logging_lvl == "Error":
        return ERROR
    elif logging_lvl == "Warning":
        return WARNING
    elif logging_lvl == "Info":
        return INFO
    elif logging_lvl == "Debug":
        return DEBUG
Example #13
0
def get_sensors():
    """
    Dictionary of sensors (TEMP, PM1, PM2) and whether they are enabled in the configurations
    :return: sensors
    :rtype: dict
    """
    sensors = {s.TEMP: False, s.PM1: False, s.PM2: False}

    for sensor in sensors:
        if config.get_config(sensor) != "OFF":
            sensors[sensor] = True

    return sensors
Example #14
0
    def __init__(self, logger, data_type, lora):

        #  Arguments
        self.logger = logger
        self.data_type = data_type
        self.lora = lora

        #  Attributes
        if self.data_type == "sensors":
            self.interval_s = int(float(config.get_config("interval")) * 60)
            if self.interval_s < 15 * 60:
                self.logger.warning(
                    "Interval is less than 15 mins - real time transmission is not guaranteed"
                )
        elif self.data_type == "gps":
            self.interval_s = int(float(config.get_config("GPS_freq")) * 3600)
        self.s_to_next_lora = None
        self.first_alarm = None
        self.periodic_alarm = None
        self.random_alarm = None

        #  Start scheduling events
        self.start_events()
Example #15
0
    def __init__(self, logger, data_type, lora, sensor_loggers=[]):
        """
        Schedules events for calculating averages or getting GPS position and also schedules random LoRa messages for
        each interval
        :param logger: status logger
        :type logger: LoggerFactory object
        :param data_type: "sensors" or "gps"
        :type data_type: str
        :param lora: LoRaWan object, False if lora is not enabled
        :type lora: LoRaWAN object
        :param sensor_loggers: A list of sensor loggers if data_type is "sensors"
        :type sensor_loggers: list
        """
        self.logger = logger
        self.sensor_loggers = sensor_loggers
        self.data_type = data_type
        self.lora = lora

        if self.data_type == "sensors":
            self.interval_s = int(float(config.get_config("interval")) * 60)
            if self.interval_s < 15 * 60:
                self.logger.warning(
                    "Interval is less than 15 mins - real time transmission is not guaranteed"
                )
        elif self.data_type == "gps":
            self.interval_s = int(
                float(config.get_config("GPS_period")) * 3600)

        self.s_to_next_lora = None
        self.periodic_alarm = None
        self.random_alarm = None

        # Start scheduling events - cCalculate time (s) until the first event, and set up an alarm
        first_event_s = seconds_to_first_event(self.interval_s)
        self.first_alarm = Timer.Alarm(self.first_event,
                                       s=first_event_s,
                                       periodic=False)
Example #16
0
    def __init__(self, sensor_logger, status_logger):

        self.sensor_logger = sensor_logger
        self.status_logger = status_logger

        # Initialise i2c - bus no., type, baudrate, i2c pins
        self.i2c = I2C(0, I2C.MASTER, baudrate=9600, pins=('P9', 'P10'))
        self.address = 0x45

        # get one sensor reading upon init to catch any errors and calibrate the sensor
        self.read()
        # start a periodic timer interrupt to poll readings at a frequency
        self.processing_alarm = Timer.Alarm(
            self.process_readings,
            s=int(config.get_config("TEMP_period")),
            periodic=True)
Example #17
0
def get_logging_level():
    """
    Get logging level from configurations
    :return: logging level
    :rtype: str
    """
    logging_lvl = config.get_config("logging_lvl")
    if logging_lvl == "Critical":
        return CRITICAL
    elif logging_lvl == "Error":
        return ERROR
    elif logging_lvl == "Warning":
        return WARNING
    elif logging_lvl == "Info":
        return INFO
    elif logging_lvl == "Debug":
        return DEBUG
Example #18
0
    def __init__(self, sensor_name, terminal_out=True, terminator='\n'):
        """
        :param sensor_name: sensor name
        :type sensor_name: str
        :param terminal_out: print output to terminal
        :type terminal_out: bool
        :param terminator: end of line character
        :type terminator: object
        """
        self.terminal_out = terminal_out
        self.terminator = terminator
        self.sensor_name = sensor_name
        self.sensor_type = config.get_config(sensor_name)

        # Average data that we return if there isn't enough readings to create one
        self.zeroed_data = list('0' *
                                len(s.lora_sensor_headers[self.sensor_type]))

        # We use a lock since log_row and get_averages will be called from diff threads
        self.average_list_lock = _thread.allocate_lock()

        self.average_list = []
        self.init_averages()
Example #19
0
def calculate_average(sensor_name, logger):
    """
    Calculates averages for specific columns of sensor data to be sent over LoRa. Sets placeholders if it fails.
    :param sensor_name: PM1, PM2 or TEMP
    :type sensor_name: str
    :param logger: status logger
    :type logger: LoggerFactory object
    """

    filename = sensor_name + '.csv'
    sensor_type = config.get_config(sensor_name)
    sensor_id = str(config.get_config(sensor_name + "_id"))
    # headers in current file of the sensor according to its type
    headers = s.headers_dict_v4[sensor_type]

    # data to send if no readings are available
    avg_readings_str = list('0' * len(s.lora_sensor_headers[sensor_type]))
    count = 0

    try:
        with current_lock:
            # Move sensor_name.csv from current dir to processing dir
            os.rename(s.current_path + filename, s.processing_path + filename)

            with open(s.processing_path + filename, 'r') as f:
                # read all lines from processing
                lines = f.readlines()
                lines_lst = [
                ]  # list of lists that store average sensor readings from specific columns
                for line in lines:
                    stripped_line = line[:-1]  # strip \n
                    stripped_line_lst = str(stripped_line).split(
                        ',')  # split string to list at comas
                    named_line = dict(zip(
                        headers,
                        stripped_line_lst))  # assign each value to its header
                    sensor_reading = []
                    for header in s.lora_sensor_headers[sensor_type]:
                        sensor_reading.append(int(named_line[header]))
                    # Append extra lines here for more readings - update version number and back-end to interpret data
                    lines_lst.append(sensor_reading)
                    count += 1

                # Compute averages from sensor_name.csv.processing
                avg_readings_str = [
                    str(int(i)) for i in mean_across_arrays(lines_lst)
                ]

                # Append content of sensor_name.csv.processing into sensor_name.csv
                with open(
                        s.archive_path + sensor_name + '_' + sensor_id +
                        '.csv', 'a') as f:
                    for line in lines:
                        f.write(line)

    except Exception as e:
        logger.error("No readings from sensor {}".format(sensor_name))
        logger.warning("Setting 0 as a place holder")
        blink_led((0x550000, 0.4, True))
    finally:
        return {
            sensor_name + "_avg": avg_readings_str,
            sensor_name + "_count": count
        }
Example #20
0
def get_html_form():
    """
    Constructs a webpage that includes a form to fill in by the user to acquire new configurations for the device
    :return: html_form
    :rtype: str
    """

    if config.get_config("LORA") == "ON":
        lora_check = " checked"
    else:
        lora_check = ""

    selected_region = {
        "Europe": "",
        "Asia": "",
        "Australia": "",
        "United States": ""
    }
    for option in selected_region:
        if option == config.get_config("region"):
            selected_region[option] = " selected"

    selected_TEMP = {"SHT35": "", "OFF": ""}
    for option in selected_TEMP:
        if option == config.get_config(s.TEMP):
            selected_TEMP[option] = " selected"

    selected_PM1 = {"PMS5003": "", "SPS030": "", "OFF": ""}
    for option in selected_PM1:
        if option == config.get_config(s.PM1):
            selected_PM1[option] = " selected"

    selected_PM2 = {"PMS5003": "", "SPS030": "", "OFF": ""}
    for option in selected_PM2:
        if option == config.get_config(s.PM2):
            selected_PM2[option] = " selected"

    selected_GPS = {"SIM28": "", "OFF": ""}
    for option in selected_GPS:
        if option == config.get_config("GPS"):
            selected_GPS[option] = " selected"

    selected_logging = {
        "Critical": "",
        "Error": "",
        "Warning": "",
        "Info": "",
        "Debug": ""
    }
    for level in selected_logging:
        if level == config.get_config("logging_lvl"):
            selected_logging[level] = " selected"

    hardware_test_on = "selected" if config.get_config(
        "hardware_test").lower() == "yes" else ""
    hardware_test_off = "selected" if hardware_test_on == "" else ""

    with open("/flash/lib/setup/form.html", "r") as html_file:
        html = html_file.read()

    # This is quite cumbersome but allows for easy insertion into the html.
    # If you want to add another variable into the HTML form, all you have to do is
    # refer to it like {this}, and then in the dict below add a line like this:
    # "this": variable_you_want_to_insert,
    # Take note that in the html you surround it with {} but you dont in the dict.

    replacements = {
        "unique_id": str(config.get_config("device_id")),
        "device_name": str(config.get_config("device_name")),
        "password": str(config.get_config("password")),
        "config_timeout": str(config.get_config("config_timeout")),
        "device_eui": str(config.get_config("device_eui")),
        "application_eui": str(config.get_config("application_eui")),
        "app_key": str(config.get_config("app_key")),
        "fair_access": str(config.get_config("fair_access")),
        "air_time": str(config.get_config("air_time")),
        "SSID": str(config.get_config("SSID")),
        "wifi_password": str(config.get_config("wifi_password")),
        "TEMP_id": str(config.get_config("TEMP_id")),
        "TEMP_period": str(config.get_config("TEMP_period")),
        "PM1_id": str(config.get_config("PM1_id")),
        "PM1_init": str(config.get_config("PM1_init")),
        "interval": str(config.get_config("interval")),
        "PM2_id": str(config.get_config("PM2_id")),
        "PM2_init": str(config.get_config("PM2_init")),
        "GPS_id": str(config.get_config("GPS_id")),
        "GPS_timeout": str(config.get_config("GPS_timeout")),
        "GPS_period": str(config.get_config("GPS_period")),
        "lora_check": lora_check,
        "eu_selected": str(selected_region["Europe"]),
        "as_selected": str(selected_region["Asia"]),
        "au_selected": str(selected_region["Australia"]),
        "us_selected": str(selected_region["United States"]),
        "temp_selected_SHT35": str(selected_TEMP["SHT35"]),
        "temp_selected_off": str(selected_TEMP["OFF"]),
        "PM1_PMS5003_selected": str(selected_PM1["PMS5003"]),
        "PM1_SPS030_selected": str(selected_PM1["SPS030"]),
        "PM1_selected_off": str(selected_PM1["OFF"]),
        "PM2_PMS5003_selected": str(selected_PM2["PMS5003"]),
        "PM2_SPS030_selected": str(selected_PM2["SPS030"]),
        "PM2_selected_off": str(selected_PM2["OFF"]),
        "gps_selected_SIM28": str(selected_GPS["SIM28"]),
        "gps_selected_off": str(selected_GPS["OFF"]),
        "logging_level_selected_critical": str(selected_logging["Critical"]),
        "logging_level_selected_error": str(selected_logging["Error"]),
        "logging_level_selected_warning": str(selected_logging["Warning"]),
        "logging_level_selected_info": str(selected_logging["Info"]),
        "logging_level_selected_debug": str(selected_logging["Debug"]),
        "hardware_test_on": hardware_test_on,
        "hardware_test_off": hardware_test_off,
    }

    for where, to_copy in replacements.items():
        html = html.replace("{" + where + "}", to_copy)

    return html
Example #21
0
def get_position(logger, lora):

    if gps_lock.locked():
        logger.debug("Waiting for other gps thread to finish")
    with gps_lock:
        logger.info("Getting position via GPS")

        serial, chrono, indicator_led = gps_init(logger)
        com_counter = int(chrono.read())  # counter for checking whether gps is connected
        message = False

        while True:
            # data_in = '$GPGGA,085259.000,5056.1384,N,00123.1522,W,1,8,1.17,25.1,M,47.6,M,,*7D\r\n'
            data_in = (str(serial.readline()))[1:]

            if (int(chrono.read()) - com_counter) >= 10:
                gps_deinit(serial, logger, message, indicator_led)
                logger.error("GPS enabled, but not connected")
                return False

            if data_in[1:4] != "$GP":
                time.sleep(1)
            else:
                for char in data_in:
                    sentence = gps.update(char)
                    if sentence == "GPGGA":
                        com_counter = int(chrono.read())

                        # set aim for the quality of the signal based on the time elapsed
                        elapsed = chrono.read() / int(config.get_config("GPS_timeout"))

                        hdop_aim = [1, 1.2, 1.5, 1.8, 2, 2.5, 3, 4, 5, 6, 7]
                        time_limit = [0.4, 0.45, 0.5, 0.55, 0.6, 0.65, 0.7, 0.75, 0.8, 0.85, 0.9]

                        for index in range(len(time_limit)):
                            if elapsed < time_limit[index]:
                                break

                        # Process data only if quality of signal is great
                        if 0 < gps.hdop <= hdop_aim[index] and gps.satellites_in_use >= 3:

                            latitude = gps.latitude[0] + gps.latitude[1]/60
                            if gps.latitude[2] == 'S':
                                latitude = -latitude

                            longitude = gps.longitude[0] + gps.longitude[1]/60
                            if gps.longitude[2] == 'W':
                                longitude = -longitude

                            message = """Successfully acquired location from GPS
                            Satellites used: {}
                            HDOP: {}
                            Latitude: {}
                            Longitude: {}
                            Altitude: {}""".format(gps.satellites_in_use, gps.hdop, latitude, longitude, gps.altitude)

                            # Process GPS location
                            timestamp = s.csv_timestamp_template.format(*time.gmtime())  # get current time in desired format
                            lst_to_log = [timestamp, latitude, longitude, gps.altitude]
                            str_lst_to_log = list(map(str, lst_to_log))  # cast to string
                            line_to_log = ','.join(str_lst_to_log) + '\n'

                            # Print to terminal and log to archive
                            sys.stdout.write(s.GPS + " - " + line_to_log)
                            with open(s.archive_path + s.GPS + '.csv', 'a') as f_archive:
                                f_archive.write(line_to_log)

                            if config.get_config("LORA") == "ON":
                                # get year and month from timestamp
                                year_month = timestamp[2:4] + "," + timestamp[5:7] + ','

                                # get minutes since start of the month
                                minutes = str(minutes_of_the_month())

                                # Construct LoRa message
                                line_to_log = year_month + 'G,' + str(config.get_config("version")) + ',' + minutes + ',' \
                                              + str(config.get_config("GPS_id")) + ',' + ','.join(str_lst_to_log[1:]) + '\n'

                                # Logs line_to_log to be sent over lora
                                lora.lora_buffer.write(line_to_log)

                            gps_deinit(serial, logger, message, indicator_led)
                            return True

            # If timeout elapsed exit function or thread
            if chrono.read() >= int(config.get_config("GPS_timeout")):
                gps_deinit(serial, logger, message, indicator_led)
                logger.error("""GPS timeout
                Check if GPS module is connected
                Place device under clear sky
                Increase GPS timeout in configurations""")
                return False
Example #22
0
    def __init__(self, logger):
        """
        Connects to LoRaWAN using OTAA and sets up a ring buffer for storing messages.
        :param logger: status logger
        :type logger: LoggerFactory object
        """

        self.logger = logger
        self.message_limit = int(
            float(config.get_config("fair_access")) /
            (float(config.get_config("air_time")) / 1000))
        self.transmission_date = config.get_config(
            "transmission_date")  # last date when lora was transmitting
        today = time.gmtime()
        date = str(today[0]) + str(today[1]) + str(today[2])
        if self.transmission_date == date:  # if device was last transmitting today
            self.message_count = config.get_config(
                "message_count")  # get number of messages sent today
        else:
            self.message_count = 0  # if device was last transmitting a day or more ago, reset message_count for the day
            self.transmission_date = date
            config.save_config({
                "message_count": self.message_count,
                "transmission_date": date
            })

        regions = {
            "Europe": LoRa.EU868,
            "Asia": LoRa.AS923,
            "Australia": LoRa.AU915,
            "United States": LoRa.US915
        }
        region = regions[config.get_config("region")]

        self.lora = LoRa(mode=LoRa.LORAWAN, region=region, adr=True)

        # create an OTAA authentication parameters
        app_eui = ubinascii.unhexlify(config.get_config("application_eui"))
        app_key = ubinascii.unhexlify(config.get_config("app_key"))

        # join a network using OTAA (Over the Air Activation)
        self.lora.join(activation=LoRa.OTAA,
                       auth=(app_eui, app_key),
                       timeout=0)

        # create a LoRa socket
        self.lora_socket = socket.socket(socket.AF_LORA, socket.SOCK_RAW)

        # request acknowledgment of data sent
        # self.lora_socket.setsockopt(socket.SOL_LORA, socket.SO_CONFIRMED, True)

        # do not request acknowledgment of data sent
        self.lora_socket.setsockopt(socket.SOL_LORA, socket.SO_CONFIRMED,
                                    False)

        # sets timeout for sending data
        self.lora_socket.settimeout(
            int(config.get_config("lora_timeout")) * 1000)

        # set up callback for receiving downlink messages
        self.lora.callback(trigger=LoRa.RX_PACKET_EVENT,
                           handler=self.lora_recv)

        # initialises circular lora stack to back up data up to about 22.5 days depending on the length of the month
        self.lora_buffer = RingBuffer(self.logger, s.processing_path,
                                      s.lora_file_name,
                                      31 * self.message_limit, 100)

        try:  # this fails if the buffer is empty
            self.check_date()  # remove messages that are over a month old
        except Exception as e:
            pass
def software_update(logger):
    """
    Connects to the wlan and fetches updates from a server. After having applied the patches successfully, it reboots
    the device.
    :param logger: status logger
    :type logger: LoggerFactory object
    """

    with wifi_lock:
        try:
            logger.info("Over the Air update started")

            led_lock.acquire(1)  # disable all other indicator LEDs
            pycom.rgbled(0x555500)  # Yellow LED

            # Get credentials from configuration
            ssid = config.get_config("SSID")
            password = config.get_config("wifi_password")
            server_ip = config.get_config("server")
            port = int(config.get_config("port"))

            logger.info("SSID: " + str(ssid))
            logger.info("server_ip: " + str(server_ip))
            logger.info("port: " + str(port))

            version = config.get_config("code_version")

            # Perform OTA update
            ota = WiFiOTA(logger, ssid, password, server_ip, port, version)

            # Turn off WiFi to save power
            w = WLAN()
            w.deinit()

            logger.info("connecting...")
            ota.connect()
            if ota.update():
                new_version = str(
                    ota.get_current_version())  # get updated version
                config.save_config({"code_version": str(new_version)
                                    })  # save new version to config on SD
                logger.info(
                    "Successfully updated the device from version {} to version {}"
                    .format(version, new_version))

        except Exception as e:
            logger.exception("Failed to update the device")
            pycom.rgbled(0x550000)  # turn LED to RED
            time.sleep(3)

        finally:
            # Turn off update mode
            config.save_config({"update": False})

            # Turn off indicator LED
            pycom.rgbled(0x000000)
            led_lock.release()

            # Reboot the device to apply patches
            logger.info("rebooting...")
            machine.reset()
Example #24
0
def get_html_form():
    """
    Constructs a webpage that includes a form to fill in by the user to acquire new configurations for the device
    :return: html_form
    :rtype: str
    """

    if config.get_config("LORA") == "ON":
        lora_check = " checked"
    else:
        lora_check = ""

    selected_region = {
        "Europe": "",
        "Asia": "",
        "Australia": "",
        "United States": ""
    }
    for option in selected_region:
        if option == config.get_config("region"):
            selected_region[option] = " selected"

    selected_TEMP = {"SHT35": "", "OFF": ""}
    for option in selected_TEMP:
        if option == config.get_config(s.TEMP):
            selected_TEMP[option] = " selected"

    selected_PM1 = {"PMS5003": "", "SPS030": "", "OFF": ""}
    for option in selected_PM1:
        if option == config.get_config(s.PM1):
            selected_PM1[option] = " selected"

    selected_PM2 = {"PMS5003": "", "SPS030": "", "OFF": ""}
    for option in selected_PM2:
        if option == config.get_config(s.PM2):
            selected_PM2[option] = " selected"

    selected_GPS = {"SIM28": "", "OFF": ""}
    for option in selected_GPS:
        if option == config.get_config("GPS"):
            selected_GPS[option] = " selected"

    selected_logging = {
        "Critical": "",
        "Error": "",
        "Warning": "",
        "Info": "",
        "Debug": ""
    }
    for level in selected_logging:
        if level == config.get_config("logging_lvl"):
            selected_logging[level] = " selected"

    html_form = '''<!DOCTYPE html>
    <html>
      <head>
      <title>PyonAir Configuration</title>
      <style>
      body{
          background-color: #252525;
          margin-left: 25px;
          margin-top: 30px;
          margin-bottom: 45px;
          font-family: helvetica;
          color: white;
          font-size: 15px;
          }
        h1{
          margin-bottom: 0px;
          max-height: 999999px; //disables font boost in android
        }
        p{
          font-size: 20px;
          margin-left: 5px;
          margin-bottom: 0px;
          margin-top: 2em;
          max-height: 999999px; //disables font boost in android
        }
        hr {
            display: block;
            height: 1px;
            width: 270px;
            border: 0;
            border-top: 1px solid #909090;
            margin-top: 0.1em;
            margin-left: -5px;
        }
        .p_line{
          padding-bottom: 0.8em;
        }
        form {
          margin-left: 5px;
        }
        label {
          display: block;
          margin-top: 1em;
          max-height: 999999px; //disables font boost in android
        }
        .settings{
          margin-left: 30px;
        }
        .lora_grid1{
          display: grid;
          grid-template-columns: 120px 100px;
        }
       .lora_grid2{
          display: grid;
          grid-template-columns: 95px 100px;
        }
        .pm_sensors{
          margin-left: 15px;
          display: grid;
          grid-template-columns: 390px 110px;
        }
        .pm_sensors .pm_sensor_label{
          margin-top: 1em;
        }
        .pm_sensors hr{
          width: 360px;
        }
        .input_text{
          margin-top: 5px;
          box-sizing: border-box;
          height: 1.8em;
          padding: 2px 4px;
          width: 200px;
        }
        .input_checkbox{
          margin-top: 0.6em;
          margin-left: 5px;
          transform: scale(1.6);
        }
        select{
          margin-top: 5px;
          width: 110px;
          height: 1.8em;
        }
        .sensor{
          display: grid;
          grid-template-columns: 150px 110px 110px 110px;
          margin-left: 10px;
          margin-top: -0.5em;
          margin-bottom: 1.8em;
        }
        .input_number{
          margin-top: 5px;
          box-sizing: border-box;
          height: 1.8em;
          padding: 2px 4px;
          width: 70px;
        }
        .pm_sensors .sensor{
            grid-template-columns: 150px 110px 110px;
            margin-left: -5px;
            margin-bottom: 0;
            margin-top: -1.3em;
        }
        .grid_item3{
          grid-column-start: 2;
          grid-column-end: 3;
          grid-row-start: 1;
          grid-row-end: 4;
          margin-top: 50%;
          margin-left: -5px;
        }
        .sensor_settings{
          width: 530px;
        }
        .interval_hr{
          width: 380px;
        }
        .gps_hr{
          width: 480px;
        }
      </style>
      </head>
      <body>
        <form class="config_form">
          <h1>PyonAir Configuration</h1>
          <p>General Settings</p>
          <hr class="p_line"/>
          <div class="settings">
            <label>Unique ID: ''' + str(
        config.get_config("device_id")) + '''</label>
            <label for="device_name">Device Name</label>
            <input class="input_text" id="device_name" name="device_name" type="text" value="''' + str(
            config.get_config(
                "device_name")) + '''" required="required" maxlength="32"/>
            <label for="password">New Password</label>
            <input class="input_text" id="password" name="password" type="password" value="''' + str(
                    config.get_config("password")
                ) + '''" required="required" maxlength="32"/>
            <label for="config_timeout">Config Timeout (m)</label>
            <input class="input_number" id="config_timeout" name="config_timeout" type="number" value="''' + str(
                    config.get_config("config_timeout")
                ) + '''" required="required" min="3" max="120" step="0.01"/>
          </div>
          <p>LoRaWAN Configuration</p>
          <hr class="p_line"/>
          <div class="settings">
            <label>Device EUI: ''' + str(
                    config.get_config("device_eui")) + '''</label>
            <label for="application_eui">Application EUI</label>
            <input class="input_text" id="application_eui" name="application_eui" type="text" value="''' + str(
                        config.get_config("application_eui")
                    ) + '''" required="required" maxlength="16"/>
            <label for="app_key">App Key </label>
            <input class="input_text" id="app_key" name="app_key" type="password" value="''' + str(
                        config.get_config("app_key")
                    ) + '''" required="required" maxlength="32"/>
            <div class = "lora_grid1">
              <div>
                <label for="fair_access">Fair Access (s)</label>
                <input class="input_number" id="fair_access" name="fair_access" type="number" value="''' + str(
                        config.get_config("fair_access")
                    ) + '''" required="required" min="0" max="65535"/>
              </div>
              <div>
                <label for="air_time">Air Time (ms)</label>
                <input class="input_number" id="air_time" name="air_time" type="number" value="''' + str(
                        config.get_config("air_time")
                    ) + '''" required="required" min="0" max="5000"/>
              </div>
            </div>
            <div class = "lora_grid2">
              <div>
                <label for="LORA">On?</label>
                <input class="input_checkbox" type="checkbox" name="LORA" value="ON"''' + lora_check + '''>
              </div>
              <div>
                <label for="region">Region</label>
                <select name="region">
                  <option''' + str(
                        selected_region["Europe"]) + '''>Europe</option>
                  <option''' + str(selected_region["Asia"]) + '''>Asia</option>
                  <option''' + str(selected_region["Australia"]
                                   ) + '''>Australia</option>
                  <option''' + str(selected_region["United States"]
                                   ) + '''>United States</option>
                </select>
              </div>
            </div>
          </div>
          <p>WiFi Configuration</p>
          <hr class="p_line"/>
          <div class="settings">
            <label for="SSID">SSID</label>
            <input class="input_text" id="SSID" name="SSID" type="text" value="''' + str(
                                       config.get_config("SSID")
                                   ) + '''" required="required" maxlength="32"/>
            <label for="wifi_password">Password</label>
            <input class="input_text" id="wifi_password" name="wifi_password" type="password" value="''' + str(
                                       config.get_config("wifi_password")
                                   ) + '''" required="required" maxlength="128"/>
          </div>
          <p>Sensor Settings</p>
          <hr class="p_line sensor_settings"/>
          <div class="settings">
            <label>Temperature and Humidity Sensor</label>
            <hr class="interval_hr"/>
            <div class="sensor">
              <div>
                <label for="TEMP">Sensor Type</label>
                <select name="TEMP">
                  <option''' + str(selected_TEMP["SHT35"]) + '''>SHT35</option>
                  <option''' + str(selected_TEMP["OFF"]) + '''>OFF</option>
                </select>
              </div>
              <div>
                <label for="TEMP_id">Sensor ID</label>
                <input class="input_number" id="TEMP_id" name="TEMP_id" type="number" value="''' + str(
                                       config.get_config("TEMP_id")
                                   ) + '''" required="required" min="0" max="65535"/>
              </div>
              <div>
                <label for="TEMP_period">Period (s)</label>
                <input class="input_number" id="TEMP_period" name="TEMP_freq" type="number" value="''' + str(
                                       config.get_config("TEMP_period")
                                   ) + '''" required="required" min="1" max="120"/>
              </div>
            </div>
            <label>Particulate Matter Sensors</label>
            <hr class="interval_hr"/>
            <div class="pm_sensors">
                <div class="grid_item1">
                  <label class="pm_sensor_label">PM Sensor 1</label>
                  <hr/>
                </div>
                <div class="sensor grid_item2">
                  <div>
                    <label for="PM1">Sensor Type</label>
                    <select name="PM1">
                      <option''' + str(selected_PM1["PMS5003"]
                                       ) + '''>PMS5003</option>
                      <option''' + str(selected_PM1["SPS030"]
                                       ) + '''>SPS030</option>
                      <option''' + str(selected_PM1["OFF"]) + '''>OFF</option>
                    </select>
                  </div>
                  <div>
                    <label for="PM1_id">Sensor ID</label>
                    <input class="input_number" id="PM1_id" name="PM1_id" type="number" value="''' + str(
                                           config.get_config("PM1_id")
                                       ) + '''" required="required" min="0" max="65535"/>
                  </div>
                  <div>
                    <label for="PM1_init">Setup Time (s)</label>
                    <input class="input_number" id="PM1_init" name="PM1_init" type="number" value="''' + str(
                                           config.get_config("PM1_init")
                                       ) + '''" required="required" min="1" max="3600" step="0.01"/>
                  </div>
                </div>
                <div class="grid_item3">
                  <label for="interval">Interval (m)</label>
                  <input class="input_number" id="PM_interval" name="interval" type="number" value="''' + str(
                                           config.get_config("interval")
                                       ) + '''" required="required" min="1" max="120" step="0.01"/>
                </div>
                <div class="grid_item4">
                  <label class="pm_sensor_label">PM Sensor 2</label>
                  <hr/>
                </div>
                <div class="sensor grid_item5">
                  <div>
                    <label for="PM2">Sensor Type</label>
                    <select name="PM2">
                      <option''' + str(selected_PM2["PMS5003"]
                                       ) + '''>PMS5003</option>
                      <option''' + str(selected_PM2["SPS030"]
                                       ) + '''>SPS030</option>
                      <option''' + str(selected_PM2["OFF"]) + '''>OFF</option>
                    </select>
                  </div>
                  <div>
                    <label for="PM2_id">Sensor ID</label>
                    <input class="input_number" id="PM2_id" name="PM2_id" type="number" value="''' + str(
                                           config.get_config("PM2_id")
                                       ) + '''" required="required" min="0" max="65535"/>
                  </div>
                  <div>
                    <label for="PM2_init">Setup Time (s)</label>
                    <input class="input_number" id="PM2_init" name="PM2_init" type="number" value="''' + str(
                                           config.get_config("PM2_init")
                                       ) + '''" required="required" min="1" max="3600" step="0.01"/>
                  </div>
                </div>
              </div>
            <label>GPS</label>
            <hr class="gps_hr"/>
            <div class="sensor">
              <div>
                <label for="GPS">Sensor Type</label>
                <select name="GPS">
                  <option''' + str(selected_GPS["SIM28"]) + '''>SIM28</option>
                  <option''' + str(selected_GPS["OFF"]) + '''>OFF</option>
                </select>
              </div>
              <div>
                <label for="GPS_id">Sensor ID</label>
                <input class="input_number" id="GPS_id" name="GPS_id" type="number" value="''' + str(
                                           config.get_config("GPS_id")
                                       ) + '''" required="required" min="0" max="65535"/>
              </div>
              <div>
                <label for="GPS_timeout">Timeout (m)</label>
                <input class="input_number" id="GPS_timeout" name="GPS_timeout" type="number" value="''' + str(
                                           config.get_config("GPS_timeout")
                                       ) + '''" required="required" min="5" max="120" step="0.01"/>
              </div>
              <div>
                <label for="GPS_period">Period (h)</label>
                <input class="input_number" id="GPS_period" name="GPS_period" type="number" value="''' + str(
                                           config.get_config("GPS_period")
                                       ) + '''" required="required" min="0.1" max="8760" step="0.01"/>
              </div>
            </div>
          </div>
          <hr class="p_line sensor_settings"/>
          <label for="logging_lvl">Select Logging Level</label>
          <select id="logging_lvl" name="logging_lvl">
            <option''' + str(selected_logging["Critical"]
                             ) + '''>Critical</option>
            <option''' + str(selected_logging["Error"]) + '''>Error</option>
            <option''' + str(selected_logging["Warning"]
                             ) + '''>Warning</option>
            <option''' + str(selected_logging["Info"]) + '''>Info</option>
            <option''' + str(selected_logging["Debug"]) + '''>Debug</option>
          </select>
          <br><br>
          <button type="submit">Save</button>
        </form>
      </body>
      <script>
        //Source: https://lengstorf.com/get-form-values-as-json/
        
        const isValidElement = element => {
          return element.name && element.value;
        };

        const isValidValue = element => {
          return (!['checkbox'].includes(element.type) || element.checked);
        };

        const formToJSON = elements => [].reduce.call(elements, (data, element) => {

          // Make sure the element has the required properties and should be added.
          if (isValidElement(element) && isValidValue(element)) {
              data[element.name] = element.value;
          }
          return data;
        }, {});

        const handleFormSubmit = event => {

          // Stop the form from submitting since we’re handling that with AJAX.
          event.preventDefault();

          // Call our function to get the form data.
          const data = formToJSON(form.elements);

          // Use `JSON.stringify()` to make the output valid, human-readable JSON.
          var json_data = "json_str_begin" + JSON.stringify(data, null, "") + "json_str_end";

          json_data.replace(/\\n/g, '');

          var date = new Date();
          var now = 'time_begin'+date.getUTCFullYear()+':'+(date.getUTCMonth()+1)+':'+date.getUTCDate()+':'+date.getUTCHours()+":"+date.getUTCMinutes()+":"+date.getUTCSeconds()+'time_end';

          // ...this is where we’d actually do something with the form data...
          var xhttp = new XMLHttpRequest();
          xhttp.open("POST", "", true);
          xhttp.send(json_data+now);
        };

        const form = document.getElementsByClassName('config_form')[0];
        form.addEventListener('submit', handleFormSubmit);
    </script>
    </html>'''

    return html_form
Example #25
0
 def start_timer(self):
     # start a periodic timer interrupt to poll readings at a frequency
     self.processing_alarm = Timer.Alarm(
         self.process_readings,
         s=int(config.get_config("TEMP_period")),
         periodic=True)
Example #26
0
def pm_thread(sensor_name, sensor_logger, status_logger, pins, serial_id):
    """
    Method to run as a thread that reads, processes and logs readings form pm sensors according to their type
    :param sensor_name: PM1 or PM2
    :type sensor_name: str
    :param status_logger: status logger
    :type status_logger: LoggerFactory object
    :param pins: serial bus pins (TX, RX)
    :type pins: tuple(int, int)
    :param serial_id: serial bus id (0, 1 or 2)
    :type serial_id: int
    """

    status_logger.debug("Thread {} started".format(sensor_name))
    sensor_type = config.get_config(sensor_name)

    # sensor_logger = SensorLogger(sensor_name=sensor_name, terminal_out=True)

    init_time = int(config.get_config(sensor_name + "_init"))

    init_count = 0

    if sensor_type == "PMS5003":

        # initialise sensor
        sensor = Plantower(pins=pins, id=serial_id)

        time.sleep(1)

        # warm up time  - readings are not logged
        while init_count < init_time:
            try:
                time.sleep(1)
                sensor.read()
                init_count += 1
            except PlantowerException as e:
                status_logger.exception("Failed to read from sensor PMS5003")
                blink_led((0x550000, 0.4, True))

    elif sensor_type == "SPS030":

        # initialise sensor
        try:
            sensor = Sensirion(
                retries=1, pins=pins,
                id=serial_id)  # automatically starts measurement
        except SensirionException as e:
            status_logger.exception("Failed to read from sensor SPS030")
            blink_led((0x550000, 0.4, True))
            time.sleep(1)

        # warm up time - readings are not logged
        while init_count < init_time:
            try:
                time.sleep(1)
                sensor.read()
                init_count += 1
            except SensirionException as e:
                status_logger.exception("Failed to read from sensor SPS030")
                blink_led((0x550000, 0.4, True))

    # start a periodic timer interrupt to poll readings every second
    processing_alarm = Timer.Alarm(process_readings,
                                   arg=(sensor_type, sensor, sensor_logger,
                                        status_logger),
                                   s=1,
                                   periodic=True)
Example #27
0
def new_config(logger, arg):
    """
    Thread that turns on access point on the device to modify configurations. Name of access point: PmSensor
    Password: pmsensor Enter 192.168.4.10 on device browser to get configuration form. Indicator LEDs:
    Blue - Access point turned on, not connected, Green - Connected, configuration is open on browser,
    Red - An error has occured
    The device automatically reboots and applies modifications upon successful configuration.
    :param thread_name: Thread id
    :type thread_name: str
    :param logger: status logger
    :type logger: LoggerFactory object
    :param timeout: timeout (in seconds) for user configuration
    :type timeout: int
    """

    #  Only one of this thread is allowed to run at a time
    if not wifi_lock.locked():
        with wifi_lock:

            logger.info("New configuration setup started")

            # Config uses LED colours to indicate the state of the connection - lock is necessary to disable error pings
            led_lock.acquire(1)

            # set pycom up as access point
            wlan = network.WLAN(mode=WLAN.AP,
                                ssid=config.get_config("device_name"))
            # Connect to PmSensor using password set by the user
            wlan.init(mode=WLAN.AP,
                      ssid=config.get_config("device_name"),
                      auth=(WLAN.WPA2, config.get_config("password")),
                      channel=1,
                      antenna=WLAN.INT_ANT)
            # Load HTML via entering 192,168.4.10 to browser
            wlan.ifconfig(id=1,
                          config=('192.168.4.10', '255.255.255.0',
                                  '192.168.4.1', '192.168.4.1'))

            logger.info('Access point turned on as {}'.format(
                config.get_config("device_name")))
            logger.info(
                'Configuration website can be accessed at 192.168.4.10')

            address = socket.getaddrinfo(
                '0.0.0.0', 80)[0][-1]  # Accept stations from all addresses
            sct = socket.socket()  # Create socket for communication
            sct.settimeout(config.get_config(
                "config_timeout"))  # session times out after x seconds
            gc.collect(
            )  # frees up unused memory if there was a previous connection
            sct.bind(address)  # Bind address to socket
            sct.listen(1)  # Allow one station to connect to socket

            pycom.rgbled(0x000055)  # Blue LED - waiting for connection

            get_new_config(sct, logger)

            wlan.deinit()  # turn off wifi
            gc.collect()

            logger.info('rebooting...')
            machine.reset()
Example #28
0
    # ToDo: Update OTA.py so if version is 0.0.0, it backs up all existing files, and adds all files as new.
    # ToDo: Set code_version to '0.0.0' in default config, and remove the line below
    config.save_config({"code_version": "0.2.6"})
    """SET FORMAT VERSION NUMBER - version number is used to indicate the data format used to decode LoRa messages in
    the back end. If the structure of the LoRa message is changed during update, increment the version number and
    add a corresponding decoder to the back-end."""
    config.save_config({"fmt_version": 1})

    # Override Preferences - DEVELOPER USE ONLY - keep all overwrites here
    if "debug_config.json" in os.listdir("/flash"):
        status_logger.warning(
            "Overriding configuration with the content of debug_config.json")
        with open("/flash/debug_config.json", "r") as f:
            config.set_config(ujson.loads(f.read()))
            status_logger.warning("Configuration changed to: " +
                                  str(config.get_config()))

    # Check if GPS is enabled in configurations
    gps_on = False if config.get_config("GPS") == "OFF" else True

    # Get current time
    rtc = RTC()
    no_time, update_time_later = initialise_time(rtc, gps_on, status_logger)

    # Check if device is configured, or SD card has been moved to another device
    device_id = hexlify(unique_id()).upper().decode("utf-8")
    if (not config.is_complete(status_logger)
            or config.get_config("device_id") != device_id):
        config.reset_configuration(status_logger)
        #  Force user to configure device, then reboot
        setup_new_config(status_logger)
Example #29
0
    # Read configuration file to get preferences
    config.read_configuration()
    """SET VERSION NUMBER - version number is used to indicate the data format used to decode LoRa messages in the
    back end. If the structure of the LoRa message is changed during update, increment the version number and
    add a corresponding decoder to the back-end."""
    config.set_config({"version": 1})

    # Override Preferences - DEVELOPER USE ONLY - keep all overwrites here
    if 'debug_config.json' in os.listdir('/flash'):
        status_logger.warning(
            "Overriding configuration with the content of debug_config.json")
        with open('/flash/debug_config.json', 'r') as f:
            config.set_config(ujson.loads(f.read()))
            status_logger.warning("Configuration changed to: " +
                                  str(config.get_config()))

    # Initialize PM power circuitry
    PM_transistor = Pin('P20', mode=Pin.OUT)
    PM_transistor.value(0)
    if config.get_config(s.PM1) != "OFF" or config.get_config(s.PM2) != "OFF":
        PM_transistor.value(1)

    # Check if GPS is enabled in configurations
    gps_on = True
    if config.get_config("GPS") == "OFF":
        gps_on = False

    # Get current time
    rtc = RTC()
    no_time, update_time_later = initialize_time(rtc, gps_on, status_logger)
Example #30
0
    def run_test(self):
        pycom.rgbled(0x66039a)  # Purple :]
        self.logger.info("Running hardware test")

        active_sensors = get_sensors()
        active_sensor_names = list(active_sensors.keys())

        self.logger.info("Found {} sensors: {}".format(
            len(active_sensor_names), active_sensor_names))

        for sensor_name in active_sensor_names:
            if active_sensors[sensor_name] == False:
                self.logger.info(
                    "Skipping test of disabled sensor {}".format(sensor_name))
                continue

            self.logger.info("Testing sensor {}".format(sensor_name))

            sensor_type = config.get_config(sensor_name)

            self.logger.info("Sensor {} found to be of type {}".format(
                sensor_name, sensor_type))

            self.logger.info("Attempting to initialize sensor")
            try:
                if sensor_type == "PMS5003":
                    sensor = Plantower(
                        pins=("P3", "P17"),
                        id=1,
                    )
                elif sensor_type == "SPS030":
                    sensor = Sensirion(retries=1, pins=("P11", "P18"), id=2)
                elif sensor_type == "SHT35":
                    sensor = TempSHT35(None, None)
                else:
                    raise ValueError("Unknown sensor type")
            except Exception as e:
                self.logger.exception(
                    "Failed to initialize sensor: {}".format(e))
                continue

            time.sleep(1)
            init_count = 0

            try:
                init_limit = int(config.get_config(sensor_name + "_init"))
            except TypeError:
                # Sensor doesn't have a set init time.
                init_limit = 5

            self.logger.info(
                "Warming up sensor for {} seconds".format(init_limit))
            while init_count < init_limit:
                try:
                    time.sleep(1)
                    sensor.read()
                    init_count += 1
                except Exception as e:
                    self.logger.exception(
                        "Failed to read from sensor: {}".format(e))
                    pass

            time.sleep(3)  # We can't read the sensor too fast after warmup
            self.logger.info("Attempting read of data from sensor")
            try:
                recv = sensor.read()
                if recv:
                    recv_str = str(recv)
                    self.logger.info("Read data: {}".format(recv_str))
                else:
                    self.logger.info("sensor.read returned no data")
            except Exception as e:
                self.logger.exception(
                    "Failed to read from sensor {}".format(sensor_type))
                pass

            self.logger.info("Finished testing sensor {}".format(sensor_name))

        self.logger.info("Hardware test finished")
        pycom.rgbled(0x000000)  # Turn off