Added ability to query physical layer stats on links
This commit is contained in:
parent
a451b987aa
commit
798dfb1727
67
RNS/Link.py
67
RNS/Link.py
|
@ -169,6 +169,7 @@ class Link:
|
||||||
self.destination = destination
|
self.destination = destination
|
||||||
self.attached_interface = None
|
self.attached_interface = None
|
||||||
self.__remote_identity = None
|
self.__remote_identity = None
|
||||||
|
self.__track_phy_stats = False
|
||||||
self._channel = None
|
self._channel = None
|
||||||
if self.destination == None:
|
if self.destination == None:
|
||||||
self.initiator = False
|
self.initiator = False
|
||||||
|
@ -409,6 +410,38 @@ class Link:
|
||||||
RNS.log("Error occurred while processing RTT packet, tearing down link. The contained exception was: "+str(e), RNS.LOG_ERROR)
|
RNS.log("Error occurred while processing RTT packet, tearing down link. The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||||
self.teardown()
|
self.teardown()
|
||||||
|
|
||||||
|
def track_phy_stats(self, track):
|
||||||
|
"""
|
||||||
|
You can enable physical layer statistics on a per-link basis. If this is enabled,
|
||||||
|
and the link is running over an interface that supports reporting physical layer
|
||||||
|
statistics, you will be able to retrieve stats such as *RSSI*, *SNR* and physical
|
||||||
|
*Link Quality* for the link.
|
||||||
|
|
||||||
|
:param track: Whether or not to keep track of physical layer statistics. Value must be ``True`` or ``False``.
|
||||||
|
"""
|
||||||
|
if track:
|
||||||
|
self.__track_phy_stats = True
|
||||||
|
else:
|
||||||
|
self.__track_phy_stats = False
|
||||||
|
|
||||||
|
def get_rssi(self):
|
||||||
|
"""
|
||||||
|
:returns: The physical layer *Received Signal Strength Indication* if available, otherwise ``None``. Physical layer statistics must be enabled on the link for this method to return a value.
|
||||||
|
"""
|
||||||
|
return self.rssi
|
||||||
|
|
||||||
|
def get_snr(self):
|
||||||
|
"""
|
||||||
|
:returns: The physical layer *Signal-to-Noise Ratio* if available, otherwise ``None``. Physical layer statistics must be enabled on the link for this method to return a value.
|
||||||
|
"""
|
||||||
|
return self.rssi
|
||||||
|
|
||||||
|
def get_q(self):
|
||||||
|
"""
|
||||||
|
:returns: The physical layer *Link Quality* if available, otherwise ``None``. Physical layer statistics must be enabled on the link for this method to return a value.
|
||||||
|
"""
|
||||||
|
return self.rssi
|
||||||
|
|
||||||
def get_establishment_rate(self):
|
def get_establishment_rate(self):
|
||||||
"""
|
"""
|
||||||
:returns: The data transfer rate at which the link establishment procedure ocurred, in bits per second.
|
:returns: The data transfer rate at which the link establishment procedure ocurred, in bits per second.
|
||||||
|
@ -584,7 +617,14 @@ class Link:
|
||||||
sleep(sleep_time)
|
sleep(sleep_time)
|
||||||
|
|
||||||
|
|
||||||
def __update_phy_stats(self, packet):
|
def __update_phy_stats(self, packet, query_shared = True):
|
||||||
|
if self.__track_phy_stats:
|
||||||
|
if query_shared:
|
||||||
|
reticulum = RNS.Reticulum.get_instance()
|
||||||
|
if packet.rssi == None: packet.rssi = reticulum.get_packet_rssi(packet.packet_hash)
|
||||||
|
if packet.snr == None: packet.snr = reticulum.get_packet_snr(packet.packet_hash)
|
||||||
|
if packet.q == None: packet.q = reticulum.get_packet_q(packet.packet_hash)
|
||||||
|
|
||||||
if packet.rssi != None:
|
if packet.rssi != None:
|
||||||
self.rssi = packet.rssi
|
self.rssi = packet.rssi
|
||||||
if packet.snr != None:
|
if packet.snr != None:
|
||||||
|
@ -705,6 +745,7 @@ class Link:
|
||||||
self.status = Link.ACTIVE
|
self.status = Link.ACTIVE
|
||||||
|
|
||||||
if packet.packet_type == RNS.Packet.DATA:
|
if packet.packet_type == RNS.Packet.DATA:
|
||||||
|
should_query = False
|
||||||
if packet.context == RNS.Packet.NONE:
|
if packet.context == RNS.Packet.NONE:
|
||||||
plaintext = self.decrypt(packet.data)
|
plaintext = self.decrypt(packet.data)
|
||||||
if self.callbacks.packet != None:
|
if self.callbacks.packet != None:
|
||||||
|
@ -714,15 +755,18 @@ class Link:
|
||||||
|
|
||||||
if self.destination.proof_strategy == RNS.Destination.PROVE_ALL:
|
if self.destination.proof_strategy == RNS.Destination.PROVE_ALL:
|
||||||
packet.prove()
|
packet.prove()
|
||||||
|
should_query = True
|
||||||
|
|
||||||
elif self.destination.proof_strategy == RNS.Destination.PROVE_APP:
|
elif self.destination.proof_strategy == RNS.Destination.PROVE_APP:
|
||||||
if self.destination.callbacks.proof_requested:
|
if self.destination.callbacks.proof_requested:
|
||||||
try:
|
try:
|
||||||
self.destination.callbacks.proof_requested(packet)
|
if self.destination.callbacks.proof_requested(packet):
|
||||||
|
packet.prove()
|
||||||
|
should_query = True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
RNS.log("Error while executing proof request callback from "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
|
RNS.log("Error while executing proof request callback from "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||||
|
|
||||||
self.__update_phy_stats(packet)
|
self.__update_phy_stats(packet, query_shared=should_query)
|
||||||
|
|
||||||
elif packet.context == RNS.Packet.LINKIDENTIFY:
|
elif packet.context == RNS.Packet.LINKIDENTIFY:
|
||||||
plaintext = self.decrypt(packet.data)
|
plaintext = self.decrypt(packet.data)
|
||||||
|
@ -742,7 +786,7 @@ class Link:
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
RNS.log("Error while executing remote identified callback from "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
|
RNS.log("Error while executing remote identified callback from "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||||
|
|
||||||
self.__update_phy_stats(packet)
|
self.__update_phy_stats(packet, query_shared=True)
|
||||||
|
|
||||||
elif packet.context == RNS.Packet.REQUEST:
|
elif packet.context == RNS.Packet.REQUEST:
|
||||||
try:
|
try:
|
||||||
|
@ -750,7 +794,7 @@ class Link:
|
||||||
packed_request = self.decrypt(packet.data)
|
packed_request = self.decrypt(packet.data)
|
||||||
unpacked_request = umsgpack.unpackb(packed_request)
|
unpacked_request = umsgpack.unpackb(packed_request)
|
||||||
self.handle_request(request_id, unpacked_request)
|
self.handle_request(request_id, unpacked_request)
|
||||||
self.__update_phy_stats(packet)
|
self.__update_phy_stats(packet, query_shared=True)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
RNS.log("Error occurred while handling request. The contained exception was: "+str(e), RNS.LOG_ERROR)
|
RNS.log("Error occurred while handling request. The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||||
|
|
||||||
|
@ -762,21 +806,22 @@ class Link:
|
||||||
response_data = unpacked_response[1]
|
response_data = unpacked_response[1]
|
||||||
transfer_size = len(umsgpack.packb(response_data))-2
|
transfer_size = len(umsgpack.packb(response_data))-2
|
||||||
self.handle_response(request_id, response_data, transfer_size, transfer_size)
|
self.handle_response(request_id, response_data, transfer_size, transfer_size)
|
||||||
self.__update_phy_stats(packet)
|
self.__update_phy_stats(packet, query_shared=True)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
RNS.log("Error occurred while handling response. The contained exception was: "+str(e), RNS.LOG_ERROR)
|
RNS.log("Error occurred while handling response. The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||||
|
|
||||||
elif packet.context == RNS.Packet.LRRTT:
|
elif packet.context == RNS.Packet.LRRTT:
|
||||||
if not self.initiator:
|
if not self.initiator:
|
||||||
self.rtt_packet(packet)
|
self.rtt_packet(packet)
|
||||||
self.__update_phy_stats(packet)
|
self.__update_phy_stats(packet, query_shared=True)
|
||||||
|
|
||||||
elif packet.context == RNS.Packet.LINKCLOSE:
|
elif packet.context == RNS.Packet.LINKCLOSE:
|
||||||
self.teardown_packet(packet)
|
self.teardown_packet(packet)
|
||||||
|
self.__update_phy_stats(packet, query_shared=True)
|
||||||
|
|
||||||
elif packet.context == RNS.Packet.RESOURCE_ADV:
|
elif packet.context == RNS.Packet.RESOURCE_ADV:
|
||||||
packet.plaintext = self.decrypt(packet.data)
|
packet.plaintext = self.decrypt(packet.data)
|
||||||
self.__update_phy_stats(packet)
|
self.__update_phy_stats(packet, query_shared=True)
|
||||||
|
|
||||||
if RNS.ResourceAdvertisement.is_request(packet):
|
if RNS.ResourceAdvertisement.is_request(packet):
|
||||||
RNS.Resource.accept(packet, callback=self.request_resource_concluded)
|
RNS.Resource.accept(packet, callback=self.request_resource_concluded)
|
||||||
|
@ -804,7 +849,7 @@ class Link:
|
||||||
|
|
||||||
elif packet.context == RNS.Packet.RESOURCE_REQ:
|
elif packet.context == RNS.Packet.RESOURCE_REQ:
|
||||||
plaintext = self.decrypt(packet.data)
|
plaintext = self.decrypt(packet.data)
|
||||||
self.__update_phy_stats(packet)
|
self.__update_phy_stats(packet, query_shared=True)
|
||||||
if ord(plaintext[:1]) == RNS.Resource.HASHMAP_IS_EXHAUSTED:
|
if ord(plaintext[:1]) == RNS.Resource.HASHMAP_IS_EXHAUSTED:
|
||||||
resource_hash = plaintext[1+RNS.Resource.MAPHASH_LEN:RNS.Identity.HASHLENGTH//8+1+RNS.Resource.MAPHASH_LEN]
|
resource_hash = plaintext[1+RNS.Resource.MAPHASH_LEN:RNS.Identity.HASHLENGTH//8+1+RNS.Resource.MAPHASH_LEN]
|
||||||
else:
|
else:
|
||||||
|
@ -820,7 +865,7 @@ class Link:
|
||||||
|
|
||||||
elif packet.context == RNS.Packet.RESOURCE_HMU:
|
elif packet.context == RNS.Packet.RESOURCE_HMU:
|
||||||
plaintext = self.decrypt(packet.data)
|
plaintext = self.decrypt(packet.data)
|
||||||
self.__update_phy_stats(packet)
|
self.__update_phy_stats(packet, query_shared=True)
|
||||||
resource_hash = plaintext[:RNS.Identity.HASHLENGTH//8]
|
resource_hash = plaintext[:RNS.Identity.HASHLENGTH//8]
|
||||||
for resource in self.incoming_resources:
|
for resource in self.incoming_resources:
|
||||||
if resource_hash == resource.hash:
|
if resource_hash == resource.hash:
|
||||||
|
@ -878,7 +923,7 @@ class Link:
|
||||||
for resource in self.outgoing_resources:
|
for resource in self.outgoing_resources:
|
||||||
if resource_hash == resource.hash:
|
if resource_hash == resource.hash:
|
||||||
resource.validate_proof(packet.data)
|
resource.validate_proof(packet.data)
|
||||||
self.__update_phy_stats(packet)
|
self.__update_phy_stats(packet, query_shared=True)
|
||||||
|
|
||||||
self.watchdog_lock = False
|
self.watchdog_lock = False
|
||||||
|
|
||||||
|
|
|
@ -371,7 +371,7 @@ class PacketReceipt:
|
||||||
if packet.destination.type == RNS.Destination.LINK:
|
if packet.destination.type == RNS.Destination.LINK:
|
||||||
self.timeout = packet.destination.rtt * packet.destination.traffic_timeout_factor
|
self.timeout = packet.destination.rtt * packet.destination.traffic_timeout_factor
|
||||||
else:
|
else:
|
||||||
self.timeout = RNS.Reticulum.get_instance().get_first_hop_timeout(destination.hash)
|
self.timeout = RNS.Reticulum.get_instance().get_first_hop_timeout(self.destination.hash)
|
||||||
self.timeout += Packet.TIMEOUT_PER_HOP * RNS.Transport.hops_to(self.destination.hash)
|
self.timeout += Packet.TIMEOUT_PER_HOP * RNS.Transport.hops_to(self.destination.hash)
|
||||||
|
|
||||||
def get_status(self):
|
def get_status(self):
|
||||||
|
|
|
@ -175,7 +175,7 @@ class Resource:
|
||||||
if not resource.link.has_incoming_resource(resource):
|
if not resource.link.has_incoming_resource(resource):
|
||||||
resource.link.register_incoming_resource(resource)
|
resource.link.register_incoming_resource(resource)
|
||||||
|
|
||||||
RNS.log("Accepting resource advertisement for "+RNS.prettyhexrep(resource.hash), RNS.LOG_DEBUG)
|
RNS.log(f"Accepting resource advertisement for {RNS.prettyhexrep(resource.hash)}. Transfer size is {RNS.prettysize(resource.size)} in {resource.total_parts} parts.", RNS.LOG_DEBUG)
|
||||||
if resource.link.callbacks.resource_started != None:
|
if resource.link.callbacks.resource_started != None:
|
||||||
try:
|
try:
|
||||||
resource.link.callbacks.resource_started(resource)
|
resource.link.callbacks.resource_started(resource)
|
||||||
|
|
|
@ -1109,6 +1109,9 @@ class Reticulum:
|
||||||
if path == "packet_snr":
|
if path == "packet_snr":
|
||||||
rpc_connection.send(self.get_packet_snr(call["packet_hash"]))
|
rpc_connection.send(self.get_packet_snr(call["packet_hash"]))
|
||||||
|
|
||||||
|
if path == "packet_q":
|
||||||
|
rpc_connection.send(self.get_packet_q(call["packet_hash"]))
|
||||||
|
|
||||||
if "drop" in call:
|
if "drop" in call:
|
||||||
path = call["drop"]
|
path = call["drop"]
|
||||||
|
|
||||||
|
@ -1371,6 +1374,21 @@ class Reticulum:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def get_packet_q(self, packet_hash):
|
||||||
|
if self.is_connected_to_shared_instance:
|
||||||
|
rpc_connection = multiprocessing.connection.Client(self.rpc_addr, authkey=self.rpc_key)
|
||||||
|
rpc_connection.send({"get": "packet_q", "packet_hash": packet_hash})
|
||||||
|
response = rpc_connection.recv()
|
||||||
|
return response
|
||||||
|
|
||||||
|
else:
|
||||||
|
for entry in RNS.Transport.local_client_q_cache:
|
||||||
|
if entry[0] == packet_hash:
|
||||||
|
return entry[1]
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def should_use_implicit_proof():
|
def should_use_implicit_proof():
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -31,7 +31,7 @@ import argparse
|
||||||
from RNS._version import __version__
|
from RNS._version import __version__
|
||||||
|
|
||||||
DEFAULT_PROBE_SIZE = 16
|
DEFAULT_PROBE_SIZE = 16
|
||||||
DEFAULT_TIMEOUT = 15
|
DEFAULT_TIMEOUT = 12
|
||||||
|
|
||||||
def program_setup(configdir, destination_hexhash, size=None, full_name = None, verbosity = 0, timeout=None):
|
def program_setup(configdir, destination_hexhash, size=None, full_name = None, verbosity = 0, timeout=None):
|
||||||
if size == None: size = DEFAULT_PROBE_SIZE
|
if size == None: size = DEFAULT_PROBE_SIZE
|
||||||
|
@ -73,7 +73,7 @@ def program_setup(configdir, destination_hexhash, size=None, full_name = None, v
|
||||||
print("Path to "+RNS.prettyhexrep(destination_hash)+" requested ", end=" ")
|
print("Path to "+RNS.prettyhexrep(destination_hash)+" requested ", end=" ")
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
|
|
||||||
_timeout = time.time() + (timeout or DEFAULT_TIMEOUT)
|
_timeout = time.time() + (timeout or DEFAULT_TIMEOUT+reticulum.get_first_hop_timeout(destination_hash))
|
||||||
i = 0
|
i = 0
|
||||||
syms = "⢄⢂⢁⡁⡈⡐⡠"
|
syms = "⢄⢂⢁⡁⡈⡐⡠"
|
||||||
while not RNS.Transport.has_path(destination_hash) and not time.time() > _timeout:
|
while not RNS.Transport.has_path(destination_hash) and not time.time() > _timeout:
|
||||||
|
@ -149,6 +149,7 @@ def program_setup(configdir, destination_hexhash, size=None, full_name = None, v
|
||||||
if reticulum.is_connected_to_shared_instance:
|
if reticulum.is_connected_to_shared_instance:
|
||||||
reception_rssi = reticulum.get_packet_rssi(receipt.proof_packet.packet_hash)
|
reception_rssi = reticulum.get_packet_rssi(receipt.proof_packet.packet_hash)
|
||||||
reception_snr = reticulum.get_packet_snr(receipt.proof_packet.packet_hash)
|
reception_snr = reticulum.get_packet_snr(receipt.proof_packet.packet_hash)
|
||||||
|
reception_q = reticulum.get_packet_q(receipt.proof_packet.packet_hash)
|
||||||
|
|
||||||
if reception_rssi != None:
|
if reception_rssi != None:
|
||||||
reception_stats += " [RSSI "+str(reception_rssi)+" dBm]"
|
reception_stats += " [RSSI "+str(reception_rssi)+" dBm]"
|
||||||
|
@ -156,6 +157,9 @@ def program_setup(configdir, destination_hexhash, size=None, full_name = None, v
|
||||||
if reception_snr != None:
|
if reception_snr != None:
|
||||||
reception_stats += " [SNR "+str(reception_snr)+" dB]"
|
reception_stats += " [SNR "+str(reception_snr)+" dB]"
|
||||||
|
|
||||||
|
if reception_q != None:
|
||||||
|
reception_stats += " [Link Quality "+str(reception_q)+"%]"
|
||||||
|
|
||||||
else:
|
else:
|
||||||
if receipt.proof_packet != None:
|
if receipt.proof_packet != None:
|
||||||
if receipt.proof_packet.rssi != None:
|
if receipt.proof_packet.rssi != None:
|
||||||
|
|
Loading…
Reference in New Issue