def _build_component_list(self) -> int: logger().debug("Building component list for DACM instrument.") for component_object in self.components: del component_object self.components = [] component_dict = self._get_parameter("components") if not component_dict: return 0 for component in component_dict: component_object = Component(component["component_id"], component["component_name"]) # build sensor list for sensor in component["sensors"]: sensor_object = Sensor(sensor["sensor_id"], sensor["sensor_name"]) # build measurand list for measurand in sensor["measurands"]: try: unit = measurand["measurand_unit"] except Exception: # pylint: disable=broad-except unit = "" try: source = measurand["measurand_source"] except Exception: # pylint: disable=broad-except source = None measurand_object = Measurand( measurand["measurand_id"], measurand["measurand_name"], unit, source, ) sensor_object.measurands += [measurand_object] component_object.sensors += [sensor_object] self.components += [component_object] return len(self.components)
def _get_battery_voltage(self): battery_bytes = self._get_parameter("battery_bytes") battery_coeff = self._get_parameter("battery_coeff") ok_byte = self.family["ok_byte"] if not (battery_coeff and battery_bytes): return "This instrument type doesn't provide \ battery voltage information" reply = self.get_reply([b"\x0d", b""], battery_bytes + 1) if reply and (reply[0] == ok_byte): try: voltage = battery_coeff * int.from_bytes( reply[1:], byteorder="little", signed=False) return round(voltage, 2) except TypeError: logger().error("TypeError when parsing the payload.") return False except ReferenceError: logger().error("ReferenceError when parsing the payload.") return False except LookupError: logger().error("LookupError when parsing the payload.") return False except Exception: # pylint: disable=broad-except logger().error("Unknown error when parsing the payload.") raise else: pass else: logger().error("Device %s doesn't reply.", self.device_id) return False
def stop_cycle(self): """Stop the measuring cycle.""" ok_byte = self.family["ok_byte"] reply = self.get_reply([b"\x16", b""], 1) if reply and (reply[0] == ok_byte): logger().debug("Cycle stopped at device %s.", self.device_id) return True logger().error("stop_cycle() failed at device %s.", self.device_id) return False
def _push_button(self): reply = self.get_reply([b"\x12", b""], 1) ok_byte = self.family["ok_byte"] if reply and (reply[0] == ok_byte): logger().debug("Push button simulated at device %s.", self.device_id) return True logger().error("Push button failed at device %s.", self.device_id) return False
def set_short_interval(self): """Set the measuring interval to 1 h = 60 min = 3600 s""" ok_byte = self.family["ok_byte"] reply = self.get_reply([b"\x04", b""], 1) if reply and (reply[0] == ok_byte): self.__interval = timedelta(hours=1) logger().debug("Device %s set to 1 h interval.", self.device_id) return True logger().error("Interval setup failed at device %s.", self.device_id) return False
def set_unlock(self): """Unlock the hardware button or switch at the device.""" ok_byte = self.family["ok_byte"] reply = self.get_reply([b"\x02", b""], 1) if reply and (reply[0] == ok_byte): self.lock = self.Lock.UNLOCKED logger().debug("Device %s unlocked.", self.device_id) return True logger().error("Unlocking failed at device %s.", self.device_id) return False
def get_recent_value(self, component_id=None, sensor_id=None, _=None): """Fill component objects with recent measuring values.\ This function does the same like get_all_recent_values()\ and is only here to provide a compatible API to the DACM interface""" for measurand in self.components[component_id].sensors[ sensor_id].measurands: logger().debug(measurand) if measurand.source == 8: # battery voltage measurand.value = self._get_battery_voltage() measurand.time = datetime.utcnow().replace(microsecond=0) return measurand.value return self.get_all_recent_values()
def get_all_recent_values(self): """Fill the component objects with recent readings.""" # Do nothing as long as the previous values are valid. if self._last_sampling_time is None: logger().warning( "The gathered values might be invalid. " "You should use function start_cycle() in your application " "for a regular initialization of the measuring cycle.") return self._gather_all_recent_values() if (datetime.utcnow() - self._last_sampling_time) < self.__interval: logger().debug( "We do not have new values yet. Sample interval = %s.", self.__interval) return True return self._gather_all_recent_values()
def set_real_time_clock(self, date_time) -> bool: """Set the instrument time.""" ok_byte = self.family["ok_byte"] instr_datetime = bytearray([ date_time.second, date_time.minute, date_time.hour, date_time.day, date_time.month, ]) instr_datetime.extend((date_time.year).to_bytes(2, byteorder="big")) reply = self.get_reply([b"\x10", instr_datetime], 1) if reply and (reply[0] == ok_byte): logger().debug("Time on device %s set to UTC.", self.device_id) return True logger().error("Setting the time on device %s failed.", self.device_id) return False
def set_real_time_clock(self, date_time) -> bool: ok_byte = self.family["ok_byte"] instr_datetime = bytearray([ date_time.second, date_time.minute, date_time.hour, date_time.day, date_time.month, date_time.year - 2000, ]) reply = self.get_reply([b"\x05", instr_datetime], 1) if reply and (reply[0] == ok_byte): logger().debug("Time on device %s set to UTC.", self.device_id) return True logger().error("Setting the time on device %s failed.", {self.device_id}) return False
def get_message_payload(self, message, timeout) -> CheckedAnswerDict: """Send a message to the instrument and give back the payload of the reply. Args: message: The message to send. timeout: Timeout for waiting for a reply from instrument. Returns: A dictionary of is_valid: True if answer is valid, False otherwise, is_control_message: True if control message, is_last_frame: True if no follow-up B-E frame is expected, payload: Payload of answer, number_of_bytes_in_payload, raw: The raw byte string from _get_transparent_reply. """ # Run _check_message to get the payload of the sent message. checked_message = self._check_message(message, False) # If this is a get-data command, we expect multiple B-E frames. _multiframe = checked_message["payload"] in [b"\x60", b"\x61"] answer = self._get_transparent_reply(message, timeout=timeout, keep=True) if answer == b"": # Workaround for firmware bug in SARAD instruments. logger().debug("Play it again, Sam!") answer = self._get_transparent_reply(message, timeout=timeout, keep=True) checked_answer = self._check_message(answer, True) return { "is_valid": checked_answer["is_valid"], "is_control": checked_answer["is_control"], "is_last_frame": checked_answer["is_last_frame"], "payload": checked_answer["payload"], "number_of_bytes_in_payload": checked_answer["number_of_bytes_in_payload"], "raw": answer, }
def start_cycle(self, cycle_index=0): """Start a measuring cycle.""" logger().debug("Trying to start measuring cycle %d", cycle_index) self.__interval = self._read_cycle_start(cycle_index)["cycle_interval"] for component in self.components: for sensor in component.sensors: sensor.interval = self.__interval ok_byte = self.family["ok_byte"] reply = self.get_reply([b"\x15", bytes([cycle_index])], 3, timeout=5) if reply and (reply[0] == ok_byte): logger().debug("Cycle %s started at device %s.", cycle_index, self.device_id) return True logger().error("start_cycle() failed at device %s.", self.device_id) if reply[0] == 11: logger().error("DACM instrument replied with error code %s.", reply[1]) return False
def set_config(self): """Upload a new configuration to the device.""" ok_byte = self.family["ok_byte"] setup_word = self._encode_setup_word() interval = int(self.__interval.seconds / 60) setup_data = ((interval).to_bytes(1, byteorder="little") + setup_word + (self.__alarm_level).to_bytes(4, byteorder="little")) logger().debug(setup_data) reply = self.get_reply([b"\x0f", setup_data], 1) if reply and (reply[0] == ok_byte): logger().debug("Set config. successful at device %s.", self.device_id) return True logger().error("Set config. failed at device %s.", self.device_id) return False
def set_wifi_access(self, ssid, password, ip_address, server_port): """Set the WiFi access data.""" ok_byte = self.family["ok_byte"] access_data = b"".join([ bytes(ssid, "utf-8").ljust(33, b"0"), bytes(password, "utf-8").ljust(64, b"0"), bytes(ip_address, "utf-8").ljust(24, b"0"), server_port.to_bytes(2, "big"), ]) logger().debug(access_data) reply = self.get_reply([b"\x17", access_data], 118) if reply and (reply[0] == ok_byte): logger().debug("WiFi access data on device %s set.", self.device_id) return True logger().error("Setting WiFi access data on device %s failed.", self.device_id) return False
def _get_description(self): """Get descriptive data about DACM instrument.""" ok_byte = self.family["ok_byte"] id_cmd = self.family["get_id_cmd"] length_of_reply = self.family["length_of_reply"] reply = self.get_reply(id_cmd, length_of_reply) if reply and (reply[0] == ok_byte): logger().debug("Get description successful.") try: self._type_id = reply[1] self._software_version = reply[2] self._serial_number = int.from_bytes(reply[3:5], byteorder="big", signed=False) manu_day = reply[5] manu_month = reply[6] manu_year = int.from_bytes(reply[7:9], byteorder="big", signed=False) self._date_of_manufacture = datetime(manu_year, manu_month, manu_day) upd_day = reply[9] upd_month = reply[10] upd_year = int.from_bytes(reply[11:13], byteorder="big", signed=False) self._date_of_update = datetime(upd_year, upd_month, upd_day) self._module_blocksize = reply[13] self._component_blocksize = reply[14] self._component_count = reply[15] self._bit_ctrl = BitVector(rawbytes=reply[16:20]) self._value_ctrl = BitVector(rawbytes=reply[20:24]) self._cycle_blocksize = reply[24] self._cycle_count_limit = reply[25] self._step_count_limit = reply[26] self._language = reply[27] return True and self._get_module_information() except TypeError: logger().error("TypeError when parsing the payload.") return False except ReferenceError: logger().error("ReferenceError when parsing the payload.") return False except LookupError: logger().error("LookupError when parsing the payload.") return False except Exception: # pylint: disable=broad-except logger().debug( "The connected instrument does not belong to the DACM family." ) self._valid_family = False return False logger().debug("Get description failed.") return False
def get_recent_value(self, component, sensor=0, measurand=0): """Get a dictionaries with recent measuring values from one sensor. component_id: one of the 34 sensor/actor modules of the DACM system measurand_id: 0 = recent sampling, 1 = average of last completed interval, 2 = minimum of last completed interval, 3 = maximum sensor_id: only for sensors delivering multiple measurands""" component_id = self.components[component].component_id sensor_id = self.components[component].sensors[sensor].sensor_id measurand_id = (self.components[component].sensors[sensor]. measurands[measurand].measurand_id) reply = self.get_reply( [ b"\x1a", bytes([component_id]) + bytes([sensor_id]) + bytes([measurand_id]), ], 1000, ) if reply and (reply[0] > 0): output = {} output["component_name"] = reply[1:17].split(b"\x00")[0].decode( "cp1252") output["measurand_id"] = measurand_id output["sensor_name"] = reply[18:34].split(b"\x00")[0].decode( "cp1252") output["measurand"] = ( reply[35:51].split(b"\x00")[0].strip().decode("cp1252")) measurand_dict = self._parse_value_string(output["measurand"]) output["measurand_operator"] = measurand_dict["measurand_operator"] output["value"] = measurand_dict["measurand_value"] output["measurand_unit"] = measurand_dict["measurand_unit"] date = reply[52:68].split(b"\x00")[0].split(b"/") meas_time = reply[69:85].split(b"\x00")[0].split(b":") if date != [b""]: output["datetime"] = datetime( int(date[2]), int(date[0]), int(date[1]), int(meas_time[0]), int(meas_time[1]), int(meas_time[2]), ) else: output["datetime"] = None try: gps_list = re.split("[ ]+ |ΓΈ|M[ ]*", reply[86:].decode("cp1252")) gps_dict = { "valid": True, "latitude": float(gps_list[0]) if gps_list[1] == "N" else -float(gps_list[0]), "longitude": float(gps_list[2]) if gps_list[3] == "E" else -float(gps_list[2]), "altitude": float(gps_list[4]), "deviation": float(gps_list[5]), } output["gps"] = gps_dict except Exception: # pylint: disable=broad-except gps_dict = { "valid": False, "latitude": None, "longitude": None, "altitude": None, "deviation": None, } this_measurand = (self.components[component].sensors[sensor]. measurands[measurand]) this_measurand.operator = measurand_dict["measurand_operator"] this_measurand.value = measurand_dict["measurand_value"] this_measurand.unit = measurand_dict["measurand_unit"] this_measurand.time = output["datetime"] this_measurand.gps = gps_dict return output if reply[0] == 0: logger().error("Measurand not available.") return False logger().error("The instrument doesn't reply.") return False
def get_config(self): """Get configuration from device.""" ok_byte = self.family["ok_byte"] reply = self.get_reply([b"\x10", b""], 8) if reply and (reply[0] == ok_byte): logger().debug("Getting config. from device %s.", self.device_id) try: self.__interval = timedelta(minutes=reply[1]) setup_word = reply[2:3] self._decode_setup_word(setup_word) self.__alarm_level = int.from_bytes(reply[4:8], byteorder="little", signed=False) except TypeError: logger().error("TypeError when parsing the payload.") return False except ReferenceError: logger().error("ReferenceError when parsing the payload.") return False except LookupError: logger().error("LookupError when parsing the payload.") return False except Exception: # pylint: disable=broad-except logger().debug( "The connected instrument does not belong to the RadonScout family." ) self._valid_family = False return False return True logger().error("Get config. failed at device %s.", self.device_id) return False
def _read_cycle_continue(self): """Get description of subsequent cycle intervals.""" reply = self.get_reply([b"\x07", b""], 16) if reply and not len(reply) < 16: logger().debug("Get information about cycle interval successful.") try: seconds = int.from_bytes(reply[0:4], byteorder="little", signed=False) bit_ctrl = BitVector(rawbytes=reply[4:8]) value_ctrl = BitVector(rawbytes=reply[8:12]) rest = BitVector(rawbytes=reply[12:16]) return { "seconds": seconds, "bit_ctrl": bit_ctrl, "value_ctrl": value_ctrl, "rest": rest, } except TypeError: logger().error("TypeError when parsing the payload.") return False except ReferenceError: logger().error("ReferenceError when parsing the payload.") return False except LookupError: logger().error("LookupError when parsing the payload.") return False except Exception: # pylint: disable=broad-except logger().error("Unknown error when parsing the payload.") return False logger().debug("Get info about cycle interval failed.") return False
def _read_cycle_start(self, cycle_index=0): """Get description of a measuring cycle.""" ok_byte = self.family["ok_byte"] reply = self.get_reply([b"\x06", bytes([cycle_index])], 28) if reply and (reply[0] == ok_byte) and reply[1]: logger().debug("Get primary cycle information successful.") try: cycle_name = reply[2:19].split(b"\x00")[0].decode("cp1252") cycle_interval = timedelta(seconds=int.from_bytes( reply[19:21], byteorder="little", signed=False)) cycle_steps = int.from_bytes(reply[21:24], byteorder="big", signed=False) cycle_repetitions = int.from_bytes(reply[24:28], byteorder="little", signed=False) return { "cycle_name": cycle_name, "cycle_interval": cycle_interval, "cycle_steps": cycle_steps, "cycle_repetitions": cycle_repetitions, } except TypeError: logger().error("TypeError when parsing the payload.") return False except ReferenceError: logger().error("ReferenceError when parsing the payload.") return False except LookupError: logger().error("LookupError when parsing the payload.") return False except Exception: # pylint: disable=broad-except logger().error("Unknown error when parsing the payload.") return False logger().debug("Get primary cycle info failed.") return False
def _get_component_configuration(self, component_index): """Get information about the configuration of a component of a DACM instrument.""" ok_byte = self.family["ok_byte"] reply = self.get_reply([b"\x04", bytes([component_index])], 73) if reply and (reply[0] == ok_byte): logger().debug("Get component configuration successful.") try: sensor_name = reply[8:16].split(b"\x00")[0].decode("cp1252") sensor_value = reply[8:16].split(b"\x00")[0].decode("cp1252") sensor_unit = reply[8:16].split(b"\x00")[0].decode("cp1252") input_config = int.from_bytes(reply[6:8], byteorder="big", signed=False) alert_level_lo = int.from_bytes(reply[6:8], byteorder="big", signed=False) alert_level_hi = int.from_bytes(reply[6:8], byteorder="big", signed=False) alert_output_lo = int.from_bytes(reply[6:8], byteorder="big", signed=False) alert_output_hi = int.from_bytes(reply[6:8], byteorder="big", signed=False) return { "sensor_name": sensor_name, "sensor_value": sensor_value, "sensor_unit": sensor_unit, "input_config": input_config, "alert_level_lo": alert_level_lo, "alert_level_hi": alert_level_hi, "alert_output_lo": alert_output_lo, "alert_output_hi": alert_output_hi, } except TypeError: logger().error("TypeError when parsing the payload.") return False except ReferenceError: logger().error("ReferenceError when parsing the payload.") return False except LookupError: logger().error("LookupError when parsing the payload.") return False except Exception: # pylint: disable=broad-except logger().error("Unknown error when parsing the payload.") return False logger().debug("Get component configuration failed.") return False
def _get_component_information(self, component_index): """Get information about one component of a DACM instrument.""" ok_byte = self.family["ok_byte"] reply = self.get_reply([b"\x03", bytes([component_index])], 21) if reply and (reply[0] == ok_byte): logger().debug("Get component information successful.") try: revision = reply[1] component_type = reply[2] availability = reply[3] ctrl_format = reply[4] conf_block_size = reply[5] data_record_size = int.from_bytes(reply[6:8], byteorder="big", signed=False) name = reply[8:16].split(b"\x00")[0].decode("cp1252") hw_capability = BitVector(rawbytes=reply[16:20]) return { "revision": revision, "component_type": component_type, "availability": availability, "ctrl_format": ctrl_format, "conf_block_size": conf_block_size, "data_record_size": data_record_size, "name": name, "hw_capability": hw_capability, } except TypeError: logger().error("TypeError when parsing the payload.") return False except ReferenceError: logger().error("ReferenceError when parsing the payload.") return False except LookupError: logger().error("LookupError when parsing the payload.") return False except Exception: # pylint: disable=broad-except logger().error("Unknown error when parsing the payload.") return False logger().debug("Get component information failed.") return False
def _get_module_information(self): """Get descriptive data about DACM instrument.""" ok_byte = self.family["ok_byte"] reply = self.get_reply([b"\x01", b""], 73) if reply and (reply[0] == ok_byte): logger().debug("Get module information successful.") try: self._address = reply[1] config_day = reply[2] config_month = reply[3] config_year = int.from_bytes(reply[4:6], byteorder="big", signed=False) self._date_of_config = datetime(config_year, config_month, config_day) self._module_name = reply[6:39].split(b"\x00")[0].decode( "cp1252") self._config_name = reply[39:].split(b"\x00")[0].decode( "cp1252") return True except TypeError: logger().error("TypeError when parsing the payload.") return False except ReferenceError: logger().error("ReferenceError when parsing the payload.") return False except LookupError: logger().error("LookupError when parsing the payload.") return False except Exception: # pylint: disable=broad-except logger().error("Unknown error when parsing the payload.") return False logger().debug("Get module information failed.") return False
def get_wifi_access(self): """Get the Wi-Fi access data from instrument.""" ok_byte = self.family["ok_byte"] reply = self.get_reply([b"\x18", b""], 125) if reply and (reply[0] == ok_byte): try: logger().debug(reply) self.__wifi["ssid"] = reply[0:33].rstrip(b"0") self.__wifi["password"] = reply[33:97].rstrip(b"0") self.__wifi["ip_address"] = reply[97:121].rstrip(b"0") self.__wifi["server_port"] = int.from_bytes( reply[121:123], "big") return True except TypeError: logger().error("TypeError when parsing the payload.") return False except ReferenceError: logger().error("ReferenceError when parsing the payload.") return False except LookupError: logger().error("LookupError when parsing the payload.") return False except Exception: # pylint: disable=broad-except logger().error("Unknown error when parsing the payload.") return False else: pass else: logger().error("Cannot get Wi-Fi access data from device %s.", self.device_id) return False
def _gather_all_recent_values(self): ok_byte = self.family["ok_byte"] reply = self.get_reply([b"\x14", b""], 33) self._last_sampling_time = datetime.utcnow() if reply and (reply[0] == ok_byte): try: sample_interval = timedelta(minutes=reply[1]) device_time_min = reply[2] device_time_h = reply[3] device_time_d = reply[4] device_time_m = reply[5] device_time_y = reply[6] source = [] # measurand_source source.append(round(self._bytes_to_float(reply[7:11]), 2)) # 0 source.append(reply[11]) # 1 source.append(round(self._bytes_to_float(reply[12:16]), 2)) # 2 source.append(reply[16]) # 3 source.append(round(self._bytes_to_float(reply[17:21]), 2)) # 4 source.append(round(self._bytes_to_float(reply[21:25]), 2)) # 5 source.append(round(self._bytes_to_float(reply[25:29]), 2)) # 6 source.append( int.from_bytes(reply[29:33], byteorder="big", signed=False)) # 7 source.append(self._get_battery_voltage()) # 8 device_time = datetime( device_time_y + 2000, device_time_m, device_time_d, device_time_h, device_time_min, ) except TypeError: logger().error("TypeError when parsing the payload.") return False except ReferenceError: logger().error("ReferenceError when parsing the payload.") return False except LookupError: logger().error("LookupError when parsing the payload.") return False except ValueError: logger().error("ValueError when parsing the payload.") return False except Exception: # pylint: disable=broad-except logger().error("Unknown error when parsing the payload.") return False self.__interval = sample_interval for component in self.components: for sensor in component.sensors: sensor.interval = sample_interval for measurand in sensor.measurands: try: measurand.value = source[measurand.source] measurand.time = device_time if measurand.source == 8: # battery voltage sensor.interval = timedelta(seconds=5) except Exception: # pylint: disable=broad-except logger().error( "Can't get value for source %s in %s/%s/%s.", measurand.source, component.name, sensor.name, measurand.name, ) return True logger().error("Device %s doesn't reply.", self.device_id) return False