feat: use uv and protobuf

This commit is contained in:
2025-06-10 16:26:23 +08:00
parent 6a6622eff0
commit 884a575d7d
7 changed files with 1776 additions and 0 deletions

221
app/proto/__init__.py Normal file
View File

@ -0,0 +1,221 @@
# Generated by the protocol buffer compiler. DO NOT EDIT!
# sources: nanopb.proto
# plugin: python-betterproto
# This file has been @generated
from dataclasses import dataclass
from typing import List
import betterproto
import betterproto.lib.google.protobuf as betterproto_lib_google_protobuf
class FieldType(betterproto.Enum):
FT_DEFAULT = 0
FT_CALLBACK = 1
FT_POINTER = 4
FT_STATIC = 2
FT_IGNORE = 3
FT_INLINE = 5
class IntSize(betterproto.Enum):
IS_DEFAULT = 0
IS_8 = 8
IS_16 = 16
IS_32 = 32
IS_64 = 64
class TypenameMangling(betterproto.Enum):
M_NONE = 0
M_STRIP_PACKAGE = 1
M_FLATTEN = 2
M_PACKAGE_INITIALS = 3
class DescriptorSize(betterproto.Enum):
DS_AUTO = 0
DS_1 = 1
DS_2 = 2
DS_4 = 4
DS_8 = 8
@dataclass(eq=False, repr=False)
class NanoPbOptions(betterproto.Message):
"""
This is the inner options message, which basically defines options for
a field. When it is used in message or file scope, it applies to all
fields.
"""
max_size: int = betterproto.int32_field(1)
"""
Allocated size for 'bytes' and 'string' fields.
For string fields, this should include the space for null terminator.
"""
max_length: int = betterproto.int32_field(14)
"""
Maximum length for 'string' fields. Setting this is equivalent
to setting max_size to a value of length+1.
"""
max_count: int = betterproto.int32_field(2)
"""Allocated number of entries in arrays ('repeated' fields)"""
int_size: "IntSize" = betterproto.enum_field(7)
"""
Size of integer fields. Can save some memory if you don't need
full 32 bits for the value.
"""
enum_intsize: "IntSize" = betterproto.enum_field(34)
"""Size for enum fields. Supported by C++11 and C23 standards."""
type: "FieldType" = betterproto.enum_field(3)
"""Force type of field (callback or static allocation)"""
long_names: bool = betterproto.bool_field(4)
"""Use long names for enums, i.e. EnumName_EnumValue."""
packed_struct: bool = betterproto.bool_field(5)
"""
Add 'packed' attribute to generated structs.
Note: this cannot be used on CPUs that break on unaligned
accesses to variables.
"""
packed_enum: bool = betterproto.bool_field(10)
"""Add 'packed' attribute to generated enums."""
skip_message: bool = betterproto.bool_field(6)
"""Skip this message"""
no_unions: bool = betterproto.bool_field(8)
"""Generate oneof fields as normal optional fields instead of union."""
msgid: int = betterproto.uint32_field(9)
"""integer type tag for a message"""
anonymous_oneof: bool = betterproto.bool_field(11)
"""decode oneof as anonymous union"""
proto3: bool = betterproto.bool_field(12)
"""Proto3 singular field does not generate a "has_" flag"""
proto3_singular_msgs: bool = betterproto.bool_field(21)
"""
Force proto3 messages to have no "has_" flag.
This was default behavior until nanopb-0.4.0.
"""
enum_to_string: bool = betterproto.bool_field(13)
"""
Generate an enum->string mapping function (can take up lots of space).
"""
enum_validate: bool = betterproto.bool_field(32)
"""Generate validation methods for enums"""
fixed_length: bool = betterproto.bool_field(15)
"""Generate bytes arrays with fixed length"""
fixed_count: bool = betterproto.bool_field(16)
"""Generate repeated field with fixed count"""
submsg_callback: bool = betterproto.bool_field(22)
"""
Generate message-level callback that is called before decoding submessages.
This can be used to set callback fields for submsgs inside oneofs.
"""
mangle_names: "TypenameMangling" = betterproto.enum_field(17)
"""
Shorten or remove package names from type names.
This option applies only on the file level.
"""
callback_datatype: str = betterproto.string_field(18)
"""Data type for storage associated with callback fields."""
callback_function: str = betterproto.string_field(19)
"""
Callback function used for encoding and decoding.
Prior to nanopb-0.4.0, the callback was specified in per-field pb_callback_t
structure. This is still supported, but does not work inside e.g. oneof or pointer
fields. Instead, a new method allows specifying a per-message callback that
will be called for all callback fields in a message type.
"""
descriptorsize: "DescriptorSize" = betterproto.enum_field(20)
"""
Select the size of field descriptors. This option has to be defined
for the whole message, not per-field. Usually automatic selection is
ok, but if it results in compilation errors you can increase the field
size here.
"""
default_has: bool = betterproto.bool_field(23)
"""Set default value for has_ fields."""
include: List[str] = betterproto.string_field(24)
"""Extra files to include in generated `.pb.h`"""
exclude: List[str] = betterproto.string_field(26)
"""
Automatic includes to exclude from generated `.pb.h`
Same as nanopb_generator.py command line flag -x.
"""
package: str = betterproto.string_field(25)
"""Package name that applies only for nanopb."""
type_override: "betterproto_lib_google_protobuf.FieldDescriptorProtoType" = (
betterproto.enum_field(27)
)
"""
Override type of the field in generated C code. Only to be used with related field types
"""
label_override: "betterproto_lib_google_protobuf.FieldDescriptorProtoLabel" = (
betterproto.enum_field(31)
)
"""
Override of the label of the field (see FieldDescriptorProto.Label). Can be used to create
fields which nanopb considers required in proto3, or whether nanopb treats the field as
optional/required/repeated.
"""
sort_by_tag: bool = betterproto.bool_field(28)
"""
Due to historical reasons, nanopb orders fields in structs by their tag number
instead of the order in .proto. Set this to false to keep the .proto order.
The default value will probably change to false in nanopb-0.5.0.
"""
fallback_type: "FieldType" = betterproto.enum_field(29)
"""
Set the FT_DEFAULT field conversion strategy.
A field that can become a static member of a c struct (e.g. int, bool, etc)
will be a a static field.
Fields with dynamic length are converted to either a pointer or a callback.
"""
initializer: str = betterproto.string_field(30)
"""
Override initializer used in generated MyMessage_init_zero and MyMessage_init_default macros
By default decided automatically based on field default value and datatype.
"""
discard_unused_automatic_types: bool = betterproto.bool_field(33)
"""
Discard unused types that are automatically generated by protoc if they are not actually
needed. Currently this applies to map< > types when the field is ignored by options.
"""
discard_deprecated: bool = betterproto.bool_field(35)
"""
Discard messages and fields marked with [deprecated = true] in the proto file.
"""

View File

@ -0,0 +1,96 @@
# Generated by the protocol buffer compiler. DO NOT EDIT!
# sources: hr_packet.proto
# plugin: python-betterproto
# This file has been @generated
from dataclasses import dataclass
from typing import List
import betterproto
class HrConfidence(betterproto.Enum):
ZERO = 0
"""[0,25)"""
LOW = 1
"""[25,50)"""
MEDIUM = 2
"""[50,75]"""
HIGH = 3
"""(75,100]"""
class LoRaBw(betterproto.Enum):
BW_NONE = 0
BW_10_4 = 8
"""
nobody is using 7.8 kHz bandwidth
so to satisfy protobuf, we use NONE for zero value
"""
BW_15_6 = 1
BW_20_8 = 9
BW_31_25 = 2
BW_41_7 = 10
BW_62_5 = 3
BW_125_0 = 4
BW_250_0 = 5
BW_500_0 = 6
@dataclass(eq=False, repr=False)
class LoRaParameters(betterproto.Message):
bw: "LoRaBw" = betterproto.enum_field(1)
sf: int = betterproto.int32_field(2)
frequency: float = betterproto.float_field(3)
@dataclass(eq=False, repr=False)
class StatusFlag(betterproto.Message):
hr_confidence: "HrConfidence" = betterproto.enum_field(1)
is_active: bool = betterproto.bool_field(2)
is_on_skin: bool = betterproto.bool_field(3)
battery: int = betterproto.uint32_field(4)
@dataclass(eq=False, repr=False)
class HrOnlyPacket(betterproto.Message):
status: "StatusFlag" = betterproto.message_field(1)
id: int = betterproto.uint32_field(2)
packet_num: int = betterproto.uint32_field(3)
hr: int = betterproto.uint32_field(4)
@dataclass(eq=False, repr=False)
class HrPpgPacket(betterproto.Message):
status: "StatusFlag" = betterproto.message_field(1)
id: int = betterproto.uint32_field(2)
packet_num: int = betterproto.uint32_field(3)
hr: int = betterproto.uint32_field(4)
ppg_data: List[int] = betterproto.uint32_field(5)
@dataclass(eq=False, repr=False)
class PacketStatus(betterproto.Message):
signal_rssi_pkt: int = betterproto.sint32_field(1)
"""
Estimation of RSSI of the LoRa® signal (after despreading) on last packet received.
"""
@dataclass(eq=False, repr=False)
class GatewayInfo(betterproto.Message):
region_id: int = betterproto.uint32_field(1)
gateway_mac: bytes = betterproto.bytes_field(2)
radio_parameters: "LoRaParameters" = betterproto.message_field(3)
@dataclass(eq=False, repr=False)
class HrPacket(betterproto.Message):
gateway_info: "GatewayInfo" = betterproto.message_field(1)
hr_only_packet: "HrOnlyPacket" = betterproto.message_field(2, group="packet")
hr_ppg_packet: "HrPpgPacket" = betterproto.message_field(3, group="packet")
packet_status: "PacketStatus" = betterproto.message_field(4)

View File

@ -0,0 +1,86 @@
// See `Generator Options` section in
// https://jpa.kapsi.fi/nanopb/docs/reference.html#generator-options
// for nanopb specific options
//
// Remember to include
// https://github.com/nanopb/nanopb/blob/master/generator/proto/nanopb.proto
// when generating the proto file
syntax = "proto3";
import "nanopb.proto";
package hr_packet;
enum HrConfidence {
// [0,25)
ZERO = 0;
// [25,50)
LOW = 1;
// [50,75]
MEDIUM = 2;
// (75,100]
HIGH = 3;
}
enum LoRaBW {
BW_NONE = 0;
// nobody is using 7.8 kHz bandwidth
// so to satisfy protobuf, we use NONE for zero value
BW_10_4 = 0x08;
BW_15_6 = 0x01;
BW_20_8 = 0x09;
BW_31_25 = 0x02;
BW_41_7 = 0x0A;
BW_62_5 = 0x03;
BW_125_0 = 0x04;
BW_250_0 = 0x05;
BW_500_0 = 0x06;
}
message LoRaParameters {
LoRaBW bw = 1;
int32 sf = 2 [(nanopb).int_size = IS_8];
float frequency = 3;
}
message StatusFlag {
HrConfidence hr_confidence = 1 [(nanopb).int_size = IS_8];
bool is_active = 2;
bool is_on_skin = 3;
uint32 battery = 4 [(nanopb).int_size = IS_8];
}
message HrOnlyPacket {
StatusFlag status = 1 [(nanopb).int_size = IS_8];
uint32 id = 2 [(nanopb).int_size = IS_8];
uint32 packet_num = 3 [(nanopb).int_size = IS_8];
uint32 hr = 4 [(nanopb).int_size = IS_8];
}
message HrPpgPacket {
StatusFlag status = 1 [(nanopb).int_size = IS_8];
uint32 id = 2 [(nanopb).int_size = IS_8];
uint32 packet_num = 3 [(nanopb).int_size = IS_8];
uint32 hr = 4 [(nanopb).int_size = IS_8];
repeated uint32 ppg_data = 5 [(nanopb).max_count = 36];
}
message PacketStatus {
// Estimation of RSSI of the LoRa® signal (after despreading) on last packet received.
sint32 signal_rssi_pkt = 1;
}
message GatewayInfo {
uint32 region_id = 1 [(nanopb).int_size = IS_8];
bytes gateway_mac = 2 [(nanopb).max_size = 6];
LoRaParameters radio_parameters = 3;
}
message HrPacket {
GatewayInfo gateway_info = 1;
oneof packet {
HrOnlyPacket hr_only_packet = 2;
HrPpgPacket hr_ppg_packet = 3;
}
PacketStatus packet_status = 4;
}

View File

@ -0,0 +1,213 @@
// This file contains definitions of custom options used to control the
// code generator in nanopb protocol buffers library.
//
// Most commonly used options are max_count and max_size, which allow
// the generator to allocate static arrays for repeated and string fields.
//
// There are three ways to use these options:
// 1. Use a separate <protofile>.options file
// 2. Use command line switches to nanopb_generator.py
// 3. Use [(nanopb).option = value] in your <protofile>.proto file
//
// For detailed documentation, refer to "Generator options" in docs/reference.md
syntax = "proto2";
import "google/protobuf/descriptor.proto";
option java_package = "fi.kapsi.koti.jpa.nanopb";
enum FieldType {
FT_DEFAULT = 0; // Automatically decide field type, generate static field if possible.
FT_CALLBACK = 1; // Always generate a callback field.
FT_POINTER = 4; // Always generate a dynamically allocated field.
FT_STATIC = 2; // Generate a static field or raise an exception if not possible.
FT_IGNORE = 3; // Ignore the field completely.
FT_INLINE = 5; // Legacy option, use the separate 'fixed_length' option instead
}
enum IntSize {
IS_DEFAULT = 0; // Default, 32/64bit based on type in .proto
IS_8 = 8;
IS_16 = 16;
IS_32 = 32;
IS_64 = 64;
}
enum TypenameMangling {
M_NONE = 0; // Default, no typename mangling
M_STRIP_PACKAGE = 1; // Strip current package name
M_FLATTEN = 2; // Only use last path component
M_PACKAGE_INITIALS = 3; // Replace the package name by the initials
}
enum DescriptorSize {
DS_AUTO = 0; // Select minimal size based on field type
DS_1 = 1; // 1 word; up to 15 byte fields, no arrays
DS_2 = 2; // 2 words; up to 4095 byte fields, 4095 entry arrays
DS_4 = 4; // 4 words; up to 2^32-1 byte fields, 2^16-1 entry arrays
DS_8 = 8; // 8 words; up to 2^32-1 entry arrays
}
// This is the inner options message, which basically defines options for
// a field. When it is used in message or file scope, it applies to all
// fields.
message NanoPBOptions {
// Allocated size for 'bytes' and 'string' fields.
// For string fields, this should include the space for null terminator.
optional int32 max_size = 1;
// Maximum length for 'string' fields. Setting this is equivalent
// to setting max_size to a value of length+1.
optional int32 max_length = 14;
// Allocated number of entries in arrays ('repeated' fields)
optional int32 max_count = 2;
// Size of integer fields. Can save some memory if you don't need
// full 32 bits for the value.
optional IntSize int_size = 7 [default = IS_DEFAULT];
// Size for enum fields. Supported by C++11 and C23 standards.
optional IntSize enum_intsize = 34 [default = IS_DEFAULT];
// Force type of field (callback or static allocation)
optional FieldType type = 3 [default = FT_DEFAULT];
// Use long names for enums, i.e. EnumName_EnumValue.
optional bool long_names = 4 [default = true];
// Add 'packed' attribute to generated structs.
// Note: this cannot be used on CPUs that break on unaligned
// accesses to variables.
optional bool packed_struct = 5 [default = false];
// Add 'packed' attribute to generated enums.
optional bool packed_enum = 10 [default = false];
// Skip this message
optional bool skip_message = 6 [default = false];
// Generate oneof fields as normal optional fields instead of union.
optional bool no_unions = 8 [default = false];
// integer type tag for a message
optional uint32 msgid = 9;
// decode oneof as anonymous union
optional bool anonymous_oneof = 11 [default = false];
// Proto3 singular field does not generate a "has_" flag
optional bool proto3 = 12 [default = false];
// Force proto3 messages to have no "has_" flag.
// This was default behavior until nanopb-0.4.0.
optional bool proto3_singular_msgs = 21 [default = false];
// Generate an enum->string mapping function (can take up lots of space).
optional bool enum_to_string = 13 [default = false];
// Generate validation methods for enums
optional bool enum_validate = 32 [default = false];
// Generate bytes arrays with fixed length
optional bool fixed_length = 15 [default = false];
// Generate repeated field with fixed count
optional bool fixed_count = 16 [default = false];
// Generate message-level callback that is called before decoding submessages.
// This can be used to set callback fields for submsgs inside oneofs.
optional bool submsg_callback = 22 [default = false];
// Shorten or remove package names from type names.
// This option applies only on the file level.
optional TypenameMangling mangle_names = 17 [default = M_NONE];
// Data type for storage associated with callback fields.
optional string callback_datatype = 18 [default = "pb_callback_t"];
// Callback function used for encoding and decoding.
// Prior to nanopb-0.4.0, the callback was specified in per-field pb_callback_t
// structure. This is still supported, but does not work inside e.g. oneof or pointer
// fields. Instead, a new method allows specifying a per-message callback that
// will be called for all callback fields in a message type.
optional string callback_function = 19 [default = "pb_default_field_callback"];
// Select the size of field descriptors. This option has to be defined
// for the whole message, not per-field. Usually automatic selection is
// ok, but if it results in compilation errors you can increase the field
// size here.
optional DescriptorSize descriptorsize = 20 [default = DS_AUTO];
// Set default value for has_ fields.
optional bool default_has = 23 [default = false];
// Extra files to include in generated `.pb.h`
repeated string include = 24;
// Automatic includes to exclude from generated `.pb.h`
// Same as nanopb_generator.py command line flag -x.
repeated string exclude = 26;
// Package name that applies only for nanopb.
optional string package = 25;
// Override type of the field in generated C code. Only to be used with related field types
optional google.protobuf.FieldDescriptorProto.Type type_override = 27;
// Override of the label of the field (see FieldDescriptorProto.Label). Can be used to create
// fields which nanopb considers required in proto3, or whether nanopb treats the field as
// optional/required/repeated.
optional google.protobuf.FieldDescriptorProto.Label label_override = 31;
// Due to historical reasons, nanopb orders fields in structs by their tag number
// instead of the order in .proto. Set this to false to keep the .proto order.
// The default value will probably change to false in nanopb-0.5.0.
optional bool sort_by_tag = 28 [default = true];
// Set the FT_DEFAULT field conversion strategy.
// A field that can become a static member of a c struct (e.g. int, bool, etc)
// will be a a static field.
// Fields with dynamic length are converted to either a pointer or a callback.
optional FieldType fallback_type = 29 [default = FT_CALLBACK];
// Override initializer used in generated MyMessage_init_zero and MyMessage_init_default macros
// By default decided automatically based on field default value and datatype.
optional string initializer = 30;
// Discard unused types that are automatically generated by protoc if they are not actually
// needed. Currently this applies to map< > types when the field is ignored by options.
optional bool discard_unused_automatic_types = 33 [default = true];
// Discard messages and fields marked with [deprecated = true] in the proto file.
optional bool discard_deprecated = 35 [default = false];
}
// Extensions to protoc 'Descriptor' type in order to define options
// inside a .proto file.
//
// Protocol Buffers extension number registry
// --------------------------------
// Project: Nanopb
// Contact: Petteri Aimonen <jpa@kapsi.fi>
// Web site: http://kapsi.fi/~jpa/nanopb
// Extensions: 1010 (all types)
// --------------------------------
extend google.protobuf.FileOptions {
optional NanoPBOptions nanopb_fileopt = 1010;
}
extend google.protobuf.MessageOptions {
optional NanoPBOptions nanopb_msgopt = 1010;
}
extend google.protobuf.EnumOptions {
optional NanoPBOptions nanopb_enumopt = 1010;
}
extend google.protobuf.FieldOptions {
optional NanoPBOptions nanopb = 1010;
}

7
gen_client.sh Executable file
View File

@ -0,0 +1,7 @@
#!/usr/bin/env bash
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
PROJECT_ROOT=$SCRIPT_DIR
APP_PROTO_DIR=$PROJECT_ROOT/components/app_proto/proto
OUTPUT_DIR=$SCRIPT_DIR/app/proto
uv run python -m grpc_tools.protoc --python_betterproto_out=$OUTPUT_DIR -I $APP_PROTO_DIR $APP_PROTO_DIR/hr_packet.proto

20
pyproject.toml Normal file
View File

@ -0,0 +1,20 @@
[project]
name = "max_visualizer"
version = "0.1.0"
description = "python scripts for testing embedded system"
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
"anyio>=4.9.0",
"awkward>=2.8.3",
"betterproto[compiler]>=2.0.0b7",
"grpcio-tools>=1.71.0",
"loguru>=0.7.3",
"paho-mqtt>=2.1.0",
"plotly>=6.1.2",
"pydantic>=2.11.5",
"streamlit>=1.45.1",
]
[project.scripts]
main = "main:main"

1133
uv.lock generated Normal file

File diff suppressed because it is too large Load Diff