feat(python): Update trezorctl telemetry command with battery_cycle field.
What changed, and why it matters
This commit is a routine feature update to the trezorctl command-line tool and related firmware code. It adds display of a 'battery cycle count' field in telemetry data and changes the internal data type from signed to unsigned integer. There is no indication this change fixes or introduces a security vulnerability.
No security action required. Review as normal feature/code-quality change.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The commit updates the telemetry protobuf definition and generated bindings across Python, Rust, and C to expose battery_cycles as a uint32 (milli-cycles) rather than sint32. It also updates the CLI output to print the cycle count. The change is additive and type-correcting; no security-relevant code paths (authentication, cryptography, memory safety, privilege boundaries) are modified.
Changed components
trezorctl CLI telemetry commandTelemetry protobuf message definitionCore MicroPython telemetry_get() wrapperPython trezorlib messagesRust trezor-client protobuf bindingsInspect captured patch +20 / −14
diff --git a/common/protob/messages-telemetry.proto b/common/protob/messages-telemetry.proto
index 8dd5559f5..c97348abd 100644
--- a/common/protob/messages-telemetry.proto
+++ b/common/protob/messages-telemetry.proto
@@ -26,5 +26,5 @@ message Telemetry {
optional sint32 min_temp_c = 1; // Minimum recorded battery temperature in milli-Celsius
optional sint32 max_temp_c = 2; // Maximum recorded battery temperature in milli-Celsius
optional uint32 battery_errors = 3; // Bitfield of battery errors
- optional sint32 battery_cycles = 4; // Total battery cycles
+ optional uint32 battery_cycles = 4; // Total battery milli-cycles
}
diff --git a/core/embed/upymod/modtrezorutils/modtrezorutils.c b/core/embed/upymod/modtrezorutils/modtrezorutils.c
index 57bba05ab..1a0d4fe85 100644
--- a/core/embed/upymod/modtrezorutils/modtrezorutils.c
+++ b/core/embed/upymod/modtrezorutils/modtrezorutils.c
@@ -71,7 +71,7 @@
/// def telemetry_get() -> tuple[int, int, int, int] | None:
/// """
/// Retrieves the stored telemetry data. Returns a tuple
-/// (min_temp_milli_c, max_temp_milli_c, battery_errors)
+/// (min_temp_milli_c, max_temp_milli_c, battery_errors, battery_cycles)
/// or None if telemetry is not available.
/// """
STATIC mp_obj_t mod_trezorutils_telemetry_get(void) {
@@ -84,7 +84,7 @@ STATIC mp_obj_t mod_trezorutils_telemetry_get(void) {
tuple[0] = mp_obj_new_int((int32_t)(data.min_temp_c * 1000.0f));
tuple[1] = mp_obj_new_int((int32_t)(data.max_temp_c * 1000.0f));
tuple[2] = mp_obj_new_int(data.battery_errors.all);
- tuple[3] = mp_obj_new_int((int32_t)(data.battery_cycles *1000.0f));
+ tuple[3] = mp_obj_new_int(data.battery_cycles * 1000.0f);
return mp_obj_new_tuple(4, tuple);
}
diff --git a/core/mocks/generated/trezorutils.pyi b/core/mocks/generated/trezorutils.pyi
index 299024661..4c07a3c04 100644
--- a/core/mocks/generated/trezorutils.pyi
+++ b/core/mocks/generated/trezorutils.pyi
@@ -14,7 +14,7 @@ from trezor import utils
def telemetry_get() -> tuple[int, int, int, int] | None:
"""
Retrieves the stored telemetry data. Returns a tuple
- (min_temp_milli_c, max_temp_milli_c, battery_errors)
+ (min_temp_milli_c, max_temp_milli_c, battery_errors, battery_cycles)
or None if telemetry is not available.
"""
diff --git a/python/src/trezorlib/cli/telemetry.py b/python/src/trezorlib/cli/telemetry.py
index 3228f4c46..5200cfc5c 100644
--- a/python/src/trezorlib/cli/telemetry.py
+++ b/python/src/trezorlib/cli/telemetry.py
@@ -60,3 +60,6 @@ def get(session: Session) -> None:
for bit, name in BATTERY_ERRORS.items():
if res.battery_errors & bit:
click.echo(f" - {name}")
+
+ if res.battery_cycles is not None:
+ click.echo(f"Battery cycles: {res.battery_cycles / 1000:.2f}")
diff --git a/python/src/trezorlib/messages.py b/python/src/trezorlib/messages.py
index 8ab8abc39..faee6c143 100644
--- a/python/src/trezorlib/messages.py
+++ b/python/src/trezorlib/messages.py
@@ -7872,6 +7872,7 @@ class Telemetry(protobuf.MessageType):
1: protobuf.Field("min_temp_c", "sint32", repeated=False, required=False, default=None),
2: protobuf.Field("max_temp_c", "sint32", repeated=False, required=False, default=None),
3: protobuf.Field("battery_errors", "uint32", repeated=False, required=False, default=None),
+ 4: protobuf.Field("battery_cycles", "uint32", repeated=False, required=False, default=None),
}
def __init__(
@@ -7880,10 +7881,12 @@ class Telemetry(protobuf.MessageType):
min_temp_c: Optional["int"] = None,
max_temp_c: Optional["int"] = None,
battery_errors: Optional["int"] = None,
+ battery_cycles: Optional["int"] = None,
) -> None:
self.min_temp_c = min_temp_c
self.max_temp_c = max_temp_c
self.battery_errors = battery_errors
+ self.battery_cycles = battery_cycles
class TezosGetAddress(protobuf.MessageType):
diff --git a/rust/trezor-client/src/protos/generated/messages_telemetry.rs b/rust/trezor-client/src/protos/generated/messages_telemetry.rs
index aedd9ea18..c90677893 100644
--- a/rust/trezor-client/src/protos/generated/messages_telemetry.rs
+++ b/rust/trezor-client/src/protos/generated/messages_telemetry.rs
@@ -138,7 +138,7 @@ pub struct Telemetry {
// @@protoc_insertion_point(field:hw.trezor.messages.telemetry.Telemetry.battery_errors)
pub battery_errors: ::std::option::Option<u32>,
// @@protoc_insertion_point(field:hw.trezor.messages.telemetry.Telemetry.battery_cycles)
- pub battery_cycles: ::std::option::Option<i32>,
+ pub battery_cycles: ::std::option::Option<u32>,
// special fields
// @@protoc_insertion_point(special_field:hw.trezor.messages.telemetry.Telemetry.special_fields)
pub special_fields: ::protobuf::SpecialFields,
@@ -212,9 +212,9 @@ impl Telemetry {
self.battery_errors = ::std::option::Option::Some(v);
}
- // optional sint32 battery_cycles = 4;
+ // optional uint32 battery_cycles = 4;
- pub fn battery_cycles(&self) -> i32 {
+ pub fn battery_cycles(&self) -> u32 {
self.battery_cycles.unwrap_or(0)
}
@@ -227,7 +227,7 @@ impl Telemetry {
}
// Param is passed by value, moved
- pub fn set_battery_cycles(&mut self, v: i32) {
+ pub fn set_battery_cycles(&mut self, v: u32) {
self.battery_cycles = ::std::option::Option::Some(v);
}
@@ -282,7 +282,7 @@ impl ::protobuf::Message for Telemetry {
self.battery_errors = ::std::option::Option::Some(is.read_uint32()?);
},
32 => {
- self.battery_cycles = ::std::option::Option::Some(is.read_sint32()?);
+ self.battery_cycles = ::std::option::Option::Some(is.read_uint32()?);
},
tag => {
::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?;
@@ -306,7 +306,7 @@ impl ::protobuf::Message for Telemetry {
my_size += ::protobuf::rt::uint32_size(3, v);
}
if let Some(v) = self.battery_cycles {
- my_size += ::protobuf::rt::sint32_size(4, v);
+ my_size += ::protobuf::rt::uint32_size(4, v);
}
my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields());
self.special_fields.cached_size().set(my_size as u32);
@@ -324,7 +324,7 @@ impl ::protobuf::Message for Telemetry {
os.write_uint32(3, v)?;
}
if let Some(v) = self.battery_cycles {
- os.write_sint32(4, v)?;
+ os.write_uint32(4, v)?;
}
os.write_unknown_fields(self.special_fields.unknown_fields())?;
::std::result::Result::Ok(())
@@ -384,9 +384,9 @@ static file_descriptor_proto_data: &'static [u8] = b"\
options.proto\"\x0e\n\x0cTelemetryGet\"\x95\x01\n\tTelemetry\x12\x1c\n\n\
min_temp_c\x18\x01\x20\x01(\x11R\x08minTempC\x12\x1c\n\nmax_temp_c\x18\
\x02\x20\x01(\x11R\x08maxTempC\x12%\n\x0ebattery_errors\x18\x03\x20\x01(\
- \rR\rbatteryErrors\x12%\n\x0ebattery_cycles\x18\x04\x20\x01(\x11R\rbatte\
- ryCyclesBA\n#com.satoshilabs.trezor.lib.protobufB\x16TrezorMessageTeleme\
- try\x80\xa6\x1d\x01\
+ \rR\rbatteryErrors\x12%\n\x0ebattery_cycles\x18\x04\x20\x01(\rR\rbattery\
+ CyclesBA\n#com.satoshilabs.trezor.lib.protobufB\x16TrezorMessageTelemetr\
+ y\x80\xa6\x1d\x01\
";
/// `FileDescriptorProto` object which was a source for this generated file
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.