def get_new_config(sct, logger): """ Sends an html form to a web socket, and waits for the user to connect :param sct: web socket :type sct: socket object :param logger: status logger :type logger: LoggerFactory object """ try: while True: try: client, address = sct.accept() # wait for new connection except Exception as e: raise Exception("Configuration timeout") client.send(get_html_form() ) # send html page with form to submit by the user pycom.rgbled(0x005500) # Green LED - Connection successful received_data = str(client.recv(3000)) # wait for client response # logger.debug(received_data) client.close() # socket has to be closed because of the loop if process_data(received_data, logger): return except Exception as e: logger.exception("Failed to configure the device") led_lock.release() blink_led((0x550000, 3, True)) # Red LED - Error return
def process_readings(args): """ Method to be evoked by a timed alarm, which reads and processes data from the PM sensor, and logs it to the sd card :param args: sensor_type, sensor, sensor_logger, status_logger :type args: str, str, SensorLogger object, LoggerFactory object """ sensor_type, sensor, sensor_logger, status_logger = args[0], args[1], args[ 2], args[3] try: recv = sensor.read() if recv: recv_lst = str(recv).split(',') curr_timestamp = recv_lst[0] sensor_reading_float = [float(i) for i in recv_lst[1:]] sensor_reading_round = [round(i) for i in sensor_reading_float] lst_to_log = [curr_timestamp ] + [str(i) for i in sensor_reading_round] line_to_log = ','.join(lst_to_log) sensor_logger.log_row(line_to_log) except Exception as e: status_logger.error( "Failed to read from sensor {}".format(sensor_type)) blink_led((0x550000, 0.4, True))
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))
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))
def process_readings(self, arg): # read and log pm sensor data try: timestamp = csv_timestamp_template.format( *time.gmtime()) # get current time in desired format read_lst = self.read( ) # read SHT35 sensor - [celsius, humidity] to ~5 significant figures round_lst = [ int(round(x, 1) * 10) for x in read_lst ] # round readings to 1 significant figure, shift left, cast to int str_round_lst = list(map(str, round_lst)) # cast int to string lst_to_log = [timestamp] + str_round_lst line_to_log = ','.join(lst_to_log) self.sensor_logger.log_row(line_to_log) except Exception as e: self.status_logger.exception( "Failed to read from temperature and humidity sensor") blink_led((0x550000, 0.4, True))
def process_readings(args): sensor_type, sensor, sensor_logger, status_logger = args[0], args[1], args[ 2], args[3] try: recv = sensor.read() if recv: recv_lst = str(recv).split(',') curr_timestamp = recv_lst[0] sensor_reading_float = [float(i) for i in recv_lst[1:]] sensor_reading_round = [round(i) for i in sensor_reading_float] lst_to_log = [curr_timestamp ] + [str(i) for i in sensor_reading_round] line_to_log = ','.join(lst_to_log) sensor_logger.log_row(line_to_log) except Exception as e: status_logger.error( "Failed to read from sensor {}".format(sensor_type)) blink_led((0x550000, 0.4, True))
def lora_send(self): """Lora send method to run as a thread. Checks if messages are up to date in the lora buffer, pops the one on top of the stack, encodes it to a message and sends it to the right port. Takes two dummy arguments required by the threading library""" if lora_lock.locked(): self.logger.debug("Waiting for other lora thread to finish") with lora_lock: self.logger.debug("LoRa thread started") try: self.check_date() # remove messages that are over a month old if self.lora.has_joined(): self.logger.debug("LoRa connected") else: raise Exception("LoRa is not connected") if s.lora_file_name not in os.listdir(s.root_path + s.processing): raise Exception('LoRa - File: {} does not exist'.format( s.lora_file_name)) else: port, payload = self.get_sending_details() self.lora_socket.bind( port) # bind to port to decode at backend self.lora_socket.send( payload) # send payload to the connected socket self.logger.debug("LoRa - sent payload") self.message_count += 1 # increment number of files sent over LoRa today config.save_config({"message_count": self.message_count }) # save number of messages today # remove message sent self.lora_buffer.remove_head() except Exception: self.logger.exception("Sending payload over LoRaWAN failed") blink_led((0x550000, 0.4, True))
def lora_send(self, arg1, arg2): if lora_lock.locked(): self.logger.debug("Waiting for other lora thread to finish") with lora_lock: self.logger.debug("LoRa thread started") try: self.check_date() # remove messages that are over a month old if self.lora.has_joined(): self.logger.debug("LoRa connected") else: raise Exception("LoRa is not connected") if s.lora_file_name not in os.listdir(s.root_path + s.processing): raise Exception('LoRa - File: {} does not exist'.format( s.lora_file_name)) else: port, payload = self.get_sending_details() self.lora_socket.bind( port) # bind to port to decode at backend self.lora_socket.send( payload) # send payload to the connected socket self.logger.debug("LoRa - sent payload") self.message_count += 1 # increment number of files sent over LoRa today config.save_configuration( {"message_count": self.message_count}) # save number of messages today # remove message sent self.lora_buffer.remove_head() except Exception as e: self.logger.exception("Sending payload over LoRaWAN failed") blink_led((0x550000, 0.4, True))
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)
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 }
# Initialize button interrupt on pin 14 for user interaction user_button = UserButton(status_logger) pin_14 = Pin("P14", mode=Pin.IN, pull=Pin.PULL_DOWN) pin_14.callback(Pin.IRQ_RISING | Pin.IRQ_FALLING, user_button.button_handler) # Mount SD card sd = SD() os.mount(sd, '/sd') except Exception as e: print(str(e)) reboot_counter = 0 while True: blink_led((0x550000, 0.5, True)) # blink red LED reboot_counter += 1 if reboot_counter >= 180: reset() try: from machine import RTC, unique_id, Timer from initialisation import initialize_time from ubinascii import hexlify from Configuration import config from new_config import new_config import strings as s import ujson # Read configuration file to get preferences config.read_configuration()
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)