Skip to content

Commit

Permalink
Merge pull request #192 from Luos-io/rc_3.0.0
Browse files Browse the repository at this point in the history
RC 3.0.0
  • Loading branch information
nicolas-rabault authored Dec 13, 2023
2 parents e251cf2 + 5fe1e02 commit 481170f
Show file tree
Hide file tree
Showing 8 changed files with 433 additions and 318 deletions.
8 changes: 0 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,3 @@ Luos proposes organized and effective development practices, guaranteeing develo

* → Try on your own with the [get started](https://www.luos.io/tutorials/get-started)
* → Consult the full [documentation](https://www.luos.io/docs)

## Disclaimer
This library send some anonymous information to Luos allowing to improve Pyluos experience.
To disable the telemetry please add `telemetry=False` parameter at Device creation.
For example:
```python
device = Device('/dev/cu.usbserial-DN2EUDGP', telemetry=False)
```
163 changes: 85 additions & 78 deletions pyluos/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
def run_from_unittest():
return 'unittest' in sys.services


class contList(list):
def __repr__(self):
s = '-------------------------------------------------\n'
Expand All @@ -31,52 +32,45 @@ def __repr__(self):
s += '{:<20s}{:<20s}{:<5d}\n'.format(elem.type, elem.alias, elem.id)
return s


class nodeList(list):
def __repr__(self):
# Display the topology
s = ''
prefill = ''
prechild = False
for pre, fill, node in RenderTree(self[0], style=DoubleStyle()):
child = []
# Draw the input part
if (node.parent == None):
branch = " ┃ "
for i,x in enumerate(node.port_table):
child.append(i)
else:
l_port_id = '?'
for i,x in enumerate(node.parent.port_table):
if (x == node.id):
l_port_id = str(i)
r_port_id = node.port_table.index(min(node.port_table))
for i,x in enumerate(node.port_table):
if ((i != r_port_id) and (x != 65535)):
child.append(i)
branch = str(l_port_id) + ">┃" + str(r_port_id) + " "
branch = "═■┫ "

# Draw the node body
prefill = (prefill[:len(fill)]) if len(prefill) > len(fill) else prefill
s +='{:<{fillsize}s}'.format(prefill, fillsize=len(fill))
s += '{:<{fillsize}s}'.format(prefill, fillsize=len(fill))
if (prechild == True):
position = -4
s = s[:position] + '║' + s[position+1:]
s += " ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓\n"
tmpstr = "%s╭node %s" % (branch, node.id)
s += pre + '{:^10s}'.format(tmpstr)
if (node.certified == True):
s += '{:^41s}'.format("Certified") + "┃\n"
else:
s += '{:^41s}'.format("/!\\ Not certified") + "┃\n"
s += fill + " ┃ │ " + '{:<20s}{:<20s}{:<5s}'.format("Type", "Alias", "ID")+ "┃\n"
for y,elem in enumerate(node.services):
if (y == (len(node.services)-1)):
s += fill + " ┃ ╰> " + '{:<20s}{:<20s}{:<5d}'.format(elem.type, elem.alias, elem.id)+ "┃\n"
s = s[:-4] + '║' + s[-4 + 1:]
s += '{:<54s}'.format(" ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓\n")
tmpstr = '{:<52s}'.format("%s╭────────────────── Node %s ──────────────────" % (branch, node.id))

if (len(pre) > 0):
pre = pre[:-1] + "═"
s += pre + tmpstr + '{:>3s}'.format("┃\n")
s += fill + " ┃ │ " + '{:<20s}{:<20s}{:<4s}'.format("Type", "Alias", "ID") + '{:>3s}'.format("┃\n")
for y, elem in enumerate(node.services):
if (y == (len(node.services) - 1)):
s += fill + " ┃ ╰> " + '{:<20s}{:<20s}{:<4d}'.format(elem.type, elem.alias, elem.id) + '{:>3s}'.format("┃\n")
else:
s += fill + " ┃ ├> " + '{:<20s}{:<20s}{:<5d}'.format(elem.type, elem.alias, elem.id) + "┃\n"
if (not child):
s += fill + " >┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛\n"
prechild = False
else:
s += fill + "╔>┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛\n"
s += fill + " ┃ ├> " + '{:<20s}{:<20s}{:<4d}'.format(elem.type, elem.alias, elem.id) + '{:>3s}'.format("┃\n")

# Draw the output part
if (node.children):
s += fill + "╔■┻━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛\n"
prechild = True
else:
s += fill + " ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛\n"
prechild = False
prefill = fill
return s

Expand All @@ -93,7 +87,6 @@ def __init__(self, host,
log_conf=_base_log_conf,
test_mode=False,
background_task=True,
telemetry=True,
*args, **kwargs):
if IO is not None:
self._io = IO(host=host, *args, **kwargs)
Expand All @@ -106,7 +99,6 @@ def __init__(self, host,
config = json.load(f)
logging.config.dictConfig(config)

self.telemetry = telemetry
self.logger = logging.getLogger(__name__)
self.logger.info('Connected to "{}".'.format(host))

Expand All @@ -121,7 +113,7 @@ def __init__(self, host,
self._running = True
self._pause = False

if(background_task == True):
if (background_task == True):
# Setup both poll/push synchronization loops.
self._poll_bg = threading.Thread(target=self._poll_and_up)
self._poll_bg.daemon = True
Expand Down Expand Up @@ -160,35 +152,37 @@ def _setup(self):
retry = 0
while ('routing_table' not in state):
if ('route_table' in state):
self.logger.info("Watch out the Luos revision you are using on your board is too old to work with this revision on pyluos.\n Please consider updating Luos on your boards")
self.logger.info("Watch out the Luos revision you are using on your board is too old to work with this revision of pyluos.\n Please consider updating Luos on your boards")
return
state = self._poll_once()
if (time.time()-startTime > 1):
retry = retry +1
if (time.time() - startTime > 5):
retry = retry + 1
if retry > 5:
# detection is not working
sys.exit("Detection failed.")
self._send({'detection': {}})
startTime = time.time()
# Save routing table data
self._routing_table = state
# Create nodes
self._services = []
self._nodes = []
for i, node in enumerate(state['routing_table']):
if ('node_id' not in node):
self.logger.info("Watch out the Luos revision you are using on your board is too old to work with this revision on pyluos.\n Please consider updating Luos on your boards")
self.logger.info("Watch out the Luos revision you are using on your board is too old to work with this revision of pyluos.\n Please consider updating Luos on your boards")
parent_elem = None
# find a parent and create a link
if (min(node["port_table"]) < node["services"][0]["id"]):
parent_id = min(node["port_table"])
# find a parent and create the link
if (node["con"]["parent"][0] != 0):
parent_id = node["con"]["parent"][0]
for elem in self._nodes:
if (elem.id == parent_id):
parent_elem = elem
break;
break
# create the node
self._nodes.append(AnyNode(id=node["node_id"], certified=node["certified"], parent=parent_elem, port_table=node["port_table"]))
self._nodes.append(AnyNode(id=node["node_id"], parent=parent_elem, connection=node["con"]))

filtered_services = contList([mod for mod in node["services"]
if 'type' in mod and mod['type'] in name2mod.keys()])
if 'type' in mod and mod['type'] in name2mod.keys()])
# Create a list of services in the node
self._nodes[i].services = [
name2mod[mod['type']](id=mod['id'],
Expand All @@ -206,25 +200,6 @@ def _setup(self):
self._cmd_data = []
self._binary = []

if (self.telemetry == True):
from pyluos.version import version
self.logger.info('Sending telemetry...')
luos_telemetry = {"telemetry_type": "pyluos",
"mac": hex(uuid.getnode()),
"system": sys.platform,
"unix_time": int(time.time()),
"pyluos_rev": version,
"routing_table":state['routing_table']}
try:
r = requests.post("https://monorepo-services.vercel.app/api/telemetry",
data=luos_telemetry)
if not r:
print("Telemetry request failed : error " + str(r.status_code))
except:
print("Telemetry request failed.")
else:
self.logger.info("Telemetry disabled, please consider enabling it by removing the 'telemetry=False' argument of your Device creation.")

# We push our current state to make sure that
# both our model and the hardware are synced.
self._push_once()
Expand All @@ -247,30 +222,63 @@ def _poll_once(self):

def _poll_and_up(self):
while self._running:
if not self._pause :
if not self._pause:
state = self._poll_once()
if self._state != []:
self._update(state)
self._push_once()
else :
else:
time.sleep(0.1)

# Update our model with the new state.
def _update(self, new_state):
if 'dead_service' in new_state.keys() :
#we have lost a service put a flag on this service
alias = new_state['dead_service']
if hasattr(self, alias):
getattr(self, alias)._kill()
if (self._freedomLink != None):
self._freedomLink._kill(alias)
if 'assert' in new_state.keys() :
if 'dead_service' in new_state.keys():
# We have lost a service put a flag on this service
service_id = new_state['dead_service']
# Find the service.
for service in self._services:
if (service.id == service_id):
s = "************************* EXCLUSION *************************\n"
s += "* Service " + str(service.alias) + " have been excluded from the network due to no responses."
s += "\n*************************************************************"
print(s)
if (self._freedomLink != None):
self._freedomLink._kill(service.alias)
service._kill()
break

if 'dead_node' in new_state.keys():
# We have lost a node put a flag on all node services
node_id = new_state['dead_node']
for node in self._nodes:
if (node.id == node_id):
s = "************************* EXCLUSION *************************\n"
s += "* Node " + str(service.alias) + "have been excluded from the network due to no responses."
s += "\nThis exclude all services from this node :"
for service in node.services:
if (self._freedomLink != None):
self._freedomLink._kill(service.alias)
service._kill()
s += "\n* Service " + str(service.alias) + " have been excluded from the network due to no responses."

s += "\n*************************************************************"
print(s)
break

if 'assert' in new_state.keys():
# A node assert, print assert informations
if (('node_id' in new_state['assert']) and ('file' in new_state['assert']) and ('line' in new_state['assert'])):
s = "************************* ASSERT *************************\n"
s += "* Node " + str(new_state['assert']['node_id']) + " assert in file " + new_state['assert']['file'] + " line " + str(new_state['assert']['line'])
s += "\n**********************************************************"
print (s)
print(s)
# Consider this service as dead.
# Find the service from it's node id.
for node in self._nodes:
if (node.id == new_state['assert']['node_id']):
for service in node.services:
service._kill()
break
if (self._freedomLink != None):
self._freedomLink._assert(alias)
if 'services' not in new_state.keys():
Expand All @@ -296,16 +304,15 @@ def update_data(self, alias, key, val, data):
def _push_once(self):
with self._cmd_lock:
if self._cmd:
self._write( json.dumps({'services': self._cmd}).encode())
self._write(json.dumps({'services': self._cmd}).encode())
self._cmd = defaultdict(lambda: defaultdict(lambda: None))
for cmd, binary in zip(self._cmd_data, self._binary):
time.sleep(0.01)
self._write( json.dumps({'services': cmd}).encode() + '\n'.encode() + binary)
self._write(json.dumps({'services': cmd}).encode() + '\n'.encode() + binary)

self._cmd_data = []
self._binary = []


def _send(self, msg):
with self._send_lock:
self._io.send(msg)
Expand Down
3 changes: 2 additions & 1 deletion pyluos/io/ws.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from threading import Event, Thread
from . import IOHandler


def resolve_hostname(hostname, port):
# We do our own mDNS resolution
# to enforce we only search for IPV4 address
Expand Down Expand Up @@ -48,7 +49,7 @@ def __init__(self, host, port=9342, baudrate=None):
host = resolve_hostname(host, port)

self._ws = websocket.WebSocket()
self._ws.connect("ws://" + str(host) + ":" + str(port)+"/ws")
self._ws.connect("ws://" + str(host) + ":" + str(port) + "/ws")

self._msg = queue.Queue(4096)
self._running = True
Expand Down
34 changes: 20 additions & 14 deletions pyluos/services/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ def widgets(*args, **kwargs):

READ_TIMEOUT = 0.3


class Service(object):
possible_events = set()

Expand All @@ -31,6 +32,7 @@ def __init__(self,
self.type = type
self.alias = alias
self.refresh_freq = 0.0
self.max_refresh_time = 0.0
self._update_time = 0.01
self._delegate = device
self._value = None
Expand All @@ -42,7 +44,7 @@ def __init__(self,
self._luos_revision = "Unknown"
self._robus_revision = "Unknown"
self._killed = False
self._last_update = time.time()
self._last_update = []
self._luos_statistics = {}

def __repr__(self):
Expand All @@ -54,9 +56,14 @@ def _update(self, new_state):
if not isinstance(new_state, dict):
new_state = {new_state: ""}

if ((time.time() - self._last_update) != 0):
self.refresh_freq = ((200.0 * self.refresh_freq) + (1.0 / (time.time() - self._last_update))) / 201.0
self._last_update = time.time()
self._last_update.append(time.time())
if (len(self._last_update) > 1):
self.max_refresh_time = max(self.max_refresh_time, self._last_update[-1] - self._last_update[-2])
if (self._last_update[0] < time.time() - 1.0):
while (self._last_update[0] < time.time() - 10.0):
self._last_update.pop(0)
self.refresh_freq = (len(self._last_update) / 10.0) * 0.05 + 0.95 * self.refresh_freq

if 'revision' in new_state.keys():
self._firmware_revision = new_state['revision']
if 'luos_revision' in new_state.keys():
Expand All @@ -67,21 +74,20 @@ def _update(self, new_state):

def _kill(self):
self._killed = True
print ("service", self.alias, "have been excluded from the network due to no responses.")

def _push_value(self, key, new_val):
if (self._killed) :
print("service", self.alias,"is excluded.")
else :
if isinstance(new_val, float) :
if (self._killed):
print("service", self.alias, "have been excluded, you can no longer acess it.")
else:
if isinstance(new_val, float):
self._delegate.update_cmd(self.alias, key, float(str("%.3f" % new_val)))
else :
else:
self._delegate.update_cmd(self.alias, key, new_val)

def _push_data(self, key, new_val, data):
if (self._killed) :
print("service", self.alias,"is excluded.")
else :
if (self._killed):
print("service", self.alias, "have been excluded, you can no longer acess it.")
else:
self._delegate.update_data(self.alias, key, new_val, data)

@property
Expand Down Expand Up @@ -146,7 +152,7 @@ def update_time(self):
@update_time.setter
def update_time(self, time):
self._push_value('update_time', time)
self._update_time= time
self._update_time = time

# Events cb handling

Expand Down
Loading

0 comments on commit 481170f

Please sign in to comment.