diff --git a/meshtastic/__main__.py b/meshtastic/__main__.py index 6229e7d..ae85289 100644 --- a/meshtastic/__main__.py +++ b/meshtastic/__main__.py @@ -441,6 +441,16 @@ def onConnected(interface): waitForAckNak = True interface.getNode(args.dest, False, **getNode_kwargs).removeNode(args.remove_node) + if args.set_favorite_node: + closeNow = True + waitForAckNak = True + interface.getNode(args.dest, False, **getNode_kwargs).setFavorite(args.set_favorite_node) + + if args.remove_favorite_node: + closeNow = True + waitForAckNak = True + interface.getNode(args.dest, False, **getNode_kwargs).removeFavorite(args.remove_favorite_node) + if args.reset_nodedb: closeNow = True waitForAckNak = True @@ -1699,6 +1709,16 @@ def addRemoteAdminArgs(parser: argparse.ArgumentParser) -> argparse.ArgumentPars help="Tell the destination node to remove a specific node from its DB, by node number or ID", metavar="!xxxxxxxx" ) + group.add_argument( + "--set-favorite-node", + help="Tell the destination node to set the specified node to be favorited on the NodeDB on the devicein its DB, by number or ID", + metavar="!xxxxxxxx" + ) + group.add_argument( + "--remove-favorite-node", + help="Tell the destination node to set the specified node to be un-favorited on the NodeDB on the device, by number or ID", + metavar="!xxxxxxxx" + ) group.add_argument( "--reset-nodedb", help="Tell the destination node to clear its list of nodes", diff --git a/meshtastic/node.py b/meshtastic/node.py index fa02c6a..edba1ae 100644 --- a/meshtastic/node.py +++ b/meshtastic/node.py @@ -668,6 +668,42 @@ class Node: onResponse = self.onAckNak return self._sendAdmin(p, onResponse=onResponse) + def setFavorite(self, nodeId: Union[int, str]): + """Tell the node to set the specified node ID to be favorited on the NodeDB on the device""" + self.ensureSessionKey() + if isinstance(nodeId, str): + if nodeId.startswith("!"): + nodeId = int(nodeId[1:], 16) + else: + nodeId = int(nodeId) + + p = admin_pb2.AdminMessage() + p.set_favorite_node = nodeId + + if self == self.iface.localNode: + onResponse = None + else: + onResponse = self.onAckNak + return self._sendAdmin(p, onResponse=onResponse) + + def removeFavorite(self, nodeId: Union[int, str]): + """Tell the node to set the specified node ID to be un-favorited on the NodeDB on the device""" + self.ensureSessionKey() + if isinstance(nodeId, str): + if nodeId.startswith("!"): + nodeId = int(nodeId[1:], 16) + else: + nodeId = int(nodeId) + + p = admin_pb2.AdminMessage() + p.remove_favorite_node = nodeId + + if self == self.iface.localNode: + onResponse = None + else: + onResponse = self.onAckNak + return self._sendAdmin(p, onResponse=onResponse) + def resetNodeDb(self): """Tell the node to reset its list of nodes.""" self.ensureSessionKey() diff --git a/meshtastic/tests/test_main.py b/meshtastic/tests/test_main.py index 86d3ad2..f01a67f 100644 --- a/meshtastic/tests/test_main.py +++ b/meshtastic/tests/test_main.py @@ -2652,3 +2652,34 @@ def test_tunnel_tunnel_arg( out, err = capsys.readouterr() assert re.search(r"Connected to radio", out, re.MULTILINE) assert err == "" + + +@pytest.mark.unit +@pytest.mark.usefixtures("reset_mt_config") +def test_set_favorite_node(): + """Test --set-favorite-node node""" + sys.argv = ["", "--set-favorite-node", "!12345678"] + mt_config.args = sys.argv + mocked_node = MagicMock(autospec=Node) + iface = MagicMock(autospec=SerialInterface) + iface.getNode.return_value = mocked_node + with patch("meshtastic.serial_interface.SerialInterface", return_value=iface): + main() + + mocked_node.setFavorite.assert_called_once_with("!12345678") + + +@pytest.mark.unit +@pytest.mark.usefixtures("reset_mt_config") +def test_remove_favorite_node(): + """Test --remove-favorite-node node""" + sys.argv = ["", "--remove-favorite-node", "!12345678"] + mt_config.args = sys.argv + mocked_node = MagicMock(autospec=Node) + iface = MagicMock(autospec=SerialInterface) + iface.getNode.return_value = mocked_node + mocked_node.iface = iface + with patch("meshtastic.serial_interface.SerialInterface", return_value=iface): + main() + + mocked_node.removeFavorite.assert_called_once_with("!12345678") diff --git a/meshtastic/tests/test_node.py b/meshtastic/tests/test_node.py index 61ab84a..0a7ad09 100644 --- a/meshtastic/tests/test_node.py +++ b/meshtastic/tests/test_node.py @@ -6,7 +6,7 @@ from unittest.mock import MagicMock, patch import pytest -from ..protobuf import localonly_pb2, config_pb2 +from ..protobuf import admin_pb2, localonly_pb2, config_pb2 from ..protobuf.channel_pb2 import Channel # pylint: disable=E0611 from ..node import Node from ..serial_interface import SerialInterface @@ -1426,6 +1426,33 @@ def test_requestChannels_non_localNode_starting_index(caplog): # assert err == '' +@pytest.mark.unit +@pytest.mark.parametrize("favorite", ["!1dec0ded", 502009325]) +def test_set_favorite(favorite): + """Test setFavorite""" + iface = MagicMock(autospec=SerialInterface) + node = Node(iface, 12345678) + amesg = admin_pb2.AdminMessage() + with patch("meshtastic.admin_pb2.AdminMessage", return_value=amesg): + node.setFavorite(favorite) + assert amesg.set_favorite_node == 502009325 + iface.sendData.assert_called_once() + + +@pytest.mark.unit +@pytest.mark.parametrize("favorite", ["!1dec0ded", 502009325]) +def test_remove_favorite(favorite): + """Test setFavorite""" + iface = MagicMock(autospec=SerialInterface) + node = Node(iface, 12345678) + amesg = admin_pb2.AdminMessage() + with patch("meshtastic.admin_pb2.AdminMessage", return_value=amesg): + node.removeFavorite(favorite) + + assert amesg.remove_favorite_node == 502009325 + iface.sendData.assert_called_once() + + # TODO # @pytest.mark.unitslow # def test_waitForConfig():