Skip to content

Commit

Permalink
Initial message generation work
Browse files Browse the repository at this point in the history
  • Loading branch information
mcm001 committed Aug 4, 2024
1 parent 37e9d40 commit c07a983
Show file tree
Hide file tree
Showing 12 changed files with 661 additions and 116 deletions.
2 changes: 2 additions & 0 deletions photon-targeting/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ nativeUtils {
}
}

sourceSets.main.java.srcDir "${projectDir}/src/generated/main/java"

model {
components {
"${nativeName}"(NativeLibrarySpec) {
Expand Down
108 changes: 108 additions & 0 deletions photon-targeting/generate_messages.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
#!/usr/bin/env python3

import argparse
import hashlib
import json
import os
import sys
from pathlib import Path
from typing import Any, Dict, List, TypedDict, cast

import yaml
from jinja2 import Environment, FileSystemLoader
from jinja2.environment import Template


class SerdeField(TypedDict):
name: str
type: str

class MessageType(TypedDict):
name: str
fields: List[SerdeField]


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: List[MessageType] = yaml.safe_load(file)

# Print for testing
print(file_dict)

return file_dict


data_types = yaml_to_dict("src/generate/message_data_types.yaml")

def parse_yaml():
config = yaml_to_dict("src/generate/messages.yaml")

# Hash a comments-stripped version for message integrity checking
cleaned_yaml = yaml.dump(config, default_flow_style=False).strip()
message_hash = hashlib.md5(cleaned_yaml.encode("ascii")).digest()
message_hash = list(message_hash)
print(message_hash)

return config, message_hash


def generate_photon_messages(output_root, template_root):
messages, message_hash = parse_yaml()

env = Environment(
loader=FileSystemLoader(str(template_root)),
# autoescape=False,
# keep_trailing_newline=False,
)


# add our custom types
for message in messages:
name = message['name']
data_types[name] = {
'len': -1,
'java_type': name,
'cpp_type': name,
}

root_path = Path(output_root) / "main/java/org/photonvision/struct"
template = env.get_template("Message.java.jinja")

root_path.mkdir(parents=True, exist_ok=True)

for message in messages:
java_name = f"{message['name']}Serde.java"


output_file = root_path / java_name
output_file.write_text(template.render(message, type_map=data_types), encoding="utf-8")



def main(argv):
script_path = Path(__file__).resolve()
dirname = script_path.parent

parser = argparse.ArgumentParser()
parser.add_argument(
"--output_directory",
help="Optional. If set, will output the generated files to this directory, otherwise it will use a path relative to the script",
default=dirname / "src/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 / "src/generate",
type=Path,
)
args = parser.parse_args(argv)

generate_photon_messages(args.output_directory, args.template_root)


if __name__ == "__main__":
main(sys.argv[1:])
64 changes: 64 additions & 0 deletions photon-targeting/src/generate/Message.java.jinja
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* 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-targeting/generate_messages.py. DO NOT MODIFY

package org.photonvision.struct;

import org.photonvision.common.dataflow.structures.Packet;
import org.photonvision.common.dataflow.structures.PacketSerde;

// Assume that the base class lives here and we can import it
import org.photonvision.targeting.*;


/**
* This is a test
*/
public class {{ name }}Serde implements PacketSerde<{{name}}> {


{% for field in fields %}public {{ type_map[field.type].java_type }} {{ field.name }};
{% endfor %}

@Override
public int getMaxByteSize() {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'getMaxByteSize'");
}

@Override
public void pack(Packet packet, {{ name }} value) {
// explicitly cast to avoid accidentally encoding the wrong thing
{% for field in fields -%}
packet.encode(({{type_map[field.type].java_type}}) value.{{field.name}});
{%- if not loop.last %}
{% endif -%}
{% endfor %}
}

@Override
public {{ name }} unpack(Packet packet) {
var ret = new {{ name }}();
{% for field in fields -%}
ret.{{field.name}} = packet.{{type_map[field.type].java_decode_method}}();
{%- if not loop.last %}
{% endif -%}
{% endfor %}
return ret;
}
}
12 changes: 12 additions & 0 deletions photon-targeting/src/generate/message_data_types.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
bool:
# length in bytes
len: 1
java_type: bool
cpp_type: bool
java_decode_method: decodeBoolean
int64:
len: 8
java_type: long
cpp_type: int64_t
java_decode_method: decodeLong
17 changes: 17 additions & 0 deletions photon-targeting/src/generate/messages.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
- name: PhotonPipelineMetadata
fields:
- name: sequenceID
type: int64
- name: captureTimestampMicros
type: int64
- name: publishTimestampMicros
type: int64

- name: PhotonPipelineResult
fields:
- name: metadata
type: PhotonPipelineMetadata
- name: targets
type: int64
list_len: VLA
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* 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-targeting/generate_messages.py. DO NOT MODIFY

package org.photonvision.struct;

import org.photonvision.common.dataflow.structures.Packet;
import org.photonvision.common.dataflow.structures.PacketSerde;

// Assume that the base class lives here and we can import it
import org.photonvision.targeting.*;


/**
* This is a test
*/
public class PhotonPipelineMetadataSerde implements PacketSerde<PhotonPipelineMetadata> {


public long sequenceID;
public long captureTimestampMicros;
public long publishTimestampMicros;


@Override
public int getMaxByteSize() {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'getMaxByteSize'");
}

@Override
public void pack(Packet packet, PhotonPipelineMetadata value) {
// explicitly cast to avoid accidentally encoding the wrong thing
packet.encode((long) value.sequenceID);
packet.encode((long) value.captureTimestampMicros);
packet.encode((long) value.publishTimestampMicros);
}

@Override
public PhotonPipelineMetadata unpack(Packet packet) {
var ret = new PhotonPipelineMetadata();
ret.sequenceID = packet.decodeLong();
ret.captureTimestampMicros = packet.decodeLong();
ret.publishTimestampMicros = packet.decodeLong();
return ret;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* 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-targeting/generate_messages.py. DO NOT MODIFY

package org.photonvision.struct;

import org.photonvision.common.dataflow.structures.Packet;
import org.photonvision.common.dataflow.structures.PacketSerde;

// Assume that the base class lives here and we can import it
import org.photonvision.targeting.*;


/**
* This is a test
*/
public class PhotonPipelineResultSerde implements PacketSerde<PhotonPipelineResult> {


public PhotonPipelineMetadata metadata;
public long targets;


@Override
public int getMaxByteSize() {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'getMaxByteSize'");
}

@Override
public void pack(Packet packet, PhotonPipelineResult value) {
// explicitly cast to avoid accidentally encoding the wrong thing
packet.encode((PhotonPipelineMetadata) value.metadata);
packet.encode((long) value.targets);
}

@Override
public PhotonPipelineResult unpack(Packet packet) {
var ret = new PhotonPipelineResult();
ret.metadata = packet.();
ret.targets = packet.decodeLong();
return ret;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package org.photonvision.targeting;

import org.photonvision.struct.PhotonPacketSerdeStruct;

public class PhotonPipelineMetadata {
// Mirror of the heartbeat entry -- monotonically increasing
public long sequenceID;

// Image capture and NT publish timestamp, in microseconds and in the
// coprocessor timebase. As
// reported by WPIUtilJNI::now.
public long captureTimestampMicros;
public long publishTimestampMicros;

public PhotonPipelineMetadata(
long captureTimestampMicros, long publishTimestampMicros, long sequenceID) {
this.captureTimestampMicros = captureTimestampMicros;
this.publishTimestampMicros = publishTimestampMicros;
this.sequenceID = sequenceID;
}

public PhotonPipelineMetadata() {
this(-1, -1, -1);
}

/** Returns the time between image capture and publish to NT */
public double getLatencyMillis() {
return (publishTimestampMicros - captureTimestampMicros) / 1e3;
}

/** The time that this image was captured, in the coprocessor's time base. */
public long getCaptureTimestampMicros() {
return captureTimestampMicros;
}

/** The time that this result was published to NT, in the coprocessor's time base. */
public long getPublishTimestampMicros() {
return publishTimestampMicros;
}

/**
* The number of non-empty frames processed by this camera since boot. Useful to checking if a
* camera is alive.
*/
public long getSequenceID() {
return sequenceID;
}

PhotonPipelineResultMetadataSerde serde = new PhotonPipelineResultMetadataSerde();
}
Loading

0 comments on commit c07a983

Please sign in to comment.