Skip to content

Devices

Any electrical item, active or passive, that is not a harness.


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 CSV file called a "Signals List".

Signals List

Interacting with Signals Lists

A Signals List is an exhaustive list of every signal is going into or out of a thing. Signals Lists are the primary way Harnice stores information about devices, and act as the source of truth for devices and disconnects.


Signals List Validation Checks:

(These are automatically validated when you build the device or disconnect 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

    1. In a repository of your choice (or start with harnice_library_public on your own branch), navigate to library_repo/channel_types/channel_types.csv
    2. 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.
    3. 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).
    4. 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.
    chtype.path(channel_type)

    Resolve the on-disk path to the channel_types.tsv file for a given channel type.

    Args

    • channel_type: Channel type identifier in standard tuple format (channel_type_id, lib_repo) or any string representation that parse can understand (for example "(5, 'https://github.com/harnice/harnice')").

    Returns

    • str: Absolute path to channel_types.tsv at 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.
    chtype.parse(val)

    Convert stored string into a tuple (chid:int, lib_repo:str). Handles both single tuples and extracts first tuple from lists.

    chtype.compatibles(channel_type)

    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 that parse can understand.

    Returns

    • list[tuple[int, str]]: List of (channel_type_id, lib_repo) tuples taken directly from the compatible_channel_types column of channel_types.tsv. Returns an empty list if no compatibles are defined or if the channel type cannot be found.

    Data format

    • The compatible_channel_types column must be an AST-parseable Python value:
      • Single tuple: (1, "library_repo")
      • List of tuples: [(1, "library_repo"), (2, "library_repo")]
    chtype.attribute(channel_type, attribute)

    Read any additional column from channel_types.tsv for a given channel type.

    Args

    • channel_type: Channel type identifier in standard tuple format (channel_type_id, lib_repo) or any string representation that parse can understand.
    • attribute: Column header name in channel_types.tsv for the value you want to read (for example "description", "notes", "voltage_rating").

    Returns

    • Any: Value stored in the requested attribute column for the matching channel_type_id. Returns an empty list [] if the channel type cannot be found.

    Notes

    • Reads <library_root>/channel_types.tsv (same file as path()).
    • Use this for any per-channel-type metadata you've added as extra columns beyond the core ones like channel_type_id, signals, and compatible_channel_types.
    chtype.signals(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 that parse can understand.

    Returns

    • list[str]: List of signal names from the signals column of channel_types.tsv for the matching channel_type_id. If the column is blank or the channel type cannot be found, returns an empty list.

    Data format

    • The signals column is expected to be a comma-separated string, for example: "CAN_H, CAN_L, SHIELD".
    chtype.is_or_is_compatible_with(channel_type)

    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 that parse can understand.

    Returns

    • list[tuple[int, str]]: List of (channel_type_id, lib_repo) tuples where the first entry is the parsed channel_type itself and the remaining entries are the compatibles returned by compatibles(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.
  • 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) must be unique within the signals list

    • you can’t have two i.e. “ch1, pos” signals on the same device
    • 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")
  • You can’t put signals of the same channel on different connectors

    • this is because doing so breaks a lot of internal assumptions Harnice is making while mapping channels.

    • the following two options are recommended work-arounds:

      • Most correct but confusing: Define one channel type per signal, then manually chmap your channels or write a macro for mapping the channels to their respective destinations.

      • Janky but easiest to understand: Define a connector part number that actually represents multiple connectors, while using cavities to reference each connector.

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 device's configuration-setup.yaml defaults, or—when the device is placed in a system—from that instance's answers on the block diagram / BOM.

  • Declare fields in *-configuration-setup.yaml, then read them in build instructions

    • Each field has an id. In the script, use configuration["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.
  • The list you emit still has to obey the general signals-list rules

    • Complete channels, unique (channel_id, signal), one connector per channel. Validation only sees the list for this configuration.
  • Do not use configuration to change the physical part

    • If connectors, cavities, or the build of the device change, that is a new part number, not a configuration. Unused signals that are still on the connector should stay in the list.

Disconnect Signals List Rules

  • “A” and “B” channels of the same disconnect must be compatible with each other

    • this is to ensure when you actually mate the disconnect that the channels inside will be compatible.

Columns

Columns are automatically generated when signals_list.new() is called. Additional columns are not supported and may result in an error when parsing.

Columns of Signals Lists for Devices

Column Description
channel_id Unique identifier for the channel.
signal Name of the electrical function of that signal, as it pertains to its channel type defition. i.e. "positive"
connector_name Unique identifier for the connector that this signal and channel is a part of.
cavity Identifier of the pin, socket, stud, etc, that this signal is internally electrically routed to within its connector.
connector_mpn MPN of the connector in this device (NOT the mating connector).
channel_type The channel type of this signal.
Touple (x, y) where x is the channel id within a library repo and y is the traceable name or url where that channel type library is defined

Columns of Signals Lists for Disconnects

Column Description
channel_id Unique identifier for the channel.
signal Name of the electrical function of that signal, as it pertains to its channel type defition. i.e. "positive"
A_cavity Identifier of the pin, socket, stud, etc, that this signal is internally electrically routed to within that side of the connector.
??? question "Why are A and B different here?"
Sometimes it's possible to have connectors that have cavities that may mate electrically, but have different names. For example, suppose two connectors physically mate, but are made by different manufacturers. One manufacturer used lowercase (a, b, c) to reference the cavities but the other used uppercase (A, B, C), or numbers (1, 2, 3), or colors (red, green, blue), etc.
B_cavity Identifier of the pin, socket, stud, etc, that this signal is internally electrically routed to within that side of the connector.
??? question "Why are A and B different here?"
Sometimes it's possible to have connectors that have cavities that may mate electrically, but have different names. For example, suppose two connectors physically mate, but are made by different manufacturers. One manufacturer used lowercase (a, b, c) to reference the cavities but the other used uppercase (A, B, C), or numbers (1, 2, 3), or colors (red, green, blue), etc.
A_connector_mpn MPN of the connector of the harness on this side of the disconnect
A_channel_type The channel type of this side of the discconect.
??? question "Why are A and B different here?"
It's important to keep track of which side has which channel type so that you cannot accidentally flip pins and sockets, for example, by mapping the wrong channel type to the wrong pin gender. Careful validation should be done when mapping channels through disconnects to ensure the disconnects have channels that pass through them in the correct direction.
B_connector_mpn MPN of the connector of the harness on this side of the disconnect
B_channel_type The channel type of this side of the discconect.
??? question "Why are A and B different here?"
It's important to keep track of which side has which channel type so that you cannot accidentally flip pins and sockets, for example, by mapping the wrong channel type to the wrong pin gender. Careful validation should be done when mapping channels through disconnects to ensure the disconnects have channels that pass through them in the correct direction.


Commands:

Use the following functions by first importing the module in your script like this:

from harnice.lists import signals_list
then use as written.

signals_list.set_list_type(x)

Documentation needed.

signals_list.new()

Creates a new signals TSV file at fileio.path("signals list") with only the header row. Overwrites any existing file.

signals_list.append(**kwargs)

Appends a new row to the signals TSV file. Missing optional fields will be written as empty strings. Raises ValueError if required fields are missing.

Required kwargs: For 'device': channel_id, signal, connector_name, cavity, connector_mpn, channel_type For 'disconnect': A_channel_id, A_signal, A_connector_name, A_cavity, A_connector_mpn, A_channel_type, B_channel_id, B_signal, B_connector_name, B_cavity, B_connector_mpn, B_channel_type

signals_list.cavity_of_signal(channel_id, signal, path_to_signals_list)

Documentation needed.

signals_list.connector_name_of_channel(channel_id, path_to_signals_list)

Documentation needed.


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")                 |-- yourpn-revX-build_instructions.py
fileio.path("signals list")                       |-- yourpn-revX-signals_list.tsv
fileio.path("attributes")                         |-- yourpn-revX-attributes.json
fileio.path("block diagram symbol")               |-- yourpn-revX-block-diagram-symbol.svg
fileio.path("configuration setup")                L-- yourpn-revX-configuration-setup.yaml

Building a device

When a Device is built in Harnice, here's what happens:

  1. Harnice runs the build instructions if it's found in the device directory.
  2. The signals list is validated and verified.
  3. A block diagram symbol SVG that can represent this device in a system block diagram is generated or updated based on the signals list.

How to define a new device

  1. 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

    1. In a repository of your choice (or start with harnice_library_public on your own branch), navigate to library_repo/channel_types/channel_types.csv
    2. 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.
    3. 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).
    4. 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.
    chtype.path(channel_type)

    Resolve the on-disk path to the channel_types.tsv file for a given channel type.

    Args

    • channel_type: Channel type identifier in standard tuple format (channel_type_id, lib_repo) or any string representation that parse can understand (for example "(5, 'https://github.com/harnice/harnice')").

    Returns

    • str: Absolute path to channel_types.tsv at 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.
    chtype.parse(val)

    Convert stored string into a tuple (chid:int, lib_repo:str). Handles both single tuples and extracts first tuple from lists.

    chtype.compatibles(channel_type)

    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 that parse can understand.

    Returns

    • list[tuple[int, str]]: List of (channel_type_id, lib_repo) tuples taken directly from the compatible_channel_types column of channel_types.tsv. Returns an empty list if no compatibles are defined or if the channel type cannot be found.

    Data format

    • The compatible_channel_types column must be an AST-parseable Python value:
      • Single tuple: (1, "library_repo")
      • List of tuples: [(1, "library_repo"), (2, "library_repo")]
    chtype.attribute(channel_type, attribute)

    Read any additional column from channel_types.tsv for a given channel type.

    Args

    • channel_type: Channel type identifier in standard tuple format (channel_type_id, lib_repo) or any string representation that parse can understand.
    • attribute: Column header name in channel_types.tsv for the value you want to read (for example "description", "notes", "voltage_rating").

    Returns

    • Any: Value stored in the requested attribute column for the matching channel_type_id. Returns an empty list [] if the channel type cannot be found.

    Notes

    • Reads <library_root>/channel_types.tsv (same file as path()).
    • Use this for any per-channel-type metadata you've added as extra columns beyond the core ones like channel_type_id, signals, and compatible_channel_types.
    chtype.signals(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 that parse can understand.

    Returns

    • list[str]: List of signal names from the signals column of channel_types.tsv for the matching channel_type_id. If the column is blank or the channel type cannot be found, returns an empty list.

    Data format

    • The signals column is expected to be a comma-separated string, for example: "CAN_H, CAN_L, SHIELD".
    chtype.is_or_is_compatible_with(channel_type)

    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 that parse can understand.

    Returns

    • list[tuple[int, str]]: List of (channel_type_id, lib_repo) tuples where the first entry is the parsed channel_type itself and the remaining entries are the compatibles returned by compatibles(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.
  2. 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
    1. In Harnice, click New project…. You do not need a rev folder yet.

    2. Fill in part number, revision (1 is fine), project type, and a description, then click OK. Harnice creates the folders and an example you can edit.

    3. Click Build to generate the rest of the project files.

  3. Edit the attributes of your new device.

    Editing the Attributes of a Project
    1. Navigate to the device folder, find the new rev folder you just made, open *-attributes.json.
    2. Change the default reference designator here, as well as any other attributes you may want to record.
    3. Click Build again.
  4. Edit the signals list of your new device.

    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.

    1. If your device is very simple (has only a small number of signals), you are free to edit *-signals_list.tsv manually. If you choose to do this, delete *-build_instructions.py 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.
    2. 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.
      1. These are useful modules you'll want to reference. Leave them there. from harnice.lists import signals_list from harnice.project_types import chtype
      2. 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"), "out": (4, "https://github.com/harnice/harnice"), "chassis": (5, "https://github.com/harnice/harnice") }
        1. 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 }

        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.
        2. Bespoke logic should be written in python for the remainder of this script, making use of patterns and naming conventions of this specific device.
        3. 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
  5. 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.

    1. Open the Harnice console on your device (or disconnect) and open the block diagram symbol editor.
    2. 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.
    3. 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.