-
Notifications
You must be signed in to change notification settings - Fork 0
WCv2.0 RESTful API Documents
This guide explains how to use the RESTful API for the ESPEC North America Web Controller, Version 2. The following figure depicts the user interface of the Web Controller.
The RESTful API is disabled by default and must be enabled from the Setup menu.
Note: The RESTful API for the ESPEC Web Controller, Version 3, differs from that of Version 2. Version 3 can be identified by its dark-themed user interface, as shown below.
Table of Contents:
[TOC]
Returns top level uri list
-
URL
/api/v1.0/ -
Method
GET -
URL Params
-
Optional:
-
envelope=[Boolean(False)]
Place response data into a{"data":response}envelope. -
cached=[Boolean(True)]Force the server to refresh the cache before responding. -
file=[Boolean(False)]Respond with a file instead of putting the response into the message body.
-
-
Optional:
-
Success Response
-
Code:
200 -
Content:
:::json { "uri": [ "http://10.30.100.151/api/v1.0/chambers", "http://10.30.100.151/api/v1.0/systems" ] }
-
-
Error Response
-
Code:
403 FORBIDDEN
Content:None -
Code:
422 UNPROCESSABLE ENTITY
Content:{ "exception": "python exception class", "message": "python exception message", "traceback": "python traceback" }
-
Code:
-
Sample Call
-
Python using requests library.
:::python import requests host = 'YOUR HOST NAME OR IP HERE' rsp = requests.get('http://%s/api/v1.0' % host) rsp.raise_for_status() print(rsp.json())
-
Returns a list of available chambers (only 1 for now)
-
URL
/api/v1.0/chambers -
Method
GET -
URL Params
-
Optional:
-
envelope=[Boolean(False)]
Place response data into a{"data":response}envelope. -
cached=[Boolean(True)]Force the server to refresh the cache before responding. -
file=[Boolean(False)]Respond with a file instead of putting the response into the message body. -
everything=[Boolean(True)]Respond with all data (True) or just the uri's (False).
-
-
Optional:
-
Success Response
-
Code:
200 -
Content:
:::json { "chambers": [ { "alarms": { "list": [ { "controller": 1, "id": 115, "name": "Sensor Failure", "number": 0, "triggered": false }, ], "ok": true }, "controllers": [ { "cascade": true, "humidity": false, "indConstant": true, "model": "P300" } ], "events": [ { "N": 1, "controller": 1, "manual": true, "name": "Time Signal #1", "status": { "constant": false, "current": false }, "uri": "http://10.30.100.151/api/v1.0/chambers/1/events/1", "valid": true } ], "loops": [ { "N": 1, "controller": 1, "deviation": { "negative": -10.0, "positive": 10.0 }, "enable": { "constant": true, "current": true }, "enable_cascade": { "constant": false, "current": false }, "mode": { "constant": "On", "current": "On" }, "modes": [ "On" ], "name": "Temperature", "power": { "constant": null, "current": null }, "processValue": { "air": 21.7, "product": 21.8 }, "range": { "max": 190.0, "min": -80.0 }, "setValue": { "air": -40.0, "constant": -40.0, "current": -40.0, "product": 0.0 }, "units": "\u00b0C", "uri": "http://10.30.100.151/api/v1.0/chambers/1/loops/1", "valid": true } ], "operations": { "datetime": { "strftime": "Tue Jan 31 2017 16:35:58", "uts": 1485880558.0 }, "mode": "Constant", "program": { "editor": "/program/editor", "name": null, "number": null, "step": null, "time_end": null, "time_step": null, "time_total": null, "uri": null } }, "programs": { "all": [ { "name": "", "number": 1, "uri": "http://10.30.100.151/api/v1.0/chambers/1/programs/1" }, ], "existing": [] }, "refrigerators": [ { "controller": 1, "mode": "auto", "modes": [ { "text": "Off", "value": "off" }, { "text": "20%", "value": "20%" }, { "text": "50%", "value": "50%" }, { "text": "100%", "value": "100%" }, { "text": "Auto", "value": "auto" } ], "valid": true } ], "uri": "http://10.30.100.151/api/v1.0/chambers/1" } ] }
-
-
Error Response
-
Code:
403 FORBIDDEN
Content:None -
Code:
422 UNPROCESSABLE ENTITY
Content:{ "exception": "python exception class", "message": "python exception message", "traceback": "python traceback" }
-
Code:
-
Sample Call
-
Python using requests library.
:::python import requests host = 'YOUR HOST NAME OR IP HERE' rsp = requests.get('http://%s/api/v1.0/chambers' % host) rsp.raise_for_status() print(rsp.json())
-
Get all cache-able information about the chamber, This includes operation state, alarm state, control loop status, available programs, time signal status, refrigeration status.
-
URL
/api/v1.0/chambers/:cid -
Method:
GET|POST|PUT -
URL Params
-
Required:
-
cid=[integer]
Chamber id, must be1(used for future expansion)
-
-
Optional:
-
envelope=[Boolean(False)]Place response data into a{"data":response}envelope. -
cached=[Boolean(True)]
Force the server to refresh the cache before responding. -
file=[Boolean(False)]
Respond with a file instead of putting the response into the message body.
-
-
Required:
-
Data Params
All parameters aside from"N"&"controller"are optional, any parameter not shown below are ignored. Where a parameter's value is{"constant":value }thevaluemay be applied directly ie:
{"loops":[{"enable":{"constant":value }}]}=={"loops":[{"enable":value}]}:::json { "loops": [ { "N": 1, "controller": 1, "deviation": { "negative": -10.0, "positive": 10.0 }, "enable": { "constant": true }, "enable_cascade": { "constant": false }, "mode": { "constant": "On" }, "power": { "constant": 0.0 }, "range": { "max": 190.0, "min": -80.0 }, "setValue": { "constant": -40.0 } } ], "events": [ { "N": 1, "controller": 1, "status": { "constant": false } } ], "refrigerators": [ { "controller": 1, "mode": "auto" } ], } -
Success Response:
-
GETRequest:-
Code:
200
Content::::json { "alarms": { "list": [ { "controller": 1, "id": 115, "name": "Sensor Failure", "number": 0, "triggered": false } ], "ok": true }, "controllers": [ { "cascade": true, "humidity": false, "indConstant": true, "model": "P300" } ], "events": [ { "N": 1, "controller": 1, "manual": true, "name": "Time Signal #1", "status": { "constant": false, "current": false }, "uri": "http://10.30.100.151/api/v1.0/chambers/1/events/1", "valid": true } ], "loops": [ { "N": 1, "controller": 1, "deviation": { "negative": -10.0, "positive": 10.0 }, "enable": { "constant": true, "current": true }, "enable_cascade": { "constant": false, "current": false }, "mode": { "constant": "On", "current": "On" }, "modes": [ "On" ], "name": "Temperature", "power": { "constant": null, "current": null }, "processValue": { "air": 20.8, "product": 20.9 }, "range": { "max": 190.0, "min": -80.0 }, "setValue": { "air": -40.0, "constant": -40.0, "current": -40.0, "product": 0.0 }, "units": "\u00b0C", "uri": "http://10.30.100.151/api/v1.0/chambers/1/loops/1", "valid": true } ], "operations": { "datetime": { "strftime": "Wed Feb 01 2017 08:12:44", "uts": 1485936764.0 }, "mode": "Constant", "program": { "editor": "/program/editor", "name": null, "number": null, "step": null, "time_end": null, "time_step": null, "time_total": null, "uri": null } }, "programs": { "all": [ { "name": "", "number": 1, "uri": "http://10.30.100.151/api/v1.0/chambers/1/programs/1" } ], "existing": [] }, "refrigerators": [ { "controller": 1, "mode": "auto", "modes": [ { "text": "Off", "value": "off" }, { "text": "20%", "value": "20%" }, { "text": "50%", "value": "50%" }, { "text": "100%", "value": "100%" }, { "text": "Auto", "value": "auto" } ], "valid": true } ], "uri": "http://10.30.100.151/api/v1.0/chambers/1" }
-
-
POST|PUTRequest-
Code: 200
Content::::json { "events": [ { "N": 1, "controller": 1, "manual": true, "name": "Time Signal #1", "status": { "constant": false, "current": false }, "uri": "http://10.30.100.151/api/v1.0/chambers/1/events/1", "valid": true } ], "loops": [ { "N": 1, "controller": 1, "deviation": { "negative": -10.0, "positive": 10.0 }, "enable": { "constant": true, "current": true }, "enable_cascade": { "constant": false, "current": false }, "mode": { "constant": "On", "current": "On" }, "modes": [ "On" ], "name": "Temperature", "power": { "constant": null, "current": null }, "processValue": { "air": 20.8, "product": 20.9 }, "range": { "max": 190.0, "min": -80.0 }, "setValue": { "air": -40.0, "constant": -40.0, "current": -40.0, "product": 0.0 }, "units": "\u00b0C", "uri": "http://10.30.100.151/api/v1.0/chambers/1/loops/1", "valid": true } ], "refrigerators": [ { "controller": 1, "mode": "auto", "modes": [ { "text": "Off", "value": "off" }, { "text": "20%", "value": "20%" }, { "text": "50%", "value": "50%" }, { "text": "100%", "value": "100%" }, { "text": "Auto", "value": "auto" } ], "valid": true } ], "uri": "http://10.30.100.151/api/v1.0/chambers/1" }
-
-
-
Error Response
-
Code:
403 FORBIDDEN
Content:None -
Code:
422 UNPROCESSABLE ENTITY
Content:{ "exception": "python exception class", "message": "python exception message", "traceback": "python traceback" }
-
Code:
-
Sample Call
-
Python using requests library.
:::python import requests, json host = 'YOUR HOST NAME OR IP HERE' # get request rsp = requests.get('http://%s/api/v1.0/chambers/1' % host) rsp.raise_for_status() print(rsp.json()) # post request (set temperature to 25 degrees) rsp = requests.post( 'http://%s/api/v1.0/chambers/1' % host, data=json.dumps({"loops":[{"N": 1, "controller": 1, "setValue": 25.0}]}), headers={"Content-type": "application/json"} ) rsp.raise_for_status() print(rsp.json())
-
Get a list of all control loops for the chamber.
-
URL
/api/v1.0/chambers/:cid/loops -
Method:
GET|POST|PUT -
URL Params
-
Required:
-
cid=[integer]
Chamber id, must be1(used for future expansion)
-
-
Optional:
-
envelope=[Boolean(False)]
Place response data into a{"data":response}envelope. -
cached=[Boolean(True)]
Force the server to refresh the cache before responding. -
file=[Boolean(False)]
Respond with a file instead of putting the response into the message body.
-
-
Required:
-
Data Params
All parameters aside from"N"&"controller"are optional, all extra parameters will be ignored.
Where a parameter is{"constant":value }thevaluemay be applied directly ie:
[{"enable":{"constant":value }}]==[{"enable":value}]:::json [ { "N": 1, "controller": 1, "deviation": { "negative": -10.0, "positive": 10.0 }, "enable": { "constant": true }, "enable_cascade": { "constant": false }, "mode": { "constant": "On" }, "power": { "constant": 0.0 }, "range": { "max": 190.0, "min": -80.0 }, "setValue": { "constant": -40.0 } } ] -
Success Response:
Code:200
Content::::json { "loops": [ { "N": 1, "controller": 1, "deviation": { "negative": -10.0, "positive": 10.0 }, "enable": { "constant": true, "current": true }, "enable_cascade": { "constant": false, "current": false }, "mode": { "constant": "On", "current": "On" }, "modes": [ "On" ], "name": "Temperature", "power": { "constant": null, "current": null }, "processValue": { "air": 20.8, "product": 20.9 }, "range": { "max": 190.0, "min": -80.0 }, "setValue": { "air": -40.0, "constant": -40.0, "current": -40.0, "product": 0.0 }, "units": "\u00b0C", "uri": "http://10.30.100.151/api/v1.0/chambers/1/loops/1", "valid": true } ] } -
Error Response
-
Code:
403 FORBIDDEN
Content:None -
Code:
422 UNPROCESSABLE ENTITY
Content:{ "exception": "python exception class", "message": "python exception message", "traceback": "python traceback" }
-
Code:
-
Notes
- The following parameters may or may not be present:
enable_cascade,deviation, depending on if the loop/controller supports cascade control/product temperature control. - The
powerparameter is a read only on all supported controllers except th Watlow F4T.
- The following parameters may or may not be present:
-
Sample Call
-
Python using requests library.
:::python import requests, json host = 'YOUR HOST NAME OR IP HERE' # get request rsp = requests.get('http://%s/api/v1.0/chambers/1/loops' % host) rsp.raise_for_status() print(rsp.json()) # post request (set temperature to 25 degrees) rsp = requests.post( 'http://%s/api/v1.0/chambers/1/loops' % host, data=json.dumps({"loops":[{"N": 1, "controller": 1, "setValue": 25.0}]}), headers={"Content-type": "application/json"} ) rsp.raise_for_status() print(rsp.json())
-
Get a list of all events (Time Signals) for the chamber.
-
URL
/api/v1.0/chambers/:cid/events -
Method:
GET|POST|PUT -
URL Params
-
Required:
-
cid=[integer]
Chamber id, must be1(used for future expansion)
-
-
Optional:
-
envelope=[Boolean(False)]
Place response data into a{"data":response}envelope. -
cached=[Boolean(True)]
Force the server to refresh the cache before responding. -
file=[Boolean(False)]
Respond with a file instead of putting the response into the message body.
-
-
Required:
-
Data Params
All parameters aside from"N"&"controller"are optional, all extra parameters will be ignored.
Where a parameter is{"constant":value }thevaluemay be applied directly ie:
[{"enable":{"constant":value }}]==[{"enable":value}]:::json [ { "N": 1, "controller": 1, "status": { "constant": false }, } ] -
Success Response:
Code:200
Content::::json { "events": [ { "N": 1, "controller": 1, "manual": true, "name": "Time Signal #1", "status": { "constant": false, "current": false }, "uri": "http://10.30.100.151/api/v1.0/chambers/1/events/1", "valid": true } ] } -
Error Response
-
Code:
403 FORBIDDEN
Content:None -
Code:
422 UNPROCESSABLE ENTITY
Content:{ "exception": "python exception class", "message": "python exception message", "traceback": "python traceback" }
-
Code:
-
Sample Call
-
Python using requests library.
:::python import requests, json host = 'YOUR HOST NAME OR IP HERE' # get request rsp = requests.get('http://%s/api/v1.0/chambers/1/events' % host) rsp.raise_for_status() print(rsp.json()) # post request (set time signal #1 to on) rsp = requests.post( 'http://%s/api/v1.0/chambers/1/events' % host, data=json.dumps({"events":[{"N": 1, "controller": 1, "status": True}]}), headers={"Content-type": "application/json"} ) rsp.raise_for_status() print(rsp.json())
-
Get a list of all refrigeration systems for the chamber.
-
URL
/api/v1.0/chambers/:cid/refrigerators -
Method:
GET|POST|PUT -
URL Params
-
Required:
-
cid=[integer]
Chamber id, must be1(used for future expansion)
-
-
Optional:
-
envelope=[Boolean(False)]
Place response data into a{"data":response}envelope. -
cached=[Boolean(True)]
Force the server to refresh the cache before responding. -
file=[Boolean(False)]
Respond with a file instead of putting the response into the message body.
-
-
Required:
-
Data Params
All parameters aside from"controller"are optional, all extra parameters will be ignored.:::json [ { "controller": 1, "mode": "auto" } ] -
Success Response:
Code:200
Content::::json { "refrigerators": [ { "controller": 1, "mode": "auto", "modes": [ { "text": "Off", "value": "off" }, { "text": "20%", "value": "20%" }, { "text": "50%", "value": "50%" }, { "text": "100%", "value": "100%" }, { "text": "Auto", "value": "auto" } ], "valid": true } ] } -
Error Response
-
Code:
403 FORBIDDEN
Content:None -
Code:
422 UNPROCESSABLE ENTITY
Content:{ "exception": "python exception class", "message": "python exception message", "traceback": "python traceback" }
-
Code:
-
Sample Call
-
Python using requests library.
:::python import requests, json host = 'YOUR HOST NAME OR IP HERE' # get request rsp = requests.get('http://%s/api/v1.0/chambers/1/refrigerators' % host) rsp.raise_for_status() print(rsp.json()) # post request (set time refrig to auto) rsp = requests.post( 'http://%s/api/v1.0/chambers/1/refrigerators' % host, data=json.dumps([{"controller": 1, "mode": "auto"}]), headers={"Content-type": "application/json"} ) rsp.raise_for_status() print(rsp.json())
-
Get a list of all available circulator air speeds for the chamber. Added with version: 2.4.0 Beta2
-
URL
/api/v1.0/chambers/:cid/airspeeds -
Method:
GET|POST|PUT -
URL Params
-
Required:
-
cid=[integer]
Chamber id, must be1(used for future expansion)
-
-
Optional:
-
envelope=[Boolean(False)]
Place response data into a{"data":response}envelope. -
cached=[Boolean(True)]
Force the server to refresh the cache before responding. -
file=[Boolean(False)]
Respond with a file instead of putting the response into the message body.
-
-
Required:
-
Data Params
All parameters aside from"controller"are optional, all extra parameters will be ignored.:::json [ { "controller": 1, "constant": 2 } ] -
Success Response:
Code:200
Content::::json { "airspeeds": [ { "constant": 2, "controller": 1, "current": 2, "options": [ { "name": "#1 (low)", "value": 1 }, { "name": "#2", "value": 2 }, { "name": "#3", "value": 3 }, { "name": "#4 (high)", "value": 4 } ], "uri": "http://10.30.200.149/api/v2/chambers/1/airspeeds/1" } ] } -
Error Response
-
Code:
403 FORBIDDEN
Content:None -
Code:
422 UNPROCESSABLE ENTITY
Content:{ "exception": "python exception class", "message": "python exception message", "traceback": "python traceback" }
-
Code:
-
Sample Call
-
Python using requests library.
:::python import requests, json host = 'YOUR HOST NAME OR IP HERE' # get request rsp = requests.get('http://%s/api/v1.0/chambers/1/airspeeds' % host) rsp.raise_for_status() print(rsp.json()) # post request (set time refrig to auto) rsp = requests.post( 'http://%s/api/v1.0/chambers/1/refrigerators' % host, data=json.dumps([{"controller": 1, "constant": 1}]), headers={"Content-type": "application/json"} ) rsp.raise_for_status() print(rsp.json())
-
Get a list of all process controllers on the chamber.
-
URL /api/v1.0/chambers/:cid/controllers
-
Method:
GET -
URL Params
-
Required:
-
cid=[integer]
Chamber id, must be1(used for future expansion)
-
-
Optional:
-
envelope=[Boolean(False)]
Place response data into a{"data":response}envelope. -
cached=[Boolean(True)]
Force the server to refresh the cache before responding. -
file=[Boolean(False)]
Respond with a file instead of putting the response into the message body.
-
-
Required:
-
Data Params
None -
Success Response:
Code:200
Content::::json { "controllers": [ { "cascade": true, "humidity": false, "indConstant": true, "model": "P300" } ] } -
Error Response
-
Code:
403 FORBIDDEN
Content:None -
Code:
422 UNPROCESSABLE ENTITY
Content:{ "exception": "python exception class", "message": "python exception message", "traceback": "python traceback" }
-
Code:
-
Sample Call
-
Python using requests library.
:::python import requests, json host = 'YOUR HOST NAME OR IP HERE' rsp = requests.get('http://%s/api/v1.0/chambers/1/controllers' % host) rsp.raise_for_status() print(rsp.json())
-
Direct access to the chamber controller's native communication protocol. Modbus, delimited ascii, etc. Added with version 2.5.11
-
URL /api/v1.0/chambers/:cid/controllers/:ctlrid/client
-
Method:
POST -
URL Params
-
Required:
-
cid=[integer]
Chamber id, must be1(used for future expansion) -
ctlrid=[integer]Chamber controller id, in almost all cases this will be1, confirm byGET /api/v1.0/chambers/:cid/controllers
-
-
Optional:
-
envelope=[Boolean(False)]
Place response data into a{"data":response}envelope. -
cached=[Boolean(True)]
Force the server to refresh the cache before responding. -
file=[Boolean(False)]
Respond with a file instead of putting the response into the message body.
-
-
Required:
-
Data Params Format must match exactly as shown:
{"commands": [{"method":string, "args":array}]}- Available methods P300/SCP-220:
-
interact- Write a command to the controller and read the response.
- arguments:
- command(string): command to write, without delimiter.
- returns:
- string
-
- Available methods Watlow F4/F4T:
-
read_input- read an 16bit unsigned input register(s) from the controller
- arguments:
- register(int): Modbus register to read from.
- count(int, default=1): Number of registers to read.
- returns:
- unsigned int
-
read_input_signed- read an 16bit signed input register(s) from the controller, signed
- arguments:
- register(int): Modbus register to read from.
- count(int, default=1): Number of registers to read.
- returns:
- int
-
read_input_float- read 2 input register(s) as single precision floating point from the controller
- arguments:
- register(int): Modbus register to read from.
- count(int, default=1): Number of registers to read.
- returns:
- float
-
read_input_string- read some input registers from the controller and convert them to a string, 1 byte per register.
- arguments:
- register(int): Modbus register to read from.
- count(int): Number of registers to read.
- returns:
- float
-
read_holding- read an 16bit unsigned holding register(s) from the controller
- arguments:
- register(int): Modbus register to read from.
- count(int, default=1): Number of registers to read.
- returns:
- unsigned int
-
read_holding_signed- read an 16bit signed holding register(s) from the controller, signed
- arguments:
- register(int): Modbus register to read from.
- count(int, default=1): Number of registers to read.
- returns:
- int
-
read_holding_float- read 2 holding register(s) as single precision floating point from the controller
- arguments:
- register(int): Modbus register to read from.
- count(int, default=1): Number of registers to read.
- returns:
- float
-
read_holding_string- read some holding registers from the controller and convert them to a string, 1 byte per register.
- arguments:
- register(int): Modbus register to read from.
- count(int): Number of registers to read.
- returns:
- float
-
write_holding- write an 16bit unsigned holding register(s) to the controller
- arguments:
- register(int): Modbus register to write to.
- value(int or list): value to write
-
write_holding_signed- write an 16bit signed holding register(s) to the controller
- arguments:
- register(int): Modbus register to write to.
- value(int or list): value to write
-
write_holding_float- write 2 holding register(s) as single precision floating point to the controller
- arguments:
- register(int): Modbus register to write to.
- count(float or list): Number of registers to read.
-
write_holding_string- read some holding registers from the controller and convert them to a string, 1 byte per register.
- arguments:
- register(int): Modbus register to write to.
- value(string): value to write
-
Example P300/SCP-220; get rom version.
:::json { "commands": [ { "method": "interact", "args":["ROM?"] } ] }Example Watlow F4/F4T; get current temperature (F4T).
:::json { "commands": [ { "method": "read_holding_float", "args":[2742] } ] } - Available methods P300/SCP-220:
-
Success Response (P300/SCP-220):
-
POSTRequest
Code:200
Content::::json { "commands": [ { "method": "interact", "args":["ROM?"], "response": "EMLC 5.00 " } ] }
-
-
Success Response (Watlow F4/F4T):
-
POSTRequest
Code:200
Content::::json { "commands": [ { "method": "read_holding_float", "args":[2742], "response": [43.900001525878906] } ] }
-
-
Error Response
-
Code:
403 FORBIDDEN
Content:None -
Code:
422 UNPROCESSABLE ENTITY
Content:{ "exception": "python exception class", "message": "python exception message", "traceback": "python traceback" }
-
Code:
-
Sample Call
-
Python using requests library.
:::python import requests, json host = 'YOUR HOST NAME OR IP HERE' rsp = requests.post( 'http://%s/api/v1.0/chambers/1/controllers/1/client' % host, data=json.dumps({"commands": [{"method": "interact","args":["ROM?"]}]}), headers={"Content-type": "application/json"} ) rsp.raise_for_status() print(rsp.json())
-
Get the current mode of operation for the chamber.
-
URL
/api/v1.0/chambers/:cid/operations -
Method:
GET -
URL Params
-
Required:
-
cid=[integer]
Chamber id, must be1(used for future expansion)
-
-
Optional:
-
envelope=[Boolean(False)]
Place response data into a{"data":response}envelope. -
cached=[Boolean(True)]
Force the server to refresh the cache before responding. -
file=[Boolean(False)]
Respond with a file instead of putting the response into the message body.
-
-
Required:
-
Data Params
None -
Success Response:
Code:200
Content::::json { "operations": { "datetime": { "strftime": "Wed Feb 01 2017 12:51:26", "uts": 1485953486 }, "mode": "Constant", "program": { "editor": "/program/editor", "name": null, "number": null, "step": null, "time_end": null, "time_step": null, "time_total": null, "uri": null } } } -
Error Response
-
Code:
403 FORBIDDEN
Content:None -
Code:
422 UNPROCESSABLE ENTITY
Content:{ "exception": "python exception class", "message": "python exception message", "traceback": "python traceback" }
-
Code:
-
Sample Call
-
Python using requests library.
:::python import requests, json host = 'YOUR HOST NAME OR IP HERE' rsp = requests.get('http://%s/api/v1.0/chambers/1/operations' % host) rsp.raise_for_status() print(rsp.json())
-
Get a list of all available programs for the chamber.
-
URL
/api/v1.0/chambers/:cid/programs -
Method:
GET -
URL Params
-
Required:
-
cid=[integer]
Chamber id, must be1(used for future expansion)
-
-
Optional:
-
envelope=[Boolean(False)]
Place response data into a{"data":response}envelope. -
cached=[Boolean(True)]
Force the server to refresh the cache before responding. -
file=[Boolean(False)]
Respond with a file instead of putting the response into the message body.
-
-
Required:
-
Data Params
None -
Success Response:
Code:200
Content::::json { "programs": { "all": [ { "name": "", "number": 1, "uri": "http://10.30.100.151/api/v1.0/chambers/1/programs/1" } ], "existing": [] } } -
Error Response
-
Code:
403 FORBIDDEN
Content:None -
Code:
422 UNPROCESSABLE ENTITY
Content:{ "exception": "python exception class", "message": "python exception message", "traceback": "python traceback" }
-
Code:
-
Sample Call
-
Python using requests library.
:::python import requests, json host = 'YOUR HOST NAME OR IP HERE' rsp = requests.get('http://%s/api/v1.0/chambers/1/programs' % host) rsp.raise_for_status() print(rsp.json())
-
Get a list of all alarms possible alarms, there status, and if any are currently triggered.
-
URL
/api/v1.0/chambers/:cid/alarms -
Method:
GET -
URL Params
-
Required:
-
cid=[integer]
Chamber id, must be1(used for future expansion)
-
-
Optional:
-
envelope=[Boolean(False)]
Place response data into a{"data":response}envelope. -
cached=[Boolean(True)]
Force the server to refresh the cache before responding. -
file=[Boolean(False)]
Respond with a file instead of putting the response into the message body.
-
-
Required:
-
Data Params
None -
Success Response:
Code:200
Content::::json { "alarms": { "list": [ { "controller": 1, "id": 115, "name": "Sensor Failure", "number": 0, "triggered": false } ], "ok": true } } -
Error Response
-
Code:
403 FORBIDDEN
Content:None -
Code:
422 UNPROCESSABLE ENTITY
Content:{ "exception": "python exception class", "message": "python exception message", "traceback": "python traceback" }
-
Code:
-
Sample Call
-
Python using requests library.
:::python import requests, json host = 'YOUR HOST NAME OR IP HERE' rsp = requests.get('http://%s/api/v1.0/chambers/1/alarms' % host) rsp.raise_for_status() print(rsp.json())
-
Get any combination of the individual Chamber Loops\Events\Refrigerators\Controllers\Operations\Programs\Alarms routes in one call.
-
URL
/api/v1.0/chambers/:cid/:params -
Method:
GET|POST|PUT -
URL Params
-
Required:
-
cid=[integer]
Chamber id, must be1(used for future expansion) -
params=[list[strings]]
Valid parameters:loops,events,refrigerators,controllers,operations,programs,alarms
separate desired params with a+ie:/api/v1.0/chambers/1/loops+events
-
-
Optional:
-
envelope=[Boolean(False)]
Place response data into a{"data":response}envelope. -
cached=[Boolean(True)]
Force the server to refresh the cache before responding. -
file=[Boolean(False)]
Respond with a file instead of putting the response into the message body.
-
-
Required:
-
Data Params
Only paramsloops,events, andrefrigeratorsare settable All parameters aside from"N"&"controller"are optional, all extra parameters will be ignored.
Where a parameter is{"constant":value }thevaluemay be applied directly ie:
{"loops":[{"enable":{"constant":value }}]}=={"loops":[{"enable":value}]}:::json { "loops": [...], "events": [...], "refrigerators": [...] } -
Success Response:
-
GETRequest:-
Code:
200
Content::::json { "alarms": {...}, "controllers": [...], "events": [...], "loops": [...], "operations": {...}, "programs": {...}, "refrigerators": [...] }
-
-
POST|PUTRequest-
Code: 200
Content::::json { "events": [...], "loops": [...], "refrigerators": [...] }
-
-
-
Error Response
-
Code:
403 FORBIDDEN
Content:None -
Code:
422 UNPROCESSABLE ENTITY
Content:{ "exception": "python exception class", "message": "python exception message", "traceback": "python traceback" }
-
Code:
-
Sample Call
-
Python using requests library.
:::python import requests, json host = 'YOUR HOST NAME OR IP HERE' # get request rsp = requests.get('http://%s/api/v1.0/chambers/1/loops+events' % host) rsp.raise_for_status() print(rsp.json()) # post request (set time signal #1 to on, and set temperature to 25.0) rsp = requests.post( 'http://%s/api/v1.0/chambers/1/loops+events' % host, data=json.dumps({ "events":[{"N": 1, "controller": 1, "status": True, "manual":True}], "loops":[{"N": 1, "controller": 1, "setValue": 25.0}] }), headers={"Content-type": "application/json"} ) rsp.raise_for_status() print(rsp.json())
-
-
Notes
- See the individual routes for an exact explanation on what the "Data Params" & "Success Response" data will look like.
Get a specific set of loop parameters by id
-
URL
/api/v1.0/chambers/:cid/loops/:lid -
Method:
GET|POST|PUT -
URL Params
-
Required:
-
cid=[integer]
Chamber id, must be1(used for future expansion) -
lid=[integer]
Loop id.
-
-
Optional:
-
envelope=[Boolean(False)]
Place response data into a{"data":response}envelope. -
cached=[Boolean(True)]
Force the server to refresh the cache before responding. -
file=[Boolean(False)]
Respond with a file instead of putting the response into the message body.
-
-
Required:
-
Data Params
All parameters aside from
"N"&"controller"are optional, all extra parameters will be ignored. Where a parameter is{"constant":value }thevaluemay be applied directly ie:
{"enable":{"constant":value }}=={"enable":value}:::json { "N": 1, "controller": 1, "deviation": { "negative": -10, "positive": 10 }, "enable": { "constant": true }, "enable_cascade": { "constant": false }, "mode": { "constant": "On" }, "power": { "constant": 0.0 }, "range": { "max": 190, "min": -80 }, "setValue": { "constant": -40 } } -
Success Response:
-
GETRequest
Code:200
Content::::json { "N": 1, "controller": 1, "deviation": { "negative": -10, "positive": 10 }, "enable": { "constant": true, "current": true }, "enable_cascade": { "constant": false, "current": false }, "mode": { "constant": "On", "current": "On" }, "modes": [ "On" ], "name": "Temperature", "power": { "constant": null, "current": null }, "processValue": { "air": 21.6, "product": 21.6 }, "range": { "max": 190, "min": -80 }, "setValue": { "air": -40, "constant": -40, "current": -40, "product": 0 }, "units": "°C", "uri": "http://10.30.100.151/api/v1.0/chambers/1/loops/1", "valid": true } -
PUT|POSTRequest
Code:204
Content: None
-
-
Error Response
-
Code:
403 FORBIDDEN
Content:None -
Code:
422 UNPROCESSABLE ENTITY
Content:{ "exception": "python exception class", "message": "python exception message", "traceback": "python traceback" }
-
Code:
-
Sample Call
-
Python using requests library.
:::python import requests, json host = 'YOUR HOST NAME OR IP HERE' # get request rsp = requests.get('http://%s/api/v1.0/chambers/1/loops/1' % host) rsp.raise_for_status() print(rsp.json()) # post request (set temperature to 25.0) rsp = requests.post( 'http://%s/api/v1.0/chambers/1/loops/1' % host, data=json.dumps({"N": 1, "controller": 1, "setValue": 25.0}), headers={"Content-type": "application/json"} ) rsp.raise_for_status()
-
Get a specific set of Event parameters by id
-
URL
/api/v1.0/chambers/:cid/events/:eid -
Method:
GET|POST|PUT -
URL Params
-
Required:
-
cid=[integer]
Chamber id, must be1(used for future expansion) -
eid=[integer]
Event id.
-
-
Optional:
-
envelope=[Boolean(False)]
Place response data into a{"data":response}envelope. -
cached=[Boolean(True)]
Force the server to refresh the cache before responding. -
file=[Boolean(False)]
Respond with a file instead of putting the response into the message body.
-
-
Required:
-
Data Params
All parameters aside from
"N"&"controller"are optional, all extra parameters will be ignored.
Where a parameter is{"constant":value }thevaluemay be applied directly ie:
{"status":{"constant":value }}=={"status":value}:::json { "N" :1, "controller": 1, "status": {"constant": true}, "manual", true } -
Success Response:
-
GETRequest
Code:200
Content::::json { "N": 1, "controller": 1, "manual": true, "name": "Time Signal #1", "status": { "constant": false, "current": false }, "uri": "http://10.30.200.254/api/v1.0/chambers/1/events/1", "valid": true }
-
-
Error Response
-
Code:
403 FORBIDDENContent:None -
Code:
422 UNPROCESSABLE ENTITY
Content:{ "exception": "python exception class", "message": "python exception message", "traceback": "python traceback" }
-
Code:
-
Sample Call
-
Python using requests library.
:::python import requests, json host = 'YOUR HOST NAME OR IP HERE' # get request rsp = requests.get('http://%s/api/v1.0/chambers/1/events/1' % host) rsp.raise_for_status() print(rsp.json()) # post request (set time signal #1 to on) rsp = requests.post( 'http://%s/api/v1.0/chambers/1/events/1' % host, data=json.dumps({"N": 1, "controller": 1, "status": True, "manual":True}), headers={"Content-type": "application/json"} ) rsp.raise_for_status()
-
Get a specific set of Air Speed parameters by id Added with version: 2.4.0 Beta2
-
URL
/api/v1.0/chambers/:cid/airspeeds/:aid -
Method:
GET|POST|PUT -
URL Params
-
Required:
-
cid=[integer]
Chamber id, must be1(used for future expansion) -
aid=[integer]
Air Speed id.
-
-
Optional:
-
envelope=[Boolean(False)]
Place response data into a{"data":response}envelope. -
cached=[Boolean(True)]
Force the server to refresh the cache before responding. -
file=[Boolean(False)]
Respond with a file instead of putting the response into the message body.
-
-
Required:
-
Data Params
Where a parameter is
{"constant":value }thevaluemay be applied directly ie:
{"constant":value }==value:::json 1 -
Success Response:
-
GETRequest
Code:200
Content::::json { "constant": 3, "controller": 1, "current": 3, "options": [ { "name": "#1 (low)", "value": 1 }, { "name": "#2", "value": 2 }, { "name": "#3", "value": 3 }, { "name": "#4 (high)", "value": 4 } ], "uri": "http://10.30.100.144:5555/api/v2/chambers/1/airspeeds/1" }
-
-
Error Response
-
Code:
403 FORBIDDENContent:None -
Code:
422 UNPROCESSABLE ENTITY
Content:{ "exception": "python exception class", "message": "python exception message", "traceback": "python traceback" }
-
Code:
-
Sample Call
-
Python using requests library.
:::python import requests, json host = 'YOUR HOST NAME OR IP HERE' # get request rsp = requests.get('http://%s/api/v1.0/chambers/1/airspeeds/1' % host) rsp.raise_for_status() print(rsp.json()) # post request (set time signal #1 to on) rsp = requests.post( 'http://%s/api/v1.0/chambers/1/events/1' % host, data=json.dumps(1), headers={"Content-type": "application/json"} ) rsp.raise_for_status()
-
Get/update/delete a specific program
-
URL
/api/v1.0/chambers/:cid/programs/:pid -
Method:
GET|POST|PUT|DELETE -
URL Params
-
Required:
-
cid=[integer]
Chamber id, must be1(used for future expansion) -
pid=[integer]
Program Number.
-
-
Optional:
-
envelope=[Boolean(False)]
Place response data into a{"data":response}envelope. -
cached=[Boolean(True)]
Force the server to refresh the cache before responding. -
file=[Boolean(False)]
Respond with a file instead of putting the response into the message body. -
delete=[Boolean(False)]
Override the requestPOSTorPUTmethods to preform aDELETErequest. -
knockout=[Boolean(False)]When True apply web controller chamber configuration data to assist in gui display.
-
-
Required:
-
Data Params
Post same data type received via theGETResponse -
Success Response:
-
GETRequest
Code:200
Content:-
P300/SCP-220 Based Chambers
:::json { "controllerType": "Espec,P300", "counter_a": { "cycles": 0, "end": 0, "start": 0 }, "counter_b": { "cycles": 0, "end": 0, "start": 0 }, "end": "OFF", "humiDetail": { "mode": "OFF", "range": { "max": 100, "min": 0 }, "setpoint": null }, "name": "TEST1", "next_prgm": 0, "steps": [ { "granty": false, "humidity": { "enable": false, "ramp": false, "setpoint": 0 }, "number": 1, "paused": false, "refrig": { "mode": "auto", "setpoint": 0 }, "relay": [ false, false, false, false, false, false, false, false, false, false, false, false ], "temperature": { "deviation": { "negative": -10, "positive": 10 }, "enable_cascade": false, "ramp": false, "setpoint": 10 }, "time": { "hour": 1, "minute": 0 } } ], "tempDetail": { "mode": "OFF", "range": { "max": 190, "min": -80 }, "setpoint": null } } -
Watlow F4T Based Chamber
:::json { "controllerType": "WatlowF4T", "gs_dev": [ { "value": 5.6 }, { "value": 10 } ], "hasenables": true, "haswaits": false, "log": false, "name": "Test1", "steps": [ { "duration": { "hours": 1, "minutes": 0, "seconds": 0 }, "events": [ { "number": 1, "value": "off" }, { "number": 2, "value": "off" }, { "number": 3, "value": "off" }, { "number": 4, "value": "off" }, { "number": 5, "value": "off" }, { "number": 6, "value": "off" }, { "number": 7, "value": "off" }, { "number": 8, "value": "off" } ], "jcount": 1, "jstep": 1, "loops": [ { "cascade": false, "enable": true, "gsoak": false, "isCascade": false, "max": 180, "min": -70, "mode": "", "rate": 0, "showEnable": false, "target": 10 }, { "cascade": false, "enable": false, "gsoak": false, "isCascade": false, "max": 95, "min": 10, "mode": "", "rate": 0, "showEnable": true, "target": 10 } ], "type": "instant", "waits": [] } ] }
-
-
POST|PUT|DELETERequest
Code:200
Content::::json { "programs": { "all": [ { "name": "TEST1", "number": 1, "uri": "http://10.30.100.151/api/v1.0/chambers/1/programs/1" } ], "existing": [ { "name": "TEST1", "number": 1, "uri": "http://10.30.100.151/api/v1.0/chambers/1/programs/1" } ] } }
-
-
Error Response
-
Code:
403 FORBIDDEN
Content:None -
Code:
422 UNPROCESSABLE ENTITY
Content:{ "exception": "python exception class", "message": "python exception message", "traceback": "python traceback" }
-
Code:
-
Sample Call
-
Python using requests library.
:::python import requests, json host = 'YOUR HOST NAME OR IP HERE' # get request rsp = requests.get('http://%s/api/v1.0/chambers/1/programs/1' % host) rsp.raise_for_status() my_program = rsp.json() print(my_program) # delete request (delete program #1) rsp = requests.delete('http://%s/api/v1.0/chambers/1/programs/1' % host) rsp.raise_for_status() # post request (upload a program to #1) rsp = requests.post( 'http://%s/api/v1.0/chambers/1/programs/1' % host, data=json.dumps(my_program), headers={"Content-type": "application/json"} ) rsp.raise_for_status() print(rsp.json())
-
Get the number of steps in a program, and the currently running step if that program is running.
-
URL
/api/v1.0/chambers/:cid/programs/:pid/steps -
Method:
GET -
URL Params
-
Required:
-
cid=[integer]
Chamber id, must be1(used for future expansion) -
pid=[integer]
Program number.
-
-
Optional:
-
envelope=[Boolean(False)]
Place response data into a{"data":response}envelope. -
cached=[Boolean(True)]
Force the server to refresh the cache before responding. -
file=[Boolean(False)]
Respond with a file instead of putting the response into the message body.
-
-
Required:
-
Data Params
None -
Success Response:
Code:200
Content:{"count":10, "current":1} -
Error Response
-
Code:
403 FORBIDDEN
Content:None -
Code:
422 UNPROCESSABLE ENTITY
Content:{ "exception": "python exception class", "message": "python exception message", "traceback": "python traceback" }
-
Code:
-
Sample Call
-
Python using requests library.
:::python import requests, json host = 'YOUR HOST NAME OR IP HERE' rsp = requests.get('http://%s/api/v1.0/chambers/1/programs/1/steps' % host) rsp.raise_for_status() print(rsp.json())
-
Change the run mode of the chamber(Constant, Stop, Program).
-
URL
/api/v1.0/chambers/:cid/operations/:oper
/api/v1.0/chambers/:cid/operations/:oper/:pid
/api/v1.0/chambers/:cid/operations/:oper/:pid/:step -
Method:
POST -
URL Params
-
Required:
-
cid=[integer]
Chamber id, must be1(used for future expansion) -
oper=[string]
standby,off,stop= Stop operation
constant= Run the constant setpoint
program= Run a program, requirespid=[int]programpause= Pause the running program
programresume= Resume the paused program
programnextstep= Run the next step in the running program -
pid=[int]
The number of the program to run ifoper == program
-
-
Optional:
-
step=[int(1)]
The step of the program to run, step 1 is run if this is omitted. -
envelope=[Boolean(False)]
Place response data into a{"data":response}envelope. -
cached=[Boolean(True)]
Force the server to refresh the cache before responding. -
file=[Boolean(False)]
Respond with a file instead of putting the response into the message body.
-
-
Required:
-
Data Params
None -
Success Response:
Code:200
Content::::json { "operations": { "datetime": { "strftime": "Wed Feb 01 2017 14:45:09", "uts": 1485960309 }, "mode": "Constant", "program": { "editor": "/program/editor", "name": null, "number": null, "step": null, "time_end": null, "time_step": null, "time_total": null, "uri": null } } } -
Error Response
-
Code:
403 FORBIDDEN
Content:None -
Code:
422 UNPROCESSABLE ENTITY
Content:{ "exception": "python exception class", "message": "python exception message", "traceback": "python traceback" }
-
Code:
-
Sample Call
-
Python using requests library.
:::python import requests, json host = 'YOUR HOST NAME OR IP HERE' # start constant mode rsp = requests.post('http://%s/api/v1.0/chambers/1/operations/constant' % host) rsp.raise_for_status() print(rsp.json()) # start program #1 step #2 rsp = requests.post('http://%s/api/v1.0/chambers/1/operations/program/1/2' % host) rsp.raise_for_status() print(rsp.json())
-
Get/Delete the chambers logged data in several formats.
-
URL
/api/v1.0/chambers/:cid/logs/trend -
Method:
GET|DELETE -
URL Params
-
Required:
-
cid=[integer]
Chamber id, must be1(used for future expansion)
-
-
Optional:
-
format=[string(JSON)]-
JSON= Formatted for a flot chart. -
CSV= Formatted as comma separated values.
-
-
newest=[int(null)]
Unix time stamp for the newest data point in a range (requires oldest param too). -
oldest=[int(null)]Unix time stamp for the oldest data point in a range (requires newest param too). -
recent=[int(21600)]Get data points for the most recent number of seconds. -
columns=[string('seconds,datetime,loops,mode')](added w/ firmware v2.3.0) Columns to get data points for; comma separated list. Available columns:-
secondsDate/Time formatted as a unix time stamp. -
loopsAll PID loop set point(s) (null values when not controlling) and process value(s). -
loops_fullAll PID loop parameters. -
eventsThe state of all events (time signals). -
alarmsThe state of all alarms on the chamber. -
alarm_activeAll active alarms as a comma separated list. -
programsAll program parameters. -
programs_stateThe program state as a formatted string. -
extsrcsAll external source parameters.
-
-
units=[Boolean(False)](added w/ firmware v2.3.0) Add the units to the column labels whenformat=CSV. -
envelope=[Boolean(False)]Place response data into a{"data":response}envelope. -
cached=[Boolean(True)]Force the server to refresh the cache before responding. -
file=[Boolean(False)]Respond with a file instead of putting the response into the message body.
-
-
Required:
-
Data Params
None -
Success Response:
-
GETRequest: Code:200
Content: Varies by configuration/format parameter. -
DELETERequest: Code:204
Content: None
-
-
Error Response
-
Code:
403 FORBIDDEN
Content:None -
Code:
422 UNPROCESSABLE ENTITY
Content:{ "exception": "python exception class", "message": "python exception message", "traceback": "python traceback" }
-
Code:
-
Sample Call
-
Python using requests library.
:::python import requests, json host = 'YOUR HOST NAME OR IP HERE' # get data log as a csv rsp = requests.get('http://%s/api/v1.0/chambers/1/logs/trend' % host, params={"recent":3600, "format":"CSV"}) rsp.raise_for_status() print(rsp.text) # delete the data logs rsp = requests.delete('http://%s/api/v1.0/chambers/1/logs/trend' % host) rsp.raise_for_status()
-
Get usage chamber operation usage statistics as well as operating mode change points (added w/ firmware v2.3.0).
-
URL /api/v1.0/chambers/:cid/logs/trend/stats
-
Method:
GET -
URL Params
-
Optional:
-
interval=[int(4464)]The amount of time to calculate the operating statistics for in hours. The default of 4464 is 6 months. -
limit=[int(20)]The maximum number of operating mode changes to find. -
offset=[int(0)]The number of operating mode changes to skip before returning the found changes, ie for a limit of 20:- offset=0: 0-19
- offset=20: 20-39
-
envelope=[Boolean(False)]
Place response data into a{"data":response}envelope. -
cached=[Boolean(True)]
Force the server to refresh the cache before responding. -
file=[Boolean(False)]Respond with a file instead of putting the response into the message body.
-
-
Optional:
-
Data Params None
-
Success Response:
Code:200
Content: -
Error Response
-
Code:
403 FORBIDDEN
Content:None -
Code:
422 UNPROCESSABLE ENTITY
Content:{ "exception": "python exception class", "message": "python exception message", "traceback": "python traceback" }
-
Code:
-
Sample Call
-
Python using requests library.
:::python import requests, json host = 'YOUR HOST NAME OR IP HERE' rsp = requests.get('http://%s/api/v1.0/chambers/1/logs/trend/stats' % host) rsp.raise_for_status() print(rsp.json())
-
Get the humidity graph for the chamber formatted for flot chart
-
URL
/api/v1.0/chambers/:cid/logs/humidityGraph -
Method:
GET -
URL Params
-
Required:
-
cid=[integer]
Chamber id, must be1(used for future expansion)
-
-
Optional:
-
envelope=[Boolean(False)]
Place response data into a{"data":response}envelope. -
cached=[Boolean(True)]
Force the server to refresh the cache before responding. -
file=[Boolean(False)]Respond with a file instead of putting the response into the message body.
-
-
Required:
-
Data Params
None -
Success Response:
Code:200
Content::::json { "graph": [ { "color": "blue", "data": [[85,95],[20,95],[10,90],[10,80],[20,45],[40,20],[60,10],[85,10],[85,95]], "label": "Standard Humidity" }, { "color": "green", "data": [[40,20],[20,45],[10,80],[10,40],[20,30],[40,20]], "label": "Low Humidity (Enable Dry Air)" }, { "color": "red", "data": [[60,10],[40,20],[20,30],[10,40],[10,15],[40,15],[60,10]], "label": "Low Humidity (Dry Air & Low Humidity)" } ], "xlabel": "Temperature(°C)", "xmax": 100, "xmin": 0, "xticks": [[0,"0"],[100,"100"],[85,"85"],[20,"20"],[10,"10"],[40,"40"],[60,"60"]], "ylabel": "Humidity(%RH)", "yticks": [[0,"0"],[100,"100"],[95,"95"],[90,"90"],[80,"80"],[45,"45"],[20,"20"],[10,"10"],[40,"40"],[30,"30"],[15,"15"]] } -
Error Response
-
Code:
403 FORBIDDEN
Content:None -
Code:
422 UNPROCESSABLE ENTITY
Content:{ "exception": "python exception class", "message": "python exception message", "traceback": "python traceback" }
-
Code:
-
Sample Call
-
Python using requests library.
:::python import requests, json host = 'YOUR HOST NAME OR IP HERE' rsp = requests.get('http://%s/api/v1.0/chambers/1/logs/humidityGraph' % host) rsp.raise_for_status() print(rsp.json())
-
Navigation: Home | GL Controller Home | Chamber Connect Library | ESPEC Web Controller Home | F4T SCPI Programming Library
Need help? Contact ESPEC Support Page | ESPEC Web Controller Support Page: PDF
Version: v2.0 | Last Updated: May 2026
Copyright © 2026 ESPEC North America, Inc. All rights reserved.
The documentation, illustrations, screenshots, and other original materials contained in this wiki are the property of ESPEC North America, Inc., unless otherwise indicated.
Created & Maintained by Paul Nong-Laolam
