""" Provides functions for the discovery of ODrive devices """ import sys import json import struct import threading import odrive.protocol #class ObjectDisappearedError(Exception): # def __init__(self, channel): # self._obj = obj # pass class ObjectDefinitionError(Exception): pass class RemoteProperty(): """ Used internally by dynamically created objects to translate property assignments and fetches into endpoint operations on the object's associated channel """ def __init__(self, json_data, parent): self._parent = parent id_str = json_data.get("id", None) if id_str is None: raise ObjectDefinitionError("unspecified endpoint ID") self._id = int(id_str) self._name = json_data.get("name", None) if self._name is None: self._name = "[anonymous]" type_str = json_data.get("type", None) if type_str is None: raise ObjectDefinitionError("unspecified type") if type_str == "float": self._property_type = float self._struct_format = " 0: return self._outputs[0].get_value() def dump(self): return "{}({})".format(self._name, ", ".join("{}: {}".format(x._name, x._property_type.__name__) for x in self._inputs)) class RemoteObject(object): """ Object with functions and properties that map to remote endpoints """ def __init__(self, json_data, parent, channel, printer): """ Creates an object that implements the specified JSON type description by communicating over the provided channel """ # Directly write to __dict__ to avoid calling __setattr__ too early object.__getattribute__(self, "__dict__")["_remote_attributes"] = {} object.__getattribute__(self, "__dict__")["__sealed__"] = False # Assign once more to make linter happy self._remote_attributes = {} self.__sealed__ = False self.__channel__ = channel self.__parent__ = parent # Build attribute list from JSON for member_json in json_data.get("members", []): member_name = member_json.get("name", None) if member_name is None: printer("ignoring unnamed attribute") continue try: type_str = member_json.get("type", None) if type_str == "object": attribute = RemoteObject(member_json, self, channel, printer) elif type_str == "function": attribute = RemoteFunction(member_json, self) elif type_str != None: attribute = RemoteProperty(member_json, self) else: raise ObjectDefinitionError("no type information") except ObjectDefinitionError as ex: printer("malformed member {}: {}".format(member_name, str(ex))) continue self._remote_attributes[member_name] = attribute self.__dict__[member_name] = attribute # Ensure that from here on out assignments to undefined attributes # raise an exception self.__sealed__ = True channel._channel_broken.subscribe(self._tear_down) def dump(self, indent, depth): if depth <= 0: return "..." lines = [] for key, val in self._remote_attributes.items(): if isinstance(val, RemoteObject): val_str = indent + key + (": " if depth == 1 else ":\n") + val.dump(indent + " ", depth - 1) else: val_str = indent + val.dump() lines.append(val_str) return "\n".join(lines) def __str__(self): return self.dump("", depth=2) def __repr__(self): return self.__str__() def __getattribute__(self, name): attr = object.__getattribute__(self, "_remote_attributes").get(name, None) if isinstance(attr, RemoteProperty): if attr._can_read: return attr.get_value() else: raise Exception("Cannot read from property {}".format(name)) elif attr != None: return attr else: return object.__getattribute__(self, name) #raise AttributeError("Attribute {} not found".format(name)) def __setattr__(self, name, value): attr = object.__getattribute__(self, "_remote_attributes").get(name, None) if isinstance(attr, RemoteProperty): if attr._can_write: attr.set_value(value) else: raise Exception("Cannot write to property {}".format(name)) elif not object.__getattribute__(self, "__sealed__") or name in object.__getattribute__(self, "__dict__"): object.__getattribute__(self, "__dict__")[name] = value else: raise AttributeError("Attribute {} not found".format(name)) def _tear_down(self): # Clear all remote members for k in self._remote_attributes.keys(): self.__dict__.pop(k) self._remote_attributes = {}