Skip to main content

Attributes

Attributes are the named values hung off everything in GEM. A device's IP address and password are attributes. A dimmer zone's current level, a thermostat's setpoint and reported temperature, a camera's stream URL, the site's retention settings — all attributes. Anything a driver needs to reach a piece of hardware, and everything that hardware reports back, lands here.

Overview

Most of the time you don't come to this page: you edit attributes on the Attributes tab of the device, zone, subsystem or UI you're working on, where you see only that entity's values and get a name picker that knows what its driver supports. That editor is documented under Attribute Editor below.

This page is the system-wide view — every attribute of one kind across the whole site, on four tabs:

TabWhat it is for
AttributesFind and edit attributes across every entity at once, and change many of them in one action — see Viewing Attributes and Changing many at once
AuditCheck what is stored against what the registry says it should be: plaintext secrets, rows whose entity was deleted, missing required attributes, values outside their option list — see Audit
RegistryRead the catalog itself, with how many attributes in this system use each entry — see Registry tab
Linked AttributesMirror one attribute onto another whenever it changes — see Linked Attributes

Come here to:

  • Find an attribute when you don't know which entity holds it.
  • Set the site-wide values, which have no entity of their own (System Target: system).
  • Compare one name across many entities — every zone's level, every device's ip — and set them all in one go.
  • Copy a configured device's attributes onto the other twenty like it.
  • Find the configuration drift you would otherwise discover on a call-out.

Attribute Registry

GEM maintains an Attribute Registry — a catalog of known attributes for each system target and driver. When editing attributes on a device or zone, the registry provides:

  • Auto-complete — the attribute name field suggests registered attributes relevant to the entity's driver.
  • Auto-fill — selecting a registered attribute auto-fills the value type, history, secure, and readonly flags.
  • Descriptions — registered attributes show a description hint and unit below the name.
  • Driver context — attributes are filtered by the entity's driver inheritance chain (e.g., a lutron_qsx device sees attributes from lutron_qsx, lutron_base, and core). Cross-cutting targets without a driver (e.g., ui, site, site_space) accept attributes from any integration and are shown the full registry for that target.
  • Automatic history — every registry entry flagged for history turns history on by itself the first time a driver writes that attribute on a zone or device. You do not have to tick History on temperature, level, power, battery and the like; the platform already did. See History.
  • User-overridable settings — the registry is also the allowlist for Personal Settings: a ui setting may be overridden per user only if its registry entry is flagged for it. The flag ships with GEM and is not editable from this page, so a site cannot widen the list by editing a row — which is what keeps permission-bearing settings out of it.

The registry ships with GEM and is refreshed into the database every time the server starts, so entries cannot be added, removed or reshaped from the admin UI — a row that is not in the shipped catalog is removed again at the next boot. The one field an operator can change is an entry's description, either on the Registry tab or by asking the AI Assistant to rewrite it; an edited description is preserved on subsequent restarts instead of being reset to the shipped text.

The catalog also takes in what each driver declares about itself — the required and optional attributes it asks for when you add a device, with their type, description, unit, default, allowed values and whether they are secret. That is where most driver attributes are written down, so entries for them now carry the driver's own words rather than being absent or typed string by default. Where a driver's declaration and the shipped entry disagree, the shipped entry wins: a driver can declare a value one way and store it another, and what is stored is what the registry has to describe.

You can read the whole catalog, with a usage count per entry, on the Registry tab of this page, and check it against what is actually stored on the Audit tab.

note

The registry is a catalog of attributes GEM knows about, not a restriction. Any attribute name you type is accepted and works — the registry only decides what the name picker suggests and what gets auto-filled.

Viewing Attributes

The list's columns, left to right:

ColumnShows
CheckboxPuts the row in the selection every bulk action works on
EntityThe name of the thing the attribute is attached to, with its id. Click the name to open that record in a reference modal over the list — the target, the name filter, the page you paged to and any selection staged for a bulk edit all stay put. Targets with no editor of their own (ui_page, site, …) are plain text
AttributeThe attribute name, with its registry description underneath
ValueCurrent value, rendered the way the registry says it should be — a dropdown's label rather than its id, true/false for a bool, •••••••• for a secret. Click it to edit in place
Typestring, bool, int, float, date or json, with the registry's unit beside it
Flagshistory, read-only, secret, plus unlisted when the name is not in the registry and type when the stored type disagrees with it
UpdatedWhen the value last changed

Editing a value in place writes it the same way a driver does — the previous value is kept, last_update moves, read-only rows are refused, and anything watching the attribute sees the change. Values are never written straight to the database from this page.

Rows are listed by attribute name, then by entity. If the server cannot run the query, the error is reported as a notification rather than as an empty table, so an empty list always means that nothing matched the filters.

Filtering

Five filters narrow the list, and they compose:

FilterWhat it does
System targetThe kind of entity — zone, device, system, … (see the table below)
Attribute nameOne name across every entity. The picker is grouped by registry category, shows each name's description, and says how many entities currently hold it
EntityMatches the entity's name — "kitchen", "gate"
Value containsSubstring match on the stored value
ShowEverything, secrets only, history enabled, read-only, not in the registry, or type disagrees with the registry

With a single name selected, the Values button breaks that name down by value with a count per bucket — every connected attribute at a glance, or every poll_interval that is still on the default. Click a bucket to filter to exactly those rows.

The choices for System target:

System TargetWhat it holds
zonePer-zone settings and live state — the biggest set by far
devicePer-device connection settings, credentials, and reported state
subsystemSettings that apply to a whole subsystem (lights, doors, av, …)
macroValues a macro stores about itself between runs
uiPer-UI behavior and appearance settings
variableAutomation variables (weather, sun/moon, calendar, custom)
systemSite-wide settings — retention, API keys, deadbands, feature flags
site / site_spaceSite and space settings, including current site mode
monitorSettings on a monitor definition
device_subtypeDefaults shared by every device of a subtype
addressReserved for address-scoped values
applicationSecret settings belonging to an installed application package

For system, variable and address there is only one target — the picker shows a single entry named all.

tip

You can land on this page pre-filtered. /admin/attributes?filter_system_target=system opens it with System Target already set to system — the same link the Server page uses to jump you to the site-wide settings.

Row Actions

  • New — opens the attribute form for a brand-new attribute
  • Edit (pencil) — opens the same form loaded with that row, including its Triggers tab
  • History (clock) — jumps to the History page for that device or zone; shown only where history is on
  • Delete — removes the attribute row
  • CSV — exports the filtered set, up to 5,000 rows (a warning names the cap when it is reached). Secure values export as the word (secure), never the value
warning

Deleting an attribute is not undone by anything. A driver that expects the attribute will usually recreate it with a default on its next write, but a hand-entered value — an IP address, a Modbus register map, a credential — is gone. Check the Triggers tab before deleting anything an automation might be watching.

Changing many at once

Tick rows and a bar appears above the list with the count and what you can do to them. Select all n matching extends the selection past the current page to everything the filters match, and a selection survives paging and re-filtering, so you can build one out of several passes. A single "select all matching" pull tops out at 2,000 rows and says so when it hits the cap — narrow the filters and take the rest in a second pass rather than assuming everything is selected.

Every bulk action shows exactly what it will do before it does it — a row-by-row from → to preview, plus the rows it is going to skip and why — and afterwards reports back what the server actually did, per row.

The entity grids offer the same thing from the other direction: select multiple rows on Zones, Devices, or any other attribute-bearing grid and press Set Attribute to write one attribute across the selection without leaving the page. This page starts from attribute rows; that button starts from entities — both go through the same write path and show the same preview-then-readback flow.

ActionWhat it does
Set valueWrites one value to every selected attribute. When they all share a name you get that attribute's real editor — its dropdown, its switch, its unit — and its registry default one click away
FlagsTurns history, read-only and secure on or off, and can correct value_type. Values are left alone
Apply defaultsFills blank attributes from their registry default. Never overwrites a configured value
DeleteRemoves the selected rows
Copy to…Copies one entity's attributes onto many others — see below

Things worth knowing before you commit:

  • Read-only rows are skipped unless you tick the override in the dialog. Even then, a driver that owns the value will overwrite it on its next update — the override is for correcting a stored value, not for taking ownership.
  • Protected names are refused by the server and reported as skipped. These are names that would shadow an entity's own column (id, name, driver, enabled, is_elevated, …).
  • Secure attributes cannot be compared, so a bulk value write overwrites every selected secret with what you typed. The dialog says how many that is.
  • Turning secure off does not work for names GEM always treats as secret (password, anything ending _api_key, and the rest of the auto-detected list). Those rows report as skipped rather than reporting a success the next read would contradict.
  • Each row written emits an attribute-change event, exactly as a single edit does. A flag change on two hundred rows makes any trigger watching those attributes evaluate two hundred times, even though no value moved.

Copy to…

Copy to… takes one entity's attributes and writes them onto as many others as you tick — the twenty-identical-fixtures case.

Pick the source, tick the attributes to copy, tick the destinations (filtered to the same driver by default), and choose whether to overwrite attributes the destination already has. Identity attributes (ip_address, mac_address, serial_number, url, …) and secure attributes start unticked: copying an address across a fleet points every one of them at the same piece of hardware, and sharing one password is a decision worth making deliberately rather than by default.

Audit

The registry has always described what an attribute should be. The Audit tab is what compares that description against what is actually stored, for one system target or all of them at once.

Findings are ordered by what they cost you:

FindingWhat it means
Stored in plain textThe registry — or the sensitive-name rule — says this value is a secret, and the row is not marked secure, so it sits in the database readable. One click marks it secure and encrypts it in place
Orphaned rowsThe entity these rows belonged to no longer exists. Nothing reads them
Missing required attributesThe registry marks the attribute required for that entity's driver and the entity does not have it, or it is blank. Where the registry has a default, one click writes it
Value outside allowed optionsThe stored value is not one the registry lists, so its dropdown cannot show it. Fixed by hand — which of the allowed values it should be is a decision
Value type disagrees with registryThe catalog names a type for the attribute and the row stores a different one. Entries whose catalog type is the default (string) are not compared — that is the registry having no opinion about the type, not a disagreement with the row
History off where registry enables itNothing is being recorded for trending or reports
Writable where registry marks read-onlyA driver owns the value, but the row does not say so
Belongs to another driverThe name is registered, but only for drivers outside this entity's chain — usually left behind when a device was moved to a different driver
Not in the registryNo catalog entry for the name. Not an error: any name works. It just gets no description, no type auto-fill and no dropdown

Each finding can be fixed one at a time or all at once, and every fix routes through the same paths a manual edit uses. Re-scan afterwards; the counts are exact even when the list is capped.

A finding names a row it does not show, and the buttons beside it are one click from changing it — so the entity name in each row is a link that opens that record in a reference modal, leaving the scan, the expanded finding and the rest of the list where they were. Orphaned rows are the exception: the finding is that the entity no longer exists, so there is nothing to open.

A scan reads at most 50,000 attribute rows. Past that it says so in a banner above the findings and reports what it did check — scope the scan to a single system target to see the rest.

note

A device whose driver is not loaded — disabled, unlicensed, or a driver this build does not ship — has no known driver chain, so the audit matches its attributes on name alone rather than accusing them of belonging to another driver.

Registry tab

The catalog itself: every attribute GEM knows about for a target, per driver context, searchable by name, description, unit or category, with a count of how many attributes in this system actually use each entry. Filter to In use only to see what this site touches; the rest is what the drivers you don't run would use.

Expanding an entry shows its category, unit, metric role, allowed values, dynamic option source and suggested alarm definition, plus a button that takes you to the attributes using it.

The registry ships with GEM and is rewritten from the seed on every restart, so it is a reader, not an editor — with one exception. Description can be edited here, and an edited description is preserved across restarts instead of being reset to the shipped text. Everything else is shown read-only rather than offered and then silently reverted at the next boot.

Deletion Cascade

When an entity that carries attributes is deleted (a zone, device, site, site space, subsystem, UI, variable, remote, or user), all of its attribute rows are deleted with it — including secure values such as passwords and API keys, which never outlive the entity that owned them. The daily database maintenance job also sweeps out any attribute rows orphaned by deletes that predate this cascade. Global system attributes (target ID 0) are never touched by either path.

Attribute Editor

Most day-to-day attribute work does not happen on this page at all — it happens on the Attributes tab of a device, zone, subsystem or UI, which shows only that one entity's attributes. That inline editor uses a click-to-edit pattern:

  • The table columns are Name, Value, Type, Read Only, History, Secure, Updated and Actions. Updated shows how long ago the value last changed (12s ago, 4h ago) and flashes when a value moves in the last ten seconds.
  • Double-click any row (or click the edit pencil in Actions) to enter edit mode for that row. The row highlights amber and swaps in full input fields.
  • Click Save (disk icon) to save that single row, or Cancel (×) to discard changes. Cancel restores the row exactly as it was, and a new row that was never saved disappears.
  • The Read Only, History, and Secure switches write as soon as they're flipped — no need to enter edit mode. On a row that is being edited they hold and are written with the row's own Save.
  • Click Add to add a new attribute. It opens in edit mode at the top of the list under a New attribute heading, with the registry-aware name picker (grouped by integration, with the registry description as a hint). Selecting a registered name auto-fills the type and, where the registry supplies them, the History / Read Only / Secure flags, a default value and a dropdown of allowed values.
  • Attribute names must be unique per entity — picking or typing a name that already exists on another row is rejected immediately with an error, and the name field is cleared.
  • Click Save Edits to commit every row currently open for editing at once. The button shows how many rows that is, and is disabled when none are open.
  • The Reload button at the bottom is named for what you are editing (Reload Device, Reload Zone, …) and pushes the saved attributes into the running system — use it after changing anything a driver reads at connect time (IP address, port, credentials, register maps). Back leaves without reloading.
note

The name of an existing attribute can't be changed. Once saved, the Name cell is locked and only the picker on a new row is editable — to rename, add the new name and delete the old row.

Toolbar

Above the table:

  • Refresh — re-reads this entity's attributes from the server.
  • Import — reads a JSON file of attributes and writes every entry onto this entity, after confirming the count.
  • Export — downloads this entity's attributes as <target>_<id>_attributes.json (for example device_14_attributes.json). Export from a commissioned device and import onto an identical one to copy its whole configuration in a single step.
  • Add — as above.
  • Live Values — a switch that re-reads every value every two seconds so you can watch feedback arrive while you exercise the hardware.
  • A filter box appears once the entity has more than five attributes; it matches on name, value or type and shows a "12 of 80" count.
warning

Import overwrites. Every attribute in the file is written onto this entity, including secure values, and there is no per-row confirmation — only a single "Import N attributes?" prompt. Exported files contain the decrypted value of every secure attribute, so treat an export of a device that holds credentials as a credential file.

note

Live Values and editing are mutually exclusive, and GEM switches between them for you: turning Live Values on closes any rows you had open for editing, and double-clicking a row to edit it turns Live Values off. Polling also pauses while the browser tab is in the background.

Grouping

Rows are grouped by their registry category — Configuration, Network, Authentication, AV, Climate, Lighting, Media, Security, Info, then State, with anything the registry doesn't know under Other. Each group header shows its row count and collapses.

On an entity with more than 15 attributes, State and Other start collapsed — those are usually the long tail of driver telemetry — and everything else starts open. A shorter list opens flat, exactly as before. Filtering expands every group that contains a match.

Secure values

A value marked Secure shows as *********** in the list and edits behind a password field with a reveal (padlock) button, so opening a row doesn't put an API key or token on screen. The copy-value button is hidden for secure rows. The same masking applies to any attribute literally named password, whether or not its Secure switch is on yet.

History from the row

When a row has History on, a clock icon appears in its Actions column. Clicking it opens the full Attribute History chart for that one attribute in a modal, without leaving the entity you're commissioning.

Duplicate IP warning

Saving an attribute named ip whose value is already the ip of some other device or zone raises a confirmation naming every entity that already uses it. This is a warning, not a block — dual-purpose gear that legitimately shares an address can be confirmed through. Note it only fires for the exact name ip, not ip_address.

Creating an Attribute

To create a new attribute from this page:

  1. Select the System Target (e.g., zone) in the filter bar.
  2. Click New. The form opens with the heading New Attribute.
  3. Under Target Configuration:
    • System Target — pre-filled from the filter bar; change it here if needed.
    • Target Object — the specific zone, device, subsystem, … the attribute belongs to. For system, variable and address the only choice is all.
  4. Under Attribute Details:
    • Name — typed free-hand here, and normalized as you leave the field: uppercase becomes lowercase, spaces and punctuation become underscores. This form does not suggest registry names — that picker is in the per-entity editor described above.
    • Value Type — one of string, int, float, bool, json, date. Changing it converts what you've already typed where it can (a JSON object becomes its text form and back).
  5. Under Value, enter the value. The editor matches the type: a switch for bool, a number field for int/float, a structured object editor for json, a date/time picker for date.
  6. Under Value Options (hidden for bool), optionally add a fixed list of allowed values — see Value Options.
  7. Under Settings, set the Read Only, Secure and History switches.
  8. Click Create Attribute (on an existing row the button reads Update Attribute). Cancel returns to the list.

All four of System Target, Target Object, Name and Value Type are required; leaving one blank outlines it in red and shows a message naming the missing field.

When you reopen an existing attribute, an information strip under the form shows its ID, target, previous value, and when it was last updated.

Zone Picker

Attributes that point at a zone hold a zone ID, which is not something you should have to look up. Whenever the attribute name contains zone, GEM offers a picker instead:

  • On a string, int or float value — in this form and in the per-entity editor — a small search button sits next to the value field. It opens a subsystem → zone browser, and choosing a zone writes that zone's ID into the value.
  • On a json value in this form, a labelled Select zone(s) button appears above the value editor and the browser opens in multi-select mode, so you can accumulate several zones (shown as removable chips) and write them as an array. The Select multiple zones switch inside the browser turns that mode on and off.

The picker is suppressed when the attribute already has a dropdown of its own (value options or a live option source) — that list is the more specific answer.

Triggers Tab

The attribute form has two tabs: Attribute (the form described above) and Triggers. The Triggers tab lists every attribute trigger that reads the attribute being edited — matched exactly the way the server evaluates trigger conditions:

  • Direct — the trigger condition targets this entity and attribute name.
  • Subsystem-wide — a subsystem-scoped condition covering all zones in the attribute's parent subsystem (including name contains and device narrowing).
  • Name pattern — a [name:*] glob condition matching the entity's name.
  • Driver scope — a [driver:*] condition matching the entity's driver (zones resolve through their bound device).
  • Gate conditions — triggers whose gate (run/skip) condition reads the attribute, including [self] gates when the primary condition also fires for this entity, and [parent_subsystem] gates when you are looking at a subsystem attribute.

A trigger whose condition matches multiple attributes is listed under every attribute in its set, not just the primary; its summary reads any of [name, name, …].

Each row shows how the trigger references the attribute (condition vs. gate, with the operator and comparison value), the trigger's action types, its enabled state, and when it last fired. Click Edit to jump to that trigger in the Triggers editor (/admin/triggers?edit=<id> deep link).

The list is computed when the tab is opened; use Refresh after changing the target or name fields.

Attribute Properties

Name

Attribute names are lowercase letters, numbers and underscores only — ip_address, heat_setpoint, stream_url. Both editors normalize what you type when you leave the field, so Heat Setpoint becomes heat_setpoint on its own; if the result isn't what you meant, that conversion is why.

The name is what a driver looks for, so match the one the picker suggests rather than a near-miss of your own.

Reserved names

An attribute is merged onto the live entity in memory, so a name that collides with one of the entity's own columns would overwrite that column everywhere the rest of the system reads it. These names are refused:

id, name, label, description, sort_index, enabled, driver, is_elevated, controller_id, subsystem_id, zone_id, device_id, macro_id, command_set_id, device_type_id, device_subtype_id

Each of these is a field on the row itself — edit it on the entity's own editor page, not as an attribute. is_elevated, driver, enabled and controller_id matter most: they decide who may command a device and what it connects to. Drivers write attributes from data devices report, so a device that named one of these fields could otherwise strip its own elevation gate or repoint itself.

Credential attributes (username, password, api_key, ip, port, …) are not reserved — those are ordinary attributes and are how device configuration works.

Value Types

Every value is stored as text; the value type tells GEM how to hand it to a driver, a trigger comparison or a macro. There are six:

TypeShown in the per-entity editor asNotes
stringtextAnything. No conversion — "Living Room", "192.168.1.100", "HDMI 1"
intwhole numberA decimal entered here is rounded, not rejected: 72.6 reads back as 73
floatdecimal number72.5, -10.25, 3.14159; scientific notation (1e3) is accepted
booltrue/falseEdited with a switch
datedate/timeParsed as a date; the editor offers a date/time picker when creating one
jsonjsonAn object or array, edited in a structured editor rather than as raw text
warning

bool recognises exactly one true value: the text true. 1, on, yes and TRUE all read back as false. If a driver or an external integration writes 1 into an attribute you have typed as bool, the system will treat it as off. Use string and compare against 1 if you can't control what the device sends, or fix the write to send true/false.

note

Some older rows and some drivers use the longer spellings integer, boolean and datetime. They mean exactly the same thing as int, bool and date and behave identically — the per-entity editor writes datetime where this page's form writes date. There is nothing to migrate.

Example json values:

{"red": 255, "green": 128, "blue": 0}
["HDMI1", "HDMI2", "Component"]

Large values

Values are unbounded text — a multi-kilobyte JSON snapshot (an energy baseline covering every zone, a widget layout) saves like any other value. Change notifications for a value too large to fit in a database notification payload carry the attribute id only; the server re-reads the row and dispatches it through the normal change path, so subscriptions, triggers, and in-memory entities update the same way. The only difference is one extra read on that write.

Large values are truncated to 200 characters in the server log (with the full length noted) so a single write can't bury the surrounding log lines. The stored value is never truncated.

Read Only

Read Only pins a value against the system, not against you. With it on, ordinary writes are refused and logged — a driver reporting device feedback, a macro's Set Attribute step, an automation trigger action. The admin editors on this page and on each entity's Attributes tab always override it, so you can still edit a read-only attribute by hand; that is deliberate, and it is the only way to change one.

Use it when a value must survive whatever the device says:

  • A commissioning value you entered that a chatty driver keeps overwriting — a corrected model string, a friendly input name.
  • A calculated or manually surveyed figure (a meter's CT ratio, a fixture count) that nothing should re-derive.
  • A device-reported field you have deliberately corrected and want to stay corrected.

Around sixty registry-known attributes that are pure device identity or capability read-out arrive with Read Only already set — model, serial_number, hardware_version, software_version, input_count, video_resolution, a thermostat's system_modes / fan_modes list, and similar.

note

Read Only is not a permission. It does not stop another operator editing the value, and it is not a security control — attribute writes of any kind already require an elevated (admin) sign-in. For "nobody may see this", use Secure.

Value Options

Define a fixed set of allowed values for an attribute. When value options are set, the value field becomes a dropdown instead of a free-text input, everywhere that attribute is edited — including the trigger condition builder.

  • Available for all types except bool (which already has true/false)
  • Add options one at a time in the attribute form: type the option, press Enter or click Add
  • Each option appears as a removable tag; click its × to drop it
  • Adding an option that's already in the list is refused with an "Option already exists" message

Use cases:

  • Restrict a mode attribute to home, away, vacation
  • Limit an input attribute to known source names
  • Constrain a color_temp attribute to preset values
warning

Value options are a hard list, not a suggestion. Once set, the editor offers no way to type a value that isn't on it — so include every value the device can legitimately report, or a driver's feedback will have no matching entry to display. Clear the list to get the free-text field back.

Dynamic Option Source

A few registry-known attributes fill their dropdown live from the rest of the system rather than from a fixed list you typed. Where one does, the value field becomes a searchable picker with a Loading… placeholder and a spinner while it fetches, and the read-only display shows the resolved name instead of the raw ID that's actually stored. Three ship today:

  • camera_device_id — lists the cameras defined on this site.
  • camera_zone_id — lists the zones under the camera subsystem, with each zone's address as the hint. The same picker appears everywhere the link is set: on an access device (badge snapshots) and on a zone or subsystem (inline feed on its control).
  • lutron_control_station_id — asks the Lutron processor for its control stations and lists them with their area.

The mechanism behind them can also list rows from any table, values from a site setting, or a fixed list, and the same descriptor drives widget configuration dropdowns — so a new one appears wherever that attribute is edited without any per-screen work. These are part of the shipped catalog and are not something you configure from this page.

tip

If a live picker shows an error instead of a list of choices, the source it queries is unreachable — bring that device online before assuming the attribute is broken. The stored value is untouched either way; only the list of choices failed to load.

Metric Role

Power and energy attributes can carry a metric_role tag (e.g. power.solar, power.grid, power.battery, power.load, battery.soc) that maps a driver's vendor-specific attribute name onto a shared, canonical concept. This lets a single generic dashboard read heterogeneous drivers — production_now_w (Enphase), pv_power (SolarEdge) and solar_power_w (Tesla) all resolve to power.solar — without per-driver code. A companion metric_sign: 'inverted' flag flips the value when a driver's native sign is the opposite of the canonical convention.

Roughly forty attributes across the energy drivers carry a role today. The tags are part of the shipped catalog rather than something you set on this page, and they are what the Power Flow widget reads. See Power Management for the full role list, sign conventions, and how a new driver gets tagged.

Alarm Template

Some registry entries carry a curated alarm template — a ready-made alarm definition for that attribute, complete with severity, comparison, deadband, on/off delays, latching and message. Templates ship for battery (low battery), leak, smoke, co, gas, and panic. When the site actually starts reporting one of those attributes, its template surfaces as a one-click suggestion on the Alarms page's Definitions tab; accepting it opens the alarm editor pre-filled for you to review and adjust. Templates are never activated on their own, and like metric roles they are part of the shipped catalog rather than something you set on this page.

History

With History on, every change to the value is written to the history log and can be charted.

Around seventy-five attributes have it on already without you doing anything: every entry the shipped catalog flags for history turns itself on the first time a driver writes it on a zone or device. That covers the ones you'd want — temperature, setpoint, humidity, level, state, mode, source, battery, power, connected, power_state, input, signal_strength, plus the energy and water meters. You do not need to hunt for them and tick the box.

Turn it on by hand for anything else you want a record of, and off for anything the platform turned on that you don't want — your setting on the row is respected either way.

Weigh it against how often the value moves:

Change rateExampleCost
OccasionalSetpoint, mode, arm stateNegligible — always worth it
RegularTemperature, humidity, levelModest; this is what history is for
ConstantMotion sensors, sub-second metersHeavy — thousands of rows per device per day

For fast analog values there is an automatic sampler that keeps the volume sane; see History Deadband below. How long history is kept is set on the Data Retention page.

View attribute history at Insights > Attribute History, or click the clock icon on the row itself in the per-entity editor.

History Deadband

Fast-polling analog meters generate far more history than they carry information. A 10-second-poll energy monitor writes roughly 7,800 power rows per channel per day, and most of those record a change of a few watts on a circuit drawing hundreds — jitter, not signal.

GEM samples those down automatically. A new value is recorded only if it differs from the last recorded value by at least a percentage of the larger of the two, so the threshold scales with magnitude and behaves the same in both directions. Drift is measured against the last row actually written, not the last value seen, so a slow ramp of small steps still gets recorded once it adds up.

The deadband is deliberately narrow, and never applies to:

  • Anything but float. Integer attributes in the auto-history set — temperature, setpoint, level, humidity, battery — are quantized values where a change of 1 is the smallest meaningful step. Strings, booleans, and JSON are untouched.
  • Attributes outside the registry's auto-history set. Only history the platform turned on by default is sampled down. In practice this means the metering floats (power, energy_kwh, grid_power, flow_rate, water_usage, and similar).
  • Transitions to and from zero. "The circuit turned off" and "the circuit came back" always record, however small the step.
  • A flat value held past the heartbeat interval. A sample is forced through so a stalled series still has a heartbeat in the data.

Two system attributes tune it (set them under System Target: system):

AttributeTypeDefaultMeaning
history_deadband_percentfloat2Minimum change, as a percentage of the larger magnitude, required to record a sample. Set to 0 to disable the deadband entirely and record every sample.
history_deadband_max_intervalfloat300Heartbeat, in seconds. A flat value is recorded anyway once this long has passed since the last recorded sample.

Settings are cached and re-read about once a minute, so a change takes effect without a restart. If either attribute is absent the default applies.

warning

The second exclusion cuts both ways. An attribute with history enabled on its own row but no registry entry enabling history is not merely un-sampled — it is unreachable by the deadband entirely, and history_deadband_percent has no effect on it at any value. Every poll writes a row. On one site six such float attributes on a single device produced 10.8% of a 2.7-million-row history table.

Diagnostics reports these under History attributes the deadband cannot reach, and Top Attribute History Writers marks them with a no deadband badge. To bring one under the deadband, give it a registry entry with history enabled and value_type float, or give it a per-attribute override — the override reaches it regardless of registry membership.

Per-attribute overrides

One global percentage suits a room temperature and a revenue meter equally badly. The Tune button on each row of Top Attribute History Writers gives a single attribute name its own settings, applied across every entity writing it:

  • its own percentage, absolute floor and heartbeat, in place of the global pair — and unlike the global deadband, this reaches attributes with no registry entry, and integer attributes when you ask for it explicitly
  • or no history at all for that attribute, without touching the history flag on hundreds of individual attribute rows

Overrides are stored in the history_policy system attribute, keyed <system_target>:<name>:

{
"device:instant_power_w": {"mode": "deadband", "percent": 10, "absolute": 15, "max_interval": 900},
"device:rssi": {"mode": "off"}
}
FieldMeaning
modedeadband or off. Anything else is ignored
percentRelative threshold for this attribute. Omit to use history_deadband_percent
absoluteMinimum change in the attribute's own units — watts, degrees, litres. The percentage may raise the threshold above this floor but never below it. Omit for percentage-only
max_intervalHeartbeat in seconds for this attribute. Omit to use history_deadband_max_interval

absolute is the field that fixes metering. A relative threshold is structurally wrong for power, which spends much of its time near zero: at a mean of 55 W, 2% is roughly 1 W and over half the recorded changes were smaller than 10 W — jitter that clears a percentage threshold every single sample. No value of history_deadband_percent filters that; a 10 W floor removes all of it while leaving every real load step intact.

An attribute absent from the map behaves exactly as it always has — the map is only consulted for names that appear in it. Edit it from the Data Retention page rather than by hand; a malformed entry is ignored rather than guessed at.

tip

If a specific attribute needs every raw sample regardless — a revenue-grade meter, a compliance trace — give it a per-attribute override with a percentage of 0, which records everything for that attribute while leaving the rest of the site sampled. Setting history_deadband_percent to 0 does the same thing globally.

Common Attribute Names

These are the names GEM itself uses across integrations, so they are the ones worth knowing. Every driver adds its own on top — the name picker on an entity's Attributes tab shows exactly what that entity's driver supports, grouped by integration, and is the authoritative list for the gear in front of you.

note

There is no fixed vocabulary. If a name isn't here or in the picker, type it — it works. This is a list of conventions, not a schema.

Device — connection and credentials

NameTypeNotes
ipstringThe address most drivers actually read. Saving a duplicate raises a warning naming the other device
ip_addressstringUsed by some integrations instead of ip — check the driver's page
portintTCP/UDP port
mac_addressstringPhysical address, where a driver reports or needs one
usernamestring
passwordstringEncrypted automatically
api_key, token, access_token, refresh_tokenstringEncrypted automatically
client_id, client_secretstringOAuth integrations; client_secret is encrypted
ca_cert, client_cert, client_keystringTLS material; client_key is encrypted
pin, otp_code, two_factor_codestringEncrypted automatically
unit_idintModbus slave / unit address

Serial settings are per-driver, not universal. A driver that opens a serial port directly takes the port and speed on the device — Modbus RTU uses comm_port (e.g. /dev/ttyUSB0) plus baud_rate, Zigbee puts the same path in address with baud_rate alongside it. Gear reached through an IP-to-serial controller (a Global Caché iTach and similar) carries no serial attributes at all; the port settings live on the controller. Check the driver's own page in the Drivers section of this reference rather than assuming either shape.

Device — reported state

NameTypeNotes
connectedboolThe universal online/offline flag; drives Device Health
power_statestringon / off
statestringDriver-defined state string
inputstringCurrently selected input
error, last_errorstringLast fault the driver saw
model, serial_number, hardware_version, software_version, firmware_versionstringIdentity; most arrive read-only
update_availableboolFirmware update pending
signal_strength, batteryintWireless and battery-powered gear
current_media, title, artist, album, position, shuffle, repeatmixedMedia players
volume, mute_state, max_volumemixedAV devices

Zone — state and control

NameTypeNotes
statestringThe zone's primary state — on/off, open/closed, locked/unlocked, per subsystem
levelint0–100. Brightness, volume, shade position
brightnessintLighting-specific level where a driver reports both
powerfloatInstantaneous watts (metering)
battery, battery_levelintPercent
connectedboolZone-level online flag
sourceintThe AV source currently routed to this zone. This is a reference to a source, never free text
volume, mute_state, volume_ramp_speed, volume_controlmixedAV zones. volume_control is discrete (the default when unset), discrete_ramping or ramp — see Volume control mode
input, input_device, input_device_idmixedAV input selection
temperature, humidity, setpoint, heat_setpoint, cool_setpoint, deadbandint/floatClimate
mode, system_mode, system_modes, fan_mode, fan_modesstring/jsonClimate modes; the *_modes lists are read-only capability read-outs
leak, smoke, co, gas, panicboolLife-safety sensors — each has an alarm template ready to enable
stream_url, image_url, snapshot_uristringCamera URLs. On a zone these are treated as secrets — see Names that are secure only on one target
stream_modestringHow a camera reaches the browser — jpeg stills (default) or video, the camera's own H.264 remuxed for the browser. See Stream mode: stills or video
stream_audioboolCarry a camera's microphone to the browser, muted until a viewer presses the speaker button. video mode only, off by default. See Audio
frame_rate, resolution, frame_quality, hardware_acceleratorint/stringTune the JPEG stills path; ignored in video mode, which never decodes. See Stream Performance Attributes
camera_zone_idintCamera zone whose live feed shows inline on this zone's control (door, gate, and open/close/stop controls) — set from a camera-zone picker. On a garage door it also files that door under the camera watching it when the garage subsystem lists more than one camera (see Zone Controls). Renamed from camera_zone; existing rows are converted automatically on upgrade
sip_addressstringSIP address of the intercom or callbox at this entry (e.g. sip:front_door@192.168.1.50). Draws a Call button on the door, gate and open/close/stop controls; leave it empty and no button appears
hide_controlsboolHide the operate buttons on those same controls, leaving the camera feed and the Call button — the intercom-only entry GEM answers but must never release. Presentation only: macros, triggers, voice and the API still operate the zone. See Controls with their own settings
held_open, last_held_open_atbool/stringDoor held-open detection

Zone — behavior overrides

NameTypeNotes
open_action, close_action, stop_action, on_action, off_actionjsonReplace the standard verb for this zone with a specific command or macro — the retrofit path for hardware that needs, say, a relay pulse instead of an open. See Zones
relay_statesjsonOverride how a relay's open/closed maps to this zone's state. Also settable on the subsystem
normally_closedboolOlder single-flag form of the same idea. Drivers that poll a raw digital point rather than a relay contact (Modbus, groov I/O) use it to invert the reported state — the fix for a normally-closed contact or an active-low input
status_addressstringSeparate feedback address when a device reports state on a different point than it accepts commands
linked_zonesjsonArray of zone ids this zone fans every command out to — one master zone operating a group. See Linked Zones
status_zonestring/jsonZone id whose state this zone mirrors, or an array of ids to aggregate across a group (any active member shows active). See Linked Zones
already_on, warm_upbool/intAV warm-up handling
power_reconcile, power_reconcile_secondsbool/intRe-check and correct a zone's power state after a delay

Subsystem

Only four attributes are meaningful on a subsystem: relay_states (default relay mapping for every zone under it), deadband (climate), camera_zone_id (inline feed on the subsystem's control page) and arm_enabled (security). A subsystem's name, label, sort order and enabled state are fields on the subsystem itself — edit them on the Subsystems page, not here.

camera_zone_id takes more than one camera on the garage subsystem — a JSON array ([45, 46]) or a comma-delimited list (45,46), rendered in that order. The picker fills a single id, so a list is typed by hand. With two or more cameras, setting camera_zone_id on each garage zone groups its doors under the camera that watches them — see Zone Controls.

System — site-wide settings

Set these under System Target: system, where the only target object is all.

NameTypeNotes
db_maintenance_enabledboolNightly database maintenance on/off
db_retention_daysintDefault retention; per-store overrides live on Data Retention
attribute_history_retention_days, access_log_retention_days, request_history_retention_days, monitor_history_retention_days, alarm_event_retention_days, energy_daily_retention_days, report_run_retention_days, site_mode_history_retention_daysintPer-store retention
automation_history_success_days, automation_history_failure_daysintMacro/trigger run history, kept separately for successes and failures
history_deadband_percentfloatMinimum percent change to record an auto-history float sample; 0 disables — see History Deadband
history_deadband_max_intervalfloatHeartbeat in seconds that forces a sample through when the value is flat
history_policyjsonPer-attribute history overrides — see Per-attribute overrides
attribute_history_flood_alarm_ratefloatCombined unfiltered rows/hour that raises the Attribute History Flooding alarm; 0 disables the alarm, not the detection
disk_alarm_percent, memory_alarm_percent, cpu_alarm_percent, cpu_alarm_minutesfloatThresholds for the host resource alarms; each 0 disables that alarm, not its measurement
disk_used_percent, disk_free_gb, memory_used_percent, cpu_load_percent, database_size_mbfloatSampled every five minutes by the host resource monitor — read-only, and written only when the value changes
anthropic_api_key, ai_providers, ai_model, ai_model_voice, ai_voice_provider, ai_deferred_toolsmixedAI Assistant; the key is encrypted. ai_model defaults to claude-opus-5 when unset; ai_deferred_tools (default on) loads the assistant's tool schemas on demand instead of sending all of them with every request
access_lockdownboolSite-wide access lockdown
access_offhours_start, access_offhours_end, access_offhours_alarm_enabledstring/boolOff-hours access alerting
pin_scan_window_seconds, pin_scan_distinct_codes, pin_scan_alarm_enabledint/boolCredential-scanning detection
facial_recognition_enabledboolSee Facial recognition
backup_settingsjsonOffsite backup destinations — holds credentials, encrypted
wan_ip, alert_wan_ip_changestring/boolWAN address tracking and alerting
internal_url, mobile_app_urlstringAddresses handed to clients and the mobile app
apns_config, fcm_config, vapid_keysjsonPush notification credentials, encrypted

UI

UI attributes are how a UI's behavior is configured, and most are set from the UIs page rather than typed here. Common ones include theme, home_page, homepage, mobile_homepage, nav_layout, control_order, background_image, background_fit, background_dim, screen_timeout_seconds, screen_timeout_action, weather_locations, default_weather_location, gallery_photo_path, slideshow_interval_seconds, and the family of hide_* switches (hide_home_button, hide_climate_button, hide_voice_button, …) that strip chrome off a wall panel.

A row here is the value for that UI. The three background_* settings have no field on the UIs form — set them here to give a whole UI a wallpaper, or let each person choose their own.

Seven of these settings — theme, homepage, mobile_homepage, default_weather_location, background_image, background_fit and background_dim — can additionally be overridden for one person, as an attribute row on the auth_user target. See Personal Settings.

gallery_photo_path must never point inside static/: that directory is served without authentication, so photographs placed there are readable by anyone who can reach the controller. See Gallery.

Site and site space

mode and effective_mode carry the current site modemode is what was set on that space, effective_mode is what it resolves to after inheritance from its parent.

wifi_ssid and wifi_password on the local site (is_me) hold the house wireless network. They are written from the Connection section of the RTI page and baked into every exported RTI program's 802.11 settings; wifi_password is encrypted at rest by the _password suffix rule below.

Secure Attributes

Certain attributes are automatically encrypted for security:

Auto-Detected Names

Attributes with these names are encrypted:

  • password
  • api_key
  • private_key
  • secret
  • token
  • passphrase

Matching is case-insensitive, and works two ways:

  • Exact name against a longer built-in list that also covers common variants — gateway_password, client_secret, refresh_token, client_key, security_keys (Z-Wave network keys), network_key, application_key, code_verifier, backup_settings, and many others.
  • Suffix, so an integration that prefixes the usual word is still caught: _api_key, _apikey, _password, _passwd, _secret, _token, _private_key, _passphrase. This is what covers names like anthropic_api_key, onvif_password, and webhook_token without each one having to be listed.

A bare _key suffix is deliberately not matched — some attributes ending in _key are ordinary state (a doorbell's call_key button press), and encrypting one would silently blank working data. Real secrets ending in _key are listed by name instead. A small exclusion list protects flags that only look sensitive, such as must_change_password.

At startup, GEM retroactively marks any existing attribute whose name matches as secure and encrypts its stored value. Values written by older versions — or through API paths that never set the secure flag — are healed automatically on the next boot. Because the read paths consult the name as well as the stored flag, a not-yet-healed row is still withheld from non-elevated readers.

Names that are secure only on one target

A few attributes are a secret in one place and ordinary data in another, so they are marked secure by target rather than by name alone:

TargetAttributeWhy
zonestream_urlA camera URL. ONVIF discovery writes rtsp://user:password@host/…, so the URL is the camera credential
zoneimage_urlA camera snapshot URL, carrying the same credentials

The same names are left alone everywhere else on purpose. stream_url on a device is an AV source's stream URL that the video widgets play in the browser, and image_url appears as a field inside the current_media value a music driver writes (album art). Marking those secure would blank working features, so the rule is scoped to zones — which is where cameras live.

Existing zones are converted on the first boot after upgrade: the rows are marked secure and their stored values encrypted in place. Nothing needs to be re-entered, and cameras keep working — the server decrypts these when it resolves a stream or a snapshot. What changes is that the URL is no longer readable by a client; see What the browser is told.

Live value updates

An attribute change is broadcast to every client subscribed to that entity. Secure attributes are redacted before the broadcast: subscribers are told the attribute changed (so a cached view can invalidate) but the value, previous value, and raw value all come through empty. Reading a secure value still requires an elevated role and an explicit read — and the role alone is not enough: a session signed in by PIN gets secure values filtered out like any non-elevated session, as does writing one with set_attribute.

What a non-elevated read returns

The socket connection and the REST API apply one shared set of rules — to entity queries and to direct attribute reads by name alike — so a guest tablet, a kiosk and an outside system all see the same thing:

  • Secure rows are excluded. A query from a non-elevated session has secure=false forced into it. Asking for secure=true outright returns an empty result rather than quietly inverting the filter.
  • Sensitively-named rows are blanked even when their Secure switch was never flipped — value and previous_value come back empty. That covers rows written by an older version before the naming rule was applied.
  • Secrets nested inside a JSON value are blanked. Some attributes hold a JSON blob whose name reads as ordinary configuration while a leaf inside it is a credential — a sync profile carrying an SFTP password, or the site's Web Push signing key. The sensitive leaves come back empty and the rest of the blob stays usable, so an admin screen still gets its host, port and schedule.
  • Attributes merged onto a nested entity are blanked too. A device query that merges live state also carries the device's zones, each with its own merged attributes. Those are stripped at every level, so a camera zone's password or a LoRaWAN zone's session keys are withheld whether they are asked for directly or reached through the device that owns them. Labels, state and addresses are untouched.
  • The value cannot be used as a filter. Blanking a value on the way out still leaves whether the row came back as an answer, and a _like filter plus a count reads a PIN back a character at a time. So a non-elevated query that filters on value is refused unless it also names a specific, non-sensitive attribute — {name: 'state', value: 'on'} ("which zones are on") works, a value sweep across every row does not. previous_value is never filterable; it is a second copy of the same value.

Elevated sessions are unaffected by all of these.

Encryption Details

  • Algorithm: AES-256-GCM
  • Key storage: a key file on the controller, held outside the database — see Key Management
  • Stored form: a value that starts gem-crypt: is encrypted; that prefix is what you'll see if you ever look at a secure value in a raw database dump
  • When it happens: on every write and read of the row, so nothing in the system has to remember to encrypt

Marking something secure by hand

Any attribute whose name doesn't match the patterns above can be made secure by flipping its Secure switch. In the per-entity editor that writes straight away — the stored value is encrypted in place, no re-entry needed — and the row immediately masks itself and hides its copy button.

This is worth doing for anything a name-based rule can't see: a webhook URL with a key in the query string, an installer PIN stored under a name of your own, a customer account number a driver needs. Turning Secure back off on one of these decrypts the value again.

It does not work in reverse. Switching Secure off on an attribute whose name matches the patterns above — anything ending _password, _api_key, _token and the rest — doesn't stick: the name rule re-applies it on the write, and the boot check re-applies it again. To store something under one of those names in the clear, give it a different name.

Key Management

The encryption key lives on the controller, outside the database. A backup restored onto different hardware is undecryptable without it — every password, API key and camera URL comes back as gibberish — so back it up as part of commissioning, not as part of a disaster.

Save the key:

  1. Go to Backup & Restore (System > Backup & Restore).
  2. Click Download Keys in the encryption-keys banner at the top of the page. The banner tracks whether you've done this and shows the date.
  3. Store the file somewhere that isn't the controller — a password manager, an encrypted archive, the customer's document safe.

Restore the key onto new hardware:

  1. On the new controller, go to System > Backup & Restore.
  2. Expand Need to restore a backup from a previous installation?
  3. Choose the keys file you saved and upload it. The controller adopts those keys, and backups taken on the old hardware become restorable.
danger

Do this before you need it. There is no recovery path for a lost encryption key — not from Harness, not from a backup of the database. Every secure value has to be re-entered by hand.

Reading and writing attributes outside the admin UI

Three surfaces reach attributes without going through these screens. All three require an elevated (admin) sign-in for writes.

In a macro. The Set Attribute step writes one, and a Run Script step has the full set:

let value = await gem.getAttribute('zone', zoneId, 'brightness');
await gem.setAttribute('zone', zoneId, 'brightness', 75, 'int');

let attrs = await gem.getAttributes('device', deviceId);
console.log(attrs.ip, attrs.port);

gem.setAttribute takes an options object in place of, or after, the type when you need the flags:

await gem.setAttribute('device', deviceId, 'site_token', tokenValue, 'string', {secure: true});
await gem.setAttribute('zone', zoneId, 'commissioned_level', 80, 'int', {readonly: true});

Writing to a Read Only attribute is refused unless the call passes {force_update: true} — that's the point of the flag, and it applies to the Set Attribute step too, which does not pass it. If a Set Attribute step silently does nothing, check whether its target has Read Only on. See Macro Script API.

Over REST. The data API reads attributes and only reads them:

GET /api/data/attribute?system_target=zone&target_id=123&name=brightness

There is no REST endpoint that writes an attribute — POST, PATCH and DELETE on /api/data/… are not implemented, and /api/control/… only accepts command and macro. To change an attribute from an outside system, call a macro that contains a Set Attribute step. See REST API.

Non-elevated API sessions never see secure attributes: a query that asks for one comes back empty rather than partially filtered, and the value column cannot be used as a search filter. See What a non-elevated read returns.

Over the socket connection. Attribute reads and writes are available to connected clients and are what the admin UI itself uses. See WebSocket API for the exact events and their arguments.

Attribute Triggers

Any attribute change can start automation. This is the main reason attributes matter beyond configuration — a driver writing motion on a sensor zone is what makes the lights come on.

  • A motion sensor's state goes to on → run the evening lighting macro
  • temperature on a mechanical-room zone rises above a threshold → send an alert
  • A panel's arm_state becomes armed_away → run the away scene
  • connected on a critical device goes false → notify

See Triggers to build one, and the Triggers tab on this page to see which triggers already read a given attribute.

Practices that hold up on a job site

  1. Take the name the picker offers. A driver reads the exact name it was written against. ip and ip_address are different attributes, and a driver that wants one will not find the other. When the picker suggests a name, that is the name that driver reads.

  2. Pick the type the value really is. A setpoint typed as string compares as text in a trigger, so 9 is "greater than" 80. Numeric values belong in int or float.

  3. Reload after changing connection settings. Changing an IP, port, credential or register map on a device edits the stored value; the running driver keeps using what it connected with until you press Reload Device.

  4. Turn History on before the problem, not after. History only records changes from the moment it is enabled. If you are chasing an intermittent fault, enable it on the suspect attribute and leave it a day.

  5. Use Read Only to defend a commissioned value that a chatty driver keeps overwriting — not as a permission.

  6. Let the secure naming do its job. Name a credential <something>_password, <something>_api_key or <something>_token and it encrypts itself. A credential named cfg1 does not.

  7. Export before you experiment. The per-entity editor's Export gives you a one-click restore point for a device's whole configuration before you start changing register maps.

  8. Check the Triggers tab before deleting. It tells you exactly which automations read the attribute you're about to remove — including the pattern and driver-scoped ones you would never find by searching.

  9. Delete what a swapped-out device left behind. Attributes follow their entity when it's deleted, but attributes you added by hand to a device that is still there — a stale register_map from the previous model — will keep being read.

Dynamic Attribute Values

Dynamic attribute values use bracket expressions to reference other attributes, variables, or cross-entity values at runtime. They are resolved in macro steps, trigger conditions, and scheduled macros.

Syntax

All dynamic values start with [ and end with ], optionally followed by arithmetic and clamping.

Local Attribute Reference

[level] → value of 'level' on the current entity
[brightness]+20 → brightness + 20

Variable Reference

[$sunrise] → value of the 'sunrise' system variable
[$brightness]+10 → brightness variable + 10

Cross-Entity Reference

[zone.5.level] → level attribute of zone 5
[device.12.temperature] → temperature attribute of device 12
Secure attributes are not readable this way

A reference to a secure attribute — a password, API key, private key or any other sensitive name — is not resolved. The expression is left exactly as written ([device.12.password] stays literal text) and a warning is logged.

Drivers hold the decrypted credential because they need it to connect. Dynamic values are substituted into fields that then go somewhere — an email body, an SMS, a web request URL — so resolving one there would turn template substitution into a way to read back secrets that cannot be read directly. Leaving the token visible is the deliberate failure mode: the step still runs, and an obviously unsubstituted placeholder shows up in the result.

Arithmetic Operators

Append an operator and operand after the closing bracket:

OperatorExampleResult
+[level]+20level + 20
-[level]-10level - 10
*[level]*2level × 2
/[level]/3level ÷ 3

Clamping

Constrain the result to a range with :min:max:

[level]+50:0:100 → add 50, clamp between 0 and 100
[level]*3:0:200 → multiply by 3, clamp between 0 and 200
[level]:0:50 → clamp raw value between 0 and 50 (no operator)

Date Arithmetic

For date type values, the offset is in minutes:

[$sunrise]+30 → 30 minutes after sunrise
[$sunset]-15 → 15 minutes before sunset

Dynamic Value Builder

Building these by hand is error-prone, so the Command step in the macro editor has a builder. Click the gear icon beside a command argument and a Build Dynamic Value dialog opens. Choose a source:

  • Static — a plain literal value
  • Local Attribute — an attribute on the entity the step is acting on
  • Cross-Entity — pick a zone or device, then one of its attributes
  • Variable — pick an automation variable

Then set the optional arithmetic operator and operand and the clamp range, and click Apply. The finished expression is written into the argument for you.

Notes on evaluation

  • Cross-entity references reach zones and devices only — [zone.5.level] and [device.12.temperature] work; there is no [subsystem.…] or [ui.…] form.
  • A reference to something that doesn't exist is left in place as literal text rather than becoming blank, so an unsubstituted [level] in a result means the attribute wasn't found.
  • Dividing by zero, or doing arithmetic on a value that isn't a number, yields NaN rather than an error.
  • On an int-typed value the arithmetic result is rounded.

Copying and moving attributes

Duplicating a device or zone's configuration

Use Copy to… on this page: pick the source, tick what to copy, tick the destinations. That is the one-to-many path, and it deliberately leaves addresses and passwords unticked.

For one-to-one, Export on the source entity's Attributes tab and Import on the target carries every attribute, including secure ones, so a second identical device needs only its address changed afterwards.

The AI Assistant can also clone a zone — the zone row plus every attribute on it, optionally to several new names at once, with per-clone overrides for the fields that differ (address, device, space). Ask it in plain language: "clone the kitchen island light zone for the dining room".

Mirroring one live value onto another

Don't script this — use the Linked Attributes tab, which copies a source attribute onto a destination every time the source changes.

Renaming an attribute

There is no rename. Add the attribute under its new name with the same value, update whatever reads it (check the Triggers tab first), then delete the old row.

Troubleshooting

The attribute won't save

  1. Read Only is on and you're writing from a macro or driver. The admin editors override Read Only; a Set Attribute step does not, and its write is discarded. Turn Read Only off, or set the value from the admin UI.
  2. The name is reserved. The write is refused outright for the names listed under Reserved names — those are fields on the entity itself, edited on its own page.
  3. The name isn't valid. Names accept lowercase letters, numbers and underscores. The form rewrites what you type as you leave the field; if the result isn't what you expected, that's why.
  4. Another row already has the name. Names are unique per entity; the picker clears itself and shows an "already exists" error.
  5. You aren't signed in as an administrator. Attribute writes require an elevated session — including from the API. A PIN-unlocked session does not count as elevated.

Older versions rejected a write whose value was larger than the database notification payload limit, logging payload string too long. Large values now save normally — see Large values.

The value never changes

  1. The device isn't reporting. Check connected on the device and confirm the driver is polling or subscribed. Watch it live with the Live Values switch while you operate the hardware.
  2. The driver doesn't parse that feedback. The device may be sending something the driver doesn't map to this attribute — the driver's own page lists what it reads.
  3. Something else is overwriting it. A trigger action or macro may be writing the attribute back. The Triggers tab lists everything that watches it; a trigger that also writes it will be there.
  4. You reloaded nothing. After changing connection settings, press Reload Device — the running driver keeps its old settings until you do.

No history is being recorded

  1. History isn't on for that row — check the switch in the per-entity editor.
  2. Retention already pruned it. Attribute history defaults to a short window; check Data Retention.
  3. The deadband sampled it out — see below.

History looks sparser than expected

Small changes on a fast-polling analog value are sampled out by the History Deadband — by default a float the platform put history on must move at least 2% before a new row is written, with a forced sample every 5 minutes and every crossing of zero. Set history_deadband_percent to 0 if you need every raw sample. Integer, string, and boolean attributes are never affected, and neither is history you turned on yourself.

Secure values come back as gibberish

The controller is using a different encryption key than the one that wrote them — the usual cause is a database restored onto new hardware without its keys file. Upload the original keys on the Backup & Restore page; see Key Management. If the keys are genuinely lost, every secure value has to be re-entered.

A secure value reads as blank

That's a permission result, not a fault. Secure values are withheld from any session that isn't elevated, including one unlocked by PIN, and they are stripped from live update broadcasts entirely. Sign in as an administrator.

Linked Attributes Tab

The Linked Attributes tab on the Attributes page mirrors one attribute onto another: whenever the source attribute changes, its value is copied to the destination. Typical use is feeding a status zone's state from a security panel device attribute, or mirroring a sensor reading onto a UI-level attribute so a panel can display it.

The tab opens on the list of existing links, each row reading source entity · attribute → destination entity · attribute, with edit and remove icons. Add Link opens the editor, Refresh re-reads the list.

The editor has two halves — Source — the attribute to watch and Destination — receives the mirrored value — each with Type, Entity and Attribute pickers. The attribute pickers list the names that entity already carries but also accept a name you type, which is how you point at a destination that doesn't exist yet: it is created the first time the source changes. Click Save Link.

Links take effect immediately on save — no reload needed.

note

A link is one-way, but writing the destination is itself a change, so links chain: A → B plus B → C carries a value all the way to C. Pointing two links back at each other doesn't spin — a write that doesn't change the value stops there — but it does mean whichever side moves last wins, which is rarely what anyone intended. Mirror in one direction.

The link definitions live on the destination entity as an attribute named linked_attribute. You'll see it in that entity's attribute list; edit it from this tab rather than by hand, and it disappears on its own when the last link into that entity is removed.