koeng
October 5, 2025, 5:54am
1
We’re been talking about networking PLR in Resource backend (theory-craft) - #14 by rickwierenga and in Networked interface for PLR - #28 by koeng
Here are the requirements
Standard debugging tools (curl, etc)
Primarily unary calls with server streaming back to client
Latency is more acceptable than usual
Primarily function calls
rpc
I’d like to propose using connectrpc (python implementation ). It has the nice generation facilities of protocol buffers but is much simpler - you can read the spec and understand what is going on. Importantly, even though it takes in a gRPC-like file, you can curl against it and get data back (it officially supports a JSON mode).
There is actually a pretty darn large quantity of commands to implement, and a .proto definition just makes this WAY easier to implement, especially with server generation and such. How it would work is we write a .proto file, which basically defines the types that the server / client will agree on. We can actually do that ahead of actually implementing the thing, just so we have agreement on what it should do.
At first, it’ll basically just be generating a server that we pipe commands in and out of. After we get that working, we implement any feature we think is important (serialization, etc). But this would get us started with a defined interface. We’d do basically the same thing for the resources as we would for other backends.
The bad part of connectrpc is that it doesn’t have as many client generators as gRPC. But the protocol is much simpler, so it makes it a lot easier to make a client (throw some JSON into an HTTP1.1 message). And it is much better than just pydantic / OpenAPI in that we can get some level of autogeneration and better type checking.
Serialization
We need some way to ensure that the client and the server don’t desync their states. The idea is this:
Upon connection, you first initialize, which gives you a key. You can use this key to run commands. Any command that takes a while or mutates state will also return a task_id
Those task_id commands will lock the server for a while. The client must wait until the server streams back a new key along with the task_id completion details.
The client may disconnect, and upon initializing again will get a full update and a new key.
This key serializes commands so you never have a desync between server and client. The state will always be up-to-date. I think this is the only thing we really need to change vs typical PLR so that we can get disconnects and such.
We could also implement a blockchain. (/s)
koeng
October 6, 2025, 12:35am
2
Had a .proto generated for the resource API
syntax = "proto3";
package pylabrobot.resources;
import "google/protobuf/struct.proto";
import "google/protobuf/empty.proto";
// ============================================================================
// Basic Types
// ============================================================================
message Coordinate {
double x = 1;
double y = 2;
double z = 3;
}
message Rotation {
double x = 1; // around x-axis, roll (degrees)
double y = 2; // around y-axis, pitch (degrees)
double z = 3; // around z-axis, yaw (degrees)
}
message Barcode {
string data = 1;
string symbology = 2;
BarcodePosition position_on_resource = 3;
}
enum BarcodePosition {
BARCODE_POSITION_UNSPECIFIED = 0;
BARCODE_POSITION_RIGHT = 1;
BARCODE_POSITION_FRONT = 2;
BARCODE_POSITION_LEFT = 3;
BARCODE_POSITION_BACK = 4;
BARCODE_POSITION_BOTTOM = 5;
BARCODE_POSITION_TOP = 6;
}
// ============================================================================
// Liquid and Volume Tracking
// ============================================================================
message Liquid {
string name = 1;
double density = 2; // g/mL
double viscosity = 3; // cP
map<string, string> properties = 4;
}
message LiquidVolume {
Liquid liquid = 1;
double volume = 2; // microliters
}
message VolumeTracker {
string thing = 1;
double max_volume = 2;
repeated LiquidVolume liquids = 3;
repeated LiquidVolume pending_liquids = 4;
repeated string liquid_history = 5;
bool is_disabled = 6;
bool is_cross_contamination_tracking_disabled = 7;
}
message TipTracker {
string thing = 1;
bool has_tip = 2;
bool is_disabled = 3;
}
// ============================================================================
// Resource Base Types
// ============================================================================
message Resource {
string name = 1;
string type = 2; // Class name for polymorphism
double size_x = 3;
double size_y = 4;
double size_z = 5;
Coordinate location = 6;
Rotation rotation = 7;
string category = 8;
string model = 9;
Barcode barcode = 10;
repeated Resource children = 11;
string parent_name = 12;
google.protobuf.Struct metadata = 13; // For extensibility
}
message Container {
Resource resource = 1;
double max_volume = 2;
double material_z_thickness = 3;
VolumeTracker tracker = 4;
// Functions are represented as strings for serialization
string compute_volume_from_height_func = 5;
string compute_height_from_volume_func = 6;
}
// ============================================================================
// ItemizedResource Types
// ============================================================================
message ItemOrdering {
map<string, string> ordering = 1; // identifier -> item name
}
message ItemizedResource {
Resource resource = 1;
ItemOrdering ordering = 2;
}
// ============================================================================
// Well Types
// ============================================================================
enum WellBottomType {
WELL_BOTTOM_TYPE_UNSPECIFIED = 0;
WELL_BOTTOM_TYPE_FLAT = 1;
WELL_BOTTOM_TYPE_U = 2;
WELL_BOTTOM_TYPE_V = 3;
WELL_BOTTOM_TYPE_UNKNOWN = 4;
}
enum CrossSectionType {
CROSS_SECTION_TYPE_UNSPECIFIED = 0;
CROSS_SECTION_TYPE_CIRCLE = 1;
CROSS_SECTION_TYPE_RECTANGLE = 2;
}
message Well {
Container container = 1;
WellBottomType bottom_type = 2;
CrossSectionType cross_section_type = 3;
}
// ============================================================================
// Plate Types
// ============================================================================
enum PlateType {
PLATE_TYPE_UNSPECIFIED = 0;
PLATE_TYPE_SKIRTED = 1;
PLATE_TYPE_SEMI_SKIRTED = 2;
PLATE_TYPE_NON_SKIRTED = 3;
}
message Lid {
Resource resource = 1;
double nesting_z_height = 2;
}
message Plate {
ItemizedResource itemized_resource = 1;
Lid lid = 2;
PlateType plate_type = 3;
}
// ============================================================================
// Tip Types
// ============================================================================
message Tip {
bool has_filter = 1;
double total_tip_length = 2; // mm
double maximal_volume = 3; // microliters
double fitting_depth = 4; // mm
VolumeTracker tracker = 5;
}
message TipSpot {
Resource resource = 1;
TipTracker tracker = 2;
Tip prototype_tip = 3; // Factory function represented as prototype
}
message TipRack {
ItemizedResource itemized_resource = 1;
bool with_tips = 2;
}
message NestedTipRack {
TipRack tip_rack = 1;
double stacking_z_height = 2;
}
// ============================================================================
// Carrier Types
// ============================================================================
message ResourceHolder {
Resource resource = 1;
Coordinate child_location = 2;
Resource held_resource = 3; // The resource being held
}
message PlateHolder {
ResourceHolder resource_holder = 1;
double pedestal_size_z = 2;
}
message Carrier {
Resource resource = 1;
map<int32, ResourceHolder> sites = 2;
}
message TipCarrier {
Carrier carrier = 1;
}
message PlateCarrier {
Carrier carrier = 1;
}
message MFXCarrier {
Carrier carrier = 1;
}
message TubeCarrier {
Carrier carrier = 1;
}
message TroughCarrier {
Carrier carrier = 1;
}
// ============================================================================
// Deck Types
// ============================================================================
message Deck {
Resource resource = 1;
Coordinate origin = 2;
map<string, Resource> resources_index = 3; // For O(1) lookup
}
// ============================================================================
// Other Resource Types
// ============================================================================
message Trough {
Container container = 1;
}
message Trash {
Resource resource = 1;
}
message PetriDish {
Container container = 1;
}
message ResourceStack {
Resource resource = 1;
string direction = 2; // "x", "y", or "z"
}
message PlateAdapter {
Resource resource = 1;
string adapter_type = 2;
}
// ============================================================================
// Request/Response Messages for Operations
// ============================================================================
// Resource operations
message GetResourceRequest {
string resource_name = 1;
string parent_name = 2; // Search within this parent
}
message GetResourceResponse {
Resource resource = 1;
}
message AssignChildResourceRequest {
string parent_name = 1;
Resource child = 2;
Coordinate location = 3;
bool reassign = 4;
}
message UnassignChildResourceRequest {
string parent_name = 1;
string child_name = 2;
}
message GetAbsoluteLocationRequest {
string resource_name = 1;
string x_anchor = 2; // "l", "c", "r"
string y_anchor = 3; // "f", "c", "b"
string z_anchor = 4; // "b", "c", "t"
}
message GetAbsoluteLocationResponse {
Coordinate location = 1;
}
message GetAnchorRequest {
string resource_name = 1;
string x_anchor = 2;
string y_anchor = 3;
string z_anchor = 4;
}
message GetAnchorResponse {
Coordinate anchor = 1;
}
message RotateRequest {
string resource_name = 1;
double x = 2;
double y = 3;
double z = 4;
}
message CenterRequest {
string resource_name = 1;
bool x = 2;
bool y = 3;
bool z = 4;
}
message CenterResponse {
Coordinate center = 1;
}
message CentersRequest {
string resource_name = 1;
int32 xn = 2;
int32 yn = 3;
int32 zn = 4;
}
message CentersResponse {
repeated Coordinate centers = 1;
}
// Serialization operations
message SerializeRequest {
string resource_name = 1;
}
message SerializeResponse {
google.protobuf.Struct serialized = 1;
}
message DeserializeRequest {
google.protobuf.Struct data = 1;
bool allow_marshal = 2;
}
message DeserializeResponse {
Resource resource = 1;
}
message SaveRequest {
string resource_name = 1;
string filename = 2;
int32 indent = 3;
}
message LoadRequest {
string filename = 1;
}
message LoadResponse {
Resource resource = 1;
}
// State operations
message SerializeStateRequest {
string resource_name = 1;
}
message SerializeStateResponse {
google.protobuf.Struct state = 1;
}
message SerializeAllStateRequest {
string resource_name = 1;
}
message SerializeAllStateResponse {
map<string, google.protobuf.Struct> states = 1;
}
message LoadStateRequest {
string resource_name = 1;
google.protobuf.Struct state = 2;
}
message LoadAllStateRequest {
string resource_name = 1;
map<string, google.protobuf.Struct> states = 2;
}
message SaveStateToFileRequest {
string resource_name = 1;
string filename = 2;
int32 indent = 3;
}
message LoadStateFromFileRequest {
string resource_name = 1;
string filename = 2;
}
// ItemizedResource operations
message GetItemRequest {
string resource_name = 1;
oneof identifier {
string string_id = 2;
int32 int_id = 3;
TupleIdentifier tuple_id = 4;
}
}
message TupleIdentifier {
int32 row = 1;
int32 column = 2;
}
message GetItemResponse {
Resource item = 1;
}
message GetItemsRequest {
string resource_name = 1;
oneof identifiers {
string string_range = 2; // e.g., "A1:H1"
IntList int_list = 3;
StringList string_list = 4;
}
}
message IntList {
repeated int32 values = 1;
}
message StringList {
repeated string values = 1;
}
message GetItemsResponse {
repeated Resource items = 1;
}
message TraverseRequest {
string resource_name = 1;
int32 batch_size = 2;
string start = 3; // "top_left", "bottom_left", etc.
string direction = 4; // "up", "down", "right", "left", "snake_up", etc.
bool repeat = 5;
}
message TraverseResponse {
repeated Resource batch = 1;
bool has_more = 2;
}
message GetColumnRequest {
string resource_name = 1;
int32 column = 2;
}
message GetColumnResponse {
repeated Resource items = 1;
}
message GetRowRequest {
string resource_name = 1;
oneof row_identifier {
int32 row_int = 2;
string row_letter = 3;
}
}
message GetRowResponse {
repeated Resource items = 1;
}
// Plate operations
message GetWellRequest {
string plate_name = 1;
oneof identifier {
string string_id = 2;
int32 int_id = 3;
TupleIdentifier tuple_id = 4;
}
}
message GetWellResponse {
Well well = 1;
}
message GetWellsRequest {
string plate_name = 1;
oneof identifiers {
string string_range = 2;
IntList int_list = 3;
}
}
message GetWellsResponse {
repeated Well wells = 1;
}
message SetWellLiquidsRequest {
string plate_name = 1;
oneof liquids_spec {
LiquidVolume single_liquid = 2; // Apply to all wells
LiquidVolumeList liquid_list = 3; // One per well
LiquidVolumeGrid liquid_grid = 4; // 2D grid
}
}
message LiquidVolumeList {
repeated LiquidVolume liquids = 1;
}
message LiquidVolumeGrid {
repeated LiquidVolumeList rows = 1;
}
message DisableVolumeTrackersRequest {
string plate_name = 1;
}
message EnableVolumeTrackersRequest {
string plate_name = 1;
}
message GetQuadrantRequest {
string plate_name = 1;
string quadrant = 2; // "tl", "tr", "bl", "br", etc.
string quadrant_type = 3; // "block" or "checkerboard"
string internal_fill_order = 4; // "column-major" or "row-major"
}
message GetQuadrantResponse {
repeated Well wells = 1;
}
// TipRack operations
message GetTipRequest {
string tip_rack_name = 1;
oneof identifier {
string string_id = 2;
int32 int_id = 3;
}
}
message GetTipResponse {
Tip tip = 1;
}
message GetTipsRequest {
string tip_rack_name = 1;
oneof identifiers {
string string_range = 2;
IntList int_list = 3;
}
}
message GetTipsResponse {
repeated Tip tips = 1;
}
message SetTipStateRequest {
string tip_rack_name = 1;
oneof state {
BoolList bool_list = 2;
BoolMap bool_map = 3;
}
}
message BoolList {
repeated bool values = 1;
}
message BoolMap {
map<string, bool> values = 1;
}
message DisableTipTrackersRequest {
string tip_rack_name = 1;
}
message EnableTipTrackersRequest {
string tip_rack_name = 1;
}
message EmptyTipRackRequest {
string tip_rack_name = 1;
}
message FillTipRackRequest {
string tip_rack_name = 1;
}
message GetAllTipsRequest {
string tip_rack_name = 1;
}
message GetAllTipsResponse {
repeated Tip tips = 1;
}
// TipSpot operations
message HasTipRequest {
string tip_spot_name = 1;
}
message HasTipResponse {
bool has_tip = 1;
}
message EmptyTipSpotRequest {
string tip_spot_name = 1;
}
message GetTipSpotIdentifierRequest {
string tip_spot_name = 1;
}
message GetTipSpotIdentifierResponse {
string identifier = 1;
}
// Container operations
message ComputeVolumeFromHeightRequest {
string container_name = 1;
double height = 2;
}
message ComputeVolumeFromHeightResponse {
double volume = 1;
}
message ComputeHeightFromVolumeRequest {
string container_name = 1;
double volume = 2;
}
message ComputeHeightFromVolumeResponse {
double height = 1;
}
// Carrier operations
message AssignResourceToSiteRequest {
string carrier_name = 1;
int32 site = 2;
Resource resource = 3;
}
message GetCarrierResourcesRequest {
string carrier_name = 1;
}
message GetCarrierResourcesResponse {
repeated Resource resources = 1;
}
message GetFreeSitesRequest {
string carrier_name = 1;
}
message GetFreeSitesResponse {
repeated int32 sites = 1;
}
// Deck operations
message DeckGetResourceRequest {
string deck_name = 1;
string resource_name = 2;
}
message DeckGetResourceResponse {
Resource resource = 1;
}
message DeckHasResourceRequest {
string deck_name = 1;
string resource_name = 2;
}
message DeckHasResourceResponse {
bool has_resource = 1;
}
message GetAllResourcesRequest {
string deck_name = 1;
}
message GetAllResourcesResponse {
repeated Resource resources = 1;
}
message ClearDeckRequest {
string deck_name = 1;
bool include_trash = 2;
}
message GetTrashAreaRequest {
string deck_name = 1;
}
message GetTrashAreaResponse {
Trash trash = 1;
}
message DeckSummaryRequest {
string deck_name = 1;
}
message DeckSummaryResponse {
string summary = 1;
}
// Resource tree operations
message GetAllChildrenRequest {
string resource_name = 1;
}
message GetAllChildrenResponse {
repeated Resource children = 1;
}
message GetRootRequest {
string resource_name = 1;
}
message GetRootResponse {
Resource root = 1;
}
message IsInSubtreeOfRequest {
string resource_name = 1;
string other_resource_name = 2;
}
message IsInSubtreeOfResponse {
bool is_in_subtree = 1;
}
message GetHighestKnownPointRequest {
string resource_name = 1;
}
message GetHighestKnownPointResponse {
double highest_point = 1;
}
// Utility operations
message CreateOrderedItems2DRequest {
string class_name = 1;
int32 num_items_x = 2;
int32 num_items_y = 3;
double dx = 4;
double dy = 5;
double dz = 6;
double item_dx = 7;
double item_dy = 8;
google.protobuf.Struct kwargs = 9;
}
message CreateOrderedItems2DResponse {
map<string, Resource> items = 1;
}
message QueryResourcesRequest {
string root_name = 1;
string type = 2;
string name_pattern = 3;
Coordinate location = 4;
}
message QueryResourcesResponse {
repeated Resource resources = 1;
}
// ============================================================================
// Service Definition
// ============================================================================
service ResourceService {
// ========== Resource Base Operations ==========
rpc GetResource(GetResourceRequest) returns (GetResourceResponse);
rpc AssignChildResource(AssignChildResourceRequest) returns (google.protobuf.Empty);
rpc UnassignChildResource(UnassignChildResourceRequest) returns (google.protobuf.Empty);
rpc GetAbsoluteLocation(GetAbsoluteLocationRequest) returns (GetAbsoluteLocationResponse);
rpc GetAnchor(GetAnchorRequest) returns (GetAnchorResponse);
rpc Rotate(RotateRequest) returns (google.protobuf.Empty);
rpc Center(CenterRequest) returns (CenterResponse);
rpc Centers(CentersRequest) returns (CentersResponse);
rpc GetAllChildren(GetAllChildrenRequest) returns (GetAllChildrenResponse);
rpc GetRoot(GetRootRequest) returns (GetRootResponse);
rpc IsInSubtreeOf(IsInSubtreeOfRequest) returns (IsInSubtreeOfResponse);
rpc GetHighestKnownPoint(GetHighestKnownPointRequest) returns (GetHighestKnownPointResponse);
// ========== Serialization Operations ==========
rpc Serialize(SerializeRequest) returns (SerializeResponse);
rpc Deserialize(DeserializeRequest) returns (DeserializeResponse);
rpc Save(SaveRequest) returns (google.protobuf.Empty);
rpc Load(LoadRequest) returns (LoadResponse);
// ========== State Management Operations ==========
rpc SerializeState(SerializeStateRequest) returns (SerializeStateResponse);
rpc SerializeAllState(SerializeAllStateRequest) returns (SerializeAllStateResponse);
rpc LoadState(LoadStateRequest) returns (google.protobuf.Empty);
rpc LoadAllState(LoadAllStateRequest) returns (google.protobuf.Empty);
rpc SaveStateToFile(SaveStateToFileRequest) returns (google.protobuf.Empty);
rpc LoadStateFromFile(LoadStateFromFileRequest) returns (google.protobuf.Empty);
// ========== ItemizedResource Operations ==========
rpc GetItem(GetItemRequest) returns (GetItemResponse);
rpc GetItems(GetItemsRequest) returns (GetItemsResponse);
rpc Traverse(TraverseRequest) returns (stream TraverseResponse);
rpc GetColumn(GetColumnRequest) returns (GetColumnResponse);
rpc GetRow(GetRowRequest) returns (GetRowResponse);
// ========== Plate Operations ==========
rpc GetWell(GetWellRequest) returns (GetWellResponse);
rpc GetWells(GetWellsRequest) returns (GetWellsResponse);
rpc SetWellLiquids(SetWellLiquidsRequest) returns (google.protobuf.Empty);
rpc DisableVolumeTrackers(DisableVolumeTrackersRequest) returns (google.protobuf.Empty);
rpc EnableVolumeTrackers(EnableVolumeTrackersRequest) returns (google.protobuf.Empty);
rpc GetQuadrant(GetQuadrantRequest) returns (GetQuadrantResponse);
// ========== TipRack Operations ==========
rpc GetTip(GetTipRequest) returns (GetTipResponse);
rpc GetTips(GetTipsRequest) returns (GetTipsResponse);
rpc SetTipState(SetTipStateRequest) returns (google.protobuf.Empty);
rpc DisableTipTrackers(DisableTipTrackersRequest) returns (google.protobuf.Empty);
rpc EnableTipTrackers(EnableTipTrackersRequest) returns (google.protobuf.Empty);
rpc EmptyTipRack(EmptyTipRackRequest) returns (google.protobuf.Empty);
rpc FillTipRack(FillTipRackRequest) returns (google.protobuf.Empty);
rpc GetAllTips(GetAllTipsRequest) returns (GetAllTipsResponse);
// ========== TipSpot Operations ==========
rpc HasTip(HasTipRequest) returns (HasTipResponse);
rpc EmptyTipSpot(EmptyTipSpotRequest) returns (google.protobuf.Empty);
rpc GetTipSpotIdentifier(GetTipSpotIdentifierRequest) returns (GetTipSpotIdentifierResponse);
// ========== Container Operations ==========
rpc ComputeVolumeFromHeight(ComputeVolumeFromHeightRequest) returns (ComputeVolumeFromHeightResponse);
rpc ComputeHeightFromVolume(ComputeHeightFromVolumeRequest) returns (ComputeHeightFromVolumeResponse);
// ========== Carrier Operations ==========
rpc AssignResourceToSite(AssignResourceToSiteRequest) returns (google.protobuf.Empty);
rpc GetCarrierResources(GetCarrierResourcesRequest) returns (GetCarrierResourcesResponse);
rpc GetFreeSites(GetFreeSitesRequest) returns (GetFreeSitesResponse);
// ========== Deck Operations ==========
rpc DeckGetResource(DeckGetResourceRequest) returns (DeckGetResourceResponse);
rpc DeckHasResource(DeckHasResourceRequest) returns (DeckHasResourceResponse);
rpc GetAllResources(GetAllResourcesRequest) returns (GetAllResourcesResponse);
rpc ClearDeck(ClearDeckRequest) returns (google.protobuf.Empty);
rpc GetTrashArea(GetTrashAreaRequest) returns (GetTrashAreaResponse);
rpc DeckSummary(DeckSummaryRequest) returns (DeckSummaryResponse);
// ========== Utility Operations ==========
rpc CreateOrderedItems2D(CreateOrderedItems2DRequest) returns (CreateOrderedItems2DResponse);
rpc QueryResources(QueryResourcesRequest) returns (QueryResourcesResponse);
}
Kinda big, but would absolutely work.
koeng
October 6, 2025, 1:06am
3
And for the STAR:
service STARBackendService {
// ========== Setup & Configuration ==========
rpc Setup(SetupRequest) returns (SetupResponse);
rpc SetTraversalHeight(SetTraversalHeightRequest) returns (google.protobuf.Empty);
// ========== Single Channel Operations ==========
rpc PickupTips(PickupTipsRequest) returns (PickupTipsResponse);
rpc DropTips(DropTipsRequest) returns (DropTipsResponse);
rpc Aspirate(AspirateRequest) returns (AspirateResponse);
rpc Dispense(DispenseRequest) returns (DispenseResponse);
// ========== 96-Head Operations ==========
rpc PickupTips96(PickupTips96Request) returns (PickupTips96Response);
rpc DropTips96(DropTips96Request) returns (DropTips96Response);
rpc Aspirate96(Aspirate96Request) returns (Aspirate96Response);
rpc Dispense96(Dispense96Request) returns (Dispense96Response);
// ========== Gripper Operations ==========
rpc PickupResource(PickupResourceRequest) returns (PickupResourceResponse);
rpc MovePickedUpResource(MovePickedUpResourceRequest) returns (MovePickedUpResourceResponse);
rpc DropResource(DropResourceRequest) returns (DropResourceResponse);
// ========== Status & Query Operations ==========
rpc RequestFirmwareVersion(RequestFirmwareVersionRequest) returns (RequestFirmwareVersionResponse);
rpc RequestErrorCode(RequestErrorCodeRequest) returns (RequestErrorCodeResponse);
rpc RequestMasterStatus(RequestMasterStatusRequest) returns (RequestMasterStatusResponse);
rpc RequestExtendedConfiguration(RequestExtendedConfigurationRequest) returns (RequestExtendedConfigurationResponse);
rpc RequestInstrumentInitializationStatus(RequestInstrumentInitializationStatusRequest) returns (RequestInstrumentInitializationStatusResponse);
rpc RequestAutoloadInitializationStatus(RequestAutoloadInitializationStatusRequest) returns (RequestAutoloadInitializationStatusResponse);
rpc RequestElectronicBoardType(RequestElectronicBoardTypeRequest) returns (RequestElectronicBoardTypeResponse);
rpc RequestSupplyVoltage(RequestSupplyVoltageRequest) returns (RequestSupplyVoltageResponse);
rpc RequestPipChannelVersion(RequestPipChannelVersionRequest) returns (RequestPipChannelVersionResponse);
rpc RequestIswapVersion(RequestIswapVersionRequest) returns (RequestIswapVersionResponse);
// ========== Low-Level Firmware Commands ==========
rpc DispensePip(DispensePipRequest) returns (DispensePipResponse);
rpc AspiratePip(AspiratePipRequest) returns (AspiratePipResponse);
}
Interesting, I guess I didn’t realize that, for the most part, it is just 7 commands that are REALLY needed to be exposed over wire (PickupTips, DropTips, Aspirate, Dispense, PickupResource, MovePickedUpResource, and DropResource). The rest are for setup, which you kinda have to do when you are setting up the server.
this sounds a lot more complex than just websockets. is “connectrpc” hipster? can it be trusted? the python client seems pretty new, especially compared to grpc. maybe it’s an early project that’s much better than incumbent alternatives (like PLR), but I’d have to learn a bit more about their project to determine this.
would the same server expose multiple machines? if so, we need to be able to run commands on those machines concurrently. so one key per machine.
koeng:
blockchain
yes
how did you generate this? what if we update the resource model?
koeng:
Interesting, I guess I didn’t realize that, for the most part, it is just 7 commands that are REALLY needed to be exposed over wire (PickupTips, DropTips, Aspirate, Dispense, PickupResource, MovePickedUpResource, and DropResource). The rest are for setup, which you kinda have to do when you are setting up the server.
the setup command is for setup
most backend commands are for machine-specific functionality. there are couple that are frequently used during development. but this is a choice about what level of control over a machine you want to expose.
also, you might already know, many commands are still missing from service STARBackendService
koeng
October 6, 2025, 3:08am
5
Maybe? I used it a couple years ago when I was developing interconnected systems for Trilobio. Their Go version is extremely high quality and implements gRPC better than Google’s package (plus their connect protocol). I’m assuming they just started the python port.
Depends if you just want data or functions. If you add in a non-trivial amount of functions, as this would, websockets absolutely become more complex because there isn’t a pydantic equivalent for functions - that’s why RPC is a thing, and RPC generally has coordinated around a few solutions. If you’re mostly just passing data, it might be fine, but again, there is no standard for communications there. We have no way of building a websocket schema that can be type-checked.
I’d like to make it do that
Just had an AI take a look at the files and write it. They’re pretty good at that but I wouldn’t trust it with actually going through with implementation (it’s basically just converting types and pre-defined functions, something it is good at). It also commonly misses things, so it’s just a scratchpad.
Hmmm true. Wondering how much control should be exposed.
good to know
from my perspective: all
I am guessing for you: a config file where you can turn things on and off
koeng
October 6, 2025, 3:19am
7
well, unlike the resource model the star backend should be pretty easy to implement. I might just go for that when I get a little bit of time.