Skip to content

Commit d3204f6

Browse files
add java ota test (#36957)
1 parent bd273d2 commit d3204f6

File tree

11 files changed

+483
-9
lines changed

11 files changed

+483
-9
lines changed

.github/workflows/java-tests.yaml

+14
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ jobs:
9494
--target linux-x64-all-clusters-ipv6only-no-ble-no-wifi-tsan-clang-test \
9595
--target linux-x64-java-matter-controller \
9696
--target linux-x64-lit-icd-ipv6only \
97+
--target linux-x64-ota-requestor \
9798
build \
9899
"
99100
- name: Build Kotlin Matter Controller
@@ -259,6 +260,19 @@ jobs:
259260
--tool-args "onnetwork-long --nodeid 1 --setup-pin-code 20202021 --discriminator 3840 -t 1000" \
260261
--factoryreset \
261262
'
263+
- name: Run Pairing ICD Onnetwork and OTA test
264+
# Generally completes in seconds
265+
timeout-minutes: 2
266+
run: |
267+
scripts/run_in_python_env.sh out/venv \
268+
'./scripts/tests/run_java_test.py \
269+
--app out/linux-x64-ota-requestor/chip-ota-requestor-app \
270+
--app-args "--discriminator 3840 --interface-id -1" \
271+
--tool-path out/linux-x64-java-matter-controller \
272+
--tool-cluster "ota" \
273+
--tool-args "onnetwork-long-ota-over-bdx --nodeid 1 --setup-pin-code 20202021 --discriminator 3840 -t 1000" \
274+
--factoryreset \
275+
'
262276
- name: Run Pairing Onnetwork and get diagnostic log Test
263277
run: |
264278
scripts/run_in_python_env.sh out/venv \

examples/java-matter-controller/BUILD.gn

+5
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,13 @@ kotlin_binary("java-matter-controller") {
2222
output_name = "java-matter-controller"
2323
deps = [
2424
"${chip_root}/src/controller/java",
25+
"${chip_root}/src/controller/java:jsontlv",
2526
"${chip_root}/src/controller/java:onboarding_payload",
2627
"${chip_root}/third_party/java_deps:annotation",
28+
"${chip_root}/third_party/java_deps:gson",
29+
"${chip_root}/third_party/java_deps:json",
2730
"${chip_root}/third_party/java_deps:kotlin-stdlib",
31+
"${chip_root}/third_party/java_deps:protobuf-java",
2832
]
2933

3034
sources = [
@@ -44,6 +48,7 @@ kotlin_binary("java-matter-controller") {
4448
"java/src/com/matter/controller/commands/discover/DiscoverCommissionablesCommand.kt",
4549
"java/src/com/matter/controller/commands/discover/DiscoverCommissionersCommand.kt",
4650
"java/src/com/matter/controller/commands/icd/ICDListCommand.kt",
51+
"java/src/com/matter/controller/commands/ota/PairOnNetworkLongOtaOverBdxCommand.kt",
4752
"java/src/com/matter/controller/commands/pairing/CloseSessionCommand.kt",
4853
"java/src/com/matter/controller/commands/pairing/DiscoveryFilterType.kt",
4954
"java/src/com/matter/controller/commands/pairing/PairAddressPaseCommand.kt",
+12-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
11
Main-Class: com.matter.controller.MainKt
2-
Class-Path: ../lib/third_party/connectedhomeip/src/controller/java/CHIPController.jar ../lib/third_party/connectedhomeip/src/setup_payload/java/OnboardingPayload.jar ../lib/third_party/connectedhomeip/third_party/java_deps/stub_src/Android.jar ../lib/third_party/connectedhomeip/third_party/java_deps/json-20220924.jar ../lib/third_party/connectedhomeip/third_party/java_deps/jsr305-3.0.2.jar ../lib/third_party/connectedhomeip/third_party/java_deps/kotlin-stdlib-1.8.20.jar
3-
2+
Class-Path:
3+
../lib/third_party/connectedhomeip/src/controller/java/CHIPController.jar
4+
../lib/third_party/connectedhomeip/src/controller/java/CHIPInteractionModel.jar
5+
../lib/third_party/connectedhomeip/src/controller/java/libMatterTlv.jar
6+
../lib/third_party/connectedhomeip/src/controller/java/libMatterJson.jar
7+
../lib/third_party/connectedhomeip/src/setup_payload/java/OnboardingPayload.jar
8+
../lib/third_party/connectedhomeip/third_party/java_deps/stub_src/Android.jar
9+
../lib/third_party/connectedhomeip/third_party/java_deps/json-20220924.jar
10+
../lib/third_party/connectedhomeip/third_party/java_deps/gson-2.9.1.jar
11+
../lib/third_party/connectedhomeip/third_party/java_deps/jsr305-3.0.2.jar
12+
../lib/third_party/connectedhomeip/third_party/java_deps/protobuf-java-3.22.0.jar
13+
../lib/third_party/connectedhomeip/third_party/java_deps/kotlin-stdlib-1.8.20.jar

examples/java-matter-controller/java/src/com/matter/controller/Main.kt

+11-1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import com.matter.controller.commands.bdx.*
2323
import com.matter.controller.commands.common.*
2424
import com.matter.controller.commands.discover.*
2525
import com.matter.controller.commands.icd.*
26+
import com.matter.controller.commands.ota.PairOnNetworkLongOtaOverBdxCommand
2627
import com.matter.controller.commands.pairing.*
2728

2829
private fun getDiscoveryCommands(
@@ -91,6 +92,15 @@ private fun getBdxCommands(
9192
)
9293
}
9394

95+
private fun getOtaCommands(
96+
controller: ChipDeviceController,
97+
credentialsIssuer: CredentialsIssuer
98+
): List<Command> {
99+
return listOf(
100+
PairOnNetworkLongOtaOverBdxCommand(controller, credentialsIssuer),
101+
)
102+
}
103+
94104
fun main(args: Array<String>) {
95105
val controller =
96106
ChipDeviceController(
@@ -108,7 +118,7 @@ fun main(args: Array<String>) {
108118
commandManager.register("im", getImCommands(controller, credentialsIssuer))
109119
commandManager.register("icd", getICDCommands(controller, credentialsIssuer))
110120
commandManager.register("bdx", getBdxCommands(controller, credentialsIssuer))
111-
121+
commandManager.register("ota", getOtaCommands(controller, credentialsIssuer))
112122
try {
113123
commandManager.run(args)
114124
} catch (e: Exception) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,293 @@
1+
/*
2+
* Copyright (c) 2025 Project CHIP Authors
3+
* All rights reserved.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*
17+
*/
18+
19+
package com.matter.controller.commands.ota
20+
21+
import chip.devicecontroller.ChipDeviceController
22+
import chip.devicecontroller.GetConnectedDeviceCallbackJni.GetConnectedDeviceCallback
23+
import chip.devicecontroller.InvokeCallback
24+
import chip.devicecontroller.OTAProviderDelegate
25+
import chip.devicecontroller.model.InvokeElement
26+
import com.matter.controller.commands.common.CredentialsIssuer
27+
import com.matter.controller.commands.pairing.DiscoveryFilterType
28+
import com.matter.controller.commands.pairing.PairingCommand
29+
import com.matter.controller.commands.pairing.PairingModeType
30+
import com.matter.controller.commands.pairing.PairingNetworkType
31+
import java.io.BufferedInputStream
32+
import java.io.File
33+
import java.io.FileInputStream
34+
import java.io.IOException
35+
import java.io.InputStream
36+
import java.util.logging.Level
37+
import java.util.logging.Logger
38+
import matter.tlv.AnonymousTag
39+
import matter.tlv.ContextSpecificTag
40+
import matter.tlv.TlvWriter
41+
42+
class PairOnNetworkLongOtaOverBdxCommand(
43+
val controller: ChipDeviceController,
44+
credsIssue: CredentialsIssuer?
45+
) :
46+
PairingCommand(
47+
controller,
48+
"onnetwork-long-ota-over-bdx",
49+
credsIssue,
50+
PairingModeType.ON_NETWORK,
51+
PairingNetworkType.NONE,
52+
DiscoveryFilterType.LONG_DISCRIMINATOR
53+
) {
54+
private var devicePointer: Long = 0
55+
private val uri: StringBuffer = StringBuffer()
56+
private val filename: StringBuffer = StringBuffer()
57+
58+
init {
59+
addArgument("uri", uri, null, false)
60+
addArgument("filename", filename, null, false)
61+
}
62+
63+
private fun setDevicePointer(devicePointer: Long) {
64+
this.devicePointer = devicePointer
65+
}
66+
67+
private inner class InternalAnnouceOTACallback : InvokeCallback {
68+
override fun onError(e: Exception) {
69+
logger.log(Level.INFO, "AnnouceOTA receive onError" + e.message)
70+
setFailure("invoke failure")
71+
}
72+
73+
override fun onResponse(element: InvokeElement?, successCode: Long) {
74+
logger.log(
75+
Level.INFO,
76+
"AnnouceOTA success code is $successCode and start the bdx transfer soon"
77+
)
78+
}
79+
}
80+
81+
private inner class InternalGetConnectedDeviceCallback : GetConnectedDeviceCallback {
82+
override fun onDeviceConnected(devicePointer: Long) {
83+
setDevicePointer(devicePointer)
84+
logger.log(Level.INFO, "onDeviceConnected")
85+
}
86+
87+
override fun onConnectionFailure(nodeId: Long, error: Exception) {
88+
logger.log(Level.INFO, "onConnectionFailure")
89+
}
90+
}
91+
92+
private inner class OtaProviderCallback : OTAProviderDelegate {
93+
private var fileName: String? = null
94+
private var version: Long = 0
95+
private var versionString: String? = null
96+
private var uri: String? = null
97+
98+
private var inputStream: InputStream? = null
99+
private var bufferedInputStream: BufferedInputStream? = null
100+
101+
private var status: OTAProviderDelegate.QueryImageResponseStatusEnum? = null
102+
private var delayedTime: UInt? = null
103+
private var userConsentNeeded: Boolean? = null
104+
105+
fun setOTAFile(version: Long, versionString: String, fileName: String, uri: String?) {
106+
this.status = OTAProviderDelegate.QueryImageResponseStatusEnum.UpdateAvailable
107+
this.version = version
108+
this.versionString = versionString
109+
this.fileName = fileName
110+
this.uri = uri
111+
}
112+
113+
override fun handleQueryImage(
114+
vendorId: Int,
115+
productId: Int,
116+
softwareVersion: Long,
117+
hardwareVersion: Int?,
118+
location: String?,
119+
requestorCanConsent: Boolean?,
120+
metadataForProvider: ByteArray?
121+
): OTAProviderDelegate.QueryImageResponse? {
122+
logger.log(
123+
Level.INFO,
124+
"handleQueryImage, $vendorId, $productId, $softwareVersion, $hardwareVersion, $location"
125+
)
126+
return when (status) {
127+
OTAProviderDelegate.QueryImageResponseStatusEnum.UpdateAvailable ->
128+
OTAProviderDelegate.QueryImageResponse(version, versionString, fileName, null)
129+
OTAProviderDelegate.QueryImageResponseStatusEnum.Busy ->
130+
OTAProviderDelegate.QueryImageResponse(
131+
status,
132+
delayedTime?.toLong() ?: 0,
133+
userConsentNeeded ?: false
134+
)
135+
OTAProviderDelegate.QueryImageResponseStatusEnum.NotAvailable ->
136+
OTAProviderDelegate.QueryImageResponse(status, userConsentNeeded ?: false)
137+
else -> null
138+
}
139+
}
140+
141+
override fun handleOTAQueryFailure(error: Int) {
142+
setFailure("handleOTAQueryFailure, $error")
143+
}
144+
145+
override fun handleApplyUpdateRequest(
146+
nodeId: Long,
147+
newVersion: Long
148+
): OTAProviderDelegate.ApplyUpdateResponse {
149+
logger.log(Level.INFO, "handleApplyUpdateRequest, $nodeId, $newVersion")
150+
return OTAProviderDelegate.ApplyUpdateResponse(
151+
OTAProviderDelegate.ApplyUpdateActionEnum.Proceed,
152+
APPLY_WAITING_TIME
153+
)
154+
}
155+
156+
override fun handleNotifyUpdateApplied(nodeId: Long) {
157+
logger.log(Level.INFO, "handleNotifyUpdateApplied, Finish Firmware Update, $nodeId")
158+
}
159+
160+
override fun handleBDXTransferSessionBegin(
161+
nodeId: Long,
162+
fileDesignator: String?,
163+
offset: Long
164+
) {
165+
logger.log(Level.INFO, "handleBDXTransferSessionBegin, $nodeId, $fileDesignator, $offset")
166+
try {
167+
val file = File(uri + fileName)
168+
val fileInputStream = FileInputStream(file)
169+
bufferedInputStream = BufferedInputStream(fileInputStream)
170+
} catch (e: IOException) {
171+
logger.log(Level.INFO, "exception", e)
172+
inputStream?.close()
173+
bufferedInputStream?.close()
174+
inputStream = null
175+
bufferedInputStream = null
176+
return
177+
}
178+
}
179+
180+
override fun handleBDXTransferSessionEnd(errorCode: Long, nodeId: Long) {
181+
logger.log(Level.INFO, "handleBDXTransferSessionEnd, $errorCode, $nodeId")
182+
inputStream?.close()
183+
bufferedInputStream?.close()
184+
inputStream = null
185+
bufferedInputStream = null
186+
setSuccess()
187+
}
188+
189+
override fun handleBDXQuery(
190+
nodeId: Long,
191+
blockSize: Int,
192+
blockIndex: Long,
193+
bytesToSkip: Long
194+
): OTAProviderDelegate.BDXData? {
195+
// This code is just example code. This code doesn't check blockIndex and bytesToSkip
196+
// variable.
197+
logger.log(
198+
Level.INFO,
199+
"handleBDXQuery sending.., $nodeId, $blockSize, $blockIndex, $bytesToSkip"
200+
)
201+
if (bufferedInputStream == null) {
202+
return OTAProviderDelegate.BDXData(ByteArray(0), true)
203+
}
204+
val packet = ByteArray(blockSize)
205+
val len = bufferedInputStream!!.read(packet)
206+
207+
val sendPacket =
208+
if (len < blockSize) {
209+
packet.copyOf(len)
210+
} else if (len < 0) {
211+
ByteArray(0)
212+
} else {
213+
packet.clone()
214+
}
215+
216+
val isEOF = len < blockSize
217+
218+
return OTAProviderDelegate.BDXData(sendPacket, isEOF)
219+
}
220+
}
221+
222+
override fun runCommand() {
223+
currentCommissioner()
224+
.pairDeviceWithAddress(
225+
getNodeId(),
226+
getRemoteAddr().address.hostAddress,
227+
MATTER_PORT,
228+
getDiscriminator(),
229+
getSetupPINCode(),
230+
null
231+
)
232+
currentCommissioner().setCompletionListener(this)
233+
waitCompleteMs(getTimeoutMillis())
234+
235+
val version = 2L
236+
val versionString = "2.0"
237+
238+
logger.log(Level.INFO, "sendAnnounceOTAProviderBtnClick : $filename")
239+
240+
val otaProviderCallback = OtaProviderCallback()
241+
currentCommissioner().startOTAProvider(otaProviderCallback)
242+
otaProviderCallback.setOTAFile(
243+
version,
244+
versionString,
245+
this.filename.toString(),
246+
this.uri.toString()
247+
)
248+
249+
currentCommissioner()
250+
.getConnectedDevicePointer(getNodeId(), InternalGetConnectedDeviceCallback())
251+
clear()
252+
253+
val announceReason: Int = 0
254+
val annouceOTATlvWriter = TlvWriter()
255+
annouceOTATlvWriter.startStructure(AnonymousTag)
256+
annouceOTATlvWriter.put(
257+
ContextSpecificTag(PROVIDER_NODE_ID_TAG),
258+
controller.getControllerNodeId().toULong()
259+
)
260+
annouceOTATlvWriter.put(ContextSpecificTag(PROVIDER_VENDOR_ID_TAG), TEST_VENDOR_ID.toULong())
261+
annouceOTATlvWriter.put(ContextSpecificTag(ANNOUCEMENT_REASON_TAG), announceReason.toULong())
262+
annouceOTATlvWriter.put(ContextSpecificTag(ENDPOINT_TAG), OTA_PROVIDER_ENDPOINT_ID.toULong())
263+
annouceOTATlvWriter.endStructure()
264+
265+
val annouceOTACommandElement: InvokeElement =
266+
InvokeElement.newInstance(
267+
OTA_REQUESTER_ENDPOINT_ID,
268+
CLUSTER_ID_OTA_Requestor,
269+
ANNOUCE_OTA_PROVIDER_COMMAND,
270+
annouceOTATlvWriter.getEncoded(),
271+
null
272+
)
273+
currentCommissioner()
274+
.invoke(InternalAnnouceOTACallback(), devicePointer, annouceOTACommandElement, 0, 0)
275+
276+
waitCompleteMs(getTimeoutMillis())
277+
}
278+
279+
companion object {
280+
private val logger = Logger.getLogger(PairOnNetworkLongOtaOverBdxCommand::class.java.name)
281+
private const val MATTER_PORT = 5540
282+
private const val CLUSTER_ID_OTA_Requestor = 0X2AL
283+
private const val ANNOUCE_OTA_PROVIDER_COMMAND = 0X0L
284+
private const val OTA_PROVIDER_ENDPOINT_ID = 0
285+
private const val OTA_REQUESTER_ENDPOINT_ID = 0
286+
private const val APPLY_WAITING_TIME = 10L
287+
private const val TEST_VENDOR_ID = 0xFFF1L
288+
private const val PROVIDER_NODE_ID_TAG = 0
289+
private const val PROVIDER_VENDOR_ID_TAG = 1
290+
private const val ANNOUCEMENT_REASON_TAG = 2
291+
private const val ENDPOINT_TAG = 4
292+
}
293+
}

0 commit comments

Comments
 (0)