Johnson Controls Metasys
GEM connects to Johnson Controls Metasys ADS / ADX / OAS servers through the Metasys REST API. Each Metasys object (a BACnet point behind an NAE/NCE/SNE engine, an AV/BV/analog value, a schedule value) can be surfaced as a GEM zone, with present-value reads and writes flowing over HTTPS with a bearer token.
Use this driver when a site already has a Metasys front end managing the mechanical plant and the goal is to surface a handful of setpoints, fan/lighting overrides, or space temperatures on a GEM dashboard — without dropping a second BACnet client onto the JCI trunk.
This is a supervisory integration. GEM talks to the Metasys server, not directly to the field bus. Anything the Metasys server does not publish as an object is not reachable here. For direct field-bus access, use the BACnet integration instead.
Prerequisites
- A Metasys server (ADS, ADX, or OAS) at release 10.0 or later. The REST API ships with the server — there is no separate install or license part number to add, but older releases expose older API versions (see
api_versionbelow). - A Metasys user account authorized for API access. Local Metasys accounts work; Active Directory accounts must be entered in the exact domain form the site uses to sign in to the Metasys UI.
- The account needs write authorization on any object GEM will command. A read-only account can still poll zone state — writes come back as
http 403. - Network reachability from the GEM controller to the Metasys server on the HTTPS listener (normally 443).
Setup
- Add a device with driver
metasys. - Set
ipto the Metasys server hostname or IP, andportto the HTTPS listener (default443). - Set
usernameandpasswordto the API-authorized Metasys account. The password is encrypted at rest. - Set
api_versionto match the server release —v4on Metasys 11/12,v3orv2on older servers. A mismatch shows up as every call returninghttp 404. - Save and enable the device. On connect the driver posts to
/api/<version>/loginand stores the returned bearer token. - From the script console, run
get_objectsto page the server's object list. Each page is indexed in memory so item references become usable as zone addresses. - Walk deeper with
get_childrenon any object id to find the specific points you want. - Create a zone per point, with
zone.addressset to the object id (GUID) or an indexed item reference. - Set the zone's
point_type(andon_value/off_valuefor binary points) soon/off/set_levelencode the value the way that object expects.
Attribute Reference
Device
| Name | Type | Required | Default | Notes |
|---|---|---|---|---|
ip | string | yes | — | Metasys server hostname or IP. |
username | string | yes | — | API-authorized Metasys account. |
password | string | yes | — | Encrypted at rest. |
port | int | no | 443 | HTTPS listener port. |
protocol | string | no | https | Metasys publishes over HTTPS; only change this for a lab proxy. Self-signed certs are accepted. |
api_version | string | no | v4 | API version segment in the URL (v2–v5). Match the server release. |
status_interval | int | no | 60000 | How often each zone's point attribute is re-read, in ms. |
Zone
| Name | Type | Required | Default | Notes |
|---|---|---|---|---|
address | string | yes | — | Object id (GUID) or an indexed item reference. |
point_attribute | string | no | presentValue | Which Metasys attribute this zone reads and writes. |
point_type | string | no | bool | bool, number, or string — controls how on/off/set_level encode the written value. |
on_value | string | no | — | Value written by on. Leave empty for the point_type default (true / 1). |
off_value | string | no | — | Value written by off. Leave empty for the point_type default (false / 0). |
Zone Address Format
zone.address is a Metasys object id — a GUID:
11111111-2222-3333-4444-555555555555
An item reference may be used instead:
Site:NAE1/N2-1.AHU-1.ZN-T
Item references only resolve after a discovery run. The driver builds an in-memory map of itemReference → id from every get_objects / get_children / get_network_devices response, and that map is rebuilt from empty on each device reload. A GUID address always works and never depends on discovery — prefer it for anything permanent.
If an address cannot be resolved, commands return unknown object; pass an object id or run get_objects first rather than silently doing nothing.
Commands
| Command | Args | Description |
|---|---|---|
on | address | Writes the zone's on value to the point attribute. |
off | address | Writes the zone's off value to the point attribute. |
set_level | address, level | Writes level as a number to the point attribute. |
get_point | address, attribute | Reads one attribute (default presentValue). |
set_point | address, attribute, value | PATCHes one attribute of an object. |
get_objects | page, page_size, type | Pages the server object list and indexes item references. |
get_children | address, page, page_size | Lists child objects for tree discovery. |
get_network_devices | page, page_size | Lists engines / supervisory devices known to the server. |
get_alarms | page, page_size | Lists current alarms. |
get_commands | address | Lists the commands an object exposes. |
send_command | address, command_id, parameters | Invokes an object command; parameters is a JSON array. |
api_request | path, method, body | Raw authenticated call against /api/<version>/<path>. Administrator only. |
Who may write to which object
Unaddressed reads — get_objects, get_network_devices, get_alarms — are open to any caller
with access to the device. Enumeration is how an integrator finds the object to bind a zone to in
the first place.
Addressed commands — the writes (on, off, set_level, set_point, send_command) and the
reads that name an object (get_point, get_children, get_commands) — are confined to objects
this device has a zone for when the request comes from a UI, the REST API, or voice. The rule is
enforced centrally at the command dispatch layer, so it covers every command that carries an
address. An address with no matching zone returns
not authorized: address is not mapped to a zone on this device.
That matters because a Metasys server fronts far more than the points a site exposes: economizer positions, chiller and boiler setpoints, AHU safety objects, and on some installs access-control and life-safety supervision points. Without the containment, any user whose UI showed a single Metasys zone could enumerate the object tree and write to all of it.
- Administrators are not restricted — commissioning and Commands-tab testing reach any object.
- Macros, triggers, schedules and other drivers are not restricted — existing automation is unaffected, including automation that writes to an object with no zone.
api_requestis a raw passthrough carrying the driver's bearer token, so it is administrator-only outright. A non-elevated caller getsnot authorized.
path is always resolved relative to this device's /api/<version>/ prefix. An absolute URL
pasted into path is reduced to its path component and applied to this server — it cannot be used
to send the bearer token somewhere else.
Writing a setpoint
command: set_point
args: {address: "11111111-2222-3333-4444-555555555555", attribute: "presentValue", value: 72.5}
Sending an object command
send_command is the path for anything that is not a plain attribute write — overrides, releases, alarm enables. Discover the object's command ids first:
command: get_commands
args: {address: "11111111-2222-3333-4444-555555555555"}
then invoke one with its parameter list:
command: send_command
args: {address: "11111111-2222-3333-4444-555555555555", command_id: "Release", parameters: "[]"}
Authentication and Tokens
The driver exchanges username / password for a bearer token at /api/<version>/login, then:
- refreshes the token five minutes before the expiry the server reported (or every 20 minutes if the server sent no expiry),
- re-logs-in automatically if a refresh fails,
- retries a request once after re-authenticating if it comes back
401mid-flight.
Tokens are held in memory only — they are never written to the database, and a device reload starts a fresh login.
Repeated bad passwords will trip the site's Metasys account lockout policy the same way a human sign-in would. If the device is flapping on http 401, disable it before troubleshooting the credential.
Known Limitations
- Writes land at the default priority. A present-value PATCH does not carry a BACnet write priority. When a site needs an explicit priority — a true override that survives the sequence — use
send_commandwith the object's Operator Override / Release commands instead. - Binary point encoding varies by release. Some servers accept and return
true/false; others use enum strings such asattributeEnumSet.binaryPvEnum.bacbinActive. Runget_pointagainst the object, see what comes back, and set the zone'son_value/off_valueto match. - No change-of-value subscription. State comes from polling on
status_interval. Metasys does expose a stream/subscription mechanism; this driver does not consume it yet, so a large zone count means a slow round-robin — the poll paces itself at 200 ms per zone. - Discovery endpoint shapes drift across API versions.
get_children,get_network_devices, andget_alarmsare modeled against the v4 paging contract (page/pageSize,{total, items}). On an older server, a404or an unexpected body means that endpoint moved — reach it withapi_requestand the path from the release's own API reference. - Zone level is rounded. A numeric present value is mirrored to the zone
levelattribute as an integer; the unrounded value is still available fromget_point. - No alarm-spine wiring.
get_alarmsreturns the list on demand; Metasys alarms are not yet raised as GEM alarms.
Troubleshooting
| Symptom | Check |
|---|---|
login http 401 | Confirm the account can sign in to the Metasys UI. AD accounts must use the exact domain form the site uses. |
Every call returns http 404 | api_version does not match the server release. Try v3 or v2 on pre-11 servers. |
unknown object on a zone command | Run get_objects to index item references, or put the object GUID in zone.address. |
not authorized: address is not mapped to a zone on this device | The command targets an object with no GEM zone on this device. Create the zone, or run the command as an administrator. See Who may write to which object. |
not authorized on api_request | api_request is administrator-only. |
Writes return http 403 | The API account is read-only on that object. Metasys authorization is per-category and per-object, so read access does not imply write access. |
| Connect fails with a TLS error | Self-signed certificates are accepted by default, so a hard TLS failure usually means port is not the HTTPS listener. |
| Zone state never updates | Confirm get_point returns a value for that address, and that point_attribute matches an attribute the object actually publishes. |