Devices
Any electrical item, active or passive, that is not a harness.
A device may have terminating channel endpoints (one connector per channel) and passthroughs (the same channel_id on exactly two connectors, each with a repeat_channel_id). Both are devices; place them with Place device.
How device data is stored
The primary data structure of a device is a TSV called a “signals_list”. Signals lists can be written manually or generated from a python script that can help automate the generation of lists for complicated devices.
The definition of a device lives in a TSV file called a "Signals List".
Signals List
Interacting with Signals Lists
Signals Lists define every signal going into or out of a device or disconnect. They are the primary way Harnice stores information about devices.

Signals lists are usually repetative, long, and deterministic. Harnice helps you build signals lists efficiently using build instructuons, which are python rules that build the lists for you.

Columns
Columns are automatically generated when signals_list.new() is called. Additional columns are ok but discouraged due to consistency. If you need more columns, use data structures in build instructions.
| Column | Description |
|---|---|
channel_id |
the channel this row belongs to; the same id on every signal of that channel |
repeat_channel_id |
which endpoint, when the channel is a passthrough with two ends; the two repeats must differ; blank when the channel has one endpoint |
channel_type |
channel type string; every signal name it declares must be appended for this endpoint, and the string must parse as a channel type |
signal |
signal name; one of the names declared by channel_type |
cavity |
pin, socket, or stud this signal uses on its connector; required, and unique on that connector |
connector_id |
the connector that holds this signal; every signal of one endpoint uses the same connector |
device_connector_mpn |
manufacturer part number of the device-side connector; set this or harness_connector_mpn, not both, and use the same choice for every row of that connector |
harness_connector_mpn |
manufacturer part number of the harness-side connector; set this or device_connector_mpn, not both |
Signals List Validation Checks:
(These are automatically validated when you build the device that owns the list.)
General Signals List Rules
-
Every signal in the Signals List must be contained by a pre-defined channel type
Channel Types
Channel Types
How are channels mapped?
How to define a new channel type
- In a repository of your choice (or start with harnice-general-library on your own branch), navigate to
channel_types.tsvat the library root - If you want channel definitions to be private and are therefore working in a private repository, ensure the repo's path is listed in file
repository_locations.csv(located at root of your harnice source code repo). The first column is the URL or traceable path, and the second column is your local path. - If you find the channel_type you're looking for, temporarily note it as a touple in a notepad somewhere with format
(ch_type_id, universal_library_repository). - If you don't find it, make a new one. It's important to try and reduce the number of channel_types in here to reduce complexity, but it's also important that you adhere to strict and true rules about what is allowed to be mapped to what. Modifications and additions to this document should be taken and reviewed very seriously.
Two optional columns set the default appearance. Both cells are Python dict literals, parsed with
ast.literal_eval. Leave a cell blank to leave that default unset. Harnice reads thechannel_types.tsvat the root of the library named by the channel type'slib_repo.channel_default_appearance— one engineering appearance dict. A channel trace (channelorharness-channel) and the channel card in the connectors view use it. Example:{'base_color': '#D59A10'}.signal_default_appearances— signal name to appearance dict. A circuit trace uses the entry for itssignal_of_channel_type, and the connectors view uses that entry for the signal bar. Names match thesignalscolumn. Example:{'pos': {'base_color': '#E24B4A'}, 'neg': {'base_color': '#2E6B9F'}}.
An instance
appearancethat already includesbase_coloris the appearance drawn for that trace. Traces are drawn when the project is built, so a change to these columns shows up on overlays after the next build. The connectors view reads the columns when the page opens. A trace whose resolved appearance has nobase_coloris the rainbow placeholder. A card or bar with nobase_colorkeeps the console palette.The dict is the engineering appearance from the Appearance guide:
base_color, and optionallyoutline_color,parallelstripe,perpstripe, andtwisted. Single quotes match the rows already in the file. The separator is a tab, so a comma inside the literal stays in the cell.chtype.parse(val)Use this when a build has a channel type as stored text and needs the (channel_type_id, lib_repo) pair.
Convert stored string into a tuple (chid:int, lib_repo:str). Handles both single tuples and extracts first tuple from lists.
chtype.signals(channel_type)Use this when a system or device build needs the signal names declared for one channel type.
Return the list of signal names associated with a specific channel type.
Args
channel_type: Channel type identifier in standard tuple format(channel_type_id, lib_repo)or any string representation thatparsecan understand.
Returns
list[str]: List of signal names from thesignalscolumn ofchannel_types.tsvfor the matchingchannel_type_id. If the column is blank or the channel type cannot be found, returns an empty list.
Data format
- The
signalscolumn is expected to be a comma-separated string, for example:"CAN_H, CAN_L, SHIELD".
chtype.channel_default_appearance(channel_type)Use this when a drawing needs the default appearance of one channel type's trace and card.
Optional default appearance for this channel type's channel trace and card.
Reads
channel_default_appearancefrom thechannel_types.tsvat the root of the library named by this channel type'slib_repo. The cell is a Python dict literal, parsed withast.literal_eval(seeappearance.parse). Example:{'base_color': '#D59A10'}. A blank cell returnsNone.A channel trace (
item_typechannelorharness-channel) and the channel card in the connectors view use this appearance. A circuit trace usessignal_default_appearancesfor itssignal_of_channel_typeinstead.An instance
appearancethat already includesbase_coloris the appearance drawn for that trace. Traces are drawn when the project is built. The connectors view reads this column when the page opens. A trace whose resolved appearance has nobase_coloris the rainbow placeholder. A channel card with nobase_colorkeeps the console palette.The dict is an engineering appearance:
base_colorplus any ofoutline_color,parallelstripe,perpstripe, andtwisted.Args
channel_type: Channel type identifier in standard tuple format(channel_type_id, lib_repo)or any string representation thatparsecan understand.
Returns
dictorNone: Parsed appearance, orNonewhen the cell is empty or the channel type cannot be found.
chtype.signal_default_appearances(channel_type)Use this when a drawing needs the default appearance of each signal on one channel type.
Optional default appearance for each signal of this channel type.
Reads
signal_default_appearancesfrom thechannel_types.tsvat the root of the library named by this channel type'slib_repo. The cell is a Python dict literal, parsed withast.literal_eval: signal name to appearance dict. Example:{'pos': {'base_color': '#E24B4A'}, 'neg': {'base_color': '#2E6B9F'}}. A blank cell returns{}. Names that are not themselves dicts are dropped. Signal names match thesignalscolumn.A circuit trace uses the entry for its
signal_of_channel_type. The connectors view uses the same entry for that signal's bar. A channel trace useschannel_default_appearance.An instance
appearancethat already includesbase_coloris the appearance drawn for that trace. Traces are drawn when the project is built. The connectors view reads this column when the page opens. A trace whose resolved appearance has nobase_coloris the rainbow placeholder. A signal bar with nobase_colorkeeps the console palette.Each value is an engineering appearance:
base_colorplus any ofoutline_color,parallelstripe,perpstripe, andtwisted.Args
channel_type: Channel type identifier in standard tuple format(channel_type_id, lib_repo)or any string representation thatparsecan understand.
Returns
dict: Signal name to appearance dict. Empty when unset or when the channel type cannot be found.
chtype.compatibles(channel_type)Use this when a system build needs the channel types declared compatible with one channel type, not including that type itself.
Look up other channel types that are declared as compatible with the given channel type.
Args
channel_type: Channel type identifier in standard tuple format(channel_type_id, lib_repo)or any string representation thatparsecan understand.
Returns
list[tuple[int, str]]: List of(channel_type_id, lib_repo)tuples taken directly from thecompatible_channel_typescolumn ofchannel_types.tsv. Returns an empty list if no compatibles are defined or if the channel type cannot be found.
Data format
- The
compatible_channel_typescolumn must be an AST-parseable Python value:- Single tuple:
(1, "library_repo") - List of tuples:
[(1, "library_repo"), (2, "library_repo")]
- Single tuple:
chtype.is_or_is_compatible_with(channel_type)Use this when a system build is validating or mapping channels and this channel type should count if it is the requested type or listed as compatible with it.
Return the given channel type plus all channel types declared as compatible with it.
Args
channel_type: Channel type identifier in standard tuple format(channel_type_id, lib_repo)or any string representation thatparsecan understand.
Returns
list[tuple[int, str]]: List of(channel_type_id, lib_repo)tuples where the first entry is the parsedchannel_typeitself and the remaining entries are the compatibles returned bycompatibles(channel_type).
Typical use
- Use this when validating or mapping channels and you want to treat a channel type as valid if it is either exactly the requested type or explicitly listed as compatible with it.
chtype.attribute(channel_type, attribute)Use this when a system or device build needs an extra column from channel_types.tsv for one channel type.
Read any additional column from
channel_types.tsvfor a given channel type.Args
channel_type: Channel type identifier in standard tuple format(channel_type_id, lib_repo)or any string representation thatparsecan understand.attribute: Column header name inchannel_types.tsvfor the value you want to read (for example"description","notes","voltage_rating").
Returns
Any: Value stored in the requestedattributecolumn for the matchingchannel_type_id. Returns an empty list[]if the channel type cannot be found.
Notes
- Reads
<library_root>/channel_types.tsv(same file aspath()). - Use this for any per-channel-type metadata you've added as extra columns
beyond the core ones like
channel_type_id,signals, andcompatible_channel_types.
chtype.path(channel_type)Use this when a build needs the path of channel_types.tsv for one channel type's library checkout.
Resolve the on-disk path to the
channel_types.tsvfile for a given channel type.Args
channel_type: Channel type identifier in standard tuple format(channel_type_id, lib_repo)or any string representation thatparsecan understand (for example"(5, 'https://github.com/harnice/harnice-general-library')").
Returns
str: Absolute path tochannel_types.tsvat the root of the library repository that owns the given channel type.
Notes
- This does not filter rows; it only locates the TSV file that defines
all channel types for the given
lib_repo.
- In a repository of your choice (or start with harnice-general-library on your own branch), navigate to
-
Each signal in the signals list must have every other signal defined by its channel type also present in the list.
- you can't just define 'positive' if the channel type requires 'positive' and 'negative'
-
Each signal defined in the list is contained by one or more cavities of connectors.
- you can't "cap off" or not populate one of the signals within a channel because that changes the channel type.
-
Every combination of (channel_id, signal, repeat_channel_id) must be unique within the signals list
- you can’t have two i.e. “ch1, pos” signals on the same endpoint
- if you need to break one signal out onto multiple conductors, you'll need to change the channel type to one that defines multiple conductors (i.e. named "ch1, pos-1")
-
Each channel endpoint lives on exactly one connector_id
- a passthrough is the same
channel_idon two connectors, each with its ownrepeat_channel_id - signals that belong to one endpoint cannot be split across connectors
- more than two endpoints on one
channel_idis an error
- a passthrough is the same
Configurable Device Signals List Rules
A configuration is how a device is used, not what it is. The same part number can have different electrical behavior (balanced vs unbalanced, voltage setting, phantom on/off) without a new part number, as long as form, fit, and function of the hardware stay the same.
The signals list is not a pile of alternate rows that get filtered. Build instructions write one signals list for the answers currently in configuration (a dict). Harnice fills that dict from the defaults in fileio.path("configuration setup"), or—when the device is placed in a system—from that instance's answers on the block diagram / BOM.
-
Declare fields in
fileio.path("configuration setup"), then read them in build instructions- Each field has an
id. In the script, useconfiguration["that_id"](channel type, select, boolean, number, and so on). - You can have as many fields as you need. One field is enough for an SM58 that is either balanced or unbalanced. A mixing console might have a field per input (mic vs line, balanced vs unbalanced) so automatic channel mapping sees the type you actually set, and the instance records how to set the hardware up.
- Field types, validation,
show_if, and a full YAML example are in the device configurations documentation.
- Each field has an
-
The list you emit still has to obey the general signals-list rules
- Complete endpoints, unique
(channel_id, signal, repeat_channel_id), one connector per endpoint. Validation only sees the list for this configuration.
- Complete endpoints, unique
-
Do not use configuration to change the physical part
- If the connector or the hardware build changes, that is a new part number, not a configuration.
- Unused pins stay off the list
- A pin is on the signals list only when a channel in this configuration uses it. Omitting a pin this configuration does not use is correct. A channel that is present still includes every signal its channel type defines.
Passthrough Signals List Rules
- A passthrough is exactly two endpoints of the same
channel_id, each with a distinctrepeat_channel_id - The two channel types must be compatible with each other
- this is so a patch that crosses the device still mates compatible types on both faces
- A single-endpoint channel must leave
repeat_channel_idblank; a 3-tuple inmap()on that channel is an error
Commands
These functions are already available in build instructions as signals_list. Use them as written.
signals_list.read(path_to_signals_list=None)
Use this when a device or system build needs the rows of a signals list: a path, a file key, or the signals list of the revision that is open.
Return signals-list rows from a path, file key, or the current project list.
signals_list.new()
Use this when a device build is starting the signals list and should replace the file that is already there.
Create a signals TSV at fileio.path("signals list") with only the header row.
signals_list.append(**kwargs)
Use this when a device build needs one signal added to the signals list.
Append one signal row. Missing optional fields are written as empty strings.
Required: channel_id, signal, cavity, connector_id, channel_type, and exactly one of device_connector_mpn or harness_connector_mpn.
Args:
- channel_id: Channel this row belongs to. Use the same id on every signal of that channel.
- channel_type: Channel type string. Every signal name it declares must be appended for this endpoint, and the string must parse as a channel type.
- signal: Signal name. It must be one of the names declared by channel_type.
- cavity: Pin, socket, or stud this signal uses on its connector. Required, and unique on that connector.
- connector_id: Connector that holds this signal. Every signal of one endpoint must use the same connector.
- repeat_channel_id: Which endpoint, when the channel is a passthrough with two ends. The two repeats must differ. Leave blank when the channel has one endpoint. Optional.
- device_connector_mpn: Manufacturer part number of the device-side connector. Set this or harness_connector_mpn, not both, and use the same choice for every row of that connector.
- harness_connector_mpn: Manufacturer part number of the harness-side connector. Set this or device_connector_mpn, not both.
signals_list.cavity_of_signal(channel_id, signal, path_to_signals_list, repeat_channel_id='')
Use this when a build needs the cavity one signal uses on a channel endpoint.
Return the cavity a signal uses on one channel endpoint.
Args:
- channel_id: Channel id of the signal whose cavity you want.
- signal: Signal name on that channel, such as pos or neg.
- path_to_signals_list: Path, or file key, of the signals list to read.
- repeat_channel_id: Which endpoint when the channel has two. Leave blank to match a single-endpoint channel. Optional.
signals_list.connector_id_of_channel(channel_id, path_to_signals_list, repeat_channel_id='')
Use this when a build needs the connector id of one channel in a signals list.
Return connector_id for a channel endpoint.
Args:
- channel_id: Channel id whose connector you want.
- path_to_signals_list: Path, or file key, of the signals list to read. Raises if that file does not exist.
- repeat_channel_id: Which endpoint when the channel has two. Leave blank to match a single-endpoint channel. Optional.
File Structure
Reference the files in your project by calling fileio.path("file key") from your script. They'll automatically use this structure:
fileio.dirpath("part_directory") |-- yourpn/
|-- earlier revs/
fileio.path("revision history") |-- revhistory.csv
fileio.dirpath("rev_directory") L-- your rev/
fileio.path("build instructions") |-- build.py
fileio.path("library history") |-- libhist.tsv
fileio.path("signals list") |-- signals.tsv
fileio.path("attributes") |-- attributes.json
fileio.path("block diagram symbol") |-- symbol.svg
fileio.path("configuration setup") |-- config.yaml
fileio.path("body") |-- body.step
fileio.dirpath("body_images") L-- body_images/
Building a device
When a Device is built in Harnice, here's what happens:
- Harnice runs the build instructions if it's found in the device directory.
- The signals list is validated and verified.
- A block diagram symbol SVG that can represent this device in a system block diagram is generated or updated based on the signals list.
- If
attributes.jsonhasmate_connectors, every signals-listconnector_idmust have a mate connector with that same id.
The first build writes these instructions when build.py does not exist yet.
Default device build instructions
# to access configuration information, use 'configuration' (a dictionary object).
# harnice will pass in defaults or the relevant refdes configs automatically.
# print(configuration)
# ---------------------------------------------------------------------------
# Device example: four XLR channels (two in, two out).
# Each channel has one instance, so repeat_channel_id stays blank.
# ---------------------------------------------------------------------------
ch_type_ids = {
"in": (1, "https://github.com/harnice/harnice-general-library"),
"out": (4, "https://github.com/harnice/harnice-general-library"),
"chassis": (5, "https://github.com/harnice/harnice-general-library")
}
xlr_pinout = {
"pos": 2,
"neg": 3,
"chassis": 1
}
connector_mpns = {
"XLR3F": ["in1", "in2"],
"XLR3M": ["out1", "out2"]
}
def mpn_for_connector(connector_name):
for mpn, conn_list in connector_mpns.items():
if connector_name in conn_list:
return mpn
return None
signals_list.new()
for connector_name in ["in1", "in2", "out1", "out2"]:
if connector_name.startswith("in"):
channel_type = ch_type_ids["in"]
elif connector_name.startswith("out"):
channel_type = ch_type_ids["out"]
else:
continue
channel_name = connector_name
connector_mpn = mpn_for_connector(connector_name)
for signal in chtype.signals(channel_type):
signals_list.append(
channel_id=channel_name,
signal=signal,
connector_id=connector_name,
cavity=xlr_pinout.get(signal),
channel_type=channel_type,
device_connector_mpn=connector_mpn
)
signals_list.append(
channel_id=f"{channel_name}-shield",
signal="chassis",
connector_id=connector_name,
cavity=xlr_pinout.get("chassis"),
channel_type=ch_type_ids["chassis"],
device_connector_mpn=connector_mpn
)
# A passthrough is the same channel_id on exactly two connectors. Every row
# needs a repeat_channel_id, and the two repeats must differ. Use
# harness_connector_mpn when the harness owns that face.
#
#
# How to use the relevant signals list functions:
# signals_list.append(
# channel_id="ch0",
# repeat_channel_id="A",
# signal="pos",
# connector_id="A",
# cavity=2,
# channel_type=ch_type_ids["in"],
# harness_connector_mpn="XLR3F",
# )
# signals_list.append(
# channel_id="ch0",
# repeat_channel_id="B",
# signal="pos",
# connector_id="B",
# cavity=2,
# channel_type=ch_type_ids["out"],
# harness_connector_mpn="XLR3M",
# )
#
# Define the physical model of your device in this section. Choose from
# default macros or import your own STEP file.
# Each macro saves with fileio.path("body"), the device's body.step.
# A paste on the physical model page writes body_images/<face>.png and a
# comment in body.step. The next macro run rewrites body.step. Rectangular
# and rack-chassis macros link again only the faces named in images=.
# The enclosure and STEP-import macros take no images argument. Comment
# that call out after the body is right, then paste. Leaving it in build.py
# drops those face links on the next run. The PNG files stay either way.
# Rectangular sketch surface "front" is the face at y = 0.
# images = {
# "front": os.path.join(fileio.dirpath("body_images"), "front.png"),
# }
# build_utils.run_macro(
# "build_rectangular_physical_model",
# "macros/device_artifacts",
# "https://github.com/harnice/harnice-general-library",
# artifact_id="body-1",
# params={"width": 4, "height": 2, "depth": 6},
# images=images,
# )
# Rack sketch surface "bottom" is the face at z = 0. The front ears are "front".
# images = {
# "bottom": os.path.join(fileio.dirpath("body_images"), "bottom.png"),
# }
# build_utils.run_macro(
# "build_rackmount_physical_model",
# "macros/device_artifacts",
# "https://github.com/harnice/harnice-general-library",
# artifact_id="body-1",
# params={"u": 2, "depth": 10},
# images=images,
# )
# Rack enclosure. depth and units are the macro arguments. The front panel is
# 19 inches wide and the body is 19 inches wide. 1U is 1.75in tall.
# No images argument. Comment this call out before pasting face pictures.
# build_utils.run_macro(
# "build_rackmount_enclosure",
# "macros/device_artifacts",
# "https://github.com/harnice/harnice-general-library",
# artifact_id="body-1",
# depth=10,
# units=2,
# )
# Imported STEP. No images argument. Comment this call out after the copy
# looks right, then paste face pictures. The next run recopies the vendor
# file and drops those links.
# build_utils.run_macro(
# "build_physical_model_from_step",
# "macros/device_artifacts",
# "https://github.com/harnice/harnice-general-library",
# artifact_id="body-1",
# path_to_step="vendor/chassis.step",
# )
Physical model
A device body is fileio.path("body") (body.step). Mate connectors live in attributes.json under mate_connectors. How to write the solid, place mates, paste a face picture, and arrange devices in a system is CAD.
Open physical model to move mate connectors on the solid the build wrote, and to paste a picture on a face. A pasted picture stays through the next build when images names that file, or when the macro that rewrites body.step is commented out. The PNG stays in body_images/ either way. If attributes.json has mate_connectors, every signals-list connector_id must have a mate with that same id. A device with no mate_connectors still builds. Opening the page writes that key and puts any missing required mate at the origin.
How to define a new device
-
Ensure every channel going into or out of a device has a type defined in a repo somewhere.
Channel Types
Channel Types
How are channels mapped?
How to define a new channel type
- In a repository of your choice (or start with harnice-general-library on your own branch), navigate to
channel_types.tsvat the library root - If you want channel definitions to be private and are therefore working in a private repository, ensure the repo's path is listed in file
repository_locations.csv(located at root of your harnice source code repo). The first column is the URL or traceable path, and the second column is your local path. - If you find the channel_type you're looking for, temporarily note it as a touple in a notepad somewhere with format
(ch_type_id, universal_library_repository). - If you don't find it, make a new one. It's important to try and reduce the number of channel_types in here to reduce complexity, but it's also important that you adhere to strict and true rules about what is allowed to be mapped to what. Modifications and additions to this document should be taken and reviewed very seriously.
Two optional columns set the default appearance. Both cells are Python dict literals, parsed with
ast.literal_eval. Leave a cell blank to leave that default unset. Harnice reads thechannel_types.tsvat the root of the library named by the channel type'slib_repo.channel_default_appearance— one engineering appearance dict. A channel trace (channelorharness-channel) and the channel card in the connectors view use it. Example:{'base_color': '#D59A10'}.signal_default_appearances— signal name to appearance dict. A circuit trace uses the entry for itssignal_of_channel_type, and the connectors view uses that entry for the signal bar. Names match thesignalscolumn. Example:{'pos': {'base_color': '#E24B4A'}, 'neg': {'base_color': '#2E6B9F'}}.
An instance
appearancethat already includesbase_coloris the appearance drawn for that trace. Traces are drawn when the project is built, so a change to these columns shows up on overlays after the next build. The connectors view reads the columns when the page opens. A trace whose resolved appearance has nobase_coloris the rainbow placeholder. A card or bar with nobase_colorkeeps the console palette.The dict is the engineering appearance from the Appearance guide:
base_color, and optionallyoutline_color,parallelstripe,perpstripe, andtwisted. Single quotes match the rows already in the file. The separator is a tab, so a comma inside the literal stays in the cell.chtype.parse(val)Use this when a build has a channel type as stored text and needs the (channel_type_id, lib_repo) pair.
Convert stored string into a tuple (chid:int, lib_repo:str). Handles both single tuples and extracts first tuple from lists.
chtype.signals(channel_type)Use this when a system or device build needs the signal names declared for one channel type.
Return the list of signal names associated with a specific channel type.
Args
channel_type: Channel type identifier in standard tuple format(channel_type_id, lib_repo)or any string representation thatparsecan understand.
Returns
list[str]: List of signal names from thesignalscolumn ofchannel_types.tsvfor the matchingchannel_type_id. If the column is blank or the channel type cannot be found, returns an empty list.
Data format
- The
signalscolumn is expected to be a comma-separated string, for example:"CAN_H, CAN_L, SHIELD".
chtype.channel_default_appearance(channel_type)Use this when a drawing needs the default appearance of one channel type's trace and card.
Optional default appearance for this channel type's channel trace and card.
Reads
channel_default_appearancefrom thechannel_types.tsvat the root of the library named by this channel type'slib_repo. The cell is a Python dict literal, parsed withast.literal_eval(seeappearance.parse). Example:{'base_color': '#D59A10'}. A blank cell returnsNone.A channel trace (
item_typechannelorharness-channel) and the channel card in the connectors view use this appearance. A circuit trace usessignal_default_appearancesfor itssignal_of_channel_typeinstead.An instance
appearancethat already includesbase_coloris the appearance drawn for that trace. Traces are drawn when the project is built. The connectors view reads this column when the page opens. A trace whose resolved appearance has nobase_coloris the rainbow placeholder. A channel card with nobase_colorkeeps the console palette.The dict is an engineering appearance:
base_colorplus any ofoutline_color,parallelstripe,perpstripe, andtwisted.Args
channel_type: Channel type identifier in standard tuple format(channel_type_id, lib_repo)or any string representation thatparsecan understand.
Returns
dictorNone: Parsed appearance, orNonewhen the cell is empty or the channel type cannot be found.
chtype.signal_default_appearances(channel_type)Use this when a drawing needs the default appearance of each signal on one channel type.
Optional default appearance for each signal of this channel type.
Reads
signal_default_appearancesfrom thechannel_types.tsvat the root of the library named by this channel type'slib_repo. The cell is a Python dict literal, parsed withast.literal_eval: signal name to appearance dict. Example:{'pos': {'base_color': '#E24B4A'}, 'neg': {'base_color': '#2E6B9F'}}. A blank cell returns{}. Names that are not themselves dicts are dropped. Signal names match thesignalscolumn.A circuit trace uses the entry for its
signal_of_channel_type. The connectors view uses the same entry for that signal's bar. A channel trace useschannel_default_appearance.An instance
appearancethat already includesbase_coloris the appearance drawn for that trace. Traces are drawn when the project is built. The connectors view reads this column when the page opens. A trace whose resolved appearance has nobase_coloris the rainbow placeholder. A signal bar with nobase_colorkeeps the console palette.Each value is an engineering appearance:
base_colorplus any ofoutline_color,parallelstripe,perpstripe, andtwisted.Args
channel_type: Channel type identifier in standard tuple format(channel_type_id, lib_repo)or any string representation thatparsecan understand.
Returns
dict: Signal name to appearance dict. Empty when unset or when the channel type cannot be found.
chtype.compatibles(channel_type)Use this when a system build needs the channel types declared compatible with one channel type, not including that type itself.
Look up other channel types that are declared as compatible with the given channel type.
Args
channel_type: Channel type identifier in standard tuple format(channel_type_id, lib_repo)or any string representation thatparsecan understand.
Returns
list[tuple[int, str]]: List of(channel_type_id, lib_repo)tuples taken directly from thecompatible_channel_typescolumn ofchannel_types.tsv. Returns an empty list if no compatibles are defined or if the channel type cannot be found.
Data format
- The
compatible_channel_typescolumn must be an AST-parseable Python value:- Single tuple:
(1, "library_repo") - List of tuples:
[(1, "library_repo"), (2, "library_repo")]
- Single tuple:
chtype.is_or_is_compatible_with(channel_type)Use this when a system build is validating or mapping channels and this channel type should count if it is the requested type or listed as compatible with it.
Return the given channel type plus all channel types declared as compatible with it.
Args
channel_type: Channel type identifier in standard tuple format(channel_type_id, lib_repo)or any string representation thatparsecan understand.
Returns
list[tuple[int, str]]: List of(channel_type_id, lib_repo)tuples where the first entry is the parsedchannel_typeitself and the remaining entries are the compatibles returned bycompatibles(channel_type).
Typical use
- Use this when validating or mapping channels and you want to treat a channel type as valid if it is either exactly the requested type or explicitly listed as compatible with it.
chtype.attribute(channel_type, attribute)Use this when a system or device build needs an extra column from channel_types.tsv for one channel type.
Read any additional column from
channel_types.tsvfor a given channel type.Args
channel_type: Channel type identifier in standard tuple format(channel_type_id, lib_repo)or any string representation thatparsecan understand.attribute: Column header name inchannel_types.tsvfor the value you want to read (for example"description","notes","voltage_rating").
Returns
Any: Value stored in the requestedattributecolumn for the matchingchannel_type_id. Returns an empty list[]if the channel type cannot be found.
Notes
- Reads
<library_root>/channel_types.tsv(same file aspath()). - Use this for any per-channel-type metadata you've added as extra columns
beyond the core ones like
channel_type_id,signals, andcompatible_channel_types.
chtype.path(channel_type)Use this when a build needs the path of channel_types.tsv for one channel type's library checkout.
Resolve the on-disk path to the
channel_types.tsvfile for a given channel type.Args
channel_type: Channel type identifier in standard tuple format(channel_type_id, lib_repo)or any string representation thatparsecan understand (for example"(5, 'https://github.com/harnice/harnice-general-library')").
Returns
str: Absolute path tochannel_types.tsvat the root of the library repository that owns the given channel type.
Notes
- This does not filter rows; it only locates the TSV file that defines
all channel types for the given
lib_repo.
- In a repository of your choice (or start with harnice-general-library on your own branch), navigate to
-
Make a folder for the part number of your device somewhere on your computer. Click Build in Harnice, which will generate an example device that you can then edit.
Building a project
-
In Harnice, click New project…. You do not need a rev folder yet.
-
Fill in part number, revision (
1is fine), project type, and a description, then click OK. Harnice creates the folders and an example you can edit. -
Click Build to generate the rest of the project files.
-
-
Edit the attributes of your new device.
Editing the Attributes of a Project
-
Navigate to the device folder, find the new rev folder you just made, and open
fileio.path("attributes"). -
Change the default reference designator here, as well as any other attributes you may want to record.
- Click Build again.
-
-
Edit the signals list of your new device. To generate
build.pyfrom a pinout, ICD, or datasheet, paste the Device signals list prompt on Building with AI; put the source file next tobuild.pyin the revision folder.Updating a Signals List
Harnice defines a signals list to be a table that keeps track of every single signal going into and out of a device, which connector or channel it's part of, which signal names it has, which connector contact number or part number it has.
To generate
build.pyfrom a pinout, ICD, datasheet, or spreadsheet, paste the Device signals list prompt on Building with AI. Put the source file in the device revision folder (next tobuild.py) or give the agent that path.- If your device is very simple (has only a small number of signals), you are free to edit
fileio.path("signals list")manually. If you choose to do this, deletefileio.path("build instructions")before clicking Build again, because the script will overwrite the signals list. However, it is recommended that you edit the Python build instructions instead to produce a validated, reproduceable, and portable result. - To edit the build instructions Python file, this quick-start guide should give you an idea of how the default script is set up, and how you might be able to change it to suit your needs. The goal of this file is to make a signals list. Do not be concerned with writing this efficiently, as it will only be ran while building the device.
- List, utility, and channel-type modules (
signals_list,chtype, and the rest) are already available in the script — you do not need to import them from harnice. - Copy your channel_type touples in from earlier notepad step. Again, these are relevant only to the device you're trying to make. The dictionary is used to store these here so that you can reference the touples in a human-readable format in this script only.
ch_type_ids = {"in": (1, "https://github.com/harnice/harnice-general-library"),"out": (4, "https://github.com/harnice/harnice-general-library"),"chassis": (5, "https://github.com/harnice/harnice-general-library")} -
- Define your connector part numbers. Default convention is this dictionary, where you can resolve the part number by finding the connector name.
mpn_for_connector(connector_name)will search your definition dictionary for a connector name and return its part number.connector_mpns = {"XLR3F": ["in1", "in2"],"XLR3M": ["out1", "out2"]}
Define your pinouts. Default convention is this dictionary, where if you reference something like
xlr_pinout.pos, this dictionary should return the cavity name. Reminder: channels do not contain information about pinouts or connectors so this has to be done at each device. If you use the same pinout across multiple devices, you may consider importing that definition from a python library elsewhere.xlr_pinout = {"pos": 2,"neg": 3,"chassis": 1}signals_list.new()will overwrite the existing signals list with a blank one, which will be repopulated while the rest of this script is executed.- Bespoke logic should be written in python for the remainder of this script, making use of patterns and naming conventions of this specific device.
- I'd recommend you use a python editor on your screen at the same time as a csv editor that automatically refreshes (vscode, easycsveditor on the mac store) so that you can update your python, run builds frequently, and watch the updates in realtime on your csv
- Define your connector part numbers. Default convention is this dictionary, where you can resolve the part number by finding the connector name.
- List, utility, and channel-type modules (
- If your device is very simple (has only a small number of signals), you are free to edit
-
Give the device a body when it should show up in a system assembly. Paste the CAD prompt on Building with AI, or follow CAD.
-
Work on your block diagram symbol.
Working on a Block Diagram Symbol
the first time you build after updating your signals list, you may get an error like
The following pin(s) exist in SVG symbol but not in Signals List: in1, in2, out1, out2. Harnice keeps the block diagram symbol in sync with your signals list. Delete or edit the SVG so its pins match, then rebuild—missing pins are appended automatically when possible.Before you polish the symbol, name your connectors in the signals list and build once more so the pins that appear in the SVG match what you intend.
- Open the Harnice console on your device and open the block diagram symbol editor.
- Arrange the symbol the way you want it to look. Do not rename pin identifiers that must match the signals list; you can change placement and appearance.
- Text on the symbol is a Python f-string.
{refdes}becomes the instance name, and{configuration.get("field_id")}reads a configuration field, when a system builds this device. The result is written into that system's diagram.\nstarts a new line. Text with no{...}and no\nstays as written. - Save the symbol and click Build again to confirm the SVG still matches the signals list.
When you build a device from its Signals List, Harnice creates or updates a block diagram symbol SVG in the revision directory. That symbol contains ports that match the connectors in your Signals List.
Device modeling for simulation of behavior in a system (future work)
It is often useful to model how an entire electrical system will behave by aggregating up behaviors of many contained devices and how they interact with each other.
Eventually, Harnice will allow you to do this automatically within the same source-of-truth system definition that defines your harnesses.
When this feature is implemented, devices will contain an automatically generated .kicad_esch file that will allow the user to define a schematic that represents the lump behavior of your device. Harnice will ensure that every signal on your signals list is accounted for in the simulation esch, and the user may choose to connect any simulation device between those symbols. This way, when the device is used in a system block diagram, this device esch can referenced by the system simulator and its behavior can be considered while running an entire system simulation profile.