The Energy Gateway app is intended to act as a CommodityPrice
and Electrical Grid Conditions
cluster server that can share the current electrical price of energy and grid conditions (how many Grams of CO2 per kWh) to Energy Smart Appliances (ESAs).
This is primarily intended as an example reference application.
This document describes how to build and run CHIP Linux Energy Gateway Example on Raspberry Pi. This doc is tested on Ubuntu for Raspberry Pi Server 24.04 LTS (aarch64).
To cross-compile this example on x64 host and run on NXP i.MX 8M Mini EVK, see the associated README document for details.
Install tool chain
sudo apt-get install git gcc g++ python pkg-config libssl-dev libdbus-1-dev libglib2.0-dev ninja-build python3-venv python3-dev unzip
Build the example application:
cd ~/connectedhomeip/examples/energy-gateway-app/linux git submodule update --init source third_party/connectedhomeip/scripts/activate.sh gn gen out/debug ninja -C out/debug
To delete generated executable, libraries and object files use:
cd ~/connectedhomeip/examples/energy-gateway-app/linux rm -rf out
--wifi
Enables WiFi management feature. Required for WiFi commissioning.
--thread
Enables Thread management feature, requires ot-br-posix dbus daemon running. Required for Thread commissioning.
--ble-controller <selector>
Use the specific Bluetooth controller for BLE advertisement and connections. For details on controller selection refer to Linux BLE Settings.
Prerequisites
pi-bluetooth
via APT.Building
Follow Building section of this document.
Running
[Optional] Plug USB Bluetooth dongle
Run Linux Energy Gateway Example App
cd ~/connectedhomeip/examples/energy-gateway-app/linux sudo out/debug/chip-energy-gateway-app --ble-controller [bluetooth controller number] # In this example, the device we want to use is hci1 sudo out/debug/chip-energy-gateway-app --ble-controller 1
Test the device using ChipDeviceController on your laptop / workstation etc.
Device tracing is available to analyze the device performance. To turn on tracing, build with RPC enabled. See Building with RPC enabled.
Obtain tracing json file.
{PIGWEED_REPO}/pw_trace_tokenized/py/pw_trace_tokenized/get_trace.py -s localhost:33000 \ -o {OUTPUT_FILE} -t {ELF_FILE} {PIGWEED_REPO}/pw_trace_tokenized/pw_trace_protos/trace_rpc.proto
When you want to test this cluster you can use matter-repl or chip-tool by hand. MATTER-REPL is slightly easier to interact with when dealing with some of the complex structures.
There are several test scripts provided for Commodity Price cluster (in src/python_testing):
TC_SEPR_2_1
: This validates the primary functionalityTC_SEPR_2_2
: This validates GetDetailedPriceRequest commandTC_SEPR_2_3
: This validates the FORECASTING feature and GetDetailedForecastRequest command (NOTE requires TCP support - see below)There are several test scripts provided for Electrical Grid Conditions cluster (in src/python_testing):
TC_EGC_2_1
: This validates the primary functionalityTC_EGC_2_2
: This validates that when the CurrentConditions attribute changes, an event is sentTC_EGC_2_3
: This validates the FORECASTING feature using test event to simulate data*
- These scripts require the use of Test Event Triggers via the GeneralDiagnostics cluster on Endpoint 0. This requires an enableKey
(16 bytes) and a set of reserved int64_t test event trigger codes.
NOTE: that some non Linux platforms may not support TCP (for large messages). This means that the GetDetailedForecastRequest() command will not be supported on these platforms.
Once the application is built you also need to tell it at runtime what the chosen enable key is using the --enable-key
command line option.
./chip-energy-gateway-app --enable-key 000102030405060708090a0b0c0d0e0f
From the top-level of the connectedhomeip repo type:
Start the chip-energy-gateway-app:
rm -f /tmp/chip_*; out/debug/chip-energy-gateway-app --enable-key 000102030405060708090a0b0c0d0e0f
Then run the test:
python src/python_testing/TC_SEPR_2_1.py --endpoint 1 -m on-network -n 1234 -p 20202021 -d 3840 --hex-arg enableKey:000102030405060708090a0b0c0d0e0f
--endpoint 1
must be used with the example, since the SEPR cluster is on endpoint 1. The --hex-arg enableKey:<key>
value must match the --enable-key <key>
used on chip-energy-gateway-app args.The chip-energy-gateway-app will need to be stopped before running each test script as each test commissions the chip-energy-gateway-app in the first step. That is also why the /tmp/chip_*
files are deleted before running chip-energy-gateway-app as this is where the app stores the matter persistent data (e.g. fabric info).
./build_python.sh -i out/python
source out/python/bin/activate
./chip-energy-gateway-app --enable-key 000102030405060708090a0b0c0d0e0f
source out/python/bin/activate
)matter-repl
await devCtrl.CommissionOnNetwork(200,20202021) # Commission with NodeID 200 Out[1]: 200
This allows you to get current and forecast energy prices.
Commodity Price
attributes# Read from NodeID 200, Endpoint 1, all attributes on CommodityPrice cluster await devCtrl.ReadAttribute(200,[(1, chip.clusters.CommodityPrice)])
The response in the default app is a null CurrentPrice
and an empty PriceForecast
attribute:
Out[11]: { │ 1: { │ │ <class 'chip.clusters.Objects.CommodityPrice'>: { │ │ │ <class 'chip.clusters.Attribute.DataVersion'>: 3672963490, │ │ │ <class 'chip.clusters.Objects.CommodityPrice.Attributes.AcceptedCommandList'>: [ │ │ │ │ 0, │ │ │ │ 2 │ │ │ ], │ │ │ <class 'chip.clusters.Objects.CommodityPrice.Attributes.Currency'>: CurrencyStruct( │ │ │ │ currency=826, │ │ │ │ decimalPoints=5 │ │ │ ), │ │ │ <class 'chip.clusters.Objects.CommodityPrice.Attributes.GeneratedCommandList'>: [ │ │ │ │ 1, │ │ │ │ 3 │ │ │ ], │ │ │ <class 'chip.clusters.Objects.CommodityPrice.Attributes.AttributeList'>: [ │ │ │ │ 0, │ │ │ │ 1, │ │ │ │ 2, │ │ │ │ 3, │ │ │ │ 65532, │ │ │ │ 65533, │ │ │ │ 65528, │ │ │ │ 65529, │ │ │ │ 65531 │ │ │ ], │ │ │ <class 'chip.clusters.Objects.CommodityPrice.Attributes.TariffUnit'>: <TariffUnitEnum.kKWh: 0>, │ │ │ <class 'chip.clusters.Objects.CommodityPrice.Attributes.PriceForecast'>: [], │ │ │ <class 'chip.clusters.Objects.CommodityPrice.Attributes.ClusterRevision'>: 4, │ │ │ <class 'chip.clusters.Objects.CommodityPrice.Attributes.CurrentPrice'>: Null, │ │ │ <class 'chip.clusters.Objects.CommodityPrice.Attributes.FeatureMap'>: 1 │ │ } │ }
eventTrigger=0x0095000000000000
we can generate a test CurrentPrice
with sample data# Send a test event trigger NodeID 200, Endpoint 0, with eventTrigger=0x0095000000000000 await devCtrl.SendCommand(200, 0, chip.clusters.GeneralDiagnostics.Commands.TestEventTrigger(enableKey=bytes([b for b in range(16)]), eventTrigger=0x0095000000000000))
CurrentPrice
attribute (see the values have changed)await devCtrl.ReadAttribute(200,[(1,chip.clusters.CommodityPrice.Attributes.CurrentPrice)]) Out[18]: { │ 1: { │ │ <class 'chip.clusters.Objects.CommodityPrice'>: { │ │ │ <class 'chip.clusters.Attribute.DataVersion'>: 3672963491, │ │ │ <class 'chip.clusters.Objects.CommodityPrice.Attributes.CurrentPrice'>: CommodityPriceStruct( │ │ │ │ periodStart=799150761, │ │ │ │ periodEnd=799152561, │ │ │ │ price=15916, │ │ │ │ priceLevel=3, │ │ │ │ description=None, │ │ │ │ components=None │ │ │ ) │ │ } │ } }
NOTE: that the description and components are not included in the attribute READ.
To get these we need to use the GetDetailedPriceRequest command: This takes a details
bitmap which requests the type of detail to be returned:
- b0 = Description - b1 = Components
i.e.
- details = 1 (Description ONLY) - details = 2 (Components ONLY) - details = 3 (Description & Components)
GetDetailedPriceRequest()
await devCtrl.SendCommand(200, 1, chip.clusters.CommodityPrice.Commands.GetDetailedPriceRequest(3)) Out[15]: GetDetailedPriceResponse( │ currentPrice=CommodityPriceStruct( │ │ periodStart=799150761, │ │ periodEnd=799152561, │ │ price=15916, │ │ priceLevel=3, │ │ description='Medium', │ │ components=[ │ │ │ CommodityPriceComponentStruct( │ │ │ │ price=15120, │ │ │ │ source=<TariffPriceTypeEnum.kStandard: 0>, │ │ │ │ description='ExVAT', │ │ │ │ tariffComponentID=None │ │ │ ), │ │ │ CommodityPriceComponentStruct( │ │ │ │ price=795, │ │ │ │ source=<TariffPriceTypeEnum.kStandard: 0>, │ │ │ │ description='VAT', │ │ │ │ tariffComponentID=None │ │ │ ) │ │ ] │ ) )
eventTrigger=0x0095000000000001
we can generate a test PriceForecast
containing the price values for the next few hours# Send a test event trigger NodeID 200, Endpoint 0, with eventTrigger=0x0095000000000001 await devCtrl.SendCommand(200, 0, chip.clusters.GeneralDiagnostics.Commands.TestEventTrigger(enableKey=bytes([b for b in range(16)]), eventTrigger=0x0095000000000001))
PriceForecast
attributes (see the values have changed)await devCtrl.ReadAttribute(200,[(1,chip.clusters.CommodityPrice.Attributes.PriceForecast)]) Out[21]: { │ 1: { │ │ <class 'chip.clusters.Objects.CommodityPrice'>: { │ │ │ <class 'chip.clusters.Attribute.DataVersion'>: 3672963492, │ │ │ <class 'chip.clusters.Objects.CommodityPrice.Attributes.PriceForecast'>: [ │ │ │ │ CommodityPriceStruct( │ │ │ │ │ periodStart=799152701, │ │ │ │ │ periodEnd=799154500, │ │ │ │ │ price=27935, │ │ │ │ │ priceLevel=3, │ │ │ │ │ description=None, │ │ │ │ │ components=None │ │ │ │ ), │ │ │ │ CommodityPriceStruct( │ │ │ │ │ periodStart=799154501, │ │ │ │ │ periodEnd=799156300, │ │ │ │ │ price=23687, │ │ │ │ │ priceLevel=2, │ │ │ │ │ description=None, │ │ │ │ │ components=None │ │ │ │ ), │ │ │ │ CommodityPriceStruct( │ │ │ │ │ periodStart=799156301, │ │ │ │ │ periodEnd=799158100, │ │ │ │ │ price=18636, │ │ │ │ │ priceLevel=2, │ │ │ │ │ description=None, │ │ │ │ │ components=None │ │ │ │ ), │ │ │ │ CommodityPriceStruct( │ │ │ │ │ periodStart=799158101, │ │ │ │ │ periodEnd=799159900, │ │ │ │ │ price=8889, │ │ │ │ │ priceLevel=1, │ │ │ │ │ description=None, │ │ │ │ │ components=None │ │ │ │ ), ... │ │ │ │ CommodityPriceStruct( │ │ │ │ │ periodStart=799251701, │ │ │ │ │ periodEnd=799253500, │ │ │ │ │ price=24450, │ │ │ │ │ priceLevel=3, │ │ │ │ │ description=None, │ │ │ │ │ components=None │ │ │ │ ) │ │ │ ] │ │ } │ } }
NOTE: that the description and components are not included in the attribute READ.
To get these we need to use the GetDetailedForecastRequest command (USES TCP):
This takes a details
bitmap which requests the type of detail to be returned:
b0 = Description b1 = Components
i.e. details = 1 (Description ONLY) details = 2 (Components ONLY) details = 3 (Description & Components)
GetDetailedForecastRequest()
dev = await devCtrl.GetConnectedDevice(200, allowPASE=False, timeoutMs=1000, payloadCapability=chip.ChipDeviceCtrl.TransportPayloadCapability.LARGE_PAYLOAD)
GetDetailedForecastRequest()
command:await devCtrl.SendCommand(200, 1, chip.clusters.CommodityPrice.Commands.GetDetailedForecastRequest(3)) Out[24]: GetDetailedForecastResponse( │ priceForecast=[ │ │ CommodityPriceStruct( │ │ │ periodStart=799152701, │ │ │ periodEnd=799154500, │ │ │ price=27935, │ │ │ priceLevel=3, │ │ │ description='High', │ │ │ components=[ │ │ │ │ CommodityPriceComponentStruct( │ │ │ │ │ price=26538, │ │ │ │ │ source=<TariffPriceTypeEnum.kStandard: 0>, │ │ │ │ │ description='ExVAT', │ │ │ │ │ tariffComponentID=None │ │ │ │ ), │ │ │ │ CommodityPriceComponentStruct( │ │ │ │ │ price=1396, │ │ │ │ │ source=<TariffPriceTypeEnum.kStandard: 0>, │ │ │ │ │ description='VAT', │ │ │ │ │ tariffComponentID=None │ │ │ │ ) │ │ │ ] │ │ ), │ │ CommodityPriceStruct( │ │ │ periodStart=799154501, │ │ │ periodEnd=799156300, │ │ │ price=23687, │ │ │ priceLevel=2, │ │ │ description='Medium', │ │ │ components=[ │ │ │ │ CommodityPriceComponentStruct( │ │ │ │ │ price=22502, │ │ │ │ │ source=<TariffPriceTypeEnum.kStandard: 0>, │ │ │ │ │ description='ExVAT', │ │ │ │ │ tariffComponentID=None │ │ │ │ ), │ │ │ │ CommodityPriceComponentStruct( │ │ │ │ │ price=1184, │ │ │ │ │ source=<TariffPriceTypeEnum.kStandard: 0>, │ │ │ │ │ description='VAT', │ │ │ │ │ tariffComponentID=None │ │ │ │ ) │ │ │ ] │ │ ), ... │ │ CommodityPriceStruct( │ │ │ periodStart=799251701, │ │ │ periodEnd=799253500, │ │ │ price=24450, │ │ │ priceLevel=3, │ │ │ description='High', │ │ │ components=[ │ │ │ │ CommodityPriceComponentStruct( │ │ │ │ │ price=23227, │ │ │ │ │ source=<TariffPriceTypeEnum.kStandard: 0>, │ │ │ │ │ description='ExVAT', │ │ │ │ │ tariffComponentID=None │ │ │ │ ), │ │ │ │ CommodityPriceComponentStruct( │ │ │ │ │ price=1222, │ │ │ │ │ source=<TariffPriceTypeEnum.kStandard: 0>, │ │ │ │ │ description='VAT', │ │ │ │ │ tariffComponentID=None │ │ │ │ ) │ │ │ ] │ │ ) │ ] )
This allows you to get current and forecast electrical grid conditions. This assumes you have already commissioned the app (see above).
Electrical Grid Conditions
attributes# Read from NodeID 200, Endpoint 1, all attributes on ElectricalGridConditions cluster await devCtrl.ReadAttribute(200,[(1, chip.clusters.ElectricalGridConditions)]) Out[2]: { │ 1: { │ │ <class 'chip.clusters.Objects.ElectricalGridConditions'>: { │ │ │ <class 'chip.clusters.Attribute.DataVersion'>: 2998541776, │ │ │ <class 'chip.clusters.Objects.ElectricalGridConditions.Attributes.LocalGenerationAvailable'>: True, │ │ │ <class 'chip.clusters.Objects.ElectricalGridConditions.Attributes.ClusterRevision'>: 1, │ │ │ <class 'chip.clusters.Objects.ElectricalGridConditions.Attributes.ForecastConditions'>: [], │ │ │ <class 'chip.clusters.Objects.ElectricalGridConditions.Attributes.AcceptedCommandList'>: [], │ │ │ <class 'chip.clusters.Objects.ElectricalGridConditions.Attributes.FeatureMap'>: 1, │ │ │ <class 'chip.clusters.Objects.ElectricalGridConditions.Attributes.CurrentConditions'>: Null, │ │ │ <class 'chip.clusters.Objects.ElectricalGridConditions.Attributes.GeneratedCommandList'>: [], │ │ │ <class 'chip.clusters.Objects.ElectricalGridConditions.Attributes.AttributeList'>: [ │ │ │ │ 0, │ │ │ │ 1, │ │ │ │ 2, │ │ │ │ 65532, │ │ │ │ 65533, │ │ │ │ 65528, │ │ │ │ 65529, │ │ │ │ 65531 │ │ │ ] │ │ } │ } }
The response in the default app is a null CurrentConditions
and an empty ForecastConditions
attribute.
eventTrigger=0x00A0000000000000
we can generate a test CurrentConditions
with sample data# Send a test event trigger NodeID 200, Endpoint 0, with eventTrigger=0x00A0000000000000 await devCtrl.SendCommand(200, 0, chip.clusters.GeneralDiagnostics.Commands.TestEventTrigger(enableKey=bytes([b for b in range(16)]), eventTrigger=0x00A0000000000000))
CurrentConditions
attribute (see the values have changed)await devCtrl.ReadAttribute(200,[(1, chip.clusters.ElectricalGridConditions.Attributes.CurrentConditions)]) Out[7]: { │ 1: { │ │ <class 'chip.clusters.Objects.ElectricalGridConditions'>: { │ │ │ <class 'chip.clusters.Attribute.DataVersion'>: 488127616, │ │ │ <class 'chip.clusters.Objects.ElectricalGridConditions.Attributes.CurrentConditions'>: ElectricalGridConditionsStruct( │ │ │ │ periodStart=799154050, │ │ │ │ periodEnd=799155850, │ │ │ │ gridCarbonIntensity=230, │ │ │ │ gridCarbonLevel=<ThreeLevelEnum.kMedium: 1>, │ │ │ │ localCarbonIntensity=0, │ │ │ │ localCarbonLevel=<ThreeLevelEnum.kLow: 0> │ │ │ ) │ │ } │ } }
eventTrigger=0x00A0000000000001
we can generate a test ForecastConditions
with sample data# Send a test event trigger NodeID 200, Endpoint 0, with eventTrigger=0x00A0000000000001 await devCtrl.SendCommand(200, 0, chip.clusters.GeneralDiagnostics.Commands.TestEventTrigger(enableKey=bytes([b for b in range(16)]), eventTrigger=0x00A0000000000001))
ForecastConditions
attribute (see the values have changed)await devCtrl.ReadAttribute(200,[(1, chip.clusters.ElectricalGridConditions.Attributes.ForecastConditions)]) Out[9]: { │ 1: { │ │ <class 'chip.clusters.Objects.ElectricalGridConditions'>: { │ │ │ <class 'chip.clusters.Attribute.DataVersion'>: 488127617, │ │ │ <class 'chip.clusters.Objects.ElectricalGridConditions.Attributes.ForecastConditions'>: [ │ │ │ │ ElectricalGridConditionsStruct( │ │ │ │ │ periodStart=799154301, │ │ │ │ │ periodEnd=799156100, │ │ │ │ │ gridCarbonIntensity=18, │ │ │ │ │ gridCarbonLevel=<ThreeLevelEnum.kLow: 0>, │ │ │ │ │ localCarbonIntensity=18, │ │ │ │ │ localCarbonLevel=<ThreeLevelEnum.kLow: 0> │ │ │ │ ), │ │ │ │ ElectricalGridConditionsStruct( │ │ │ │ │ periodStart=799156101, │ │ │ │ │ periodEnd=799157900, │ │ │ │ │ gridCarbonIntensity=399, │ │ │ │ │ gridCarbonLevel=<ThreeLevelEnum.kHigh: 2>, │ │ │ │ │ localCarbonIntensity=399, │ │ │ │ │ localCarbonLevel=<ThreeLevelEnum.kHigh: 2> │ │ │ │ ), │ │ │ │ ElectricalGridConditionsStruct( │ │ │ │ │ periodStart=799157901, │ │ │ │ │ periodEnd=799159700, │ │ │ │ │ gridCarbonIntensity=165, │ │ │ │ │ gridCarbonLevel=<ThreeLevelEnum.kMedium: 1>, │ │ │ │ │ localCarbonIntensity=165, │ │ │ │ │ localCarbonLevel=<ThreeLevelEnum.kMedium: 1> │ │ │ │ ), ... │ │ │ │ ElectricalGridConditionsStruct( │ │ │ │ │ periodStart=799238901, │ │ │ │ │ periodEnd=799240700, │ │ │ │ │ gridCarbonIntensity=130, │ │ │ │ │ gridCarbonLevel=<ThreeLevelEnum.kMedium: 1>, │ │ │ │ │ localCarbonIntensity=130, │ │ │ │ │ localCarbonLevel=<ThreeLevelEnum.kMedium: 1> │ │ │ │ ) │ │ │ ] │ │ } │ } }