class DllConfigFile(File, Validatable): SCHEMA = [{ "active_access_class": Types.INTEGER(min=0, max=0xFF), "vid": Types.INTEGER(min=0, max=0xFFFF) # TODO others }] def __init__(self, active_access_class=0, vid=0xFFFF): self.active_access_class = active_access_class self.vid = vid Validatable.__init__(self) File.__init__(self, SystemFileIds.DLL_CONFIG.value, 6) @staticmethod def parse(s): ac = s.read("uint:8") vid = s.read("uint:16") return DllConfigFile(active_access_class=ac, vid=vid) def __iter__(self): yield self.active_access_class for byte in bytearray(struct.pack(">H", self.vid)): yield byte def __str__(self): return "active_access_class={}, vid={}".format( self.active_access_class, self.vid)
class Frame(Validatable): SCHEMA = [{ "control": Types.OBJECT(Control), "dialog_id": Types.INTEGER(min=0, max=255), "transaction_id": Types.INTEGER(min=0, max=255), "ack_template": Types.OBJECT(nullable=True), # TODO "alp_command": Types.OBJECT(Command) }] def __init__(self, control, dialog_id, transaction_id, ack_template, alp_command): self.control = control self.dialog_id = dialog_id self.transaction_id = transaction_id self.ack_template = ack_template self.alp_command = alp_command super(Frame, self).__init__() def __iter__(self): for byte in self.control: yield byte yield self.dialog_id yield self.transaction_id for byte in self.ack_template: yield byte for byte in self.alp_command: yield byte
class FileHeader(Validatable): SCHEMA = [{ "permissions": Types.OBJECT(FilePermissions), "properties": Types.OBJECT(FileProperties), "alp_command_file_id": Types.BYTE(), "interface_file_id": Types.BYTE(), "file_size": Types.INTEGER(min=0, max=0xFFFFFFFF), "allocated_size": Types.INTEGER(min=0, max=0xFFFFFFFF) }] def __init__(self, permissions, properties, alp_command_file_id, interface_file_id, file_size, allocated_size): self.permissions = permissions self.properties = properties self.alp_command_file_id = alp_command_file_id self.interface_file_id = interface_file_id self.file_size = file_size self.allocated_size = allocated_size Validatable.__init__(self) @staticmethod def parse(s): permissions = FilePermissions.parse(s) properties = FileProperties.parse(s) alp_command_file_id = s.read("uint:8") interface_file_id = s.read("uint:8") file_size = s.read("uint:32") allocated_size = s.read("uint:32") return FileHeader(permissions, properties, alp_command_file_id, interface_file_id, file_size, allocated_size) def __iter__(self): for byte in self.permissions: yield byte for byte in self.properties: yield byte yield self.alp_command_file_id yield self.interface_file_id for byte in bytearray(struct.pack(">I", self.file_size)): yield byte for byte in bytearray(struct.pack(">I", self.allocated_size)): yield byte def __eq__(self, other): if isinstance(other, FileHeader): return self.__dict__ == other.__dict__ return False def __ne__(self, other): return not self.__eq__(other) def __str__(self): return "permissions={}, properties=({}), alp_command_file_id={}, interface_file_id={}, file_size={}, allocated_size={}".format( self.permissions, self.properties, self.alp_command_file_id, self.interface_file_id, self.file_size, self.allocated_size)
class EngineeringModeFile(File, Validatable): SCHEMA = [{ "mode": Types.ENUM(EngineeringModeMode), "flags": Types.INTEGER(min=0, max=255), "timeout": Types.INTEGER(min=0, max=255), "channel_id": Types.OBJECT(ChannelID), "eirp": Types.INTEGER(min=-128, max=127) }] def __init__(self, mode=EngineeringModeMode.ENGINEERING_MODE_MODE_OFF, flags=0, timeout=0, channel_id=ChannelID(channel_header=ChannelHeader( ChannelCoding.PN9, ChannelClass.LO_RATE, ChannelBand.BAND_868), channel_index=0), eirp=0): self.mode = mode self.flags = flags self.timeout = timeout self.channel_id = channel_id self.eirp = eirp File.__init__(self, SystemFileIds.ENGINEERING_MODE, 9) Validatable.__init__(self) @staticmethod def parse(s): mode = EngineeringModeMode(int(s.read("uint:8"))) flags = s.read("uint:8") timeout = s.read("uint:8") channel_id = ChannelID.parse(s) eirp = s.read("int:8") return EngineeringModeFile(mode=mode, flags=flags, timeout=timeout, channel_id=channel_id, eirp=eirp) def __iter__(self): yield int(self.mode.value) yield self.flags yield self.timeout for byte in self.channel_id: yield byte for byte in bytearray(struct.pack(">b", self.eirp)): yield byte yield 0 yield 0 def __str__(self): return "mode={}, flags={}, timeout={}, channel_id={{{}}}, eirp={}".format( self.mode, hex(self.flags), self.timeout, self.channel_id, self.eirp)
class SubBand(Validatable): # TODO update to D7AP v1.1 SCHEMA = [{ "channel_index_start": Types.INTEGER(min=0, max=0xFFFF), "channel_index_end": Types.INTEGER(min=0, max=0xFFFF), "eirp": Types.INTEGER(min=-128, max=127), "cca": Types.INTEGER(min=0, max=255), "duty": Types.INTEGER(min=0, max=255), }] def __init__(self, channel_index_start=0, channel_index_end=0, eirp=0, cca=86, duty=255): self.channel_index_start = channel_index_start self.channel_index_end = channel_index_end self.eirp = eirp self.cca = cca self.duty = duty super(SubBand, self).__init__() def __iter__(self): for byte in bytearray(struct.pack(">H", self.channel_index_start)): yield byte for byte in bytearray(struct.pack(">H", self.channel_index_end)): yield byte for byte in bytearray(struct.pack("b", self.eirp)): yield byte yield self.cca yield self.duty @staticmethod def parse(s): channel_index_start = struct.unpack(">H", s.read("bytes:2"))[0] channel_index_end = struct.unpack(">H", s.read("bytes:2"))[0] eirp = s.read("int:8") cca = s.read("uint:8") duty = s.read("uint:8") return SubBand(channel_index_start=channel_index_start, channel_index_end=channel_index_end, eirp=eirp, cca=cca, duty=duty) def __str__(self): return "channel_index_start={}, channel_index_end={}, eirp={}, cca={}, duty={}".format( self.channel_index_start, self.channel_index_end, self.eirp, self.cca, self.duty)
class FirmwareVersionFile(File, Validatable): SCHEMA = [{ "d7a_protocol_version_major": Types.INTEGER(min=0, max=255), "d7a_protocol_version_minor": Types.INTEGER(min=0, max=255), # custom fields, specific to oss7 "application_name": Types.STRING(maxlength=6), "git_sha1": Types.STRING(maxlength=7) }] def __init__(self, d7a_protocol_version_major=0, d7a_protocol_version_minor=0, application_name="", git_sha1=""): self.d7a_protocol_version_major = d7a_protocol_version_major self.d7a_protocol_version_minor = d7a_protocol_version_minor self.application_name = application_name self.git_sha1 = git_sha1 Validatable.__init__(self) File.__init__(self, SystemFileIds.FIRMWARE_VERSION.value, 15) @property def d7ap_version(self): return str(self.d7a_protocol_version_major) + '.' + str( self.d7a_protocol_version_minor) @staticmethod def parse(s): major = s.read("uint:8") minor = s.read("uint:8") application_name = s.read("bytes:6").decode("ascii") git_sha1 = s.read("bytes:7").decode("ascii") return FirmwareVersionFile(d7a_protocol_version_major=major, d7a_protocol_version_minor=minor, application_name=application_name, git_sha1=git_sha1) def __iter__(self): yield self.d7a_protocol_version_major yield self.d7a_protocol_version_minor for byte in bytearray(self.application_name.encode("ASCII").ljust(6)): yield byte for byte in bytearray(self.git_sha1.encode("ASCII").ljust(7)): yield byte def __str__(self): return "d7ap v{}, application_name={}, git_sha1={}".format( self.d7ap_version, self.application_name, self.git_sha1)
class DllConfigFile(File, Validatable): SCHEMA = [{ "active_access_class": Types.INTEGER(min=0, max=0xFF), "LQ_filter": Types.INTEGER(min=0, max=0xFF), "NF_CTRL": Types.INTEGER(min=0, max=0xFF), "RX_NF_method_parameter": Types.INTEGER(min=0, max=0xFF), "TX_NF_method_parameter": Types.INTEGER(min=0, max=0xFF) }] def __init__(self, active_access_class=0, lq_filter=0x00, nf_ctrl=0x00, rx_nf_method_parameter=0x00, tx_nf_method_parameter=0x00): self.active_access_class = active_access_class self.lq_filter = lq_filter self.nf_ctrl = nf_ctrl self.rx_nf_method_parameter = rx_nf_method_parameter self.tx_nf_method_parameter = tx_nf_method_parameter File.__init__(self, SystemFileIds.DLL_CONFIG.value, 7) Validatable.__init__(self) @staticmethod def parse(s): ac = s.read("uint:8") _rfu = s.read("uint:16") lq_filter = s.read("uint:8") nf_ctrl = s.read("uint:8") rx_nf_method_parameter = s.read("uint:8") tx_nf_method_parameter = s.read("uint:8") return DllConfigFile(active_access_class=ac, lq_filter=lq_filter, nf_ctrl=nf_ctrl, rx_nf_method_parameter=rx_nf_method_parameter, tx_nf_method_parameter=tx_nf_method_parameter) def __iter__(self): yield self.active_access_class for byte in bytearray(struct.pack(">H", 0)): # RFU yield byte yield self.lq_filter yield self.nf_ctrl yield self.rx_nf_method_parameter yield self.tx_nf_method_parameter def __str__(self): return "active_access_class={}, lq_filter={}, nf_ctrl={}, rx_nf_method_parameter, tx_nf_method_parameter".format( self.active_access_class, self.lq_filter, self.nf_ctrl, self.rx_nf_method_parameter, self.tx_nf_method_parameter)
class ForegroundFrameControl(Validatable): SCHEMA = [{ "id_type": Types.ENUM(IdType), "eirp_index": Types.INTEGER(None, 0, 63) }] def __init__(self, id_type, eirp_index=0): self.id_type = id_type self.eirp_index = eirp_index super(ForegroundFrameControl, self).__init__() @staticmethod def parse(s): id_type = IdType(s.read("uint:2")) eirp_index = s.read("uint:6") return ForegroundFrameControl( id_type=id_type, eirp_index=eirp_index ) def __iter__(self): byte = 0 byte |= self.id_type.value << 6 byte += self.eirp_index yield byte
class Control(Validatable): SCHEMA = [{ "has_network_layer_security": Types.BOOLEAN(), "has_multi_hop": Types.BOOLEAN(), "has_origin_access_id": Types.BOOLEAN(), "is_origin_access_id_vid": Types.BOOLEAN(), "origin_access_class": Types.INTEGER(min=0, max=15) }] def __init__(self, has_network_layer_security, has_multi_hop, has_origin_access_id, is_origin_access_id_vid, origin_access_class): self.has_network_layer_security = has_network_layer_security self.has_multi_hop = has_multi_hop self.has_origin_access_id = has_origin_access_id self.is_origin_access_id_vid = is_origin_access_id_vid self.origin_access_class = origin_access_class super(Control, self).__init__() def __iter__(self): byte = 0 if self.has_network_layer_security: byte |= 1 << 7 if self.has_multi_hop: byte |= 1 << 6 if self.has_origin_access_id: byte |= 1 << 5 if self.is_origin_access_id_vid: byte |= 1 << 4 byte += self.origin_access_class yield byte
class BackgroundFrameControl(Validatable): SCHEMA = [{ "id_type": Types.ENUM(IdType), "tag": Types.INTEGER(None, 0, 63) }] def __init__(self, id_type, tag): self.id_type = id_type self.tag = tag super(BackgroundFrameControl, self).__init__() @staticmethod def parse(s): id_type = IdType(s.read("uint:2")) tag = s.read("uint:6") return BackgroundFrameControl( id_type=id_type, tag=tag ) def __iter__(self): byte = 0 byte |= self.id_type.value << 6 byte += self.tag yield byte
class ChannelID(Validatable): SCHEMA = [{ "channel_header": Types.OBJECT(ChannelHeader), "channel_index": Types.INTEGER(min=0, max=0xFFFF), }] def __init__(self, channel_header, channel_index): self.channel_header = channel_header self.channel_index = channel_index super(ChannelID, self).__init__() def __iter__(self): for byte in self.channel_header: yield byte for byte in bytearray(struct.pack(">H", self.channel_index)): yield byte @staticmethod def parse(s): channel_header = ChannelHeader.parse(s) channel_index = s.read("uint:16") return ChannelID(channel_header=channel_header, channel_index=channel_index) def __str__(self): return "{0}{1:0>3}".format(self.channel_header, self.channel_index) @staticmethod def from_string(s): channel_header = ChannelHeader.from_string(s[0:5]) channel_index = int(s[5:8]) return ChannelID(channel_header=channel_header, channel_index=channel_index)
class IndirectInterfaceOperand(Validatable): SCHEMA = [{ "interface_file_id": Types.INTEGER(min=0x40, max=0xFF), "interface_configuration_overload": Types.OBJECT(Addressee, nullable=True) # TODO assuming D7ASP interface }] def __init__(self, interface_file_id, interface_configuration_overload=None): self.interface_file_id = interface_file_id self.interface_configuration_overload = interface_configuration_overload super(IndirectInterfaceOperand, self).__init__() def __iter__(self): yield self.interface_file_id if self.interface_configuration_overload is not None: for byte in self.interface_configuration_overload: yield byte def __str__(self): return "interface-file-id={}, configuration-overload={}".format( self.interface_file_id, self.interface_configuration_overload)
class StatusAction(Action): SCHEMA = [{ "status_operand_extension": Types.INTEGER(values=StatusActionOperandExtensions.ALL), "operation": Types.OBJECT(Operation), "operand": Types.OBJECT(nullable=True) # there is no Operand base-class }] def __init__(self, status_operand_extension, operation): self.status_operand_extension = status_operand_extension self.operation = operation super(StatusAction, self).__init__(operation=operation) def __iter__(self): byte = 0 byte |= self.status_operand_extension << 6 byte += self.op yield byte for byte in self.operation: yield byte def __str__(self): return "{}".format(self.operand)
class AccessProfileFile(File, Validatable): SCHEMA = [{ "access_specifier": Types.INTEGER(min=0, max=14), "access_profile": Types.OBJECT(AccessProfile, nullable=True) }] def __init__(self, access_specifier=0, access_profile=None): self.access_specifier = access_specifier self.access_profile = access_profile Validatable.__init__(self) File.__init__(self, SystemFileIds.ACCESS_PROFILE_0.value + access_specifier, 65) def __iter__(self): for byte in self.access_profile: yield byte @staticmethod def parse(s): return AccessProfileFile( access_specifier=0, access_profile=AccessProfile.parse(s)) # TODO access_specifier? def __str__(self): return "active_specifier={}, access_profile={}".format( self.access_specifier, self.access_profile)
class DllStatusFile(File, Validatable): SCHEMA = [{ "last_rx_packet_level": Types.INTEGER(min=0, max=0xFF), "last_rx_packet_link_budget": Types.INTEGER(min=0, max=0xFF), "noise_floor": Types.INTEGER(min=0, max=0xFF), "channel_header": Types.OBJECT(ChannelHeader), "channel_index":Types.INTEGER(min=0, max=0xFFFF), "scan_timeout_ratio": Types.INTEGER(min=0, max=0xFFFF), "scan_count": Types.INTEGER(min=0, max=0xFFFFFFFF), "scan_timeout_count": Types.INTEGER(min=0, max=0xFFFFFFFF) }] def __init__(self, last_rx_packet_level=0, last_rx_packet_link_budget=0, noise_floor=0, channel_header=ChannelHeader(channel_coding=ChannelCoding.FEC_PN9, channel_band=ChannelBand.BAND_868, channel_class=ChannelClass.LO_RATE), channel_index=0, scan_timeout_ratio=0, scan_count=0, scan_timeout_count=0): self.last_rx_packet_level=last_rx_packet_level self.last_rx_packet_link_budget=last_rx_packet_link_budget self.noise_floor=noise_floor self.channel_header=channel_header self.channel_index=channel_index self.scan_timeout_ratio=scan_timeout_ratio self.scan_count=scan_count self.scan_timeout_count=scan_timeout_count File.__init__(self, SystemFileIds.DLL_STATUS.value, 16) Validatable.__init__(self) @staticmethod def parse(s): last_rx_packet_level = s.read("uint:8") last_rx_packet_link_budget = s.read("uint:8") noise_floor = s.read("uint:8") channel_header = ChannelHeader.parse(s) channel_index = s.read("uint:16") scan_timeout_ratio = s.read("uint:16") scan_count = s.read("uint:32") scan_timeout_count = s.read("uint:32") return DllStatusFile(last_rx_packet_level=last_rx_packet_level, last_rx_packet_link_budget=last_rx_packet_link_budget, noise_floor=noise_floor, channel_header=channel_header, channel_index=channel_index, scan_timeout_ratio=scan_timeout_ratio, scan_count=scan_count, scan_timeout_count=scan_timeout_count) def __iter__(self): yield self.last_rx_packet_level yield self.last_rx_packet_link_budget yield self.noise_floor for byte in self.channel_header: yield byte for byte in bytearray(struct.pack(">H", self.channel_index)): yield byte for byte in bytearray(struct.pack(">H", self.scan_timeout_ratio)): yield byte for byte in bytearray(struct.pack(">I", self.scan_count)): yield byte for byte in bytearray(struct.pack(">I", self.scan_timeout_count)): yield byte def __str__(self): return "last_rx_packet_level={}, last_rx_packet_link_budget={}, noise_floor={}, channel={}{}, scan_timeout_ratio={}, scan_count={}, scan_timeout_count={}".format(self.last_rx_packet_level, self.last_rx_packet_link_budget, self.noise_floor, self.channel_header, self.channel_index, self.scan_timeout_ratio, self.scan_count, self.scan_timeout_count)
class Length(Validatable): SCHEMA = [{ "length": Types.BITS(2), "value": Types.INTEGER(min=0, max=0x3FFFFFFF) }] def __init__(self, value=0): self.value = value super(Length, self).__init__() @staticmethod def parse(s): size = s.read("uint:2") # + 1 = already read value = s.read("uint:" + str(6 + (size * 8))) return Length(value=value) def __iter__(self): byte = 0 size = 1 if self.value > 0x3F: size = 2 if self.value > 0x3FFF: size = 3 if self.value > 0x3FFFFF: size = 4 byte += (size - 1) << 6 if size == 1: byte += self.value yield byte else: length_bytes = bytearray(struct.pack(">I", self.value)) if size == 2: length_bytes = length_bytes[2:] elif size == 3: length_bytes = length_bytes[1:] byte += length_bytes[0] yield byte for byte in length_bytes[1:]: yield byte def __str__(self): return str(self.value) def __eq__(self, other): if isinstance(other, self.__class__): return self.value == other.value elif isinstance(other, int): return self.value == other else: return False def __ne__(self, other): return not self.__eq__(other)
class Offset(Validatable): SCHEMA = [{ "id": Types.BYTE(), "size": Types.INTEGER([1]), "offset": Types.INTEGER(min=0, max=0xFF) }, { "id": Types.BYTE(), "size": Types.INTEGER([2]), "offset": Types.INTEGER(min=0, max=0xFFFF) }, { "id": Types.BYTE(), "size": Types.INTEGER([3]), "offset": Types.INTEGER(min=0, max=0xFFFFFF) }, { "id": Types.BYTE(), "size": Types.INTEGER([4]), "offset": Types.INTEGER(min=0, max=0xFFFFFFFF) }] def __init__(self, id=0, size=1, offset=0): self.id = id self.size = size self.offset = offset super(Offset, self).__init__() def __iter__(self): yield chr(self.id) byte = 0 byte += (self.size - 1) << 6 if self.size == 1: byte += self.offset yield byte else: offset = bytearray(struct.pack(">I", self.offset)) if self.size == 2: offset = offset[2:] elif self.size == 3: offset = offset[1:] byte += offset[0] yield byte for byte in offset[1:]: yield byte def __str__(self): return "file-id={}, size={}, offset={}".format(self.id, self.size, self.offset)
class UidFile(File, Validatable): SCHEMA = [{"uid": Types.INTEGER(min=0, max=0xFFFFFFFFFFFFFFFF)}] def __init__(self, uid=0): self.uid = uid File.__init__(self, SystemFileIds.UID.value, 8) Validatable.__init__(self) @staticmethod def parse(s): uid = s.read("uint:64") return UidFile(uid=uid) def __iter__(self): for byte in bytearray(struct.pack(">Q", self.uid)): yield byte def __str__(self): return "uid={}".format(hex(self.uid))
class Control(Validatable): SCHEMA = [{ "is_target_address_set": Types.BOOLEAN(), "is_target_address_vid": Types.BOOLEAN(), "eirp_index": Types.INTEGER(None, 0, 63) }] def __init__(self, is_target_address_set, is_target_address_vid, eirp_index=0): self.is_target_address_set = is_target_address_set self.is_target_address_vid = is_target_address_vid self.eirp_index = eirp_index super(Control, self).__init__() def __iter__(self): byte = 0 if self.is_target_address_set: byte |= 1 << 7 if self.is_target_address_vid: byte |= 1 << 6 byte += self.eirp_index yield byte
class ChannelStatusIdentifier(Validatable): # TODO update to D7AP v1.1 SCHEMA = [{ "channel_band": Types.ENUM(ChannelBand), "channel_bandwidth": Types.ENUM(Bandwidth), "channel_index": Types.INTEGER(min=0, max=1039) }] def __init__(self, channel_band=ChannelBand.NOT_IMPL, channel_bandwidth=Bandwidth.kHz25, channel_index=0): self.channel_band = channel_band self.channel_bandwidth = channel_bandwidth self.channel_index = channel_index super(ChannelStatusIdentifier, self).__init__() def __iter__(self): byte = self.channel_band.value << 5 byte += self.channel_bandwidth.value << 4 byte += 0 << 3 # RFU byte += (self.channel_index >> 8) & 0x07 yield byte yield self.channel_index & 0xFF @staticmethod def parse(s): channel_band = ChannelBand(s.read("uint:3")) channel_bandwidth = Bandwidth(s.read("uint:1")) s.read("uint:1") # RFU channel_index = s.read("uint:11") return ChannelStatusIdentifier(channel_band=channel_band, channel_bandwidth=channel_bandwidth, channel_index=channel_index) def __str__(self): return "channel_band={}, channel_bandwidth={}, channel_index={}".format( self.channel_band.name.lstrip("BAND_"), self.channel_bandwidth.to_string(), self.channel_index)
class Addressee(Validatable): # addressee ID length ID_LENGTH_BROADCAST = 0 ID_LENGTH_VID = 2 ID_LENGTH_UID = 8 SCHEMA = [ { # broadcast "id_type": Types.INTEGER([IdType.BCAST]), "access_class": Types.BITS(4), "id_length": Types.INTEGER([0]), "id": Types.INTEGER([None]) }, { # virtual "id_type": Types.INTEGER([IdType.VID]), "access_class": Types.BITS(4), "id_length": Types.INTEGER([2]), "id": Types.INTEGER(min=0, max=0xFFFF) }, { # unicast "id_type": Types.INTEGER([IdType.UID]), "access_class": Types.BITS(4), "id_length": Types.INTEGER([8]), "id": Types.INTEGER(min=0, max=0xFFFFFFFFFFFFFFFF) } ] # SCHEMA = [ # { # "id_type" : Types.INTEGER(IdType.ALL), # "cl" : Types.BITS(4), # "id_length" : Types.INTEGER([0, 2, 8]), # "id" : Types.INTEGER(min=0, max=0xFFFFFFFFFFFFFFFF) # } # ] def __init__(self, access_class=0, id_type=IdType.BCAST, id=None): self.id_type = id_type self.access_class = access_class self.id = id super(Addressee, self).__init__() @property def id_length(self): return Addressee.length_for(id_type=self.id_type) @classmethod def length_for(self, id_type): if id_type == IdType.BCAST: return Addressee.ID_LENGTH_BROADCAST if id_type == IdType.VID: return Addressee.ID_LENGTH_VID if id_type == IdType.UID: return Addressee.ID_LENGTH_UID @staticmethod def parse(s): _ = s.read("pad:2") id_type = s.read("uint:2") cl = s.read("uint:4") l = Addressee.length_for(id_type) id = s.read("uint:" + str(l * 8)) if l > 0 else None return Addressee(id_type=id_type, access_class=cl, id=id) def __iter__(self): byte = 0 # pad 2 << 7 << 6 byte |= self.id_type << 4 byte += self.access_class yield byte if self.id_length > 0: id = bytearray(struct.pack(">Q", self.id))[8 - self.id_length:] for byte in id: yield byte def __str__(self): return "ac={}, id_type={}, id={}".format(self.access_class, self.id_type, hex(self.id))
class PhyStatusFile(File, Validatable): SCHEMA = [{ "up_time": Types.INTEGER(min=0, max=0xFFFFFFFF), "rx_time": Types.INTEGER(min=0, max=0xFFFFFFFF), "tx_time": Types.INTEGER(min=0, max=0xFFFFFFFF), "tx_duty_cycle": Types.INTEGER(min=0, max=1000), "channel_status_list_length": Types.INTEGER(min=0, max=0xFF), "channel_status_identifier": Types.LIST(ChannelStatusIdentifier, minlength=0, maxlength=0xFF), "channel_noise_floor": Types.LIST(minlength=0, maxlength=0xFF) }] def __init__(self, up_time=0, rx_time=0, tx_time=0, tx_duty_cycle=0, channel_status_list_length=0, channel_status_identifier=[], channel_noise_floor=[]): self.up_time = up_time self.rx_time = rx_time self.tx_time = tx_time self.tx_duty_cycle = tx_duty_cycle self.channel_status_list_length = channel_status_list_length self.channel_status_identifier = channel_status_identifier if len(channel_status_identifier) != channel_status_list_length: self.channel_status_identifier.extend( [ChannelStatusIdentifier()] * (channel_status_list_length - len(channel_status_identifier))) self.channel_noise_floor = channel_noise_floor if len(channel_noise_floor) != channel_status_list_length: self.channel_noise_floor.extend( [0] * (channel_status_list_length - len(channel_noise_floor))) File.__init__(self, SystemFileIds.PHY_STATUS.value, 15 + (3 * 10)) # allocate enough space for 20 channels Validatable.__init__(self) @staticmethod def parse(s): up_time = s.read("uint:32") rx_time = s.read("uint:32") tx_time = s.read("uint:32") tx_duty_cycle = s.read("uint:16") channel_status_list_length = s.read("uint:8") channel_status_identifier = [] channel_noise_floor = [] for counter in range(channel_status_list_length): channel_status_identifier.append( ChannelStatusIdentifier().parse(s)) channel_noise_floor.append(s.read("uint:8")) return PhyStatusFile( up_time=up_time, rx_time=rx_time, tx_time=tx_time, tx_duty_cycle=tx_duty_cycle, channel_status_list_length=channel_status_list_length, channel_status_identifier=channel_status_identifier, channel_noise_floor=channel_noise_floor) def __iter__(self): for byte in bytearray(struct.pack(">I", self.up_time)): yield byte for byte in bytearray(struct.pack(">I", self.rx_time)): yield byte for byte in bytearray(struct.pack(">I", self.tx_time)): yield byte for byte in bytearray(struct.pack(">H", self.tx_duty_cycle)): yield byte yield self.channel_status_list_length for counter in range(self.channel_status_list_length): for byte in self.channel_status_identifier[counter]: yield byte yield self.channel_noise_floor[counter] def __str__(self): channel_status = "" for counter in range(self.channel_status_list_length): channel_status = channel_status + "identifier={}, noise_floor={}; ".format( str(self.channel_status_identifier[counter]), self.channel_noise_floor[counter]) channel_status = "[{}]".format(channel_status[:-2]) return "up_time={}, rx_time={}, tx_time={}, tx_duty_cycle={}, channel_status_list_length={}, list={}".format( self.up_time, self.rx_time, self.tx_time, self.tx_duty_cycle, self.channel_status_list_length, channel_status)
class Addressee(Validatable): # addressee ID length ID_LENGTH_NBID = 1 ID_LENGTH_NOID = 0 ID_LENGTH_VID = 2 ID_LENGTH_UID = 8 SCHEMA = [ { # void identifier with reached devices estimation "id_type": Types.ENUM(IdType, allowedvalues=[IdType.NBID]), "nls_method": Types.ENUM(NlsMethod), "access_class": Types.BYTE(), "id": Types.OBJECT(CT) }, { # void identifier without reached devices estimation "id_type": Types.ENUM(IdType, allowedvalues=[IdType.NOID]), "nls_method": Types.ENUM(NlsMethod), "access_class": Types.BYTE(), "id": Types.INTEGER([None]) }, { # virtual "id_type": Types.ENUM(IdType, allowedvalues=[IdType.VID]), "nls_method": Types.ENUM(NlsMethod), "access_class": Types.BYTE(), "id": Types.INTEGER(min=0, max=0xFFFF) }, { # unicast "id_type": Types.ENUM(IdType, allowedvalues=[IdType.UID]), "nls_method": Types.ENUM(NlsMethod), "access_class": Types.BYTE(), "id": Types.INTEGER(min=0, max=0xFFFFFFFFFFFFFFFF) } ] # SCHEMA = [ # { # "id_type" : Types.INTEGER(IdType.ALL), # "cl" : Types.BITS(4), # "id_length" : Types.INTEGER([0, 2, 8]), # "id" : Types.INTEGER(min=0, max=0xFFFFFFFFFFFFFFFF) # } # ] def __init__(self, access_class=0, id_type=IdType.NOID, id=None, nls_method=NlsMethod.NONE): self.id_type = id_type self.access_class = access_class self.id = id self.nls_method = nls_method super(Addressee, self).__init__() @property def id_length(self): return Addressee.length_for(id_type=self.id_type) @classmethod def length_for(self, id_type): if id_type == IdType.NBID: return Addressee.ID_LENGTH_NBID if id_type == IdType.NOID: return Addressee.ID_LENGTH_NOID if id_type == IdType.VID: return Addressee.ID_LENGTH_VID if id_type == IdType.UID: return Addressee.ID_LENGTH_UID @staticmethod def parse(s): _ = s.read("pad:2") id_type = IdType(s.read("uint:2")) nls_method = NlsMethod(s.read("uint:4")) cl = s.read("uint:8") l = Addressee.length_for(id_type) id = s.read("uint:" + str(l * 8)) if l > 0 else None if id_type == IdType.NBID: id = CT(id) return Addressee(id_type=id_type, access_class=cl, id=id, nls_method=nls_method) def __iter__(self): byte = 0 # pad 2 << 7 << 6 byte |= self.id_type.value << 4 byte += self.nls_method.value yield byte yield self.access_class if self.id_length > 0: id_value = self.id if self.id_type == IdType.NBID: # self.id is a CT id_value = self.id.compressed_value() id = bytearray(struct.pack(">Q", id_value))[8 - self.id_length:] for byte in id: yield byte def __str__(self): return "ac={}, id_type={}, id={}".format(self.access_class, self.id_type, self.id)
class Status(Validatable): SCHEMA = [{ "channel_header": Types.BYTE(), # TODO parse "channel_index": Types.INTEGER(), "rx_level": Types.BYTE(), "link_budget": Types.BYTE(), "target_rx_level": Types.BYTE(), "nls": Types.BOOLEAN(), "missed": Types.BOOLEAN(), "retry": Types.BOOLEAN(), "ucast": Types.BOOLEAN(), "fifo_token": Types.BYTE(), "seq_nr": Types.BYTE(), "response_to": Types.OBJECT(CT), "addressee": Types.OBJECT(Addressee) }] def __init__(self, channel_header, channel_index, rx_level, link_budget, target_rx_level, nls, missed, retry, unicast, fifo_token, seq_nr, response_to, addressee): self.channel_header = channel_header self.channel_index = channel_index self.rx_level = rx_level self.link_budget = link_budget self.target_rx_level = target_rx_level self.nls = nls self.missed = missed self.retry = retry self.unicast = unicast self.fifo_token = fifo_token self.seq_nr = seq_nr self.response_to = response_to self.addressee = addressee super(Status, self).__init__() def __iter__(self): yield self.channel_header for byte in bytearray(struct.pack("<h", self.channel_index)): yield byte yield self.rx_level yield self.link_budget yield self.target_rx_level byte = 0 if self.nls: byte |= 1 << 7 if self.missed: byte |= 1 << 6 if self.retry: byte |= 1 << 5 if self.unicast: byte |= 1 << 4 yield byte yield chr(self.fifo_token) yield chr(self.seq_nr) for byte in self.response_to: yield byte for byte in self.addressee: yield byte def __str__(self): return "unicast={}, nls={}, retry={}, missed={}, fifo_token={}, rx_level={}, " \ "seq_nr={}, target_rx_level={}, addressee={}, response_to={}, link_budget={}, channel_header={}".format( self.unicast, self.nls, self.retry, self.missed, self.fifo_token, self.rx_level, self.seq_nr, self.target_rx_level, self.addressee, self.response_to, self.link_budget, self.channel_header )
class FactorySettingsFile(File, Validatable): SCHEMA = [{ "gain": Types.INTEGER(min=-128, max=127), "rx_bw_low_rate": Types.INTEGER(min=0, max=0xFFFFFFFF), "rx_bw_normal_rate": Types.INTEGER(min=0, max=0xFFFFFFFF), "rx_bw_high_rate": Types.INTEGER(min=0, max=0xFFFFFFFF), "bitrate_lo_rate": Types.INTEGER(min=0, max=0xFFFFFFFF), "fdev_lo_rate": Types.INTEGER(min=0, max=0xFFFFFFFF), "bitrate_normal_rate": Types.INTEGER(min=0, max=0xFFFFFFFF), "fdev_normal_rate": Types.INTEGER(min=0, max=0xFFFFFFFF), "bitrate_hi_rate": Types.INTEGER(min=0, max=0xFFFFFFFF), "fdev_hi_rate": Types.INTEGER(min=0, max=0xFFFFFFFF), "preamble_size_lo_rate": Types.INTEGER(min=0, max=255), "preamble_size_normal_rate": Types.INTEGER(min=0, max=255), "preamble_size_hi_rate": Types.INTEGER(min=0, max=255), "preamble_detector_size_lo_rate": Types.INTEGER(min=0, max=255), "preamble_detector_size_normal_rate": Types.INTEGER(min=0, max=255), "preamble_detector_size_hi_rate": Types.INTEGER(min=0, max=255), "preamble_tol": Types.INTEGER(min=0, max=255), "rssi_smoothing": Types.INTEGER(min=0, max=255), "rssi_offset": Types.INTEGER(min=-126, max=125), "lora_bw": Types.INTEGER(min=0, max=0xFFFFFFFF), "lora_SF": Types.INTEGER(min=6, max=12), "gaussian": Types.INTEGER(min=0, max=3), "paramp": Types.INTEGER(min=0, max=0xFFFF) }] def __init__(self, gain=0, rx_bw_low_rate=10468, rx_bw_normal_rate=78646, rx_bw_high_rate=125868, bitrate_lo_rate=9600, fdev_lo_rate=4800, bitrate_normal_rate=55555, fdev_normal_rate=50000, bitrate_hi_rate=166667, fdev_hi_rate=41667, preamble_size_lo_rate=5, preamble_size_normal_rate=5, preamble_size_hi_rate=7, preamble_detector_size_lo_rate=3, preamble_detector_size_normal_rate=3, preamble_detector_size_hi_rate=3, preamble_tol_lo_rate=15, preamble_tol_normal_rate=10, preamble_tol_hi_rate=10, rssi_smoothing=8, rssi_offset=0, lora_bw=125000, lora_SF=9, gaussian=2, paramp=40): self.gain = gain self.rx_bw_low_rate = rx_bw_low_rate self.rx_bw_normal_rate = rx_bw_normal_rate self.rx_bw_high_rate = rx_bw_high_rate self.bitrate_lo_rate = bitrate_lo_rate self.fdev_lo_rate = fdev_lo_rate self.bitrate_normal_rate = bitrate_normal_rate self.fdev_normal_rate = fdev_normal_rate self.bitrate_hi_rate = bitrate_hi_rate self.fdev_hi_rate = fdev_hi_rate self.preamble_size_lo_rate = preamble_size_lo_rate self.preamble_size_normal_rate = preamble_size_normal_rate self.preamble_size_hi_rate = preamble_size_hi_rate self.preamble_detector_size_lo_rate = preamble_detector_size_lo_rate self.preamble_detector_size_normal_rate = preamble_detector_size_normal_rate self.preamble_detector_size_hi_rate = preamble_detector_size_hi_rate self.preamble_tol_lo_rate = preamble_tol_lo_rate self.preamble_tol_normal_rate = preamble_tol_normal_rate self.preamble_tol_hi_rate = preamble_tol_hi_rate self.rssi_smoothing = int(ceil(log(rssi_smoothing, 2))) - 1 self.rssi_offset = rssi_offset self.lora_bw = lora_bw self.lora_SF = lora_SF self.gaussian = gaussian self.paramp = paramp File.__init__(self, SystemFileIds.FACTORY_SETTINGS.value, 56) Validatable.__init__(self) @staticmethod def parse(s): gain = s.read("int:8") rx_bw_low_rate = s.read("uint:32") rx_bw_normal_rate = s.read("uint:32") rx_bw_high_rate = s.read("uint:32") bitrate_lo_rate = s.read("uint:32") fdev_lo_rate = s.read("uint:32") bitrate_normal_rate = s.read("uint:32") fdev_normal_rate = s.read("uint:32") bitrate_hi_rate = s.read("uint:32") fdev_hi_rate = s.read("uint:32") preamble_size_lo_rate = s.read("uint:8") preamble_size_normal_rate = s.read("uint:8") preamble_size_hi_rate = s.read("uint:8") preamble_detector_size_lo_rate = s.read("uint:8") preamble_detector_size_normal_rate = s.read("uint:8") preamble_detector_size_hi_rate = s.read("uint:8") preamble_tol_lo_rate = s.read("uint:8") preamble_tol_normal_rate = s.read("uint:8") preamble_tol_hi_rate = s.read("uint:8") rssi_smoothing = s.read("uint:8") rssi_offset = s.read("uint:8") lora_bw = s.read("uint:32") lora_SF = s.read("uint:8") gaussian = s.read("uint:8") paramp = s.read("uint:16") return FactorySettingsFile( gain=gain, rx_bw_low_rate=rx_bw_low_rate, rx_bw_normal_rate=rx_bw_normal_rate, rx_bw_high_rate=rx_bw_high_rate, bitrate_lo_rate=bitrate_lo_rate, fdev_lo_rate=fdev_lo_rate, bitrate_normal_rate=bitrate_normal_rate, fdev_normal_rate=fdev_normal_rate, bitrate_hi_rate=bitrate_hi_rate, fdev_hi_rate=fdev_hi_rate, preamble_size_lo_rate=preamble_size_lo_rate, preamble_size_normal_rate=preamble_size_normal_rate, preamble_size_hi_rate=preamble_size_hi_rate, preamble_detector_size_lo_rate=preamble_detector_size_lo_rate, preamble_detector_size_normal_rate= preamble_detector_size_normal_rate, preamble_detector_size_hi_rate=preamble_detector_size_hi_rate, preamble_tol_lo_rate=preamble_tol_lo_rate, preamble_tol_normal_rate=preamble_tol_normal_rate, preamble_tol_hi_rate=preamble_tol_hi_rate, rssi_smoothing=rssi_smoothing, rssi_offset=rssi_offset, lora_bw=lora_bw, lora_SF=lora_SF, gaussian=gaussian, paramp=paramp) def __iter__(self): yield self.gain for byte in bytearray(struct.pack(">I", self.rx_bw_low_rate)): yield byte for byte in bytearray(struct.pack(">I", self.rx_bw_normal_rate)): yield byte for byte in bytearray(struct.pack(">I", self.rx_bw_high_rate)): yield byte for byte in bytearray(struct.pack(">I", self.bitrate_lo_rate)): yield byte for byte in bytearray(struct.pack(">I", self.fdev_lo_rate)): yield byte for byte in bytearray(struct.pack(">I", self.bitrate_normal_rate)): yield byte for byte in bytearray(struct.pack(">I", self.fdev_normal_rate)): yield byte for byte in bytearray(struct.pack(">I", self.bitrate_hi_rate)): yield byte for byte in bytearray(struct.pack(">I", self.fdev_hi_rate)): yield byte yield self.preamble_size_lo_rate yield self.preamble_size_normal_rate yield self.preamble_size_hi_rate yield self.preamble_detector_size_lo_rate yield self.preamble_detector_size_normal_rate yield self.preamble_detector_size_hi_rate yield self.preamble_tol_lo_rate yield self.preamble_tol_normal_rate yield self.preamble_tol_hi_rate yield self.rssi_smoothing yield self.rssi_offset for byte in bytearray(struct.pack(">I", self.lora_bw)): yield byte yield self.lora_SF yield self.gaussian for byte in bytearray(struct.pack(">H", self.paramp)): yield byte def __str__(self): return "gain={}, rx_bw_low_rate={}, rx_bw_normal_rate={}, rx_bw_high_rate={}, low rate={} : {}, normal rate={} : {}, high rate={} : {}, preamble sizes {} : {} : {}, preamble detector size {} : {} : {} with tol {} : {} : {}, rssi smoothing {} with offset {}\nlora sf set to {}, bw to {}\ngaussian set to {} and paramp to {} microseconds".format( self.gain, self.rx_bw_low_rate, self.rx_bw_normal_rate, self.rx_bw_high_rate, self.bitrate_lo_rate, self.fdev_lo_rate, self.bitrate_normal_rate, self.fdev_normal_rate, self.bitrate_hi_rate, self.fdev_hi_rate, self.preamble_size_lo_rate, self.preamble_size_normal_rate, self.preamble_size_hi_rate, self.preamble_detector_size_lo_rate, self.preamble_detector_size_normal_rate, self.preamble_detector_size_hi_rate, self.preamble_tol_lo_rate, self.preamble_tol_normal_rate, self.preamble_tol_hi_rate, self.rssi_smoothing, self.rssi_offset, self.lora_bw, self.lora_SF, self.gaussian, self.paramp)
class Status(Validatable): SCHEMA = [{ "channel_header": Types.OBJECT(ChannelHeader), "channel_index": Types.INTEGER(), "rx_level": Types.BYTE(), "link_budget": Types.BYTE(), "target_rx_level": Types.BYTE(), "nls": Types.BOOLEAN(), "missed": Types.BOOLEAN(), "retry": Types.BOOLEAN(), "ucast": Types.BOOLEAN(), "fifo_token": Types.BYTE(), "seq_nr": Types.BYTE(), "response_to": Types.OBJECT(CT), "addressee": Types.OBJECT(Addressee) }] def __init__(self, channel_header, channel_index, rx_level, link_budget, target_rx_level, nls, missed, retry, unicast, fifo_token, seq_nr, response_to, addressee): self.channel_header = channel_header self.channel_index = channel_index self.rx_level = rx_level self.link_budget = link_budget self.target_rx_level = target_rx_level self.nls = nls self.missed = missed self.retry = retry self.unicast = unicast self.fifo_token = fifo_token self.seq_nr = seq_nr self.response_to = response_to self.addressee = addressee super(Status, self).__init__() @staticmethod def parse(s): channel_header = ChannelHeader.parse(s) channel_index = s.read("uint:16") rx_level = s.read("int:8") link_budget = s.read("uint:8") target_rx_level = s.read("uint:8") nls = s.read("bool") missed = s.read("bool") retry = s.read("bool") unicast = s.read("bool") _ = s.read("pad:4") fifo_token = s.read("uint:8") seq_nr = s.read("uint:8") response_to = CT.parse(s) addressee = Addressee.parse(s) return Status(channel_header=channel_header, channel_index=channel_index, rx_level=rx_level, link_budget=link_budget, target_rx_level=target_rx_level, nls=nls, missed=missed, retry=retry, unicast=unicast, fifo_token=fifo_token, seq_nr=seq_nr, response_to=response_to, addressee=addressee) def __iter__(self): for byte in self.channel_header: yield byte for byte in bytearray(struct.pack("<h", self.channel_index)): yield byte yield self.rx_level yield self.link_budget yield self.target_rx_level byte = 0 if self.nls: byte |= 1 << 7 if self.missed: byte |= 1 << 6 if self.retry: byte |= 1 << 5 if self.unicast: byte |= 1 << 4 yield byte yield chr(self.fifo_token) yield chr(self.seq_nr) for byte in self.response_to: yield byte for byte in self.addressee: yield byte def __str__(self): return "unicast={}, nls={}, retry={}, missed={}, fifo_token={}, rx_level={}, seq_nr={}, target_rx_level={}, " \ "addressee={}, response_to={}, link_budget={}, channel_header={}, channel_index={}".format( self.unicast, self.nls, self.retry, self.missed, self.fifo_token, self.rx_level, self.seq_nr, self.target_rx_level, self.addressee, self.response_to, self.link_budget, self.channel_header, self.channel_index )
class Frame(Validatable): SCHEMA = [{ "control": Types.OBJECT(Control), "dialog_id": Types.INTEGER(min=0, max=255), "transaction_id": Types.INTEGER(min=0, max=255), "agc_rx_level_i": Types.INTEGER(min=0, max=31), "tl": Types.OBJECT(CT, nullable=True), "te": Types.OBJECT(CT, nullable=True), "tc": Types.OBJECT(CT, nullable=True), "ack_template": Types.OBJECT(nullable=True), # TODO "alp_command": Types.OBJECT(Command) }] def __init__(self, control, dialog_id, transaction_id, alp_command, agc_rx_level_i=10, tl=None, te=None, tc=None, ack_template=None): if agc_rx_level_i == None: agc_rx_level_i = 10 self.control = control self.dialog_id = dialog_id self.transaction_id = transaction_id self.agc_rx_level_i = agc_rx_level_i self.tl = tl self.te = te self.tc = tc self.ack_template = ack_template self.alp_command = alp_command super(Frame, self).__init__() @staticmethod def parse(bitstream, payload_length): control = Control.parse(bitstream) payload_length = payload_length - 1 # subtract control byte dialog_id = bitstream.read("uint:8") payload_length = payload_length - 1 transaction_id = bitstream.read("uint:8") payload_length = payload_length - 1 target_rx_level_i = None if control.has_agc: target_rx_level_i = bitstream.read("uint:8") payload_length -= 1 tl = None if control.has_tl: tl = CT.parse(bitstream) payload_length -= 1 te = None if control.has_te: te = CT.parse(bitstream) payload_length -= 1 tc = None # TODO currently we have no way to know if Tc is present or not # Tc is present when control.is_ack_requested AND when we are requester, # while responders copy this flag but do NOT provide a Tc. # When parsing single frames without knowledge of dialogs we cannot determine this. # We use control.is_dialog_start for now but this will break when we start supporting multiple transactions per dialog if control.is_ack_requested and control.is_dialog_start: tc = CT.parse(bitstream) payload_length -= 1 ack_template = None if control.is_ack_not_void: transaction_id_start = bitstream.read("uint:8") payload_length = payload_length - 1 transaction_id_stop = bitstream.read("uint:8") payload_length = payload_length - 1 assert transaction_id_start == transaction_id, "Other case not implemented yet" assert transaction_id_stop == transaction_id, "Other case not implemented yet" # TODO ack bitmap (for when transaction_id_start != transaction_id) ack_template = [transaction_id_start, transaction_id_stop] assert control.is_ack_record_requested == False, "Not implemented yet" assert control.is_ack_not_void == False, "Not implemented yet" alp_command = AlpParser().parse(bitstream, payload_length) return Frame(control=control, dialog_id=dialog_id, transaction_id=transaction_id, agc_rx_level_i=target_rx_level_i, tl=tl, te=te, tc=tc, ack_template=ack_template, alp_command=alp_command) def __iter__(self): for byte in self.control: yield byte yield self.dialog_id yield self.transaction_id if self.control.has_agc: yield self.agc_rx_level_i if self.control.has_tl: yield self.tl if self.control.has_te: yield self.te if self.control.is_ack_not_void: for byte in self.ack_template: yield byte for byte in self.alp_command: yield byte