Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions 13 meshtastic/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1085,6 +1085,11 @@ def setSimpleConfig(modem_preset):
print(f"Waiting {args.wait_to_disconnect} seconds before disconnecting")
time.sleep(int(args.wait_to_disconnect))

if args.sensor_config:
closeNow = True
waitForAckNak = True
interface.getNode(args.dest, False, **getNode_kwargs).sensorConfig(args.sensor_config)

Comment on lines +1088 to +1092
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

--sensor-config is executed after the global ACK/NAK wait block, but it sets waitForAckNak = True only inside this late block. As a result, remote --sensor-config commands won’t trigger the “Waiting for an acknowledgment…” path and the interface may close immediately after sending. Please move this handling up with the other remote-admin actions (before the ACK/NAK wait section), or explicitly call iface.waitForAckNak() right after sensorConfig() when --dest is remote.

Copilot uses AI. Check for mistakes.
# if the user didn't ask for serial debugging output, we might want to exit after we've done our operation
if (not args.seriallog) and closeNow:
interface.close() # after running command then exit
Expand Down Expand Up @@ -1983,6 +1988,14 @@ def addRemoteAdminArgs(parser: argparse.ArgumentParser) -> argparse.ArgumentPars
metavar="TIMESTAMP",
)

group.add_argument(
"--sensor-config",
help="Send a sensor admin command to configure sensor parameters.",
action="store",
nargs='+',
default=None
)
Comment on lines +1991 to +1997
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New CLI surface area (--sensor-config) isn’t covered by the existing meshtastic/tests/test_main.py CLI tests (which already validate many other flags). Please add at least a basic unit test that initParser() accepts --sensor-config and that onConnected() routes it to Node.sensorConfig() with the expected argument list.

Copilot uses AI. Check for mistakes.

return parser

def initParser():
Expand Down
96 changes: 96 additions & 0 deletions 96 meshtastic/node.py
Original file line number Diff line number Diff line change
Expand Up @@ -977,6 +977,102 @@ def onAckNak(self, p):
print(f"Received an ACK.")
self.iface._acknowledgment.receivedAck = True

def sensorConfig(self, commands: List = None):
"""Send a sensor configuration command"""
self.ensureSessionKey()

Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sensorConfig() defaults commands to None but immediately iterates over it (for command in commands), which will raise a TypeError if the method is called without CLI args (or with an empty value from other API consumers). Please guard against commands is None (and possibly len(commands)==0) early and exit with a clear message or exception.

Suggested change
if not commands:
print("No sensor configuration commands were provided.")
return

Copilot uses AI. Check for mistakes.
p = admin_pb2.AdminMessage()
if any(['scd4x_config' in command for command in commands]):
cleanup_commands = [command.replace('scd4x_config.', '') for command in commands]

if 'factory_reset' in cleanup_commands:
print ("Performing factory reset on SCD4X")
p.sensor_config.scd4x_config.factory_reset = True
else:
if 'set_asc' in cleanup_commands:
if cleanup_commands[cleanup_commands.index('set_asc')+1] == "true":
p.sensor_config.scd4x_config.set_asc = True
print ("Setting SCD4X ASC mode")
elif cleanup_commands[cleanup_commands.index('set_asc')+1] == "false":
p.sensor_config.scd4x_config.set_asc = False
print ("Setting SCD4X FRC mode")
else:
print(
f'Not valid argument for sensor_config.scd4x_config.set_asc'
)
Comment on lines +988 to +1002
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method prints user-facing status/errors directly. In node.py, other admin actions typically use logger.info()/warning() (e.g. reboot() / shutdown()), which is easier to test and integrates with library consumers. Consider switching these print() calls to logging (and letting the CLI decide what to print) to keep Node usable as a library API.

Copilot uses AI. Check for mistakes.
if 'set_target_co2_conc' in cleanup_commands:
try:
target_co2_conc = int(cleanup_commands[cleanup_commands.index('set_target_co2_conc')+1])
except ValueError:
print(
f'Invalid value for target CO2 conc'
)
return
else:
print (f"Setting SCD4X target CO2 conc to {target_co2_conc}")
p.sensor_config.scd4x_config.set_target_co2_conc = target_co2_conc
send_command = True
if 'set_temperature' in cleanup_commands:
try:
temperature = float(cleanup_commands[cleanup_commands.index('set_temperature')+1])
except ValueError:
print(
f'Invalid value for reference temperature'
)
return
else:
print (f"Setting SCD4X Reference temperature to {temperature}")
p.sensor_config.scd4x_config.set_temperature = temperature
send_command = True
Comment on lines +1012 to +1026
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

send_command is assigned but never read. This is dead code and may trigger linting failures; either remove it or use it to control the “Nothing to request”/sending behavior consistently across all sensor config fields.

Copilot uses AI. Check for mistakes.
if 'set_altitude' in cleanup_commands:
try:
altitude = int(cleanup_commands[cleanup_commands.index('set_altitude')+1])
except ValueError:
print(
f'Invalid value for reference altitude'
)
return
else:
print (f"Setting SCD4X Reference altitude to {altitude}")
p.sensor_config.scd4x_config.set_altitude = altitude
if 'set_ambient_pressure' in cleanup_commands:
try:
ambient_pressure = int(cleanup_commands[cleanup_commands.index('set_ambient_pressure')+1])
except ValueError:
print(
f'Invalid value for reference ambient pressure'
)
return
else:
print (f"Setting SCD4X Reference ambient pressure to {ambient_pressure}")
Comment on lines +988 to +1047
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Several options read their value via cleanup_commands[cleanup_commands.index(name) + 1] without checking that a following token exists. If a user passes an option as the last argument (e.g. scd4x_config.set_asc with no value), this will raise IndexError and crash the CLI. Please validate that the option has a value (and ideally that the value token isn’t another *_config.* key) before indexing.

Suggested change
if 'factory_reset' in cleanup_commands:
print ("Performing factory reset on SCD4X")
p.sensor_config.scd4x_config.factory_reset = True
else:
if 'set_asc' in cleanup_commands:
if cleanup_commands[cleanup_commands.index('set_asc')+1] == "true":
p.sensor_config.scd4x_config.set_asc = True
print ("Setting SCD4X ASC mode")
elif cleanup_commands[cleanup_commands.index('set_asc')+1] == "false":
p.sensor_config.scd4x_config.set_asc = False
print ("Setting SCD4X FRC mode")
else:
print(
f'Not valid argument for sensor_config.scd4x_config.set_asc'
)
if 'set_target_co2_conc' in cleanup_commands:
try:
target_co2_conc = int(cleanup_commands[cleanup_commands.index('set_target_co2_conc')+1])
except ValueError:
print(
f'Invalid value for target CO2 conc'
)
return
else:
print (f"Setting SCD4X target CO2 conc to {target_co2_conc}")
p.sensor_config.scd4x_config.set_target_co2_conc = target_co2_conc
send_command = True
if 'set_temperature' in cleanup_commands:
try:
temperature = float(cleanup_commands[cleanup_commands.index('set_temperature')+1])
except ValueError:
print(
f'Invalid value for reference temperature'
)
return
else:
print (f"Setting SCD4X Reference temperature to {temperature}")
p.sensor_config.scd4x_config.set_temperature = temperature
send_command = True
if 'set_altitude' in cleanup_commands:
try:
altitude = int(cleanup_commands[cleanup_commands.index('set_altitude')+1])
except ValueError:
print(
f'Invalid value for reference altitude'
)
return
else:
print (f"Setting SCD4X Reference altitude to {altitude}")
p.sensor_config.scd4x_config.set_altitude = altitude
if 'set_ambient_pressure' in cleanup_commands:
try:
ambient_pressure = int(cleanup_commands[cleanup_commands.index('set_ambient_pressure')+1])
except ValueError:
print(
f'Invalid value for reference ambient pressure'
)
return
else:
print (f"Setting SCD4X Reference ambient pressure to {ambient_pressure}")
def _get_scd4x_option_value(option_name: str) -> Optional[str]:
"""
Safely get the value token that follows an SCD4X option name
in cleanup_commands. Returns None if no valid value is found.
"""
try:
idx = cleanup_commands.index(option_name)
except ValueError:
return None
value_index = idx + 1
if value_index >= len(cleanup_commands):
print(f"Missing value for sensor_config.scd4x_config.{option_name}")
return None
value_token = cleanup_commands[value_index]
# Treat any other known SCD4X option name as not being a valid value
scd4x_option_names = {
'factory_reset',
'set_asc',
'set_target_co2_conc',
'set_temperature',
'set_altitude',
'set_ambient_pressure',
}
if value_token in scd4x_option_names:
print(f"Missing value for sensor_config.scd4x_config.{option_name}")
return None
return value_token
if 'factory_reset' in cleanup_commands:
print("Performing factory reset on SCD4X")
p.sensor_config.scd4x_config.factory_reset = True
else:
if 'set_asc' in cleanup_commands:
asc_value = _get_scd4x_option_value('set_asc')
if asc_value == "true":
p.sensor_config.scd4x_config.set_asc = True
print("Setting SCD4X ASC mode")
elif asc_value == "false":
p.sensor_config.scd4x_config.set_asc = False
print("Setting SCD4X FRC mode")
elif asc_value is not None:
print(
f'Not valid argument for sensor_config.scd4x_config.set_asc'
)
if 'set_target_co2_conc' in cleanup_commands:
value = _get_scd4x_option_value('set_target_co2_conc')
if value is None:
return
try:
target_co2_conc = int(value)
except ValueError:
print(
f'Invalid value for target CO2 conc'
)
return
else:
print(f"Setting SCD4X target CO2 conc to {target_co2_conc}")
p.sensor_config.scd4x_config.set_target_co2_conc = target_co2_conc
send_command = True
if 'set_temperature' in cleanup_commands:
value = _get_scd4x_option_value('set_temperature')
if value is None:
return
try:
temperature = float(value)
except ValueError:
print(
f'Invalid value for reference temperature'
)
return
else:
print(f"Setting SCD4X Reference temperature to {temperature}")
p.sensor_config.scd4x_config.set_temperature = temperature
send_command = True
if 'set_altitude' in cleanup_commands:
value = _get_scd4x_option_value('set_altitude')
if value is None:
return
try:
altitude = int(value)
except ValueError:
print(
f'Invalid value for reference altitude'
)
return
else:
print(f"Setting SCD4X Reference altitude to {altitude}")
p.sensor_config.scd4x_config.set_altitude = altitude
if 'set_ambient_pressure' in cleanup_commands:
value = _get_scd4x_option_value('set_ambient_pressure')
if value is None:
return
try:
ambient_pressure = int(value)
except ValueError:
print(
f'Invalid value for reference ambient pressure'
)
return
else:
print(f"Setting SCD4X Reference ambient pressure to {ambient_pressure}")

Copilot uses AI. Check for mistakes.
p.sensor_config.scd4x_config.set_ambient_pressure = ambient_pressure

if any(['sen5x_config' in command for command in commands]):
cleanup_commands = [command.replace('sen5x_config.', '') for command in commands]
if 'set_one_shot_mode' in cleanup_commands:
if cleanup_commands[cleanup_commands.index('set_one_shot_mode')+1] == "true":
p.sensor_config.sen5x_config.set_one_shot_mode = True
print ("Setting SEN5X one shot mode")
elif cleanup_commands[cleanup_commands.index('set_one_shot_mode')+1] == "false":
p.sensor_config.sen5x_config.set_one_shot_mode = False
print ("Setting SEN5X continuous mode")
else:
print(
f'Not valid argument for sensor_config.sen5x_config.set_one_shot_mode'
)

# How to represent a HANDLED event?
if self == self.iface.localNode:
onResponse = None
else:
onResponse = self.onAckNak

if p.ByteSize():
# TODO - Should this require a response?
return self._sendAdmin(p, onResponse=onResponse)
else:
Comment on lines +980 to +1073
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New parsing/sending behavior in sensorConfig() isn’t covered by unit tests. The repo already has meshtastic/tests/test_node.py exercising other admin methods (e.g. reboot(), shutdown()). Please add tests that validate: (1) valid tokens set the expected protobuf fields, and (2) invalid/missing values are handled without raising (especially the IndexError cases).

Copilot uses AI. Check for mistakes.
print ('Nothing to request')

def _requestChannel(self, channelNum: int):
"""Done with initial config messages, now send regular
MeshPackets to ask for settings"""
Expand Down
Loading
Morty Proxy This is a proxified and sanitized view of the page, visit original site.