mirror of
https://github.com/Astatin3/photonvision-2025.0.0-beta-6.git
synced 2026-06-09 00:28:06 -06:00
Initial commit
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
# Photon Serde Autocode
|
||||
|
||||
Like Rosmsg. But worse.
|
||||
|
||||

|
||||
|
||||
## Goals
|
||||
|
||||
- As fast as possible (only slightly slower than packed structs, ideally)
|
||||
- Support for variable length arrays and optional types
|
||||
- Allow deserialization into user-defined, possibly nested, types. See [ResultList](src/targeting/resultlist.h) for an example of this.
|
||||
|
||||
## Design
|
||||
|
||||
The code for a single type is split across 3 files. Let's look at PnpResult:
|
||||
- [The struct definition](src/struct/pnpresult_struct.h): This is the data the object holds. Auto-generated. The data this object holds can be primitives or other, fully-deserialized types (like Vec2)
|
||||
- [The user class](src/targeting/pnpresult_struct.h): This is the fully-deserialized PnpResult type. This contains extra functions users might need to expose like `Amgiguity`, or other computed helper things.
|
||||
- [The serde interface](src/serde/pnpresult_struct.h): This is a template specialization for converting the user class to/from bytes
|
||||
|
||||
## Prior art
|
||||
|
||||
- Protobuf: slow on embedded platforms (at least quickbuf is)
|
||||
- Wpi's struct: no VLAs/optionals
|
||||
- Rosmsg: I'm not using ros, but I'm stealing their message hash idea
|
||||
|
||||
## Deviations from WPI's Struct Schema Typestrings
|
||||
|
||||
- Enum types are disallowed
|
||||
- Bitfields and bit packing are disallowed
|
||||
- Only variable length arrays are supported (no fixed-length arrays)
|
||||
- Arrays must be no more than 127 elements long
|
||||
- Members can be either VLAs or optional, but not both
|
||||
- A top-level NT topic type shall be a single type (eg TargetCorner), and cannot an array of types (eg TargetCorner[] or TargetCorner[?])
|
||||
- `float` and `double` types will be replaced with float32/float64 when generating message schema strings. This means that `float32 x;` and `float x;` will result in the same message hash.
|
||||
|
||||
For example, this is a valid PhotonStruct schema. Note the WPILib `Transform3d`, the Photon-defined `TargetCorner`, optional prefix, and VLA suffix.
|
||||
|
||||
```
|
||||
float64 poseAmbiguity;
|
||||
optional Transform3d altCameraToTarget;
|
||||
TargetCorner:16f6ac0dedc8eaccb951f4895d9e18b6 minAreaRectCorners[?];
|
||||
```
|
||||
|
||||
## Dynamic Decoding
|
||||
|
||||
Dynamic decoding is facilitated by publishing schemas to the `.schema` table in NT, and by encoding the `message_uuid` as a property on a `photonstruct` publisher. Schema names in the .schema table shall be formatted as `photonstruct:{Type Name}:{Message UUID}`. For example, here I've published Photon results to `/photonvision/WPI2024/rawBytes`. This topic has the typestring `photonstruct:PhotonPipelineResult:ed36092eb95e9fc254ebac897e2a74df`, with properties `{message_uuid': 'ed36092eb95e9fc254ebac897e2a74df'}`. It shall be legal to have published multiple versions of the same message, as long as their UUIDs are unique (which they'd better be).
|
||||
|
||||
| Topic Name | Type | Type String |
|
||||
|------|------|-------|
|
||||
| /.schema/photonstruct:PhotonPipelineResult:ed36092eb95e9fc254ebac897e2a74df | kRaw | photonstructschema |
|
||||
| /.schema/photonstruct:PhotonTrackedTarget:4387ab389a8a78b7beb4492f145831b4 | kRaw | photonstructschema |
|
||||
| /.schema/photonstruct:TargetCorner:16f6ac0dedc8eaccb951f4895d9e18b6 | kRaw | photonstructschema |
|
||||
| /.schema/photonstruct:MultiTargetPNPResult:af2056aaab740eeb889a926071cae6ee | kRaw | photonstructschema |
|
||||
| /.schema/photonstruct:PnpResult:ae4d655c0a3104d88df4f5db144c1e86 | kRaw | photonstructschema |
|
||||
| /.schema/photonstruct:PhotonPipelineMetadata:626e70461cbdb274fb43ead09c255f4e | kRaw | photonstructschema |
|
||||
| /.schema/proto:geometry3d.proto | kRaw | proto:FileDescriptorProto |
|
||||
| /.schema/proto:photon.proto | kRaw | proto:FileDescriptorProto |
|
||||
|
||||
The struct definition for PhotonPipelineResult we retrieved from the struct schema database shown above (via the command `python.exe scripts/catnt.py --echo /.schema/photonstruct:PhotonPipelineResult:ed36092eb95e9fc254ebac897e2a74df`) is:
|
||||
|
||||
```
|
||||
PhotonPipelineMetadata:626e70461cbdb274fb43ead09c255f4e metadata;
|
||||
PhotonTrackedTarget:4387ab389a8a78b7beb4492f145831b4[?] targets;
|
||||
MultiTargetPNPResult:af2056aaab740eeb889a926071cae6ee? multitagResult;
|
||||
```
|
||||
|
||||
If we were decoding this, we'd go retrieve the struct definitions for all our nested types. For example, `PhotonTrackedTarget:4387ab389a8a78b7beb4492f145831b4` is defined by it's .schema table entry be the following. This type also demonstrates a mix of WPILib struct types (such as Transform3d), intrinsic types (such as float64), and Photon struct types (such as TargetCorner).
|
||||
|
||||
```
|
||||
float64 yaw;
|
||||
float64 pitch;
|
||||
float64 area;
|
||||
float64 skew;
|
||||
int32 fiducialId;
|
||||
int32 objDetectId;
|
||||
float32 objDetectConf;
|
||||
Transform3d bestCameraToTarget;
|
||||
Transform3d altCameraToTarget;
|
||||
float64 poseAmbiguity;
|
||||
TargetCorner:16f6ac0dedc8eaccb951f4895d9e18b6[?] minAreaRectCorners;
|
||||
TargetCorner:16f6ac0dedc8eaccb951f4895d9e18b6[?] detectedCorners;
|
||||
```
|
||||
@@ -0,0 +1,388 @@
|
||||
#!/usr/bin/env python3
|
||||
###############################################################################
|
||||
## Copyright (C) Photon Vision.
|
||||
###############################################################################
|
||||
## This program is free software: you can redistribute it and/or modify
|
||||
## it under the terms of the GNU General Public License as published by
|
||||
## the Free Software Foundation, either version 3 of the License, or
|
||||
## (at your option) any later version.
|
||||
##
|
||||
## This program is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
##
|
||||
## You should have received a copy of the GNU General Public License
|
||||
## along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
###############################################################################
|
||||
|
||||
import argparse
|
||||
import copy
|
||||
import hashlib
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import List, TypedDict, cast
|
||||
|
||||
import yaml
|
||||
from jinja2 import Environment, FileSystemLoader
|
||||
|
||||
|
||||
class SerdeField(TypedDict):
|
||||
name: str
|
||||
type: str
|
||||
# optional extra args
|
||||
optional: bool
|
||||
vla: bool
|
||||
|
||||
|
||||
class MessageType(TypedDict):
|
||||
name: str
|
||||
fields: List[SerdeField]
|
||||
# will be 'shim' if shimmed, and the shims will be set
|
||||
shimmed: bool
|
||||
java_decode_shim: str
|
||||
java_encode_shim: str
|
||||
# C++ helpers
|
||||
cpp_include: str
|
||||
# python shim types
|
||||
python_encode_shim: str
|
||||
python_decode_shim: str
|
||||
# Java import name
|
||||
java_import: str
|
||||
# Remember our message hash. Recalculated by us. All intrinsic types are unhashed so this is fine to live here
|
||||
message_hash: str
|
||||
schema_str: str
|
||||
|
||||
|
||||
def yaml_to_dict(path: str):
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
yaml_file_path = os.path.join(script_dir, path)
|
||||
|
||||
with open(yaml_file_path, "r") as file:
|
||||
file_dict: dict = yaml.safe_load(file)
|
||||
|
||||
return file_dict
|
||||
|
||||
|
||||
data_types = yaml_to_dict("message_data_types.yaml")
|
||||
|
||||
|
||||
# Helper to check if we need to use our own decoder
|
||||
def is_intrinsic_type(type_str: str):
|
||||
ret = type_str in data_types.keys()
|
||||
return ret
|
||||
|
||||
|
||||
# Deal with shimmed types
|
||||
def get_shimmed_filter(message_db):
|
||||
def is_shimmed(message_name: str):
|
||||
# We don't (yet) support shimming intrinsic types
|
||||
if is_intrinsic_type(message_name):
|
||||
return False
|
||||
|
||||
message = get_message_by_name(message_db, message_name)
|
||||
return "shimmed" in message and message["shimmed"] == True
|
||||
|
||||
return is_shimmed
|
||||
|
||||
|
||||
def get_qualified_cpp_name(
|
||||
message_db: List[MessageType], data_types, field: SerdeField
|
||||
):
|
||||
"""
|
||||
Get the full name of the type encoded. Eg:
|
||||
std::optional<photon::TargetCorner>
|
||||
std::array<frc::Transform3d>
|
||||
"""
|
||||
|
||||
if get_shimmed_filter(message_db)(field["type"]):
|
||||
base_type = get_message_by_name(message_db, field["type"])["cpp_type"]
|
||||
else:
|
||||
base_type = data_types[field["type"]]["cpp_type"]
|
||||
|
||||
if "optional" in field and field["optional"] == True:
|
||||
typestr = f"std::optional<{base_type}>"
|
||||
elif "vla" in field and field["vla"] == True:
|
||||
typestr = f"std::vector<{base_type}>"
|
||||
else:
|
||||
typestr = base_type
|
||||
|
||||
return typestr
|
||||
|
||||
|
||||
def get_message_by_name(message_db: List[MessageType], message_name: str):
|
||||
try:
|
||||
return next(
|
||||
message for message in message_db if message["name"] == message_name
|
||||
)
|
||||
except StopIteration as e:
|
||||
raise Exception("Could not find " + message_name) from e
|
||||
|
||||
|
||||
def get_field_by_name(message: MessageType, field_name: str):
|
||||
return next(f for f in message["fields"] if f["name"] == field_name)
|
||||
|
||||
|
||||
def get_message_hash(message_db: List[MessageType], message: MessageType) -> str:
|
||||
"""
|
||||
Calculate a unique message hash via MD5 sum. This is a very similar approach to rosmsg, documented:
|
||||
http://wiki.ros.org/ROS/Technical%20Overview#Message_serialization_and_msg_MD5_sums
|
||||
|
||||
For non-intrinsic (user-defined) types, replace its type-string with the md5sum of the submessage definition
|
||||
"""
|
||||
|
||||
# replace the non-intrinsic typename with its hash
|
||||
modified_message = copy.deepcopy(message)
|
||||
fields_to_hash = [
|
||||
field
|
||||
for field in modified_message["fields"]
|
||||
if not is_intrinsic_type(field["type"])
|
||||
]
|
||||
|
||||
for field in fields_to_hash:
|
||||
sub_message = get_message_by_name(message_db, field["type"])
|
||||
get_message_hash(message_db, sub_message)
|
||||
|
||||
schema = get_struct_schema_str(message, message_db)
|
||||
message_hash = hashlib.md5(schema.encode("ascii")).hexdigest()
|
||||
|
||||
# and remember the hash
|
||||
message["message_hash"] = message_hash
|
||||
message["schema_str"] = schema
|
||||
|
||||
return message_hash
|
||||
|
||||
|
||||
def get_includes(db, message: MessageType) -> str:
|
||||
includes = []
|
||||
for field in message["fields"]:
|
||||
if not is_intrinsic_type(field["type"]):
|
||||
field_msg = get_message_by_name(db, field["type"])
|
||||
|
||||
if "shimmed" in field_msg and field_msg["shimmed"] == True:
|
||||
includes.append(field_msg["cpp_include"])
|
||||
else:
|
||||
# must be a photon type.
|
||||
includes.append(f"\"photon/targeting/{field_msg['name']}.h\"")
|
||||
|
||||
if "optional" in field and field["optional"] == True:
|
||||
includes.append("<optional>")
|
||||
if "vla" in field and field["vla"] == True:
|
||||
includes.append("<vector>")
|
||||
|
||||
# stdint types
|
||||
includes.append("<stdint.h>")
|
||||
|
||||
return sorted(set(includes))
|
||||
|
||||
|
||||
def parse_yaml() -> List[MessageType]:
|
||||
config = yaml_to_dict("messages.yaml")
|
||||
|
||||
return config
|
||||
|
||||
|
||||
INTRINSIC_TYPE_ALIASES = {
|
||||
"float": "float32",
|
||||
"double": "float64",
|
||||
}
|
||||
|
||||
|
||||
def get_fully_defined_field_name(field: SerdeField, message_db: List[MessageType]):
|
||||
"""
|
||||
Get the fully-defined, globally unique type name for a field. Returns something like
|
||||
Transform3d:b290703ff9e54f9ec2c733b90d7fc30b for user-defined types, or just
|
||||
something like int64 for built-in types. Also normalizes float/double to float32/float64
|
||||
|
||||
Args:
|
||||
field: The field we want the name of
|
||||
message_db: All other loaded messages
|
||||
"""
|
||||
|
||||
typestr = field["type"]
|
||||
if not is_intrinsic_type(field["type"]):
|
||||
msg = get_message_by_name(message_db, field["type"])
|
||||
is_shimmed = get_shimmed_filter(message_db)(field["type"])
|
||||
if not is_shimmed:
|
||||
typestr = field["type"] + ":" + msg["message_hash"]
|
||||
else:
|
||||
# handle replacing float/doubles
|
||||
typestr = field["type"]
|
||||
typestr = INTRINSIC_TYPE_ALIASES.get(typestr, typestr)
|
||||
|
||||
return typestr
|
||||
|
||||
|
||||
def get_struct_schema_str(message: MessageType, message_db: List[MessageType]):
|
||||
ret = ""
|
||||
|
||||
for field in message["fields"]:
|
||||
if (
|
||||
"optional" in field
|
||||
and field["optional"] == True
|
||||
and "vla" in field
|
||||
and field["vla"] == True
|
||||
):
|
||||
raise Exception(f"Field {field} must be optional OR vla!")
|
||||
|
||||
typestr = get_fully_defined_field_name(field, message_db)
|
||||
|
||||
array_modifier = ""
|
||||
|
||||
if "optional" in field and field["optional"] == True:
|
||||
typestr = "optional " + typestr
|
||||
if "vla" in field and field["vla"] == True:
|
||||
array_modifier = "[?]"
|
||||
|
||||
ret += f"{typestr} {field['name']}{array_modifier};"
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def generate_photon_messages(cpp_java_root, py_root, template_root):
|
||||
messages = parse_yaml()
|
||||
|
||||
for message in messages:
|
||||
message["message_hash"] = get_message_hash(messages, message)
|
||||
|
||||
env = Environment(
|
||||
loader=FileSystemLoader(str(template_root)),
|
||||
# autoescape=False,
|
||||
# keep_trailing_newline=False,
|
||||
)
|
||||
|
||||
env.filters["is_intrinsic"] = is_intrinsic_type
|
||||
env.filters["is_shimmed"] = get_shimmed_filter(messages)
|
||||
|
||||
# add our custom types
|
||||
extended_data_types = data_types.copy()
|
||||
for message in messages:
|
||||
name = message["name"]
|
||||
extended_data_types[name] = {
|
||||
"len": -1,
|
||||
"java_type": name,
|
||||
"cpp_type": "photon::" + name,
|
||||
}
|
||||
|
||||
java_output_dir = Path(cpp_java_root) / "main/java/org/photonvision/struct"
|
||||
java_output_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
cpp_serde_header_dir = Path(cpp_java_root) / "main/native/include/photon/serde/"
|
||||
cpp_serde_header_dir.mkdir(parents=True, exist_ok=True)
|
||||
cpp_serde_source_dir = Path(cpp_java_root) / "main/native/cpp/photon/serde/"
|
||||
cpp_serde_source_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
cpp_struct_header_dir = Path(cpp_java_root) / "main/native/include/photon/struct/"
|
||||
cpp_struct_header_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
py_serde_source_dir = Path(py_root)
|
||||
py_serde_source_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
env.filters["get_qualified_name"] = lambda field: get_qualified_cpp_name(
|
||||
messages, extended_data_types, field
|
||||
)
|
||||
|
||||
for message in messages:
|
||||
# don't generate shimmed types
|
||||
if get_shimmed_filter(messages)(message["name"]):
|
||||
continue
|
||||
|
||||
message = cast(MessageType, message)
|
||||
|
||||
java_name = f"{message['name']}Serde.java"
|
||||
cpp_serde_header_name = f"{message['name']}Serde.h"
|
||||
cpp_serde_source_name = f"{message['name']}Serde.cpp"
|
||||
cpp_struct_header_name = f"{message['name']}Struct.h"
|
||||
py_name = f"{message['name']}Serde.py"
|
||||
|
||||
java_template = env.get_template("Message.java.jinja")
|
||||
|
||||
cpp_serde_header_template = env.get_template("ThingSerde.h.jinja")
|
||||
cpp_serde_source_template = env.get_template("ThingSerde.cpp.jinja")
|
||||
cpp_struct_header_template = env.get_template("ThingStruct.h.jinja")
|
||||
|
||||
py_template = env.get_template("ThingSerde.py.jinja")
|
||||
|
||||
message_hash = get_message_hash(messages, message)
|
||||
|
||||
for output_name, template, output_folder in [
|
||||
[java_name, java_template, java_output_dir],
|
||||
[cpp_serde_header_name, cpp_serde_header_template, cpp_serde_header_dir],
|
||||
[cpp_serde_source_name, cpp_serde_source_template, cpp_serde_source_dir],
|
||||
[cpp_struct_header_name, cpp_struct_header_template, cpp_struct_header_dir],
|
||||
[py_name, py_template, py_serde_source_dir],
|
||||
]:
|
||||
# Hack in our message getter
|
||||
template.globals["get_message_by_name"] = lambda name: get_message_by_name(
|
||||
messages, name
|
||||
)
|
||||
|
||||
nested_photon_types = set(
|
||||
[
|
||||
field["type"]
|
||||
for field in message["fields"]
|
||||
if (
|
||||
not is_intrinsic_type(field["type"])
|
||||
and not get_shimmed_filter(messages)(field["type"])
|
||||
)
|
||||
]
|
||||
)
|
||||
nested_wpilib_types = set(
|
||||
[
|
||||
field["type"]
|
||||
for field in message["fields"]
|
||||
if (
|
||||
not is_intrinsic_type(field["type"])
|
||||
and get_shimmed_filter(messages)(field["type"])
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
output_file = output_folder / output_name
|
||||
output_file.write_text(
|
||||
template.render(
|
||||
message,
|
||||
type_map=extended_data_types,
|
||||
message_fmt=get_struct_schema_str(message, messages),
|
||||
message_hash=message_hash,
|
||||
cpp_includes=get_includes(messages, message),
|
||||
nested_photon_types=nested_photon_types,
|
||||
nested_wpilib_types=nested_wpilib_types,
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
|
||||
def main(argv):
|
||||
script_path = Path(__file__).resolve()
|
||||
dirname = script_path.parent
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
"--cpp_java_output_dir",
|
||||
help="Optional. If set, will output the generated files to this directory, otherwise it will use a path relative to the script",
|
||||
default=dirname.parent / "photon-targeting/src/generated",
|
||||
type=Path,
|
||||
)
|
||||
parser.add_argument(
|
||||
"--py_output_dir",
|
||||
help="Optional. If set, will spit Python serde files here",
|
||||
default=dirname.parent / "photon-lib/py/photonlibpy/generated",
|
||||
type=Path,
|
||||
)
|
||||
parser.add_argument(
|
||||
"--template_root",
|
||||
help="Optional. If set, will use this directory as the root for the jinja templates",
|
||||
default=dirname / "templates",
|
||||
type=Path,
|
||||
)
|
||||
args = parser.parse_args(argv)
|
||||
|
||||
generate_photon_messages(
|
||||
args.cpp_java_output_dir, args.py_output_dir, args.template_root
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main(sys.argv[1:])
|
||||
@@ -0,0 +1,51 @@
|
||||
---
|
||||
bool:
|
||||
# length in bytes
|
||||
len: 1
|
||||
java_type: bool
|
||||
cpp_type: bool
|
||||
java_decode_method: decodeBoolean
|
||||
java_encode_shim: encodeBoolean
|
||||
python_decode_shim: decodeBoolean
|
||||
python_encode_shim: encodeBoolean
|
||||
int16:
|
||||
len: 2
|
||||
java_type: short
|
||||
cpp_type: int16_t
|
||||
java_decode_method: decodeShort
|
||||
java_list_decode_method: decodeShortList
|
||||
java_encode_shim: encodeShort
|
||||
python_decode_shim: decodeShort
|
||||
python_encode_shim: encodeShort
|
||||
int32:
|
||||
len: 4
|
||||
java_type: int
|
||||
cpp_type: int32_t
|
||||
java_decode_method: decodeInt
|
||||
java_encode_shim: encodeInt
|
||||
python_decode_shim: decodeInt
|
||||
python_encode_shim: encodeInt
|
||||
int64:
|
||||
len: 8
|
||||
java_type: long
|
||||
cpp_type: int64_t
|
||||
java_decode_method: decodeLong
|
||||
java_encode_shim: encodeLong
|
||||
python_decode_shim: decodeLong
|
||||
python_encode_shim: encodeLong
|
||||
float32:
|
||||
len: 4
|
||||
java_type: float
|
||||
cpp_type: float
|
||||
java_decode_method: decodeFloat
|
||||
java_encode_shim: encodeFloat
|
||||
python_decode_shim: decodeFloat
|
||||
python_encode_shim: encodeFloat
|
||||
float64:
|
||||
len: 8
|
||||
java_type: double
|
||||
cpp_type: double
|
||||
java_decode_method: decodeDouble
|
||||
java_encode_shim: encodeDouble
|
||||
python_decode_shim: decodeDouble
|
||||
python_encode_shim: encodeDouble
|
||||
@@ -0,0 +1,94 @@
|
||||
---
|
||||
- name: PhotonPipelineMetadata
|
||||
fields:
|
||||
- name: sequenceID
|
||||
type: int64
|
||||
- name: captureTimestampMicros
|
||||
type: int64
|
||||
- name: publishTimestampMicros
|
||||
type: int64
|
||||
- name: timeSinceLastPong
|
||||
type: int64
|
||||
|
||||
- name: Transform3d
|
||||
shimmed: True
|
||||
java_decode_shim: PacketUtils.unpackTransform3d
|
||||
java_encode_shim: PacketUtils.packTransform3d
|
||||
cpp_type: frc::Transform3d
|
||||
cpp_include: "<frc/geometry/Transform3d.h>"
|
||||
python_decode_shim: decodeTransform
|
||||
python_encode_shim: encodeTransform
|
||||
java_import: edu.wpi.first.math.geometry.Transform3d
|
||||
# shim since we expect fields to at least exist
|
||||
fields: []
|
||||
|
||||
|
||||
- name: TargetCorner
|
||||
fields:
|
||||
- name: x
|
||||
type: float64
|
||||
- name: y
|
||||
type: float64
|
||||
|
||||
- name: PhotonTrackedTarget
|
||||
fields:
|
||||
- name: yaw
|
||||
type: float64
|
||||
- name: pitch
|
||||
type: float64
|
||||
- name: area
|
||||
type: float64
|
||||
- name: skew
|
||||
type: float64
|
||||
- name: fiducialId
|
||||
type: int32
|
||||
- name: objDetectId
|
||||
type: int32
|
||||
- name: objDetectConf
|
||||
type: float32
|
||||
- name: bestCameraToTarget
|
||||
type: Transform3d
|
||||
- name: altCameraToTarget
|
||||
type: Transform3d
|
||||
- name: poseAmbiguity
|
||||
type: float64
|
||||
- name: minAreaRectCorners
|
||||
type: TargetCorner
|
||||
vla: True
|
||||
- name: detectedCorners
|
||||
type: TargetCorner
|
||||
vla: True
|
||||
|
||||
- name: PnpResult
|
||||
fields:
|
||||
- name: best
|
||||
type: Transform3d
|
||||
comment: "This is a comment"
|
||||
- name: alt
|
||||
type: Transform3d
|
||||
- name: bestReprojErr
|
||||
type: float64
|
||||
- name: altReprojErr
|
||||
type: float64
|
||||
- name: ambiguity
|
||||
type: float64
|
||||
|
||||
- name: MultiTargetPNPResult
|
||||
fields:
|
||||
- name: estimatedPose
|
||||
type: PnpResult
|
||||
- name: fiducialIDsUsed
|
||||
type: int16
|
||||
vla: True
|
||||
|
||||
|
||||
- name: PhotonPipelineResult
|
||||
fields:
|
||||
- name: metadata
|
||||
type: PhotonPipelineMetadata
|
||||
- name: targets
|
||||
type: PhotonTrackedTarget
|
||||
vla: True
|
||||
- name: multitagResult
|
||||
type: MultiTargetPNPResult
|
||||
optional: True
|
||||
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* Copyright (C) Photon Vision.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// THIS FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py. DO NOT MODIFY
|
||||
|
||||
package org.photonvision.struct;
|
||||
|
||||
import org.photonvision.common.dataflow.structures.Packet;
|
||||
import org.photonvision.common.dataflow.structures.PacketSerde;
|
||||
import org.photonvision.utils.PacketUtils;
|
||||
|
||||
// Assume that the base class lives here and we can import it
|
||||
import org.photonvision.targeting.*;
|
||||
|
||||
// WPILib imports (if any)
|
||||
import edu.wpi.first.util.struct.Struct;
|
||||
{% for type in nested_wpilib_types -%}
|
||||
import {{ get_message_by_name(type).java_import }};
|
||||
{%- if not loop.last %},{% endif -%}
|
||||
{%- endfor%}
|
||||
|
||||
/**
|
||||
* Auto-generated serialization/deserialization helper for {{name}}
|
||||
*/
|
||||
public class {{ name }}Serde implements PacketSerde<{{name}}> {
|
||||
|
||||
@Override
|
||||
public final String getInterfaceUUID() { return "{{ message_hash }}"; }
|
||||
@Override
|
||||
public final String getSchema() { return "{{ message_fmt }}"; }
|
||||
@Override
|
||||
public final String getTypeName() { return "{{ name }}"; }
|
||||
|
||||
@Override
|
||||
public int getMaxByteSize() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'getMaxByteSize'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pack(Packet packet, {{ name }} value) {
|
||||
{%- for field in fields -%}
|
||||
{%- if field.type | is_shimmed %}
|
||||
{{ get_message_by_name(field.type).java_encode_shim }}(packet, value.{{ field.name }});
|
||||
{%- elif field.optional == True %}
|
||||
// {{ field.name }} is optional! it better not be a VLA too
|
||||
packet.encodeOptional(value.{{ field.name }});
|
||||
{%- elif field.vla == True and field.type | is_intrinsic %}
|
||||
// {{ field.name }} is a intrinsic VLA!
|
||||
packet.encode(value.{{ field.name }});
|
||||
{%- elif field.vla == True %}
|
||||
// {{ field.name }} is a custom VLA!
|
||||
packet.encodeList(value.{{ field.name }});
|
||||
{%- elif field.type | is_intrinsic %}
|
||||
// field {{ field.name }} is of intrinsic type {{ field.type }}
|
||||
packet.encode(({{ type_map[field.type].java_type }}) value.{{ field.name }});
|
||||
{%- else %}
|
||||
// field {{ field.name }} is of non-intrinsic type {{ field.type }}
|
||||
{{ field.type }}.photonStruct.pack(packet, value.{{ field.name }});
|
||||
{%- endif %}
|
||||
{%- if not loop.last %}
|
||||
{% endif -%}
|
||||
{% endfor%}
|
||||
}
|
||||
|
||||
@Override
|
||||
public {{ name }} unpack(Packet packet) {
|
||||
var ret = new {{ name }}();
|
||||
{% for field in fields -%}
|
||||
{%- if field.type | is_shimmed %}
|
||||
ret.{{ field.name }} = {{ get_message_by_name(field.type).java_decode_shim }}(packet);
|
||||
{%- elif field.optional == True %}
|
||||
// {{ field.name }} is optional! it better not be a VLA too
|
||||
ret.{{ field.name }} = packet.decodeOptional({{ field.type }}.photonStruct);
|
||||
{%- elif field.vla == True and not field.type | is_intrinsic %}
|
||||
// {{ field.name }} is a custom VLA!
|
||||
ret.{{ field.name }} = packet.decodeList({{ field.type }}.photonStruct);
|
||||
{%- elif field.vla == True and field.type | is_intrinsic %}
|
||||
// {{ field.name }} is a custom VLA!
|
||||
ret.{{ field.name }} = packet.decode{{ type_map[field.type].java_type.title() }}List();
|
||||
{%- elif field.type | is_intrinsic %}
|
||||
// {{ field.name }} is of intrinsic type {{ field.type }}
|
||||
ret.{{field.name}} = packet.{{ type_map[field.type].java_decode_method }}();
|
||||
{%- else %}
|
||||
// {{ field.name }} is of non-intrinsic type {{ field.type }}
|
||||
ret.{{field.name}} = {{ field.type }}.photonStruct.unpack(packet);
|
||||
{%- endif %}
|
||||
{%- if not loop.last %}
|
||||
{% endif -%}
|
||||
{% endfor%}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketSerde<?>[] getNestedPhotonMessages() {
|
||||
return new PacketSerde<?>[] {
|
||||
{% for type in nested_photon_types -%}
|
||||
{{ type }}.photonStruct
|
||||
{%- if not loop.last %},{% endif -%}
|
||||
{%- endfor%}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Struct<?>[] getNestedWpilibMessages() {
|
||||
return new Struct<?>[] {
|
||||
{% for type in nested_wpilib_types -%}
|
||||
{{ type }}.struct
|
||||
{%- if not loop.last %},{% endif -%}
|
||||
{%- endfor%}
|
||||
};
|
||||
}
|
||||
}{{'\n'}}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright (C) Photon Vision.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// THIS FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py. DO NOT MODIFY
|
||||
|
||||
#include "photon/serde/{{ name }}Serde.h"
|
||||
|
||||
namespace photon {
|
||||
|
||||
using StructType = SerdeType<{{ name }}>;
|
||||
|
||||
void StructType::Pack(Packet& packet, const {{ name }}& value) {
|
||||
{% for field in fields -%}
|
||||
packet.Pack<{{ field | get_qualified_name }}>(value.{{ field.name }});
|
||||
{%- if not loop.last %}
|
||||
{% endif -%}
|
||||
{% endfor %}
|
||||
}
|
||||
|
||||
{{ name }} StructType::Unpack(Packet& packet) {
|
||||
return {{ name }}{ {{ name }}_PhotonStruct{
|
||||
{% for field in fields -%}
|
||||
.{{ field.name}} = packet.Unpack<{{ field | get_qualified_name }}>(),
|
||||
{%- if not loop.last %}
|
||||
{% endif -%}
|
||||
{% endfor %}
|
||||
}};
|
||||
}
|
||||
|
||||
} // namespace photon{{'\n'}}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright (C) Photon Vision.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
// THIS FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py. DO NOT MODIFY
|
||||
|
||||
#include <wpi/SymbolExports.h>
|
||||
|
||||
// Include myself
|
||||
#include "photon/dataflow/structures/Packet.h"
|
||||
#include "photon/targeting/{{ name }}.h"
|
||||
|
||||
// Includes for dependant types
|
||||
{% for include in cpp_includes -%}
|
||||
#include {{ include }}
|
||||
{% endfor %}
|
||||
|
||||
namespace photon {
|
||||
|
||||
template <>
|
||||
struct WPILIB_DLLEXPORT SerdeType<{{ name }}> {
|
||||
static constexpr std::string_view GetSchemaHash() {
|
||||
return "{{ message_hash }}";
|
||||
}
|
||||
|
||||
static constexpr std::string_view GetSchema() {
|
||||
return "{{ message_fmt }}";
|
||||
}
|
||||
|
||||
static photon::{{ name }} Unpack(photon::Packet& packet);
|
||||
static void Pack(photon::Packet& packet, const photon::{{ name }}& value);
|
||||
};
|
||||
|
||||
static_assert(photon::PhotonStructSerializable<photon::{{ name }}>);
|
||||
|
||||
} // namespace photon{{'\n'}}
|
||||
@@ -0,0 +1,103 @@
|
||||
###############################################################################
|
||||
## Copyright (C) Photon Vision.
|
||||
###############################################################################
|
||||
## This program is free software: you can redistribute it and/or modify
|
||||
## it under the terms of the GNU General Public License as published by
|
||||
## the Free Software Foundation, either version 3 of the License, or
|
||||
## (at your option) any later version.
|
||||
##
|
||||
## This program is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
##
|
||||
## You should have received a copy of the GNU General Public License
|
||||
## along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
###############################################################################
|
||||
|
||||
###############################################################################
|
||||
## THIS FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py.
|
||||
## --> DO NOT MODIFY <--
|
||||
###############################################################################
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from ..packet import Packet
|
||||
from ..targeting import * # noqa
|
||||
|
||||
if TYPE_CHECKING:
|
||||
{%- set ns = namespace(types=[]) -%}
|
||||
{%- for field in fields|unique(attribute="type")-%}
|
||||
{%- set _ = ns.types.append(field.type) -%}
|
||||
{%- endfor -%}
|
||||
{% set _ = ns.types.append(name) -%}
|
||||
{%- for type in ns.types|sort%}
|
||||
{%- if not type | is_shimmed and not type | is_intrinsic %}
|
||||
from ..targeting import {{ type }} # noqa
|
||||
{%- endif %}
|
||||
{%- endfor%}
|
||||
|
||||
|
||||
class {{ name }}Serde:
|
||||
# Message definition md5sum. See photon_packet.adoc for details
|
||||
MESSAGE_VERSION = "{{ message_hash }}"
|
||||
MESSAGE_FORMAT = "{{ message_fmt }}"
|
||||
|
||||
@staticmethod
|
||||
def pack(value: "{{ name }}") -> "Packet":
|
||||
ret = Packet()
|
||||
{% for field in fields -%}
|
||||
{%- if field.type | is_shimmed %}
|
||||
ret.{{ get_message_by_name(field.type).python_encode_shim}}(value.{{ field.name }})
|
||||
{%- elif field.optional == True %}
|
||||
# {{ field.name }} is optional! it better not be a VLA too
|
||||
ret.encodeOptional(value.{{ field.name }}, {{ field.type }}.photonStruct)
|
||||
{%- elif field.vla == True and not field.type | is_intrinsic %}
|
||||
# {{ field.name }} is a custom VLA!
|
||||
ret.encodeList(value.{{ field.name }}, {{ field.type }}.photonStruct)
|
||||
{%- elif field.vla == True and field.type | is_intrinsic %}
|
||||
# {{ field.name }} is a custom VLA!
|
||||
ret.encode{{ type_map[field.type].java_type.title() }}List(value.{{ field.name }})
|
||||
{%- elif field.type | is_intrinsic %}
|
||||
# {{ field.name }} is of intrinsic type {{ field.type }}
|
||||
ret.{{ type_map[field.type].python_encode_shim }}(value.{{field.name}})
|
||||
{%- else %}
|
||||
# {{ field.name }} is of non-intrinsic type {{ field.type }}
|
||||
ret.encodeBytes({{ field.type }}.photonStruct.pack(value.{{field.name}}).getData())
|
||||
{%- endif %}
|
||||
{%- if not loop.last %}
|
||||
{% endif -%}
|
||||
{% endfor%}
|
||||
return ret
|
||||
|
||||
@staticmethod
|
||||
def unpack(packet: "Packet") -> "{{ name }}":
|
||||
ret = {{ name }}()
|
||||
{% for field in fields -%}
|
||||
{%- if field.type | is_shimmed %}
|
||||
ret.{{ field.name }} = packet.{{ get_message_by_name(field.type).python_decode_shim }}()
|
||||
{%- elif field.optional == True %}
|
||||
# {{ field.name }} is optional! it better not be a VLA too
|
||||
ret.{{ field.name }} = packet.decodeOptional({{ field.type }}.photonStruct)
|
||||
{%- elif field.vla == True and not field.type | is_intrinsic %}
|
||||
# {{ field.name }} is a custom VLA!
|
||||
ret.{{ field.name }} = packet.decodeList({{ field.type }}.photonStruct)
|
||||
{%- elif field.vla == True and field.type | is_intrinsic %}
|
||||
# {{ field.name }} is a custom VLA!
|
||||
ret.{{ field.name }} = packet.decode{{ type_map[field.type].java_type.title() }}List()
|
||||
{%- elif field.type | is_intrinsic %}
|
||||
# {{ field.name }} is of intrinsic type {{ field.type }}
|
||||
ret.{{field.name}} = packet.{{ type_map[field.type].python_decode_shim }}()
|
||||
{%- else %}
|
||||
# {{ field.name }} is of non-intrinsic type {{ field.type }}
|
||||
ret.{{field.name}} = {{ field.type }}.photonStruct.unpack(packet)
|
||||
{%- endif %}
|
||||
{%- if not loop.last %}
|
||||
{% endif -%}
|
||||
{% endfor%}
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
# Hack ourselves into the base class
|
||||
{{ name }}.photonStruct = {{ name }}Serde(){{'\n'}}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright (C) Photon Vision.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
// THIS FILE WAS AUTO-GENERATED BY ./photon-serde/generate_messages.py. DO NOT MODIFY
|
||||
|
||||
// Includes for dependant types
|
||||
{% for include in cpp_includes -%}
|
||||
#include {{ include }}
|
||||
{% endfor %}
|
||||
|
||||
namespace photon {
|
||||
|
||||
struct {{ name }}_PhotonStruct {
|
||||
{% for field in fields -%}
|
||||
{{ field | get_qualified_name }} {{ field.name }};
|
||||
{%- if not loop.last %}
|
||||
{% endif -%}
|
||||
{% endfor %}
|
||||
|
||||
friend bool operator==({{ name }}_PhotonStruct const&, {{ name }}_PhotonStruct const&) = default;
|
||||
};
|
||||
|
||||
} // namespace photon{{'\n'}}
|
||||
Reference in New Issue
Block a user