def add_node(self, node_descriptor):
        if self._max_nodes is not None and len(self.nodes) >= self._max_nodes:
            raise ResourceUsageError("Maximum number of nodes exceeded", max_nodes=self._max_nodes)
        node, inputs, processor = parse_node_descriptor(node_descriptor, self.model)
        in_root = False
        for i, input_data in enumerate(inputs):
            selector, trigger = input_data
            walker = self.sensor_log.create_walker(selector)
            if walker.selector.inexhaustible:
                walker.reading = IOTileReading(0xFFFFFFFF, walker.selector.as_stream(), 0)
            node.connect_input(i, walker, trigger)
            if selector.input and not in_root:
                self.roots.append(node)
                in_root = True
            else:
                found = False
                for other in self.nodes:
                    if selector.matches(other.stream):
                        other.connect_output(node)
                        found = True
                if not found and selector.buffered:
                    raise NodeConnectionError("Node has input that refers to another node that has not been created yet", node_descriptor=node_descriptor, input_selector=str(selector), input_index=i)
        for other_node in self.nodes:
            for selector, trigger in other_node.inputs:
                if selector.matches(node.stream):
                    node.connect_output(other_node)
        func = self.find_processing_function(processor)
        if func is None:
            raise ProcessingFunctionError("Could not find processing function in installed packages", func_name=processor)
        node.set_func(processor, func)
        self.nodes.append(node)