blob: 119638edf321b13a0faff92c1d55b06b51016d58 [file] [log] [blame]
/**
*
* Copyright (c) 2020 Silicon Labs
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* This module provides queries for endpoint configuration.
*
* @module DB API: endpoint configuration queries against the database.
*/
const dbApi = require('./db-api.js')
const bin = require('../util/bin')
const dbMapping = require('./db-mapping.js')
const dbEnum = require('../../src-shared/db-enum.js')
/**
* Returns a promise resolving into all endpoints.
*
* @param {*} db
* @param {*} sessionId
* @param {*} category
* @returns Promise resolving into all endpoints.
*/
async function selectAllEndpoints(db, sessionId, category = null) {
let categorySqlJoinString = category
? `
LEFT JOIN
ENDPOINT_TYPE
ON
E1.ENDPOINT_TYPE_REF = ENDPOINT_TYPE.ENDPOINT_TYPE_ID
LEFT JOIN
ENDPOINT_TYPE_DEVICE
ON
ENDPOINT_TYPE_DEVICE.ENDPOINT_TYPE_REF = ENDPOINT_TYPE.ENDPOINT_TYPE_ID
LEFT JOIN
DEVICE_TYPE
ON
ENDPOINT_TYPE_DEVICE.DEVICE_TYPE_REF = DEVICE_TYPE.DEVICE_TYPE_ID
LEFT JOIN
PACKAGE
ON
PACKAGE.PACKAGE_ID = DEVICE_TYPE.PACKAGE_REF`
: ``
let categorySqlSelectString = category
? `,
PACKAGE.CATEGORY`
: ``
let categorySqlWhereString = category
? `AND
PACKAGE.CATEGORY = '${category}'`
: ``
let rows = await dbApi.dbAll(
db,
`
SELECT
E1.ENDPOINT_ID,
E1.SESSION_REF,
E1.ENDPOINT_TYPE_REF,
E1.PROFILE,
E1.ENDPOINT_IDENTIFIER,
E1.NETWORK_IDENTIFIER,
E2.ENDPOINT_ID AS PARENT_ENDPOINT_REF,
E2.ENDPOINT_IDENTIFIER AS PARENT_ENDPOINT_IDENTIFIER
${categorySqlSelectString}
FROM
ENDPOINT AS E1
LEFT JOIN
ENDPOINT AS E2
ON
E2.ENDPOINT_ID = E1.PARENT_ENDPOINT_REF
${categorySqlJoinString}
WHERE
E1.SESSION_REF = ?
${categorySqlWhereString}
ORDER BY
E1.ENDPOINT_IDENTIFIER
`,
[sessionId]
)
return rows.map(dbMapping.map.endpointExtended)
}
/**
* Retrieves the root node device type references from the ENDPOINT_COMPOSITION table
* for the given package IDs.
*
* @param {*} db - The database connection object.
* @param {Array<number>} packageIds - An array of package IDs to query.
* @returns {Promise<Array>} - A promise that resolves to an array of rows containing DEVICE_TYPE_REF.
*/
async function getRootNode(db, packageIds) {
const query = `
SELECT
EC.DEVICE_TYPE_REF,
EC.CODE,
EC.TYPE,
DT.NAME,
DT.PACKAGE_REF
FROM
ENDPOINT_COMPOSITION EC
JOIN
DEVICE_TYPE DT ON EC.DEVICE_TYPE_REF = DT.DEVICE_TYPE_ID
WHERE
EC.TYPE = ? AND
DT.PACKAGE_REF IN (${packageIds.map(() => '?').join(', ')})
`
let result = await dbApi.dbAll(db, query, [
dbEnum.composition.rootNode,
...packageIds
])
return result.map(dbMapping.map.rootNode)
}
/**
* Returns a promise resolving into all endpoints based on the template
* category(eg zigbee/matter).
*
* @param {*} db
* @param {*} sessionId
* @param {*} templateCategory
* @returns Promise that resolves to endpoints.
*/
async function selectAllEndpointsBasedOnTemplateCategory(
db,
sessionId,
templateCategory
) {
let rows = await dbApi.dbAll(
db,
`
SELECT
E1.ENDPOINT_ID,
E1.SESSION_REF,
E1.ENDPOINT_TYPE_REF,
E1.PROFILE,
E1.ENDPOINT_IDENTIFIER,
E1.NETWORK_IDENTIFIER,
PACKAGE.CATEGORY,
E2.ENDPOINT_ID AS PARENT_ENDPOINT_REF,
E2.ENDPOINT_IDENTIFIER AS PARENT_ENDPOINT_IDENTIFIER
FROM
ENDPOINT AS E1
LEFT JOIN
ENDPOINT AS E2
ON
E2.ENDPOINT_ID = E1.PARENT_ENDPOINT_REF
INNER JOIN
ENDPOINT_TYPE
ON
E1.ENDPOINT_TYPE_REF = ENDPOINT_TYPE.ENDPOINT_TYPE_ID
INNER JOIN
ENDPOINT_TYPE_DEVICE
ON
ENDPOINT_TYPE_DEVICE.ENDPOINT_TYPE_REF = ENDPOINT_TYPE.ENDPOINT_TYPE_ID
INNER JOIN
DEVICE_TYPE
ON
ENDPOINT_TYPE_DEVICE.DEVICE_TYPE_REF = DEVICE_TYPE.DEVICE_TYPE_ID
INNER JOIN
PACKAGE
ON
PACKAGE.PACKAGE_ID = DEVICE_TYPE.PACKAGE_REF
WHERE
E1.SESSION_REF = ?
AND
((PACKAGE.CATEGORY = ?) OR (PACKAGE.CATEGORY IS NULL AND PACKAGE.TYPE = ?))
GROUP BY
E1.ENDPOINT_IDENTIFIER
ORDER BY E1.ENDPOINT_IDENTIFIER
`,
[sessionId, templateCategory, dbEnum.packageType.zclXmlStandalone]
)
// if now rows are found then return all endpoints in the session. This can
// happen if an endpoint has an undefined endponit type device
if (rows.length == 0) {
return selectAllEndpoints(db, sessionId)
} else {
return rows.map(dbMapping.map.endpointExtended)
}
}
/**
* Retrieves clusters on an endpoint.
*
* @param {*} db
* @param {*} endpointTypeId
* @returns promise that resolves into endpoint clusters.
*/
async function selectEndpointClusters(db, endpointTypeId) {
let rows = await dbApi.dbAll(
db,
`
SELECT
C.CLUSTER_ID,
EC.ENDPOINT_TYPE_CLUSTER_ID,
EC.ENDPOINT_TYPE_REF,
C.CODE,
C.NAME,
C.MANUFACTURER_CODE,
EC.SIDE
FROM
CLUSTER AS C
LEFT JOIN
ENDPOINT_TYPE_CLUSTER AS EC
ON
C.CLUSTER_ID = EC.CLUSTER_REF
WHERE
EC.ENABLED = 1
AND EC.ENDPOINT_TYPE_REF = ?
ORDER BY C.CODE
`,
[endpointTypeId]
)
return rows.map((row) => {
return {
clusterId: row['CLUSTER_ID'],
id: row['CLUSTER_ID'],
endpointTypeId: row['ENDPOINT_TYPE_REF'],
endpointTypeClusterId: row['ENDPOINT_TYPE_CLUSTER_ID'],
hexCode: '0x' + bin.int16ToHex(row['CODE']),
manufacturerCode: row['MANUFACTURER_CODE'],
code: row['CODE'],
name: row['NAME'],
side: row['SIDE']
}
})
}
/**
* Retrieves endpoint cluster attributes
*
* @param {*} db
* @param {*} clusterId
* @param {*} side
* @param {*} endpointTypeId
* @returns promise that resolves into endpoint cluster attributes
*/
async function selectEndpointClusterAttributes(
db,
clusterId,
side,
endpointTypeId
) {
let rows = await dbApi.dbAll(
db,
`
SELECT
A.ATTRIBUTE_ID,
A.CODE,
A.NAME,
A.SIDE,
A.TYPE,
A.ARRAY_TYPE,
A.MIN_LENGTH,
A.MAX_LENGTH,
A.MIN,
A.MAX,
A.MANUFACTURER_CODE,
A.IS_WRITABLE,
A.IS_READABLE,
A.IS_NULLABLE,
A.MUST_USE_TIMED_WRITE,
A.IS_SCENE_REQUIRED,
A.IS_OPTIONAL,
A.DEFINE,
A.API_MATURITY,
EA.STORAGE_OPTION,
EA.SINGLETON,
EA.BOUNDED,
EA.INCLUDED,
EA.DEFAULT_VALUE,
EA.INCLUDED_REPORTABLE,
EA.MIN_INTERVAL,
EA.MAX_INTERVAL,
EA.REPORTABLE_CHANGE
FROM
ATTRIBUTE AS A
LEFT JOIN
ENDPOINT_TYPE_ATTRIBUTE AS EA
ON
A.ATTRIBUTE_ID = EA.ATTRIBUTE_REF
WHERE
(A.CLUSTER_REF = ? OR A.CLUSTER_REF IS NULL)
AND A.SIDE = ?
AND ((EA.ENDPOINT_TYPE_CLUSTER_REF =
(SELECT ENDPOINT_TYPE_CLUSTER_ID
FROM ENDPOINT_TYPE_CLUSTER
WHERE CLUSTER_REF = ? AND SIDE = ? AND ENDPOINT_TYPE_REF = ?) ))
ORDER BY A.MANUFACTURER_CODE, A.CODE
`,
[clusterId, side, clusterId, side, endpointTypeId]
)
return rows.map((row) => {
return {
id: row.ATTRIBUTE_ID,
clusterId: clusterId,
code: row.CODE,
manufacturerCode: row.MANUFACTURER_CODE,
hexCode: '0x' + bin.int16ToHex(row['CODE']),
name: row.NAME,
side: row.SIDE,
type: row.TYPE,
entryType: row.ARRAY_TYPE,
minLength: row.MIN_LENGTH,
maxLength: row.MAX_LENGTH,
min: row.MIN,
max: row.MAX,
storage: row.STORAGE_OPTION,
isIncluded: dbApi.fromDbBool(row.INCLUDED),
isSingleton: dbApi.fromDbBool(row.SINGLETON),
isBound: dbApi.fromDbBool(row.BOUNDED),
isWritable: dbApi.fromDbBool(row.IS_WRITABLE),
isReadable: dbApi.fromDbBool(row.IS_READABLE),
isNullable: dbApi.fromDbBool(row.IS_NULLABLE),
mustUseTimedWrite: dbApi.fromDbBool(row.MUST_USE_TIMED_WRITE),
isSceneRequired: dbApi.fromDbBool(row.IS_SCENE_REQUIRED),
isOptional: dbApi.fromDbBool(row.IS_OPTIONA),
defaultValue: row.DEFAULT_VALUE,
includedReportable: dbApi.fromDbBool(row.INCLUDED_REPORTABLE),
minInterval: row.MIN_INTERVAL,
maxInterval: row.MAX_INTERVAL,
reportableChange: row.REPORTABLE_CHANGE,
define: row.DEFINE,
apiMaturity: row.API_MATURITY
}
})
}
/**
* Retrieves endpoint cluster commands.
*
* @param {*} db
* @param {*} clusterId
* @param {*} endpointTypeId
* @returns promise that resolves into endpoint cluster commands
*/
async function selectEndpointClusterCommands(db, clusterId, endpointTypeId) {
let rows = await dbApi.dbAll(
db,
`
SELECT * FROM (
SELECT
C.COMMAND_ID,
C.NAME,
C.CODE,
C.CLUSTER_REF,
C.SOURCE,
C.MANUFACTURER_CODE,
C.IS_OPTIONAL,
C.MUST_USE_TIMED_INVOKE,
C.IS_FABRIC_SCOPED,
C.RESPONSE_NAME,
RC.MANUFACTURER_CODE as RESPONSE_MANUFACTURER_CODE,
RC.CODE AS RESPONSE_CODE,
C.RESPONSE_REF,
CASE
WHEN
(COUNT(CASE WHEN ETC.IS_INCOMING=1 AND ETC.IS_ENABLED THEN 1 ELSE NULL END) OVER (PARTITION BY C.COMMAND_ID)) >= 1
THEN
1
ELSE
0
END AS INCOMING,
CASE
WHEN
(COUNT(CASE WHEN ETC.IS_INCOMING=0 AND ETC.IS_ENABLED THEN 1 ELSE NULL END) OVER (PARTITION BY C.COMMAND_ID)) >= 1
THEN
1
ELSE
0
END AS OUTGOING
FROM
COMMAND AS C
LEFT JOIN
ENDPOINT_TYPE_COMMAND AS ETC
ON
C.COMMAND_ID = ETC.COMMAND_REF
INNER JOIN
ENDPOINT_TYPE_CLUSTER AS ETCL
ON
ETC.ENDPOINT_TYPE_CLUSTER_REF = ETCL.ENDPOINT_TYPE_CLUSTER_ID
LEFT JOIN
COMMAND AS RC
ON
RC.COMMAND_ID = C.RESPONSE_REF
WHERE
C.CLUSTER_REF = ?
AND ETCL.ENDPOINT_TYPE_REF = ?)
GROUP BY
COMMAND_ID
ORDER BY CODE
`,
[clusterId, endpointTypeId]
)
return rows.map((row) => {
return {
id: row['COMMAND_ID'],
name: row['NAME'],
code: row['CODE'],
clusterId: row['CLUSTER_REF'],
manufacturerCode: row['MANUFACTURER_CODE'],
isOptional: dbApi.fromDbBool(row['IS_OPTIONAL']),
mustUseTimedInvoke: dbApi.fromDbBool(row['MUST_USE_TIMED_INVOKE']),
isFabricScoped: dbApi.fromDbBool(row['IS_FABRIC_SCOPED']),
source: row['SOURCE'],
isIncoming: dbApi.fromDbBool(row['INCOMING']),
isOutgoing: dbApi.fromDbBool(row['OUTGOING']),
responseName: row['RESPONSE_NAME'],
responseManufacturerCode: row['RESPONSE_MANUFACTURER_CODE'],
responseCode: row['RESPONSE_CODE'],
responseRef: row['RESPONSE_REF'],
hexCode: '0x' + bin.int8ToHex(row['CODE'])
}
})
}
/**
* Retrieves endpoint cluster events.
*
* @param {*} db
* @param {*} clusterId
* @param {*} endpointTypeId
* @returns promise that resolves into endpoint cluster events
*/
async function selectEndpointClusterEvents(db, clusterId, endpointTypeId) {
return dbApi
.dbAll(
db,
`
SELECT
E.EVENT_ID,
E.CLUSTER_REF,
E.CODE,
E.MANUFACTURER_CODE,
E.NAME,
E.DESCRIPTION,
E.SIDE,
E.IS_OPTIONAL,
E.IS_FABRIC_SENSITIVE,
E.PRIORITY,
E.API_MATURITY
FROM
EVENT AS E
LEFT JOIN
ENDPOINT_TYPE_EVENT AS ETE
ON
E.EVENT_ID = ETE.EVENT_REF
INNER JOIN
ENDPOINT_TYPE_CLUSTER AS ETC
ON
ETC.ENDPOINT_TYPE_CLUSTER_ID = ETE.ENDPOINT_TYPE_CLUSTER_REF
WHERE
E.CLUSTER_REF = ?
AND ETC.ENDPOINT_TYPE_REF = ?
ORDER BY E.MANUFACTURER_CODE, E.CODE
`,
[clusterId, endpointTypeId]
)
.then((rows) => rows.map(dbMapping.map.event))
}
/**
* Deletes an endpoint.
*
* @export
* @param {*} db
* @param {*} id
* @returns Promise to delete an endpoint that resolves with the number of rows that were deleted.
*/
async function deleteEndpoint(db, id) {
return dbApi.dbRemove(db, 'DELETE FROM ENDPOINT WHERE ENDPOINT_ID = ?', [id])
}
/**
* Returns ENDPOINT_ID of the Endpoint's Parent Endpoint
*
* @export
* @param {*} db
* @param {*} parentEndpointIdentifier
* @param {*} sessionId
* @returns Promise to select ENDPOINT_ID of an ENDPOINT in the SESSION with the ENDPOINT_IDENTIFIER which was input
*/
async function getParentEndpointRef(db, parentEndpointIdentifier, sessionId) {
let parentEndpointRef = await dbApi.dbAll(
db,
'SELECT ENDPOINT_ID FROM ENDPOINT WHERE ENDPOINT_IDENTIFIER = ? AND SESSION_REF = ?',
[parentEndpointIdentifier, sessionId]
)
if (parentEndpointRef[0]) {
return parentEndpointRef[0].ENDPOINT_ID
} else {
return null
}
}
/**
* Returns ENDPOINT_IDENTIFIER of the Endpoints' Parent Endpoint
*
* @export
* @param {*} db
* @param {*} parentRef
* @param {*} sessionId
* @returns Promise to select ENDPOINT_IDENTIFIER of an ENDPOINT in the SESSION from an ENDPOINT_ID
*/
async function getParentEndpointIdentifier(db, parentRef, sessionId) {
let parentEndpointIdentifier = await dbApi.dbAll(
db,
'SELECT ENDPOINT_IDENTIFIER FROM ENDPOINT WHERE ENDPOINT_ID = ? AND SESSION_REF = ?',
[parentRef, sessionId]
)
if (parentEndpointIdentifier[0]) {
return parentEndpointIdentifier[0].ENDPOINT_ID
} else {
return null
}
}
/**
* Promises to add an endpoint.
*
* @export
* @param {*} db
* @param {*} sessionId
* @param {*} endpointIdentifier
* @param {*} endpointTypeRef
* @param {*} networkIdentifier
* @param {*} profileIdentifier
* @param {*} parentRef
* @returns Promise to update endpoints.
*/
async function insertEndpoint(
db,
sessionId,
endpointIdentifier,
endpointTypeRef,
networkIdentifier,
profileIdentifier,
parentRef = null
) {
return dbApi.dbInsert(
db,
`
INSERT OR REPLACE
INTO ENDPOINT (
SESSION_REF,
ENDPOINT_IDENTIFIER,
ENDPOINT_TYPE_REF,
NETWORK_IDENTIFIER,
PROFILE,
PARENT_ENDPOINT_REF
) VALUES ( ?, ?, ?, ?, ?, ?)`,
[
sessionId,
endpointIdentifier,
endpointTypeRef,
networkIdentifier,
profileIdentifier,
parentRef
]
)
}
/**
* @export
* @param {*} db
* @param {*} id
* @param {*} endpointIdentifier
* @returns Promise to duplicate an endpoint.
*/
async function duplicateEndpoint(db, id, endpointIdentifier, endpointTypeId) {
return dbApi.dbInsert(
db,
`
INSERT INTO
ENDPOINT (
SESSION_REF,
ENDPOINT_IDENTIFIER,
ENDPOINT_TYPE_REF,
NETWORK_IDENTIFIER,
PROFILE
)
SELECT
SESSION_REF,
?,
?,
NETWORK_IDENTIFIER,
PROFILE
FROM
ENDPOINT
WHERE
ENDPOINT_ID = ?`,
[endpointIdentifier, endpointTypeId, id]
)
}
/**
* Returns a promise of a single endpoint.
* Mayb resolve into null if invalid reference.
*
* @param {*} db
* @param {*} endpointId
* @returns Promise of an endpoint.
*/
async function selectEndpoint(db, endpointId) {
return dbApi
.dbGet(
db,
`
SELECT
E1.ENDPOINT_ID,
E1.SESSION_REF,
E1.ENDPOINT_IDENTIFIER,
E1.ENDPOINT_TYPE_REF,
E1.PROFILE,
E1.NETWORK_IDENTIFIER,
E2.ENDPOINT_ID AS PARENT_ENDPOINT_REF,
E2.ENDPOINT_IDENTIFIER AS PARENT_ENDPOINT_IDENTIFIER,
ENDPOINT_TYPE_DEVICE.DEVICE_VERSION,
ENDPOINT_TYPE_DEVICE.DEVICE_IDENTIFIER
FROM
ENDPOINT AS E1
LEFT JOIN
ENDPOINT_TYPE_DEVICE
ON
ENDPOINT_TYPE_DEVICE.ENDPOINT_TYPE_REF = E1.ENDPOINT_TYPE_REF
LEFT JOIN
ENDPOINT AS E2
ON
E2.ENDPOINT_ID = E1.PARENT_ENDPOINT_REF
WHERE
E1.ENDPOINT_ID = ?`,
[endpointId]
)
.then(dbMapping.map.endpoint)
}
exports.selectEndpointClusters = selectEndpointClusters
exports.selectEndpointClusterAttributes = selectEndpointClusterAttributes
exports.selectEndpointClusterCommands = selectEndpointClusterCommands
exports.selectEndpointClusterEvents = selectEndpointClusterEvents
exports.insertEndpoint = insertEndpoint
exports.deleteEndpoint = deleteEndpoint
exports.selectEndpoint = selectEndpoint
exports.duplicateEndpoint = duplicateEndpoint
exports.selectAllEndpoints = selectAllEndpoints
exports.getParentEndpointRef = getParentEndpointRef
exports.getParentEndpointIdentifier = getParentEndpointIdentifier
exports.selectAllEndpointsBasedOnTemplateCategory =
selectAllEndpointsBasedOnTemplateCategory
exports.getRootNode = getRootNode