Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-meshtastic for 
openSUSE:Factory checked in at 2025-03-07 16:44:51
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-meshtastic (Old)
 and      /work/SRC/openSUSE:Factory/.python-meshtastic.new.19136 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-meshtastic"

Fri Mar  7 16:44:51 2025 rev:2 rq:1251149 version:2.5.12

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-meshtastic/python-meshtastic.changes      
2025-02-21 21:36:15.395961139 +0100
+++ 
/work/SRC/openSUSE:Factory/.python-meshtastic.new.19136/python-meshtastic.changes
   2025-03-07 16:48:50.554192496 +0100
@@ -1,0 +2,11 @@
+Thu Mar  6 11:28:09 UTC 2025 - Adrian Schröter <adr...@suse.de>
+
+- update to version 2.5.12
+  * Added descriptive FileNotFoundError handler for serial device connection
+  * catch unhandled OSError when serial port in use
+  * Add optional CLI parameter to specify node info fields to show with 
--nodes parameter.
+  * 464: allow for 0x node prefix values
+  * Add text message port cli option
+  * Add new channels from an add URL with the new --ch-add-url option
+
+-------------------------------------------------------------------

Old:
----
  meshtastic-2.5.11.tar.gz

New:
----
  meshtastic-2.5.12.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-meshtastic.spec ++++++
--- /var/tmp/diff_new_pack.ZrofLi/_old  2025-03-07 16:48:51.066214105 +0100
+++ /var/tmp/diff_new_pack.ZrofLi/_new  2025-03-07 16:48:51.066214105 +0100
@@ -17,7 +17,7 @@
 
 
 Name:           python-meshtastic
-Version:        2.5.11
+Version:        2.5.12
 Release:        0
 Summary:        A Python client for use with Meshtastic devices
 License:        GPL-3.0-only

++++++ meshtastic-2.5.11.tar.gz -> meshtastic-2.5.12.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/meshtastic-2.5.11/PKG-INFO 
new/meshtastic-2.5.12/PKG-INFO
--- old/meshtastic-2.5.11/PKG-INFO      1970-01-01 01:00:00.000000000 +0100
+++ new/meshtastic-2.5.12/PKG-INFO      1970-01-01 01:00:00.000000000 +0100
@@ -1,6 +1,6 @@
 Metadata-Version: 2.3
 Name: meshtastic
-Version: 2.5.11
+Version: 2.5.12
 Summary: Python API & client shell for talking to Meshtastic devices
 License: GPL-3.0-only
 Author: Meshtastic Developers
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/meshtastic-2.5.11/meshtastic/__main__.py 
new/meshtastic-2.5.12/meshtastic/__main__.py
--- old/meshtastic-2.5.11/meshtastic/__main__.py        1970-01-01 
01:00:00.000000000 +0100
+++ new/meshtastic-2.5.12/meshtastic/__main__.py        1970-01-01 
01:00:00.000000000 +0100
@@ -11,7 +11,7 @@
 import argparse
 argcomplete: Union[None, ModuleType] = None
 try:
-    import argcomplete
+    import argcomplete # type: ignore
 except ImportError as e:
     pass # already set to None by default above
 
@@ -226,7 +226,7 @@
     logging.debug(f"valStr:{raw_val} val:{val}")
 
     if snake_name == "wifi_psk" and len(str(raw_val)) < 8:
-        print(f"Warning: network.wifi_psk must be 8 or more characters.")
+        print("Warning: network.wifi_psk must be 8 or more characters.")
         return False
 
     enumType = pref.enum_type
@@ -483,6 +483,7 @@
             if checkChannel(interface, channelIndex):
                 print(
                     f"Sending text message {args.sendtext} to {args.dest} on 
channelIndex:{channelIndex}"
+                    f" {'using PRIVATE_APP port' if args.private else ''}"
                 )
                 interface.sendText(
                     args.sendtext,
@@ -490,6 +491,7 @@
                     wantAck=True,
                     channelIndex=channelIndex,
                     onResponse=interface.getNode(args.dest, False, 
**getNode_kwargs).onAckNak,
+                    portNum=portnums_pb2.PortNum.PRIVATE_APP if args.private 
else portnums_pb2.PortNum.TEXT_MESSAGE_APP
                 )
             else:
                 meshtastic.util.our_exit(
@@ -714,12 +716,16 @@
             closeNow = True
             export_config(interface)
 
-        if args.seturl:
+        if args.ch_set_url:
             closeNow = True
-            interface.getNode(args.dest, **getNode_kwargs).setURL(args.seturl)
+            interface.getNode(args.dest, 
**getNode_kwargs).setURL(args.ch_set_url, addOnly=False)
 
         # handle changing channels
 
+        if args.ch_add_url:
+            closeNow = True
+            interface.getNode(args.dest, 
**getNode_kwargs).setURL(args.ch_add_url, addOnly=True)
+
         if args.ch_add:
             channelIndex = mt_config.channel_index
             if channelIndex is not None:
@@ -921,7 +927,11 @@
             if args.dest != BROADCAST_ADDR:
                 print("Showing node list of a remote node is not supported.")
                 return
-            interface.showNodes()
+            interface.showNodes(True, args.show_fields)
+
+        if args.show_fields and not args.nodes:
+            print("--show-fields can only be used with --nodes")
+            return
 
         if args.qr or args.qr_all:
             closeNow = True
@@ -1245,6 +1255,19 @@
                         noProto=args.noproto,
                         noNodes=args.no_nodes,
                     )
+                except FileNotFoundError:
+                    # Handle the case where the serial device is not found
+                    message = (
+                        f"File Not Found Error:\n"
+                    )
+                    message += f"  The serial device at '{args.port}' was not 
found.\n"
+                    message += "  Please check the following:\n"
+                    message += "    1. Is the device connected properly?\n"
+                    message += "    2. Is the correct serial port specified?\n"
+                    message += "    3. Are the necessary drivers installed?\n"
+                    message += "    4. Are you using a **power-only USB 
cable**? A power-only cable cannot transmit data.\n"
+                    message += "       Ensure you are using a **data-capable 
USB cable**.\n"
+                    meshtastic.util.our_exit(message, 1)
                 except PermissionError as ex:
                     username = os.getlogin()
                     message = "Permission Error:\n"
@@ -1255,6 +1278,12 @@
                     message += "  After running that command, log out and 
re-login for it to take effect.\n"
                     message += f"Error was:{ex}"
                     meshtastic.util.our_exit(message)
+                except OSError as ex:
+                    message = f"OS Error:\n"
+                    message += "  The serial device couldn't be opened, it 
might be in use by another process.\n"
+                    message += "  Please close any applications or webpages 
that may be using the device and try again.\n"
+                    message += f"\nOriginal error: {ex}"
+                    meshtastic.util.our_exit(message)
                 if client.devPath is None:
                     try:
                         client = meshtastic.tcp_interface.TCPInterface(
@@ -1342,7 +1371,8 @@
 
     group.add_argument(
         "--dest",
-        help="The destination node id for any sent commands, if not set '^all' 
or '^local' is assumed as appropriate",
+        help="The destination node id for any sent commands. If not set '^all' 
or '^local' is assumed."
+        "Use the node ID with a '!' or '0x' prefix or the node number.",
         default=None,
         metavar="!xxxxxxxx",
     )
@@ -1489,7 +1519,20 @@
         "--set-ham", help="Set licensed Ham ID and turn off encryption", 
action="store"
     )
 
-    group.add_argument("--seturl", help="Set a channel URL", action="store")
+    group.add_argument(
+        "--ch-set-url", "--seturl",
+        help="Set all channels and set LoRa config from a supplied URL",
+        metavar="URL",
+        action="store"
+    )
+
+    group.add_argument(
+        "--ch-add-url",
+        help="Add secondary channels and set LoRa config from a supplied URL",
+        metavar="URL",
+        default=None,
+    )
+
 
     return parser
 
@@ -1627,6 +1670,13 @@
         action="store_true",
     )
 
+    group.add_argument(
+        "--show-fields",
+        help="Specify fields to show (comma-separated) when using --nodes",
+        type=lambda s: s.split(','),
+        default=None
+    )
+
     return parser
 
 def addRemoteActionArgs(parser: argparse.ArgumentParser) -> 
argparse.ArgumentParser:
@@ -1638,15 +1688,21 @@
 
     group.add_argument(
         "--sendtext",
-        help="Send a text message. Can specify a destination '--dest' and/or 
channel index '--ch-index'.",
+        help="Send a text message. Can specify a destination '--dest', use of 
PRIVATE_APP port '--private', and/or channel index '--ch-index'.",
         metavar="TEXT",
     )
 
     group.add_argument(
+        "--private",
+        help="Optional argument for sending text messages to the PRIVATE_APP 
port. Use in combination with --sendtext.",
+        action="store_true"
+    )
+
+    group.add_argument(
         "--traceroute",
         help="Traceroute from connected node to a destination. "
         "You need pass the destination ID as argument, like "
-        "this: '--traceroute !ba4bf9d0' "
+        "this: '--traceroute !ba4bf9d0' | '--traceroute 0xba4bf9d0'"
         "Only nodes with a shared channel can be traced.",
         metavar="!xxxxxxxx",
     )
@@ -1727,27 +1783,32 @@
 
     group.add_argument(
         "--remove-node",
-        help="Tell the destination node to remove a specific node from its DB, 
by node number or ID",
+        help="Tell the destination node to remove a specific node from its 
NodeDB. "
+        "Use the node ID with a '!' or '0x' prefix or the node number.",
         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",
+        help="Tell the destination node to set the specified node to be 
favorited on the NodeDB. "
+        "Use the node ID with a '!' or '0x' prefix or the node number.",
         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",
+        help="Tell the destination node to set the specified node to be 
un-favorited on the NodeDB. "
+        "Use the node ID with a '!' or '0x' prefix or the node number.",
         metavar="!xxxxxxxx"
     )
     group.add_argument(
         "--set-ignored-node",
-        help="Tell the destination node to set the specified node to be 
ignored on the NodeDB on the devicein its DB, by number or ID",
+        help="Tell the destination node to set the specified node to be 
ignored on the NodeDB. "
+        "Use the node ID with a '!' or '0x' prefix or the node number.",
         metavar="!xxxxxxxx"
     )
     group.add_argument(
         "--remove-ignored-node",
-        help="Tell the destination node to set the specified node to be 
un-ignored on the NodeDB on the device, by number or ID",
+        help="Tell the destination node to set the specified node to be 
un-ignored on the NodeDB. "
+        "Use the node ID with a '!' or '0x' prefix or the node number.",
         metavar="!xxxxxxxx"
     )
     group.add_argument(
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/meshtastic-2.5.11/meshtastic/mesh_interface.py 
new/meshtastic-2.5.12/meshtastic/mesh_interface.py
--- old/meshtastic-2.5.11/meshtastic/mesh_interface.py  1970-01-01 
01:00:00.000000000 +0100
+++ new/meshtastic-2.5.12/meshtastic/mesh_interface.py  1970-01-01 
01:00:00.000000000 +0100
@@ -222,9 +222,42 @@
         return infos
 
     def showNodes(
-        self, includeSelf: bool = True
+        self, includeSelf: bool = True, showFields: Optional[List[str]] = None
     ) -> str:  # pylint: disable=W0613
-        """Show table summary of nodes in mesh"""
+        """Show table summary of nodes in mesh
+
+           Args:
+                includeSelf (bool): Include ourself in the output?
+                showFields (List[str]): List of fields to show in output
+        """
+
+        def get_human_readable(name):
+            name_map = {
+                "user.longName": "User",
+                "user.id": "ID",
+                "user.shortName": "AKA",
+                "user.hwModel": "Hardware",
+                "user.publicKey": "Pubkey",
+                "user.role": "Role",
+                "position.latitude": "Latitude",
+                "position.longitude": "Longitude",
+                "position.altitude": "Altitude",
+                "deviceMetrics.batteryLevel": "Battery",
+                "deviceMetrics.channelUtilization": "Channel util.",
+                "deviceMetrics.airUtilTx": "Tx air util.",
+                "snr": "SNR",
+                "hopsAway": "Hops",
+                "channel": "Channel",
+                "lastHeard": "LastHeard",
+                "since": "Since",
+
+            }
+
+            if name in name_map:
+                return name_map.get(name)  # Default to a formatted guess
+            else:
+                return name
+
 
         def formatFloat(value, precision=2, unit="") -> Optional[str]:
             """Format a float value with precision."""
@@ -246,6 +279,29 @@
                 return None  # not handling a timestamp from the future
             return _timeago(delta_secs)
 
+        def getNestedValue(node_dict: Dict[str, Any], key_path: str) -> Any:
+            if key_path.index(".") < 0:
+                logging.debug("getNestedValue was called without a nested 
path.")
+                return None
+            keys = key_path.split(".")
+            value: Optional[Union[str, dict]] = node_dict
+            for key in keys:
+                if isinstance(value, dict):
+                    value = value.get(key)
+                else:
+                    return None
+            return value
+
+        if showFields is None or len(showFields) == 0:
+          # The default set of fields to show (e.g., the status quo)
+            showFields = ["N", "user.longName", "user.id", "user.shortName", 
"user.hwModel", "user.publicKey",
+                          "user.role", "position.latitude", 
"position.longitude", "position.altitude",
+                          "deviceMetrics.batteryLevel", 
"deviceMetrics.channelUtilization",
+                          "deviceMetrics.airUtilTx", "snr", "hopsAway", 
"channel", "lastHeard", "since"]
+        else:
+            # Always at least include the row number.
+            showFields.insert(0, "N")
+
         rows: List[Dict[str, Any]] = []
         if self.nodesByNum:
             logging.debug(f"self.nodes:{self.nodes}")
@@ -254,66 +310,60 @@
                     continue
 
                 presumptive_id = f"!{node['num']:08x}"
-                row = {
-                    "N": 0,
-                    "User": f"Meshtastic {presumptive_id[-4:]}",
-                    "ID": presumptive_id,
-                }
-
-                user = node.get("user")
-                if user:
-                    row.update(
-                        {
-                            "User": user.get("longName", "N/A"),
-                            "AKA": user.get("shortName", "N/A"),
-                            "ID": user["id"],
-                            "Hardware": user.get("hwModel", "UNSET"),
-                            "Pubkey": user.get("publicKey", "UNSET"),
-                            "Role": user.get("role", "N/A"),
-                        }
-                    )
 
-                pos = node.get("position")
-                if pos:
-                    row.update(
-                        {
-                            "Latitude": formatFloat(pos.get("latitude"), 4, 
"°"),
-                            "Longitude": formatFloat(pos.get("longitude"), 4, 
"°"),
-                            "Altitude": formatFloat(pos.get("altitude"), 0, " 
m"),
-                        }
-                    )
+                # This allows the user to specify fields that wouldn't 
otherwise be included.
+                fields = {}
+                for field in showFields:
+                    if "." in field:
+                        raw_value = getNestedValue(node, field)
+                    else:
+                        # The "since" column is synthesized, it's not 
retrieved from the device. Get the
+                        # lastHeard value here, and then we'll format it 
properly below.
+                        if field == "since":
+                            raw_value = node.get("lastHeard")
+                        else:
+                            raw_value = node.get(field)
+
+                    formatted_value: Optional[str] = ""
 
-                metrics = node.get("deviceMetrics")
-                if metrics:
-                    batteryLevel = metrics.get("batteryLevel")
-                    if batteryLevel is not None:
-                        if batteryLevel == 0:
-                            batteryString = "Powered"
+                    # Some of these need special formatting or processing.
+                    if field == "channel":
+                        if raw_value is None:
+                            formatted_value = "0"
+                    elif field == "deviceMetrics.channelUtilization":
+                        formatted_value = formatFloat(raw_value, 2, "%")
+                    elif field == "deviceMetrics.airUtilTx":
+                        formatted_value = formatFloat(raw_value, 2, "%")
+                    elif field == "deviceMetrics.batteryLevel":
+                        if raw_value in (0, 101):
+                            formatted_value = "Powered"
                         else:
-                            batteryString = str(batteryLevel) + "%"
-                        row.update({"Battery": batteryString})
-                    row.update(
-                        {
-                            "Channel util.": formatFloat(
-                                metrics.get("channelUtilization"), 2, "%"
-                            ),
-                            "Tx air util.": formatFloat(
-                                metrics.get("airUtilTx"), 2, "%"
-                            ),
-                        }
-                    )
+                            formatted_value = formatFloat(raw_value, 0, "%")
+                    elif field == "lastHeard":
+                        formatted_value = getLH(raw_value)
+                    elif field == "position.latitude":
+                        formatted_value = formatFloat(raw_value, 4, "°")
+                    elif field == "position.longitude":
+                        formatted_value = formatFloat(raw_value, 4, "°")
+                    elif field == "position.altitude":
+                        formatted_value = formatFloat(raw_value, 0, "m")
+                    elif field == "since":
+                        formatted_value = getTimeAgo(raw_value) or "N/A"
+                    elif field == "snr":
+                        formatted_value = formatFloat(raw_value, 0, " dB")
+                    elif field == "user.shortName":
+                        formatted_value = raw_value if raw_value is not None 
else f'Meshtastic {presumptive_id[-4:]}'
+                    elif field == "user.id":
+                        formatted_value = raw_value if raw_value is not None 
else presumptive_id
+                    else:
+                        formatted_value = raw_value  # No special formatting
 
-                row.update(
-                    {
-                        "SNR": formatFloat(node.get("snr"), 2, " dB"),
-                        "Hops": node.get("hopsAway", "?"),
-                        "Channel": node.get("channel", 0),
-                        "LastHeard": getLH(node.get("lastHeard")),
-                        "Since": getTimeAgo(node.get("lastHeard")),
-                    }
-                )
+                    fields[field] = formatted_value
 
-                rows.append(row)
+                # Filter out any field in the data set that was not specified.
+                filteredData = {get_human_readable(k): v for k, v in 
fields.items() if k in showFields}
+                filteredData.update({get_human_readable(k): v for k, v in 
fields.items()})
+                rows.append(filteredData)
 
         rows.sort(key=lambda r: r.get("LastHeard") or "0000", reverse=True)
         for i, row in enumerate(rows):
@@ -345,7 +395,7 @@
                         if new_index != last_index:
                             retries_left = requestChannelAttempts - 1
                         if retries_left <= 0:
-                            our_exit(f"Error: Timed out waiting for channels, 
giving up")
+                            our_exit("Error: Timed out waiting for channels, 
giving up")
                         print("Timed out trying to retrieve channel info, 
retrying")
                         n.requestChannels(startingIndex=new_index)
                         last_index = new_index
@@ -361,6 +411,7 @@
         wantResponse: bool = False,
         onResponse: Optional[Callable[[dict], Any]] = None,
         channelIndex: int = 0,
+        portNum: portnums_pb2.PortNum.ValueType = 
portnums_pb2.PortNum.TEXT_MESSAGE_APP
     ):
         """Send a utf8 string to some other node, if the node has a display it
            will also be shown on the device.
@@ -371,12 +422,12 @@
         Keyword Arguments:
             destinationId {nodeId or nodeNum} -- where to send this
                                                  message (default: 
{BROADCAST_ADDR})
-            portNum -- the application portnum (similar to IP port numbers)
-                       of the destination, see portnums.proto for a list
             wantAck -- True if you want the message sent in a reliable manner
                        (with retries and ack/nak provided for delivery)
             wantResponse -- True if you want the service on the other side to
                             send an application layer response
+            portNum -- the application portnum (similar to IP port numbers)
+                       of the destination, see portnums.proto for a list
 
         Returns the sent packet. The id field will be populated in this packet
         and can be used to track future message acks/naks.
@@ -385,7 +436,7 @@
         return self.sendData(
             text.encode("utf-8"),
             destinationId,
-            portNum=portnums_pb2.PortNum.TEXT_MESSAGE_APP,
+            portNum=portNum,
             wantAck=wantAck,
             wantResponse=wantResponse,
             onResponse=onResponse,
@@ -892,8 +943,10 @@
             else:
                 our_exit("Warning: No myInfo found.")
         # A simple hex style nodeid - we can parse this without needing the DB
-        elif destinationId.startswith("!"):
-            nodeNum = int(destinationId[1:], 16)
+        elif isinstance(destinationId, str) and len(destinationId) >= 8:
+            # assuming some form of node id string such as !1234578 or 
0x12345678
+            # always grab the last 8 items of the hexadecimal id str and parse 
to integer
+            nodeNum = int(destinationId[-8:], 16)
         else:
             if self.nodes:
                 node = self.nodes.get(destinationId)
@@ -927,7 +980,7 @@
         toRadio.packet.CopyFrom(meshPacket)
         if self.noProto:
             logging.warning(
-                f"Not sending packet because protocol use is disabled by 
noProto"
+                "Not sending packet because protocol use is disabled by 
noProto"
             )
         else:
             logging.debug(f"Sending packet: {stripnl(meshPacket)}")
@@ -1116,7 +1169,7 @@
         """Send a ToRadio protobuf to the device"""
         if self.noProto:
             logging.warning(
-                f"Not sending packet because protocol use is disabled by 
noProto"
+                "Not sending packet because protocol use is disabled by 
noProto"
             )
         else:
             # logging.debug(f"Sending toRadio: {stripnl(toRadio)}")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/meshtastic-2.5.11/meshtastic/node.py 
new/meshtastic-2.5.12/meshtastic/node.py
--- old/meshtastic-2.5.11/meshtastic/node.py    1970-01-01 01:00:00.000000000 
+0100
+++ new/meshtastic-2.5.12/meshtastic/node.py    1970-01-01 01:00:00.000000000 
+0100
@@ -337,14 +337,19 @@
         s = s.replace("=", "").replace("+", "-").replace("/", "_")
         return f"https://meshtastic.org/e/#{s}";
 
-    def setURL(self, url):
+    def setURL(self, url: str, addOnly: bool = False):
         """Set mesh network URL"""
-        if self.localConfig is None:
-            our_exit("Warning: No Config has been read")
+        if self.localConfig is None or self.channels is None:
+            our_exit("Warning: config or channels not loaded")
 
         # URLs are of the form https://meshtastic.org/d/#{base64_channel_set}
         # Split on '/#' to find the base64 encoded channel settings
-        splitURL = url.split("/#")
+        if addOnly:
+            splitURL = url.split("/?add=true#")
+        else:
+            splitURL = url.split("/#")
+        if len(splitURL) == 1:
+            our_exit(f"Warning: Invalid URL '{url}'")
         b64 = splitURL[-1]
 
         # We normally strip padding to make for a shorter URL, but the python 
parser doesn't like
@@ -361,20 +366,36 @@
         if len(channelSet.settings) == 0:
             our_exit("Warning: There were no settings.")
 
-        i = 0
-        for chs in channelSet.settings:
-            ch = channel_pb2.Channel()
-            ch.role = (
-                channel_pb2.Channel.Role.PRIMARY
-                if i == 0
-                else channel_pb2.Channel.Role.SECONDARY
-            )
-            ch.index = i
-            ch.settings.CopyFrom(chs)
-            self.channels[ch.index] = ch
-            logging.debug(f"Channel i:{i} ch:{ch}")
-            self.writeChannel(ch.index)
-            i = i + 1
+        if addOnly:
+            # Add new channels with names not already present
+            # Don't change existing channels
+            for chs in channelSet.settings:
+                channelExists = self.getChannelByName(chs.name)
+                if channelExists or chs.name == "":
+                    print(f"Ignoring existing or empty channel \"{chs.name}\" 
from add URL")
+                    continue
+                ch = self.getDisabledChannel()
+                if not ch:
+                    our_exit("Warning: No free channels were found")
+                ch.settings.CopyFrom(chs)
+                ch.role = channel_pb2.Channel.Role.SECONDARY
+                print(f"Adding new channel '{chs.name}' to device")
+                self.writeChannel(ch.index)
+        else:
+            i = 0
+            for chs in channelSet.settings:
+                ch = channel_pb2.Channel()
+                ch.role = (
+                    channel_pb2.Channel.Role.PRIMARY
+                    if i == 0
+                    else channel_pb2.Channel.Role.SECONDARY
+                )
+                ch.index = i
+                ch.settings.CopyFrom(chs)
+                self.channels[ch.index] = ch
+                logging.debug(f"Channel i:{i} ch:{ch}")
+                self.writeChannel(ch.index)
+                i = i + 1
 
         p = admin_pb2.AdminMessage()
         p.set_config.lora.CopyFrom(channelSet.lora_config)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/meshtastic-2.5.11/meshtastic/protobuf/config_pb2.py 
new/meshtastic-2.5.12/meshtastic/protobuf/config_pb2.py
--- old/meshtastic-2.5.11/meshtastic/protobuf/config_pb2.py     1970-01-01 
01:00:00.000000000 +0100
+++ new/meshtastic-2.5.12/meshtastic/protobuf/config_pb2.py     1970-01-01 
01:00:00.000000000 +0100
@@ -14,7 +14,7 @@
 from meshtastic.protobuf import device_ui_pb2 as 
meshtastic_dot_protobuf_dot_device__ui__pb2
 
 
-DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n 
meshtastic/protobuf/config.proto\x12\x13meshtastic.protobuf\x1a#meshtastic/protobuf/device_ui.proto\"\x8c(\n\x06\x43onfig\x12:\n\x06\x64\x65vice\x18\x01
 
\x01(\x0b\x32(.meshtastic.protobuf.Config.DeviceConfigH\x00\x12>\n\x08position\x18\x02
 
\x01(\x0b\x32*.meshtastic.protobuf.Config.PositionConfigH\x00\x12\x38\n\x05power\x18\x03
 
\x01(\x0b\x32\'.meshtastic.protobuf.Config.PowerConfigH\x00\x12<\n\x07network\x18\x04
 
\x01(\x0b\x32).meshtastic.protobuf.Config.NetworkConfigH\x00\x12<\n\x07\x64isplay\x18\x05
 
\x01(\x0b\x32).meshtastic.protobuf.Config.DisplayConfigH\x00\x12\x36\n\x04lora\x18\x06
 
\x01(\x0b\x32&.meshtastic.protobuf.Config.LoRaConfigH\x00\x12@\n\tbluetooth\x18\x07
 
\x01(\x0b\x32+.meshtastic.protobuf.Config.BluetoothConfigH\x00\x12>\n\x08security\x18\x08
 
\x01(\x0b\x32*.meshtastic.protobuf.Config.SecurityConfigH\x00\x12\x42\n\nsessionkey\x18\t
 \x01(\x0b\x32,.meshtastic.protobuf.Config.SessionkeyConfigH\x00\x12\x38\n\tdev
 ice_ui\x18\n 
\x01(\x0b\x32#.meshtastic.protobuf.DeviceUIConfigH\x00\x1a\xc7\x05\n\x0c\x44\x65viceConfig\x12;\n\x04role\x18\x01
 
\x01(\x0e\x32-.meshtastic.protobuf.Config.DeviceConfig.Role\x12\x1a\n\x0eserial_enabled\x18\x02
 \x01(\x08\x42\x02\x18\x01\x12\x13\n\x0b\x62utton_gpio\x18\x04 
\x01(\r\x12\x13\n\x0b\x62uzzer_gpio\x18\x05 
\x01(\r\x12R\n\x10rebroadcast_mode\x18\x06 
\x01(\x0e\x32\x38.meshtastic.protobuf.Config.DeviceConfig.RebroadcastMode\x12 
\n\x18node_info_broadcast_secs\x18\x07 
\x01(\r\x12\"\n\x1a\x64ouble_tap_as_button_press\x18\x08 
\x01(\x08\x12\x16\n\nis_managed\x18\t 
\x01(\x08\x42\x02\x18\x01\x12\x1c\n\x14\x64isable_triple_click\x18\n 
\x01(\x08\x12\r\n\x05tzdef\x18\x0b 
\x01(\t\x12\x1e\n\x16led_heartbeat_disabled\x18\x0c 
\x01(\x08\"\xbf\x01\n\x04Role\x12\n\n\x06\x43LIENT\x10\x00\x12\x0f\n\x0b\x43LIENT_MUTE\x10\x01\x12\n\n\x06ROUTER\x10\x02\x12\x15\n\rROUTER_CLIENT\x10\x03\x1a\x02\x08\x01\x12\x0c\n\x08REPEATER\x10\x04\x12\x0b\n\x07TRACKER\x10\x05\x12\n\n\x06SENSOR\x10\x06\x1
 
2\x07\n\x03TAK\x10\x07\x12\x11\n\rCLIENT_HIDDEN\x10\x08\x12\x12\n\x0eLOST_AND_FOUND\x10\t\x12\x0f\n\x0bTAK_TRACKER\x10\n\x12\x0f\n\x0bROUTER_LATE\x10\x0b\"s\n\x0fRebroadcastMode\x12\x07\n\x03\x41LL\x10\x00\x12\x15\n\x11\x41LL_SKIP_DECODING\x10\x01\x12\x0e\n\nLOCAL_ONLY\x10\x02\x12\x0e\n\nKNOWN_ONLY\x10\x03\x12\x08\n\x04NONE\x10\x04\x12\x16\n\x12\x43ORE_PORTNUMS_ONLY\x10\x05\x1a\x9a\x05\n\x0ePositionConfig\x12\x1f\n\x17position_broadcast_secs\x18\x01
 \x01(\r\x12(\n position_broadcast_smart_enabled\x18\x02 
\x01(\x08\x12\x16\n\x0e\x66ixed_position\x18\x03 
\x01(\x08\x12\x17\n\x0bgps_enabled\x18\x04 
\x01(\x08\x42\x02\x18\x01\x12\x1b\n\x13gps_update_interval\x18\x05 
\x01(\r\x12\x1c\n\x10gps_attempt_time\x18\x06 
\x01(\rB\x02\x18\x01\x12\x16\n\x0eposition_flags\x18\x07 
\x01(\r\x12\x0f\n\x07rx_gpio\x18\x08 \x01(\r\x12\x0f\n\x07tx_gpio\x18\t 
\x01(\r\x12(\n broadcast_smart_minimum_distance\x18\n 
\x01(\r\x12-\n%broadcast_smart_minimum_interval_secs\x18\x0b 
\x01(\r\x12\x13\n\x0bgps_en_gpio\x18\x
 0c \x01(\r\x12\x44\n\x08gps_mode\x18\r 
\x01(\x0e\x32\x32.meshtastic.protobuf.Config.PositionConfig.GpsMode\"\xab\x01\n\rPositionFlags\x12\t\n\x05UNSET\x10\x00\x12\x0c\n\x08\x41LTITUDE\x10\x01\x12\x10\n\x0c\x41LTITUDE_MSL\x10\x02\x12\x16\n\x12GEOIDAL_SEPARATION\x10\x04\x12\x07\n\x03\x44OP\x10\x08\x12\t\n\x05HVDOP\x10\x10\x12\r\n\tSATINVIEW\x10
 
\x12\n\n\x06SEQ_NO\x10@\x12\x0e\n\tTIMESTAMP\x10\x80\x01\x12\x0c\n\x07HEADING\x10\x80\x02\x12\n\n\x05SPEED\x10\x80\x04\"5\n\x07GpsMode\x12\x0c\n\x08\x44ISABLED\x10\x00\x12\x0b\n\x07\x45NABLED\x10\x01\x12\x0f\n\x0bNOT_PRESENT\x10\x02\x1a\x84\x02\n\x0bPowerConfig\x12\x17\n\x0fis_power_saving\x18\x01
 \x01(\x08\x12&\n\x1eon_battery_shutdown_after_secs\x18\x02 
\x01(\r\x12\x1f\n\x17\x61\x64\x63_multiplier_override\x18\x03 
\x01(\x02\x12\x1b\n\x13wait_bluetooth_secs\x18\x04 
\x01(\r\x12\x10\n\x08sds_secs\x18\x06 \x01(\r\x12\x0f\n\x07ls_secs\x18\x07 
\x01(\r\x12\x15\n\rmin_wake_secs\x18\x08 
\x01(\r\x12\"\n\x1a\x64\x65vice_battery_ina_address\x18\t \x01(\r
 \x12\x18\n\x10powermon_enables\x18  
\x01(\x04\x1a\xe1\x03\n\rNetworkConfig\x12\x14\n\x0cwifi_enabled\x18\x01 
\x01(\x08\x12\x11\n\twifi_ssid\x18\x03 \x01(\t\x12\x10\n\x08wifi_psk\x18\x04 
\x01(\t\x12\x12\n\nntp_server\x18\x05 
\x01(\t\x12\x13\n\x0b\x65th_enabled\x18\x06 
\x01(\x08\x12K\n\x0c\x61\x64\x64ress_mode\x18\x07 
\x01(\x0e\x32\x35.meshtastic.protobuf.Config.NetworkConfig.AddressMode\x12I\n\x0bipv4_config\x18\x08
 
\x01(\x0b\x32\x34.meshtastic.protobuf.Config.NetworkConfig.IpV4Config\x12\x16\n\x0ersyslog_server\x18\t
 \x01(\t\x12\x19\n\x11\x65nabled_protocols\x18\n 
\x01(\r\x1a\x46\n\nIpV4Config\x12\n\n\x02ip\x18\x01 
\x01(\x07\x12\x0f\n\x07gateway\x18\x02 \x01(\x07\x12\x0e\n\x06subnet\x18\x03 
\x01(\x07\x12\x0b\n\x03\x64ns\x18\x04 
\x01(\x07\"#\n\x0b\x41\x64\x64ressMode\x12\x08\n\x04\x44HCP\x10\x00\x12\n\n\x06STATIC\x10\x01\"4\n\rProtocolFlags\x12\x10\n\x0cNO_BROADCAST\x10\x00\x12\x11\n\rUDP_BROADCAST\x10\x01\x1a\xfa\x07\n\rDisplayConfig\x12\x16\n\x0escreen_on_secs\x18\x01
 \x01(\r\x12Q\
 n\ngps_format\x18\x02 
\x01(\x0e\x32=.meshtastic.protobuf.Config.DisplayConfig.GpsCoordinateFormat\x12!\n\x19\x61uto_screen_carousel_secs\x18\x03
 \x01(\r\x12\x19\n\x11\x63ompass_north_top\x18\x04 
\x01(\x08\x12\x13\n\x0b\x66lip_screen\x18\x05 
\x01(\x08\x12\x45\n\x05units\x18\x06 
\x01(\x0e\x32\x36.meshtastic.protobuf.Config.DisplayConfig.DisplayUnits\x12@\n\x04oled\x18\x07
 
\x01(\x0e\x32\x32.meshtastic.protobuf.Config.DisplayConfig.OledType\x12J\n\x0b\x64isplaymode\x18\x08
 
\x01(\x0e\x32\x35.meshtastic.protobuf.Config.DisplayConfig.DisplayMode\x12\x14\n\x0cheading_bold\x18\t
 \x01(\x08\x12\x1d\n\x15wake_on_tap_or_motion\x18\n 
\x01(\x08\x12Y\n\x13\x63ompass_orientation\x18\x0b 
\x01(\x0e\x32<.meshtastic.protobuf.Config.DisplayConfig.CompassOrientation\"M\n\x13GpsCoordinateFormat\x12\x07\n\x03\x44\x45\x43\x10\x00\x12\x07\n\x03\x44MS\x10\x01\x12\x07\n\x03UTM\x10\x02\x12\x08\n\x04MGRS\x10\x03\x12\x07\n\x03OLC\x10\x04\x12\x08\n\x04OSGR\x10\x05\"(\n\x0c\x44isplayUnits\x12\n\n\x06METRIC\x10\x00\x
 
12\x0c\n\x08IMPERIAL\x10\x01\"M\n\x08OledType\x12\r\n\tOLED_AUTO\x10\x00\x12\x10\n\x0cOLED_SSD1306\x10\x01\x12\x0f\n\x0bOLED_SH1106\x10\x02\x12\x0f\n\x0bOLED_SH1107\x10\x03\"A\n\x0b\x44isplayMode\x12\x0b\n\x07\x44\x45\x46\x41ULT\x10\x00\x12\x0c\n\x08TWOCOLOR\x10\x01\x12\x0c\n\x08INVERTED\x10\x02\x12\t\n\x05\x43OLOR\x10\x03\"\xba\x01\n\x12\x43ompassOrientation\x12\r\n\tDEGREES_0\x10\x00\x12\x0e\n\nDEGREES_90\x10\x01\x12\x0f\n\x0b\x44\x45GREES_180\x10\x02\x12\x0f\n\x0b\x44\x45GREES_270\x10\x03\x12\x16\n\x12\x44\x45GREES_0_INVERTED\x10\x04\x12\x17\n\x13\x44\x45GREES_90_INVERTED\x10\x05\x12\x18\n\x14\x44\x45GREES_180_INVERTED\x10\x06\x12\x18\n\x14\x44\x45GREES_270_INVERTED\x10\x07\x1a\xaf\x07\n\nLoRaConfig\x12\x12\n\nuse_preset\x18\x01
 \x01(\x08\x12H\n\x0cmodem_preset\x18\x02 
\x01(\x0e\x32\x32.meshtastic.protobuf.Config.LoRaConfig.ModemPreset\x12\x11\n\tbandwidth\x18\x03
 \x01(\r\x12\x15\n\rspread_factor\x18\x04 
\x01(\r\x12\x13\n\x0b\x63oding_rate\x18\x05 \x01(\r\x12\x18\n\x10\x66requenc
 y_offset\x18\x06 \x01(\x02\x12\x41\n\x06region\x18\x07 
\x01(\x0e\x32\x31.meshtastic.protobuf.Config.LoRaConfig.RegionCode\x12\x11\n\thop_limit\x18\x08
 \x01(\r\x12\x12\n\ntx_enabled\x18\t \x01(\x08\x12\x10\n\x08tx_power\x18\n 
\x01(\x05\x12\x13\n\x0b\x63hannel_num\x18\x0b 
\x01(\r\x12\x1b\n\x13override_duty_cycle\x18\x0c 
\x01(\x08\x12\x1e\n\x16sx126x_rx_boosted_gain\x18\r 
\x01(\x08\x12\x1a\n\x12override_frequency\x18\x0e 
\x01(\x02\x12\x17\n\x0fpa_fan_disabled\x18\x0f 
\x01(\x08\x12\x17\n\x0fignore_incoming\x18g 
\x03(\r\x12\x13\n\x0bignore_mqtt\x18h 
\x01(\x08\x12\x19\n\x11\x63onfig_ok_to_mqtt\x18i 
\x01(\x08\"\xf1\x01\n\nRegionCode\x12\t\n\x05UNSET\x10\x00\x12\x06\n\x02US\x10\x01\x12\n\n\x06\x45U_433\x10\x02\x12\n\n\x06\x45U_868\x10\x03\x12\x06\n\x02\x43N\x10\x04\x12\x06\n\x02JP\x10\x05\x12\x07\n\x03\x41NZ\x10\x06\x12\x06\n\x02KR\x10\x07\x12\x06\n\x02TW\x10\x08\x12\x06\n\x02RU\x10\t\x12\x06\n\x02IN\x10\n\x12\n\n\x06NZ_865\x10\x0b\x12\x06\n\x02TH\x10\x0c\x12\x0b\n\x07LORA_24\x10\r\x12\n\n\
 
x06UA_433\x10\x0e\x12\n\n\x06UA_868\x10\x0f\x12\n\n\x06MY_433\x10\x10\x12\n\n\x06MY_919\x10\x11\x12\n\n\x06SG_923\x10\x12\x12\n\n\x06PH_433\x10\x13\x12\n\n\x06PH_868\x10\x14\x12\n\n\x06PH_915\x10\x15\"\xa9\x01\n\x0bModemPreset\x12\r\n\tLONG_FAST\x10\x00\x12\r\n\tLONG_SLOW\x10\x01\x12\x16\n\x0eVERY_LONG_SLOW\x10\x02\x1a\x02\x08\x01\x12\x0f\n\x0bMEDIUM_SLOW\x10\x03\x12\x0f\n\x0bMEDIUM_FAST\x10\x04\x12\x0e\n\nSHORT_SLOW\x10\x05\x12\x0e\n\nSHORT_FAST\x10\x06\x12\x11\n\rLONG_MODERATE\x10\x07\x12\x0f\n\x0bSHORT_TURBO\x10\x08\x1a\xb6\x01\n\x0f\x42luetoothConfig\x12\x0f\n\x07\x65nabled\x18\x01
 \x01(\x08\x12\x45\n\x04mode\x18\x02 
\x01(\x0e\x32\x37.meshtastic.protobuf.Config.BluetoothConfig.PairingMode\x12\x11\n\tfixed_pin\x18\x03
 
\x01(\r\"8\n\x0bPairingMode\x12\x0e\n\nRANDOM_PIN\x10\x00\x12\r\n\tFIXED_PIN\x10\x01\x12\n\n\x06NO_PIN\x10\x02\x1a\xb6\x01\n\x0eSecurityConfig\x12\x12\n\npublic_key\x18\x01
 \x01(\x0c\x12\x13\n\x0bprivate_key\x18\x02 
\x01(\x0c\x12\x11\n\tadmin_key\x18\x03 \x03(\x0c\x
 12\x12\n\nis_managed\x18\x04 \x01(\x08\x12\x16\n\x0eserial_enabled\x18\x05 
\x01(\x08\x12\x1d\n\x15\x64\x65\x62ug_log_api_enabled\x18\x06 
\x01(\x08\x12\x1d\n\x15\x61\x64min_channel_enabled\x18\x08 
\x01(\x08\x1a\x12\n\x10SessionkeyConfigB\x11\n\x0fpayload_variantBa\n\x13\x63om.geeksville.meshB\x0c\x43onfigProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
+DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n 
meshtastic/protobuf/config.proto\x12\x13meshtastic.protobuf\x1a#meshtastic/protobuf/device_ui.proto\"\xa3(\n\x06\x43onfig\x12:\n\x06\x64\x65vice\x18\x01
 
\x01(\x0b\x32(.meshtastic.protobuf.Config.DeviceConfigH\x00\x12>\n\x08position\x18\x02
 
\x01(\x0b\x32*.meshtastic.protobuf.Config.PositionConfigH\x00\x12\x38\n\x05power\x18\x03
 
\x01(\x0b\x32\'.meshtastic.protobuf.Config.PowerConfigH\x00\x12<\n\x07network\x18\x04
 
\x01(\x0b\x32).meshtastic.protobuf.Config.NetworkConfigH\x00\x12<\n\x07\x64isplay\x18\x05
 
\x01(\x0b\x32).meshtastic.protobuf.Config.DisplayConfigH\x00\x12\x36\n\x04lora\x18\x06
 
\x01(\x0b\x32&.meshtastic.protobuf.Config.LoRaConfigH\x00\x12@\n\tbluetooth\x18\x07
 
\x01(\x0b\x32+.meshtastic.protobuf.Config.BluetoothConfigH\x00\x12>\n\x08security\x18\x08
 
\x01(\x0b\x32*.meshtastic.protobuf.Config.SecurityConfigH\x00\x12\x42\n\nsessionkey\x18\t
 \x01(\x0b\x32,.meshtastic.protobuf.Config.SessionkeyConfigH\x00\x12\x38\n\tdev
 ice_ui\x18\n 
\x01(\x0b\x32#.meshtastic.protobuf.DeviceUIConfigH\x00\x1a\xc7\x05\n\x0c\x44\x65viceConfig\x12;\n\x04role\x18\x01
 
\x01(\x0e\x32-.meshtastic.protobuf.Config.DeviceConfig.Role\x12\x1a\n\x0eserial_enabled\x18\x02
 \x01(\x08\x42\x02\x18\x01\x12\x13\n\x0b\x62utton_gpio\x18\x04 
\x01(\r\x12\x13\n\x0b\x62uzzer_gpio\x18\x05 
\x01(\r\x12R\n\x10rebroadcast_mode\x18\x06 
\x01(\x0e\x32\x38.meshtastic.protobuf.Config.DeviceConfig.RebroadcastMode\x12 
\n\x18node_info_broadcast_secs\x18\x07 
\x01(\r\x12\"\n\x1a\x64ouble_tap_as_button_press\x18\x08 
\x01(\x08\x12\x16\n\nis_managed\x18\t 
\x01(\x08\x42\x02\x18\x01\x12\x1c\n\x14\x64isable_triple_click\x18\n 
\x01(\x08\x12\r\n\x05tzdef\x18\x0b 
\x01(\t\x12\x1e\n\x16led_heartbeat_disabled\x18\x0c 
\x01(\x08\"\xbf\x01\n\x04Role\x12\n\n\x06\x43LIENT\x10\x00\x12\x0f\n\x0b\x43LIENT_MUTE\x10\x01\x12\n\n\x06ROUTER\x10\x02\x12\x15\n\rROUTER_CLIENT\x10\x03\x1a\x02\x08\x01\x12\x0c\n\x08REPEATER\x10\x04\x12\x0b\n\x07TRACKER\x10\x05\x12\n\n\x06SENSOR\x10\x06\x1
 
2\x07\n\x03TAK\x10\x07\x12\x11\n\rCLIENT_HIDDEN\x10\x08\x12\x12\n\x0eLOST_AND_FOUND\x10\t\x12\x0f\n\x0bTAK_TRACKER\x10\n\x12\x0f\n\x0bROUTER_LATE\x10\x0b\"s\n\x0fRebroadcastMode\x12\x07\n\x03\x41LL\x10\x00\x12\x15\n\x11\x41LL_SKIP_DECODING\x10\x01\x12\x0e\n\nLOCAL_ONLY\x10\x02\x12\x0e\n\nKNOWN_ONLY\x10\x03\x12\x08\n\x04NONE\x10\x04\x12\x16\n\x12\x43ORE_PORTNUMS_ONLY\x10\x05\x1a\x9a\x05\n\x0ePositionConfig\x12\x1f\n\x17position_broadcast_secs\x18\x01
 \x01(\r\x12(\n position_broadcast_smart_enabled\x18\x02 
\x01(\x08\x12\x16\n\x0e\x66ixed_position\x18\x03 
\x01(\x08\x12\x17\n\x0bgps_enabled\x18\x04 
\x01(\x08\x42\x02\x18\x01\x12\x1b\n\x13gps_update_interval\x18\x05 
\x01(\r\x12\x1c\n\x10gps_attempt_time\x18\x06 
\x01(\rB\x02\x18\x01\x12\x16\n\x0eposition_flags\x18\x07 
\x01(\r\x12\x0f\n\x07rx_gpio\x18\x08 \x01(\r\x12\x0f\n\x07tx_gpio\x18\t 
\x01(\r\x12(\n broadcast_smart_minimum_distance\x18\n 
\x01(\r\x12-\n%broadcast_smart_minimum_interval_secs\x18\x0b 
\x01(\r\x12\x13\n\x0bgps_en_gpio\x18\x
 0c \x01(\r\x12\x44\n\x08gps_mode\x18\r 
\x01(\x0e\x32\x32.meshtastic.protobuf.Config.PositionConfig.GpsMode\"\xab\x01\n\rPositionFlags\x12\t\n\x05UNSET\x10\x00\x12\x0c\n\x08\x41LTITUDE\x10\x01\x12\x10\n\x0c\x41LTITUDE_MSL\x10\x02\x12\x16\n\x12GEOIDAL_SEPARATION\x10\x04\x12\x07\n\x03\x44OP\x10\x08\x12\t\n\x05HVDOP\x10\x10\x12\r\n\tSATINVIEW\x10
 
\x12\n\n\x06SEQ_NO\x10@\x12\x0e\n\tTIMESTAMP\x10\x80\x01\x12\x0c\n\x07HEADING\x10\x80\x02\x12\n\n\x05SPEED\x10\x80\x04\"5\n\x07GpsMode\x12\x0c\n\x08\x44ISABLED\x10\x00\x12\x0b\n\x07\x45NABLED\x10\x01\x12\x0f\n\x0bNOT_PRESENT\x10\x02\x1a\x84\x02\n\x0bPowerConfig\x12\x17\n\x0fis_power_saving\x18\x01
 \x01(\x08\x12&\n\x1eon_battery_shutdown_after_secs\x18\x02 
\x01(\r\x12\x1f\n\x17\x61\x64\x63_multiplier_override\x18\x03 
\x01(\x02\x12\x1b\n\x13wait_bluetooth_secs\x18\x04 
\x01(\r\x12\x10\n\x08sds_secs\x18\x06 \x01(\r\x12\x0f\n\x07ls_secs\x18\x07 
\x01(\r\x12\x15\n\rmin_wake_secs\x18\x08 
\x01(\r\x12\"\n\x1a\x64\x65vice_battery_ina_address\x18\t \x01(\r
 \x12\x18\n\x10powermon_enables\x18  
\x01(\x04\x1a\xe1\x03\n\rNetworkConfig\x12\x14\n\x0cwifi_enabled\x18\x01 
\x01(\x08\x12\x11\n\twifi_ssid\x18\x03 \x01(\t\x12\x10\n\x08wifi_psk\x18\x04 
\x01(\t\x12\x12\n\nntp_server\x18\x05 
\x01(\t\x12\x13\n\x0b\x65th_enabled\x18\x06 
\x01(\x08\x12K\n\x0c\x61\x64\x64ress_mode\x18\x07 
\x01(\x0e\x32\x35.meshtastic.protobuf.Config.NetworkConfig.AddressMode\x12I\n\x0bipv4_config\x18\x08
 
\x01(\x0b\x32\x34.meshtastic.protobuf.Config.NetworkConfig.IpV4Config\x12\x16\n\x0ersyslog_server\x18\t
 \x01(\t\x12\x19\n\x11\x65nabled_protocols\x18\n 
\x01(\r\x1a\x46\n\nIpV4Config\x12\n\n\x02ip\x18\x01 
\x01(\x07\x12\x0f\n\x07gateway\x18\x02 \x01(\x07\x12\x0e\n\x06subnet\x18\x03 
\x01(\x07\x12\x0b\n\x03\x64ns\x18\x04 
\x01(\x07\"#\n\x0b\x41\x64\x64ressMode\x12\x08\n\x04\x44HCP\x10\x00\x12\n\n\x06STATIC\x10\x01\"4\n\rProtocolFlags\x12\x10\n\x0cNO_BROADCAST\x10\x00\x12\x11\n\rUDP_BROADCAST\x10\x01\x1a\x91\x08\n\rDisplayConfig\x12\x16\n\x0escreen_on_secs\x18\x01
 \x01(\r\x12Q\
 n\ngps_format\x18\x02 
\x01(\x0e\x32=.meshtastic.protobuf.Config.DisplayConfig.GpsCoordinateFormat\x12!\n\x19\x61uto_screen_carousel_secs\x18\x03
 \x01(\r\x12\x19\n\x11\x63ompass_north_top\x18\x04 
\x01(\x08\x12\x13\n\x0b\x66lip_screen\x18\x05 
\x01(\x08\x12\x45\n\x05units\x18\x06 
\x01(\x0e\x32\x36.meshtastic.protobuf.Config.DisplayConfig.DisplayUnits\x12@\n\x04oled\x18\x07
 
\x01(\x0e\x32\x32.meshtastic.protobuf.Config.DisplayConfig.OledType\x12J\n\x0b\x64isplaymode\x18\x08
 
\x01(\x0e\x32\x35.meshtastic.protobuf.Config.DisplayConfig.DisplayMode\x12\x14\n\x0cheading_bold\x18\t
 \x01(\x08\x12\x1d\n\x15wake_on_tap_or_motion\x18\n 
\x01(\x08\x12Y\n\x13\x63ompass_orientation\x18\x0b 
\x01(\x0e\x32<.meshtastic.protobuf.Config.DisplayConfig.CompassOrientation\x12\x15\n\ruse_12h_clock\x18\x0c
 
\x01(\x08\"M\n\x13GpsCoordinateFormat\x12\x07\n\x03\x44\x45\x43\x10\x00\x12\x07\n\x03\x44MS\x10\x01\x12\x07\n\x03UTM\x10\x02\x12\x08\n\x04MGRS\x10\x03\x12\x07\n\x03OLC\x10\x04\x12\x08\n\x04OSGR\x10\x05\"(\n\x0c
 
\x44isplayUnits\x12\n\n\x06METRIC\x10\x00\x12\x0c\n\x08IMPERIAL\x10\x01\"M\n\x08OledType\x12\r\n\tOLED_AUTO\x10\x00\x12\x10\n\x0cOLED_SSD1306\x10\x01\x12\x0f\n\x0bOLED_SH1106\x10\x02\x12\x0f\n\x0bOLED_SH1107\x10\x03\"A\n\x0b\x44isplayMode\x12\x0b\n\x07\x44\x45\x46\x41ULT\x10\x00\x12\x0c\n\x08TWOCOLOR\x10\x01\x12\x0c\n\x08INVERTED\x10\x02\x12\t\n\x05\x43OLOR\x10\x03\"\xba\x01\n\x12\x43ompassOrientation\x12\r\n\tDEGREES_0\x10\x00\x12\x0e\n\nDEGREES_90\x10\x01\x12\x0f\n\x0b\x44\x45GREES_180\x10\x02\x12\x0f\n\x0b\x44\x45GREES_270\x10\x03\x12\x16\n\x12\x44\x45GREES_0_INVERTED\x10\x04\x12\x17\n\x13\x44\x45GREES_90_INVERTED\x10\x05\x12\x18\n\x14\x44\x45GREES_180_INVERTED\x10\x06\x12\x18\n\x14\x44\x45GREES_270_INVERTED\x10\x07\x1a\xaf\x07\n\nLoRaConfig\x12\x12\n\nuse_preset\x18\x01
 \x01(\x08\x12H\n\x0cmodem_preset\x18\x02 
\x01(\x0e\x32\x32.meshtastic.protobuf.Config.LoRaConfig.ModemPreset\x12\x11\n\tbandwidth\x18\x03
 \x01(\r\x12\x15\n\rspread_factor\x18\x04 \x01(\r\x12\x13\n\x0b\x63oding_ra
 te\x18\x05 \x01(\r\x12\x18\n\x10\x66requency_offset\x18\x06 
\x01(\x02\x12\x41\n\x06region\x18\x07 
\x01(\x0e\x32\x31.meshtastic.protobuf.Config.LoRaConfig.RegionCode\x12\x11\n\thop_limit\x18\x08
 \x01(\r\x12\x12\n\ntx_enabled\x18\t \x01(\x08\x12\x10\n\x08tx_power\x18\n 
\x01(\x05\x12\x13\n\x0b\x63hannel_num\x18\x0b 
\x01(\r\x12\x1b\n\x13override_duty_cycle\x18\x0c 
\x01(\x08\x12\x1e\n\x16sx126x_rx_boosted_gain\x18\r 
\x01(\x08\x12\x1a\n\x12override_frequency\x18\x0e 
\x01(\x02\x12\x17\n\x0fpa_fan_disabled\x18\x0f 
\x01(\x08\x12\x17\n\x0fignore_incoming\x18g 
\x03(\r\x12\x13\n\x0bignore_mqtt\x18h 
\x01(\x08\x12\x19\n\x11\x63onfig_ok_to_mqtt\x18i 
\x01(\x08\"\xf1\x01\n\nRegionCode\x12\t\n\x05UNSET\x10\x00\x12\x06\n\x02US\x10\x01\x12\n\n\x06\x45U_433\x10\x02\x12\n\n\x06\x45U_868\x10\x03\x12\x06\n\x02\x43N\x10\x04\x12\x06\n\x02JP\x10\x05\x12\x07\n\x03\x41NZ\x10\x06\x12\x06\n\x02KR\x10\x07\x12\x06\n\x02TW\x10\x08\x12\x06\n\x02RU\x10\t\x12\x06\n\x02IN\x10\n\x12\n\n\x06NZ_865\x10\x0b\x12\x06\n\x02TH\
 
x10\x0c\x12\x0b\n\x07LORA_24\x10\r\x12\n\n\x06UA_433\x10\x0e\x12\n\n\x06UA_868\x10\x0f\x12\n\n\x06MY_433\x10\x10\x12\n\n\x06MY_919\x10\x11\x12\n\n\x06SG_923\x10\x12\x12\n\n\x06PH_433\x10\x13\x12\n\n\x06PH_868\x10\x14\x12\n\n\x06PH_915\x10\x15\"\xa9\x01\n\x0bModemPreset\x12\r\n\tLONG_FAST\x10\x00\x12\r\n\tLONG_SLOW\x10\x01\x12\x16\n\x0eVERY_LONG_SLOW\x10\x02\x1a\x02\x08\x01\x12\x0f\n\x0bMEDIUM_SLOW\x10\x03\x12\x0f\n\x0bMEDIUM_FAST\x10\x04\x12\x0e\n\nSHORT_SLOW\x10\x05\x12\x0e\n\nSHORT_FAST\x10\x06\x12\x11\n\rLONG_MODERATE\x10\x07\x12\x0f\n\x0bSHORT_TURBO\x10\x08\x1a\xb6\x01\n\x0f\x42luetoothConfig\x12\x0f\n\x07\x65nabled\x18\x01
 \x01(\x08\x12\x45\n\x04mode\x18\x02 
\x01(\x0e\x32\x37.meshtastic.protobuf.Config.BluetoothConfig.PairingMode\x12\x11\n\tfixed_pin\x18\x03
 
\x01(\r\"8\n\x0bPairingMode\x12\x0e\n\nRANDOM_PIN\x10\x00\x12\r\n\tFIXED_PIN\x10\x01\x12\n\n\x06NO_PIN\x10\x02\x1a\xb6\x01\n\x0eSecurityConfig\x12\x12\n\npublic_key\x18\x01
 \x01(\x0c\x12\x13\n\x0bprivate_key\x18\x02 \x01(\x
 0c\x12\x11\n\tadmin_key\x18\x03 \x03(\x0c\x12\x12\n\nis_managed\x18\x04 
\x01(\x08\x12\x16\n\x0eserial_enabled\x18\x05 
\x01(\x08\x12\x1d\n\x15\x64\x65\x62ug_log_api_enabled\x18\x06 
\x01(\x08\x12\x1d\n\x15\x61\x64min_channel_enabled\x18\x08 
\x01(\x08\x1a\x12\n\x10SessionkeyConfigB\x11\n\x0fpayload_variantBa\n\x13\x63om.geeksville.meshB\x0c\x43onfigProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
 
 _globals = globals()
 _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
@@ -35,7 +35,7 @@
   _CONFIG_LORACONFIG_MODEMPRESET.values_by_name["VERY_LONG_SLOW"]._options = 
None
   
_CONFIG_LORACONFIG_MODEMPRESET.values_by_name["VERY_LONG_SLOW"]._serialized_options
 = b'\010\001'
   _globals['_CONFIG']._serialized_start=95
-  _globals['_CONFIG']._serialized_end=5227
+  _globals['_CONFIG']._serialized_end=5250
   _globals['_CONFIG_DEVICECONFIG']._serialized_start=724
   _globals['_CONFIG_DEVICECONFIG']._serialized_end=1435
   _globals['_CONFIG_DEVICECONFIG_ROLE']._serialized_start=1127
@@ -59,29 +59,29 @@
   _globals['_CONFIG_NETWORKCONFIG_PROTOCOLFLAGS']._serialized_start=2799
   _globals['_CONFIG_NETWORKCONFIG_PROTOCOLFLAGS']._serialized_end=2851
   _globals['_CONFIG_DISPLAYCONFIG']._serialized_start=2854
-  _globals['_CONFIG_DISPLAYCONFIG']._serialized_end=3872
-  _globals['_CONFIG_DISPLAYCONFIG_GPSCOORDINATEFORMAT']._serialized_start=3418
-  _globals['_CONFIG_DISPLAYCONFIG_GPSCOORDINATEFORMAT']._serialized_end=3495
-  _globals['_CONFIG_DISPLAYCONFIG_DISPLAYUNITS']._serialized_start=3497
-  _globals['_CONFIG_DISPLAYCONFIG_DISPLAYUNITS']._serialized_end=3537
-  _globals['_CONFIG_DISPLAYCONFIG_OLEDTYPE']._serialized_start=3539
-  _globals['_CONFIG_DISPLAYCONFIG_OLEDTYPE']._serialized_end=3616
-  _globals['_CONFIG_DISPLAYCONFIG_DISPLAYMODE']._serialized_start=3618
-  _globals['_CONFIG_DISPLAYCONFIG_DISPLAYMODE']._serialized_end=3683
-  _globals['_CONFIG_DISPLAYCONFIG_COMPASSORIENTATION']._serialized_start=3686
-  _globals['_CONFIG_DISPLAYCONFIG_COMPASSORIENTATION']._serialized_end=3872
-  _globals['_CONFIG_LORACONFIG']._serialized_start=3875
-  _globals['_CONFIG_LORACONFIG']._serialized_end=4818
-  _globals['_CONFIG_LORACONFIG_REGIONCODE']._serialized_start=4405
-  _globals['_CONFIG_LORACONFIG_REGIONCODE']._serialized_end=4646
-  _globals['_CONFIG_LORACONFIG_MODEMPRESET']._serialized_start=4649
-  _globals['_CONFIG_LORACONFIG_MODEMPRESET']._serialized_end=4818
-  _globals['_CONFIG_BLUETOOTHCONFIG']._serialized_start=4821
-  _globals['_CONFIG_BLUETOOTHCONFIG']._serialized_end=5003
-  _globals['_CONFIG_BLUETOOTHCONFIG_PAIRINGMODE']._serialized_start=4947
-  _globals['_CONFIG_BLUETOOTHCONFIG_PAIRINGMODE']._serialized_end=5003
-  _globals['_CONFIG_SECURITYCONFIG']._serialized_start=5006
-  _globals['_CONFIG_SECURITYCONFIG']._serialized_end=5188
-  _globals['_CONFIG_SESSIONKEYCONFIG']._serialized_start=5190
-  _globals['_CONFIG_SESSIONKEYCONFIG']._serialized_end=5208
+  _globals['_CONFIG_DISPLAYCONFIG']._serialized_end=3895
+  _globals['_CONFIG_DISPLAYCONFIG_GPSCOORDINATEFORMAT']._serialized_start=3441
+  _globals['_CONFIG_DISPLAYCONFIG_GPSCOORDINATEFORMAT']._serialized_end=3518
+  _globals['_CONFIG_DISPLAYCONFIG_DISPLAYUNITS']._serialized_start=3520
+  _globals['_CONFIG_DISPLAYCONFIG_DISPLAYUNITS']._serialized_end=3560
+  _globals['_CONFIG_DISPLAYCONFIG_OLEDTYPE']._serialized_start=3562
+  _globals['_CONFIG_DISPLAYCONFIG_OLEDTYPE']._serialized_end=3639
+  _globals['_CONFIG_DISPLAYCONFIG_DISPLAYMODE']._serialized_start=3641
+  _globals['_CONFIG_DISPLAYCONFIG_DISPLAYMODE']._serialized_end=3706
+  _globals['_CONFIG_DISPLAYCONFIG_COMPASSORIENTATION']._serialized_start=3709
+  _globals['_CONFIG_DISPLAYCONFIG_COMPASSORIENTATION']._serialized_end=3895
+  _globals['_CONFIG_LORACONFIG']._serialized_start=3898
+  _globals['_CONFIG_LORACONFIG']._serialized_end=4841
+  _globals['_CONFIG_LORACONFIG_REGIONCODE']._serialized_start=4428
+  _globals['_CONFIG_LORACONFIG_REGIONCODE']._serialized_end=4669
+  _globals['_CONFIG_LORACONFIG_MODEMPRESET']._serialized_start=4672
+  _globals['_CONFIG_LORACONFIG_MODEMPRESET']._serialized_end=4841
+  _globals['_CONFIG_BLUETOOTHCONFIG']._serialized_start=4844
+  _globals['_CONFIG_BLUETOOTHCONFIG']._serialized_end=5026
+  _globals['_CONFIG_BLUETOOTHCONFIG_PAIRINGMODE']._serialized_start=4970
+  _globals['_CONFIG_BLUETOOTHCONFIG_PAIRINGMODE']._serialized_end=5026
+  _globals['_CONFIG_SECURITYCONFIG']._serialized_start=5029
+  _globals['_CONFIG_SECURITYCONFIG']._serialized_end=5211
+  _globals['_CONFIG_SESSIONKEYCONFIG']._serialized_start=5213
+  _globals['_CONFIG_SESSIONKEYCONFIG']._serialized_end=5231
 # @@protoc_insertion_point(module_scope)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/meshtastic-2.5.11/meshtastic/protobuf/config_pb2.pyi 
new/meshtastic-2.5.12/meshtastic/protobuf/config_pb2.pyi
--- old/meshtastic-2.5.11/meshtastic/protobuf/config_pb2.pyi    1970-01-01 
01:00:00.000000000 +0100
+++ new/meshtastic-2.5.12/meshtastic/protobuf/config_pb2.pyi    1970-01-01 
01:00:00.000000000 +0100
@@ -1118,6 +1118,7 @@
         HEADING_BOLD_FIELD_NUMBER: builtins.int
         WAKE_ON_TAP_OR_MOTION_FIELD_NUMBER: builtins.int
         COMPASS_ORIENTATION_FIELD_NUMBER: builtins.int
+        USE_12H_CLOCK_FIELD_NUMBER: builtins.int
         screen_on_secs: builtins.int
         """
         Number of seconds the screen stays on after pressing the user button 
or receiving a message
@@ -1165,6 +1166,11 @@
         """
         Indicates how to rotate or invert the compass output to accurate 
display on the display.
         """
+        use_12h_clock: builtins.bool
+        """
+        If false (default), the device will display the time in 24-hour format 
on screen.
+        If true, the device will display the time in 12-hour format on screen.
+        """
         def __init__(
             self,
             *,
@@ -1179,8 +1185,9 @@
             heading_bold: builtins.bool = ...,
             wake_on_tap_or_motion: builtins.bool = ...,
             compass_orientation: 
global___Config.DisplayConfig.CompassOrientation.ValueType = ...,
+            use_12h_clock: builtins.bool = ...,
         ) -> None: ...
-        def ClearField(self, field_name: 
typing.Literal["auto_screen_carousel_secs", b"auto_screen_carousel_secs", 
"compass_north_top", b"compass_north_top", "compass_orientation", 
b"compass_orientation", "displaymode", b"displaymode", "flip_screen", 
b"flip_screen", "gps_format", b"gps_format", "heading_bold", b"heading_bold", 
"oled", b"oled", "screen_on_secs", b"screen_on_secs", "units", b"units", 
"wake_on_tap_or_motion", b"wake_on_tap_or_motion"]) -> None: ...
+        def ClearField(self, field_name: 
typing.Literal["auto_screen_carousel_secs", b"auto_screen_carousel_secs", 
"compass_north_top", b"compass_north_top", "compass_orientation", 
b"compass_orientation", "displaymode", b"displaymode", "flip_screen", 
b"flip_screen", "gps_format", b"gps_format", "heading_bold", b"heading_bold", 
"oled", b"oled", "screen_on_secs", b"screen_on_secs", "units", b"units", 
"use_12h_clock", b"use_12h_clock", "wake_on_tap_or_motion", 
b"wake_on_tap_or_motion"]) -> None: ...
 
     @typing.final
     class LoRaConfig(google.protobuf.message.Message):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/meshtastic-2.5.11/meshtastic/protobuf/module_config_pb2.pyi 
new/meshtastic-2.5.12/meshtastic/protobuf/module_config_pb2.pyi
--- old/meshtastic-2.5.11/meshtastic/protobuf/module_config_pb2.pyi     
1970-01-01 01:00:00.000000000 +0100
+++ new/meshtastic-2.5.12/meshtastic/protobuf/module_config_pb2.pyi     
1970-01-01 01:00:00.000000000 +0100
@@ -927,7 +927,7 @@
     @typing.final
     class CannedMessageConfig(google.protobuf.message.Message):
         """
-        TODO: REPLACE
+        Canned Messages Module Config
         """
 
         DESCRIPTOR: google.protobuf.descriptor.Descriptor
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/meshtastic-2.5.11/meshtastic/tests/test_main.py 
new/meshtastic-2.5.12/meshtastic/tests/test_main.py
--- old/meshtastic-2.5.11/meshtastic/tests/test_main.py 1970-01-01 
01:00:00.000000000 +0100
+++ new/meshtastic-2.5.12/meshtastic/tests/test_main.py 1970-01-01 
01:00:00.000000000 +0100
@@ -408,8 +408,8 @@
 
     iface = MagicMock(autospec=SerialInterface)
 
-    def mock_showNodes():
-        print("inside mocked showNodes")
+    def mock_showNodes(includeSelf, showFields):
+        print(f"inside mocked showNodes: {includeSelf} {showFields}")
 
     iface.showNodes.side_effect = mock_showNodes
     with patch("meshtastic.serial_interface.SerialInterface", 
return_value=iface) as mo:
@@ -593,10 +593,10 @@
     iface = MagicMock(autospec=SerialInterface)
 
     def mock_sendText(
-        text, dest, wantAck=False, wantResponse=False, onResponse=None, 
channelIndex=0
+        text, dest, wantAck=False, wantResponse=False, onResponse=None, 
channelIndex=0, portNum=0
     ):
         print("inside mocked sendText")
-        print(f"{text} {dest} {wantAck} {wantResponse} {channelIndex}")
+        print(f"{text} {dest} {wantAck} {wantResponse} {channelIndex} 
{portNum}")
 
     iface.sendText.side_effect = mock_sendText
 
@@ -620,10 +620,10 @@
     iface = MagicMock(autospec=SerialInterface)
 
     def mock_sendText(
-        text, dest, wantAck=False, wantResponse=False, onResponse=None, 
channelIndex=0
+        text, dest, wantAck=False, wantResponse=False, onResponse=None, 
channelIndex=0, portNum=0
     ):
         print("inside mocked sendText")
-        print(f"{text} {dest} {wantAck} {wantResponse} {channelIndex}")
+        print(f"{text} {dest} {wantAck} {wantResponse} {channelIndex} 
{portNum}")
 
     iface.sendText.side_effect = mock_sendText
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/meshtastic-2.5.11/meshtastic/tests/test_node.py 
new/meshtastic-2.5.12/meshtastic/tests/test_node.py
--- old/meshtastic-2.5.11/meshtastic/tests/test_node.py 1970-01-01 
01:00:00.000000000 +0100
+++ new/meshtastic-2.5.12/meshtastic/tests/test_node.py 1970-01-01 
01:00:00.000000000 +0100
@@ -270,7 +270,7 @@
     assert pytest_wrapped_e.type == SystemExit
     assert pytest_wrapped_e.value.code == 1
     out, err = capsys.readouterr()
-    assert re.search(r"Warning: There were no settings.", out, re.MULTILINE)
+    assert re.search(r"Warning: config or channels not loaded", out, 
re.MULTILINE)
     assert err == ""
 
 
@@ -304,7 +304,7 @@
     assert pytest_wrapped_e.type == SystemExit
     assert pytest_wrapped_e.value.code == 1
     out, err = capsys.readouterr()
-    assert re.search(r"Warning: There were no settings", out, re.MULTILINE)
+    assert re.search(r"Warning: config or channels not loaded", out, 
re.MULTILINE)
     assert err == ""
 
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/meshtastic-2.5.11/pyproject.toml 
new/meshtastic-2.5.12/pyproject.toml
--- old/meshtastic-2.5.11/pyproject.toml        1970-01-01 01:00:00.000000000 
+0100
+++ new/meshtastic-2.5.12/pyproject.toml        1970-01-01 01:00:00.000000000 
+0100
@@ -1,6 +1,6 @@
 [tool.poetry]
 name = "meshtastic"
-version = "2.5.11"
+version = "2.5.12"
 description = "Python API & client shell for talking to Meshtastic devices"
 authors = ["Meshtastic Developers <cont...@meshtastic.org>"]
 license = "GPL-3.0-only"

Reply via email to