Gateway code to receive sensor and EKM meter readings from the Conectric mesh network and forward it to a HTTP POST API and optionally to the Go-IoT BACNET service if configured on your gateway.
Message formats to send to the API, and the format of the API URL used are configured in a file called clientimpl.js
, described in the "Setup" section.
Clone this repo then:
cd <directory where repo was cloned to>
npm install
Note that if you are upgrading from an older version of this software, you should run rm -rf node_modules
before running npm install
. This ensures that you have all of the dependencies at their expected versions.
Plug a Conectric USB router into an available USB port.
Edit config.json
to set the correct API base URL and other parameters. Edit meters.json
to include your EKM v3 and/or v4 meters.
Then:
$ npm install
$ npm start
Contains configurable parameters.
{
"http": {
"apiUrl": "<API URL to post data to> e.g. https://mydomain.com/api/endpoint",
"enabled": true,
"successCodes": [ 200, 201, 202, 204 ]
},
"go-iot": {
"enabled": false
},
"requestTimeout": <Seconds that a request for a chunk of meter data can run for before being considered timed out, min 1>,
"maxRetries": <Number of times to retry a request for a chunk of meter data that timed out, min 0>
"readingInterval": <Seconds between successfully reading a meter and starting the next read, min 1>,
"useMillisecondTimestamps": <true to use millisecond precision timestamps, false for second precision>,
"useFahrenheitTemps": <true for Fahrenheit temperature data, false for Celsius>,
"sendStatusMessages": <true to enable sensor status message processing, false to disable>,
"sendEventCount": <true to send event count information, false to disable>,
"sendHopData": <true to send hop count data in messages, false to disable>,
"useHaystack": <true to use haystack format messages, false to disable>
}
You may also add your own extra parameters to the config file. These can then be referenced in your customized clientimpl.js
file.
For http
, successCodes
is an array of HTTP status codes that should be considered success responses from the server.
When go-iot
is enabled, you must set useHaystack
to true
. Failure to do this will result in the gateway logging an error message and quitting on startup.
Contains an array of EKM v3 or v4 meters to be read, and information about which RS485 hub each meter is connected to.
{
"meters": [
{
"serialNumber": "000300004299",
"rs485HubId": "0000",
"version": 4,
"password": "00000000",
"ctRatio": 100
},
...
]
}
The values password
and ctRatio
are optional, and used by a separate configuration tool that sets the meter's CT ratio. Values are as follows:
password
: An eight digit number, default meter password is 00000000.ctRatio
: The CT ratio that should be set for the meter, values are between 100 and 5000.
To run this without any meters, use:
{
"meters": [
]
}
This file exports functions that the gateway code requires you to provide implementations for:
formatPayload
should perform any required transformations on the message payload that is to be sent to the API. IfuseHaystack
istrue
, then the message format passed into this function will be the haystack format.buildAPIUrl
should return the full URL of the API endpoint to post a message to.buildAPIHeaders
should return an object describing HTTP headers that will be used when posting a message to the URL generated bybuildAPIUrl
. If none are required, return an empty object{}
.getAPIVerb
should return the stringPOST
or the stringPUT
. This will be the HTTP verb used when calling the API.onMeterReadFail
, a callback function that is invoked whenever a meter reading attempt fails due to the meter not responding after retries. This is passed the serial number of the affected meter.
A stub implementation of each, with examples, is provided in the file clientimpl_template.js
. You should copy this to clientimpl.js
and add your own specific code here.
Both formatPayload
and buildAPIUrl
have access to the moment library for date formatting. buildApiURL
additionally has access to any values that you add to config.json
so you should put API keys etc in here and reference them in your code using the config
object. Similarly buildAPIHeaders
also has access to both the config
and message payload objects, allowing you to store API keys etc in config.json
and set headers per message type as needed.
When config parameter useHaystack
is false
the format of the messages you can expect to receive from each type of Conectric sensor is documented here, as part of the documentation for the mesh network to USB gateway.
When config parameter useHaystack
is true
, message formats will look like the following examples. In all cases:
connection
will be"conectric"
.networkRef
will be"conectric"
.device1Ref
will be the gateway ID.device2Ref
will benull
.id
will be the sensor ID.t
will be ISO-8601 formatted time of the message.
Note: battery
will always be null
as devices are not running from batteries.
{
"connection": "conectric",
"networkRef": "conectric",
"device1Ref": "00124b00051422cd",
"device2Ref": null,
"id": "28b0",
"gatewayId": "00124b00051422cd",
"type": "echoStatus",
"payload": {
"battery": null,
"eventCount": 236
},
"sensorId": "28b0",
"sequenceNumber": 229,
"timestamp": 1588648225536,
"numHops": 0,
"maxHops": 0,
"t": "2020-05-05T03:10:25.536Z"
}
{
"connection": "conectric",
"networkRef": "conectric",
"device1Ref": "00124b00051422cd",
"device2Ref": null,
"id": "3a4a",
"gatewayId": "00124b00051422cd",
"type": "moisture",
"payload": {
"battery": 3.1,
"moisture": false,
"eventCount": 1
},
"sensorId": "3a4a",
"sequenceNumber": 2,
"timestamp": 1588451660099,
"numHops": 1,
"maxHops": 0,
"t": "2020-05-02T20:34:20.099Z"
}
{
"connection": "conectric",
"networkRef": "conectric",
"device1Ref": "00124b00051422cd",
"device2Ref": null,
"id": "3a4a",
"gatewayId": "00124b00051422cd",
"type": "moistureStatus",
"payload": {
"battery": 3.1,
"moisture": false,
"temp": 80.2,
"unit": "F",
"humidity": 48.32,
"eventCount": 15
},
"sensorId": "3a4a",
"sequenceNumber": 16,
"timestamp": 1588453043581,
"numHops": 1,
"maxHops": 0,
"t": "2020-05-02T20:57:23.581Z"
}
{ "connection": "conectric",
"networkRef": "conectric",
"device1Ref": "00124b00051422cd",
"device2Ref": null,
"id": "1701",
"gatewayId": "00124b00051422cd",
"type": "motion",
"payload": {
"battery": 3.3,
"eventCount": 121158,
"occupancyIndicator": true
},
"sensorId": "1701",
"sequenceNumber": 62,
"timestamp": 1588283168766,
"numHops": 2,
"maxHops": 0,
"t": "2020-04-30T21:46:08.766Z"
}
{
"connection": "conectric",
"networkRef": "conectric",
"device1Ref": "00124b00051422cd",
"device2Ref": null,
"id": "15e0",
"gatewayId": "00124b00051422cd",
"type": "motionStatus",
"payload": {
"battery": 3.2,
"eventCount": 56216,
"occupancyIndicator": false
},
"sensorId": "15e0",
"sequenceNumber": 169,
"timestamp": 1588647505086,
"numHops": 1,
"maxHops": 0,
"t": "2020-05-05T02:58:25.086Z"
}
Note: occupancyIndicator
will always be false
.
{
"networkRef": "conectric",
"device1Ref": "00124b00051422cd",
"connection": "conectric",
"device2Ref": null,
"id": "1413",
"gatewayId": "00124b00051422cd",
"type": "pulse",
"payload": {
"battery": 3.1,
"pulse": true,
"eventCount": 151
},
"sensorId": "1413",
"sequenceNumber": 86,
"timestamp": 1588546855788,
"numHops": 0,
"maxHops": 0,
"t": "2020-05-03T23:00:55.788Z"
}
{
"connection": "conectric",
"networkRef": "conectric",
"device1Ref": "00124b00051422cd",
"device2Ref": null,
"id": "dbad",
"gatewayId": "00124b00051422cd",
"type": "pulseStatus",
"payload": {
"battery": 3,
"eventCount": 0,
"pulse": false
},
"sensorId": "dbad",
"sequenceNumber": 122,
"timestamp": 1588647605806,
"numHops": 1,
"maxHops": 0,
"t": "2020-05-05T03:00:05.806Z"
}
Note: pulse
will always be false
.
{
"connection": "conectric",
"networkRef": "conectric",
"device1Ref": "00124b00051422cd",
"device2Ref": null,
"id": "1d0c",
"gatewayId": "00124b00051422cd",
"type": "rs485Status",
"payload": {
"battery": null,
"eventCount": 922239
},
"sensorId": "1d0c",
"sequenceNumber": 39,
"timestamp": 1588648178159,
"numHops": 2,
"maxHops": 0,
"t": "2020-05-05T03:09:38.159Z"
}
{
"connection": "conectric",
"networkRef": "conectric",
"device1Ref": "00124b00051422cd",
"device2Ref": null,
"id": "3d94",
"gatewayId": "00124b00051422cd",
"type": "switch",
"payload": {
"battery": 3.3,
"eventCount": 152,
"switch": true
},
"sensorId": "3d94",
"sequenceNumber": 251,
"timestamp": 1588553778855,
"numHops": 3,
"maxHops": 0,
"t": "2020-05-04T00:56:18.855Z"
}
{
"connection": "conectric",
"networkRef": "conectric",
"device1Ref": "00124b00051422cd",
"device2Ref": null,
"id": "1413",
"gatewayId": "00124b00051422cd",
"type": "switchStatus",
"payload": {
"battery": 3,
"eventCount": 36,
"switch": false
},
"sensorId": "1413",
"sequenceNumber": 138,
"timestamp": 1588218274974,
"numHops": 0,
"maxHops": 0,
"t": "2020-04-30T03:44:34.974Z"
}
Note: switch
will indicate the current status of the switch.
{
"connection": "conectric",
"networkRef": "conectric",
"device1Ref": "00124b00051422cd",
"device2Ref": null,
"id": "3457",
"gatewayId": "00124b00051422cd",
"type": "tempHumidity",
"payload":
{
"eventCount": 126407,
"battery": 2.8,
"humidity": 49.48,
"temp": 76.5,
"unit": "F"
},
"sensorId": "3457",
"sequenceNumber": 152,
"timestamp": 1588218027201,
"numHops": 0,
"maxHops": 0,
"t": "2020-04-30T03:40:27.201Z"
}
{
"connection": "conectric",
"networkRef": "conectric",
"device1Ref": "00124b00051422cd",
"device2Ref": null,
"id": "4443",
"gatewayId": "00124b00051422cd",
"type": "tempHumidityAdc",
"payload": {
"battery": 3,
"eventCount": 411318,
"humidity": 49.24,
"adcIn": "008a",
"adcMax": "07ff",
"temp": 74.12,
"unit": "F"
},
"sensorId": "4443",
"sequenceNumber": 171,
"timestamp": 1588218032173,
"numHops": 0,
"maxHops": 0,
"t": "2020-04-30T03:40:32.173Z"
}
{
"connection": "conectric",
"networkRef": "conectric",
"device1Ref": "00124b00051422cd",
"device2Ref": null,
"id": "15eb",
"gatewayId": "00124b00051422cd",
"type": "tempHumidityLight",
"payload": {
"battery": 3,
"eventCount": 144848,
"humidity": 51.22,
"bucketedLux": 0,
"temp": 76.23,
"unit": "F",
"lightLevel": 19
},
"sensorId": "15eb",
"sequenceNumber": 67,
"timestamp": 1588218063484,
"numHops": 0,
"maxHops": 0,
"t": "2020-04-30T03:41:03.484Z"
}
Messages received from meters are formatted as follows. The fields:
gatewayId
type
meterModel
timestamp
sensorId
sequenceNumber
are all as described here as part of the documentation for the mesh network to USB gateway.
The format for meter messages is dependent on the useHaystack
configuration setting in config.json
. When this is set to false
, the following message format will be generated.
In common with the sensor messages, meter messages contain a payload
object with a common schema regardless of the type of smart meter hardware used. An example message from an EKM v4 meter is shown below:
{
"gatewayId": "00124b00051422cd",
"type": "meter",
"meterModel": "ekm4",
"timestamp": 1589326058877,
"sensorId": "1d0c",
"sequenceNumber": 76,
"payload": {
"battery": 3.2,
"kwh_scale": 2,
"model": "4132",
"firmware": 25,
"meter_address": "000300002255",
"kwh_tot": 6334.78,
"reactive_energy_tot": 1455.51,
"rev_kwh_tot": 0,
"kwh_ln_1": 3012.84,
"kwh_ln_2": 3321.08,
"kwh_ln_3": 0,
"rev_kwh_ln_1": 0,
"rev_kwh_ln_2": 0,
"rev_kwh_ln_3": 0,
"resettable_kwh_tot": 6334.78,
"resettable_rev_kwh_tot": 0,
"rms_volts_ln_1": 119.2,
"rms_volts_ln_2": 119.3,
"rms_volts_ln_3": 0,
"amps_ln_1": 2.6,
"amps_ln_2": 4.6,
"amps_ln_3": 0,
"rms_watts_ln_1": 198,
"rms_watts_ln_2": 510,
"rms_watts_ln_3": 0,
"rms_watts_tot": 708,
"power_factor_ln_1": "C0.92",
"power_factor_ln_2": "C0.96",
"power_factor_ln_3": "C0.00",
"reactive_pwr_ln_1": 86,
"reactive_pwr_ln_2": 146,
"reactive_pwr_ln_3": 0,
"reactive_pwr_tot": 232,
"line_freq": 59.99,
"pulse_cnt_1": 0,
"pulse_cnt_2": 1,
"pulse_cnt_3": 1,
"state_inputs": 0,
"state_watts_dir": 1,
"state_out": 1,
"meter_time": "20051304072326",
"kwh_tariff_1": 37946.6,
"kwh_tariff_2": 25401.3,
"kwh_tariff_3": 0,
"kwh_tariff_4": 0,
"rev_kwh_tariff_1": 0,
"rev_kwh_tariff_2": 0,
"rev_kwh_tariff_3": 0,
"rev_kwh_tariff_4": 0,
"cos_theta_ln_1": 0.92,
"cos_theta_ln_2": 0.96,
"cos_theta_ln_3": 0,
"rms_watts_max_demand": 4500,
"max_demand_period": 1,
"pulse_ratio_1": 1,
"pulse_ratio_2": 1,
"pulse_ratio_3": 1,
"ct_ratio": 200,
"auto_reset_max_demand": 0,
"pulse_output_ratio": 800
}
}
timestamp
is the time that the gateway received the message. meter_time
is the time according to the meter when the message was generated. meter_time
is as received from the hardware and is formatted as follows:
YYMMDDXXHHMMSS
YY
= 2 digit year.MM
= month.DD
= day.XX
= day of week, 01 = Sunday, 02 = Tuesday ... 06 = Saturday.HH
= hour of day (24hr clock).MM
= minutes.SS
= seconds.
EKM Omnimeter v3 and v4 meters are supported. The meterModel
field will be set to ekm3
for the v3 Omnimeter, and ekm4
for the v4.
The v3 Omnimeter does not have all of the features of the v4 and uses a slightly different schema. Here's an example message from a v3 meter:
{
"gatewayId": "00124b00051422cd",
"type": "meter",
"meterModel": "ekm3",
"timestamp": 1589235150812,
"sensorId": "22fb",
"sequenceNumber": 55,
"payload": {
"battery": 3.2,
"model": "4130",
"firmware": 23,
"meter_address": "000010007076",
"kwh_tot": 9583.7,
"kwh_tariff_1": 5931.3,
"kwh_tariff_2": 3652.4,
"kwh_tariff_3": 0,
"kwh_tariff_4": 0,
"rev_kwh_tot": 9499.3,
"rev_kwh_tariff_1": 5879,
"rev_kwh_tariff_2": 3620.3,
"rev_kwh_tariff_3": 0,
"rev_kwh_tariff_4": 0,
"rms_volts_ln_1": 118,
"rms_volts_ln_2": 0,
"rms_volts_ln_3": 0,
"amps_ln_1": 0.4,
"amps_ln_2": 0,
"amps_ln_3": 0,
"rms_watts_ln_1": 42,
"rms_watts_ln_2": 0,
"rms_watts_ln_3": 0,
"rms_watts_tot": 42,
"cos_theta_ln_1": 0.78,
"cos_theta_ln_2": 0,
"cos_theta_ln_3": 0,
"max_demand": 6635,
"max_demand_period": 1,
"meter_time": "20051203061051",
"ct_ratio": 200,
"pulse_cnt_1": 0,
"pulse_cnt_2": 0,
"pulse_cnt_3": 0,
"pulse_ratio_1": 0,
"pulse_ratio_2": 0,
"pulse_ratio_3": 0,
"state_inputs": 0
}
}
When haystack mode is on (useHaystack
in config.json
set to true
), messages will look like the following. Note that battery
will always be null
(devices are powered from the meter, not a battery).
EKM v4 Meter:
{
"gatewayId": "00124b00051422cd",
"type": "meter",
"meterModel": "ekm4",
"timestamp": 1588391740085,
"sensorId": "1d0c",
"sequenceNumber": 249,
"connection": "ekm",
"networkRef": "conectric",
"device1Ref": "00124b00051422cd",
"device2Ref": "1d0c",
"equip": "elec_meter",
"t": "2020-05-02T11:51:29.000Z",
"id": "000300002255",
"payload": {
"battery": null,
"volt_A": 119.7,
"volt_B": 119.6,
"volt_C": 0,
"current_A": 0.2,
"current_B": 3.8,
"current_C": 0,
"power_A": 0.006,
"power_B": 0.418,
"power_C": 0,
"reactive_power_A": 0.032,
"reactive_power_B": 0.12,
"reactive_power_C": 0,
"pf_A": 0.19,
"pf_B": 0.96,
"pf_C": 0,
"power": 0.424,
"power_reactive": 0.152,
"state_current_dir": 1,
"freq": 60.03,
"power_max": 4.5,
"power_max_period": 1,
"power_max_auto_reset": 0,
"energy_A": 3005.56,
"energy_B": 3204.98,
"energy_C": 0,
"energy": 6211.39,
"power_reactive_h": 1414.06,
"energy_tariff_1": 37217.8,
"energy_tariff_2": 24896.1,
"energy_tariff_3": 0,
"energy_tariff_4": 0,
"energy_export_A": 0,
"energy_export_B": 0,
"energy_export_C": 0,
"energy_export": 0,
"energy_export_tariff_1": 0,
"energy_export_tariff_2": 0,
"energy_export_tariff_3": 0,
"energy_export_tariff_4": 0,
"energy_resettable": 6211.39,
"energy_export_resettable": 0,
"state_pulse_inputs": 0,
"state_out_cmd": 1,
"pulse_hisTotalized_1": 0,
"pulse_hisTotalized_2": 1,
"pulse_hisTotalized_3": 1,
"pulse_ratio_1": 1,
"pulse_ratio_2": 1,
"pulse_ratio_3": 1,
"pulse_output_ratio": 800,
"sensor_ct_ratio": 200,
"decimal": 2,
"meter_time": "2020-05-02T11:51:29.000Z"
}
}
EKM v3 Meter:
{
"gatewayId": "00124b00051422cd",
"type": "meter",
"meterModel": "ekm3",
"timestamp": 1588218048516,
"sensorId": "22fb",
"sequenceNumber": 8,
"connection": "ekm",
"networkRef": "conectric",
"device1Ref": "00124b00051422cd",
"device2Ref": "22fb",
"equip": "elec_meter",
"t": "2020-04-30T11:39:11.000Z",
"id": "000010007076",
"payload": {
"battery": null,
"volt_A": 118.7,
"volt_B": 0,
"volt_C": 0,
"current_A": 0.4,
"current_B": 0,
"current_C": 0,
"power_A": 0.042,
"power_B": 0,
"power_C": 0,
"reactive_power_A": null,
"reactive_power_B": null,
"reactive_power_C": null,
"pf_A": 0.78,
"pf_B": 0,
"pf_C": 0,
"power": 0.042,
"power_reactive": null,
"state_current_dir": null,
"freq": null,
"power_max": 6.635,
"power_max_period": 1,
"power_max_auto_reset": null,
"energy_A": null,
"energy_B": null,
"energy_C": null,
"energy": 9571.7,
"power_reactive_h": null,
"energy_tariff_1": 5923.8,
"energy_tariff_2": 3647.9,
"energy_tariff_3": 0,
"energy_tariff_4": 0,
"energy_export_A": null,
"energy_export_B": null,
"energy_export_C": null,
"energy_export": 9499.3,
"energy_export_tariff_1": 5879,
"energy_export_tariff_2": 3620.3,
"energy_export_tariff_3": 0,
"energy_export_tariff_4": 0,
"energy_resettable": null,
"energy_export_resettable": null,
"state_pulse_inputs": 0,
"state_out_cmd": null,
"pulse_hisTotalized_1": 0,
"pulse_hisTotalized_2": 0,
"pulse_hisTotalized_3": 0,
"pulse_ratio_1": 0,
"pulse_ratio_2": 0,
"pulse_ratio_3": 0,
"pulse_output_ratio": null,
"sensor_ct_ratio": 200,
"decimal": null,
"meter_time": "2020-04-30T11:39:11.000Z"
}
}
The v3 Omnimeter does not support all of the fields in the meter payload schema, so you can expect the values of these fields to be null when using a v3 meter:
reactive_power_A
reactive_power_B
reactive_power_C
power_reactive_h
freq
power_max_auto_reset
energy_A
energy_B
energy_C
energy_resettable
energy_export_resettable
state_out_cmd
pulse_output_ratio
decimal
In Haystack mode, the units for each meter data field are as follows:
Key | Unit |
---|---|
battery |
V |
temp |
degrees (C/F) |
humidity |
% |
adcIn |
V |
adcMax |
V |
lightLevel |
Lux |
eventCount |
# |
volt |
V |
current |
A |
power (active) |
kW |
reactive_power |
kVAR |
pf (power factor) |
% |
frequency |
Hz |
power_max |
kW |
energy |
kWh |
power_reactive_h |
kVARh |
energy_tarriff |
kWh |
energy_export |
kWh |
energy_export_tarriff |
kWh |
energy_resettable |
kWh |
energy_export_resettable |
kWh |
pulse_hisTotalized |
# |
sensor_ct_ratio |
A |