Xeto Introduction
This tutorial demonstrates how to create a Xeto library and use it with Python to validate Haystack instance data. You'll learn to define custom Xeto specs for electric meters and verify data conformance using the phable package.
Prerequisites
- Install the latest haxall build for Java.
- Ensure the Haxall
bindirectory is in thePATHenvironment variable. - Install uv.
Project Setup
In the terminal, navigate to the directory where the new webinar project will be created.
Create Python Project
Create a Python project called webinar using uv, add the phable package dependency and sync dependencies:
uv init webinar && cd webinar
uv add phable
uv sync
Create Xeto Library
Create a Xeto library called webinar:
mkdir src && cd src
mkdir xeto && cd xeto
xeto init -dir . -noconfirm webinar
cd ../../
Open in VS Code
Open VS Code Editor:
code .
If applicable, install and enable the Xeto IDE Extension for VS Code Editor by XetoBase.
Define Xeto Specs
In this example, we'll model a common electrical metering scenario: a site with one main electric meter and two submeters. The site meter measures total electrical demand for the facility, while the submeters track demand for specific areas or systems within the site. By establishing the relationships between these meters and their points, applications can perform useful calculations—such as computing total site demand from submeter readings or identifying discrepancies between site and submeter totals.
Create Electric Meter Specs
Replace the contents of specs.xeto with the below text to define Xeto specs for an electric site meter and submeters:
ElecSiteMeter : ElecMeter {
siteMeter
subMeters: Query<of:ElecSubMeter, inverse:"phable::ElecSubMeter.mySiteMeter">
points: {
ElecAcTotalImportActiveDemandSensor
}
}
ElecSubMeter : ElecMeter {
subMeter
subMeterOf: Ref
mySiteMeter: Query<of:ElecSiteMeter, via:"subMeterOf+">
points: {
ElecAcTotalImportActiveDemandSensor
}
}
Configure Library Metadata
Create the Xeto library configuration by replacing the contents of lib.xeto with the below text. This defines the library metadata (name, description, version) and declares dependencies on other Xeto libraries that our specs will reference:
pragma: Lib <
doc: "Xeto Example for Haystack 5 and Xeto for Python Developers Webinar"
version: "0.0.1"
depends: {
{ lib: "sys" }
{ lib: "ph" }
{ lib: "ph.points" }
{ lib: "ph.equips" }
}
org: {
dis: "Project Haystack"
uri: "https://project-haystack.org/"
}
>
Build the Xeto Library
Verify webinar successfully compiles as a xetolib in the lib directory:
xeto build -allIn .
Note: This command should be run every time changes are made to xeto files.
Create Python Validation Script
Replace the contents of hello.py with the below Python code. This script creates Haystack instance data representing a site, one site meter with its demand point, and two submeters (each with its own demand point). The subMeterOf references establish the meter hierarchy, enabling applications to navigate these relationships. For example, applications can sum submeter demands or compare them against the site meter reading. The script validates this data structure against the Xeto specs we defined:
from phable import Grid, Marker, Ref, XetoCLI
def main():
recs = [
{"id": Ref("site"), "site": Marker(), "spec": Ref("ph::Site")},
{
"id": Ref("site-meter"),
"elec": Marker(),
"meter": Marker(),
"siteMeter": Marker(),
"equip": Marker(),
"spec": Ref("webinar::ElecSiteMeter"),
"siteRef": Ref("site"),
},
{
"id": Ref("site-meter-point"),
"point": Marker(),
"spec": Ref("ph.points::ElecAcTotalImportActiveDemandSensor"),
"kind": "Number",
"unit": "kW",
"elec": Marker(),
"ac": Marker(),
"total": Marker(),
"import": Marker(),
"active": Marker(),
"demand": Marker(),
"sensor": Marker(),
"equipRef": Ref("site-meter"),
},
{
"id": Ref("submeter1"),
"elec": Marker(),
"meter": Marker(),
"subMeter": Marker(),
"subMeterOf": Ref("site-meter"),
"equip": Marker(),
"spec": Ref("webinar::ElecSubMeter"),
"siteRef": Ref("site"),
},
{
"id": Ref("submeter1-point"),
"point": Marker(),
"spec": Ref("ph.points::ElecAcTotalImportActiveDemandSensor"),
"kind": "Number",
"unit": "kW",
"elec": Marker(),
"ac": Marker(),
"total": Marker(),
"import": Marker(),
"active": Marker(),
"demand": Marker(),
"sensor": Marker(),
"equipRef": Ref("submeter1"),
},
{
"id": Ref("submeter2"),
"elec": Marker(),
"meter": Marker(),
"subMeter": Marker(),
"subMeterOf": Ref("site-meter"),
"equip": Marker(),
"spec": Ref("webinar::ElecSubMeter"),
"siteRef": Ref("site"),
},
{
"id": Ref("submeter2-point"),
"point": Marker(),
"spec": Ref("ph.points::ElecAcTotalImportActiveDemandSensor"),
"kind": "Number",
"unit": "kW",
"elec": Marker(),
"ac": Marker(),
"total": Marker(),
"import": Marker(),
"active": Marker(),
"demand": Marker(),
"sensor": Marker(),
"equipRef": Ref("submeter2"),
},
]
xeto_cli = XetoCLI()
recs_fit = xeto_cli.fits_explain(recs)
try:
assert recs_fit == Grid(
{"ver": "3.0"},
[{"name": "id"}, {"name": "msg"}],
[],
)
print("Validation passed!!!")
except Exception:
print(f"Validation failed for the following reasons:\n{recs_fit.rows}")
if __name__ == "__main__":
main()
Test and Verify
Run the Validation Test
Run the Python script and verify "Validation passed!!!" is shown in the terminal:
uv run hello.py
Test Failure Cases
To verify the validation is working correctly, modify the recs to fail validation, run the script again, and verify "Validation failed" is shown in the terminal.