feat: support simultaneous Tailscale and LAN communication for Yahboom cameras
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
const { test } = require('node:test')
|
||||
const assert = require('node:assert/strict')
|
||||
const fs = require('node:fs')
|
||||
const path = require('node:path')
|
||||
const ts = require('typescript')
|
||||
|
||||
const source = ts.createSourceFile('index.ts', fs.readFileSync(path.join(__dirname, '../src/main/index.ts'), 'utf8'), ts.ScriptTarget.Latest, true)
|
||||
const names = ['buildFastDdsPeerConfig', 'parseCameraLanAddresses', 'cameraLanEnabled', 'rosNetworkEnabled', 'prepareRemoteRosNetworkConfig']
|
||||
const code = source.statements.filter(node => ts.isFunctionDeclaration(node) && names.includes(node.name?.text)).map(node => node.getText(source)).join('\n')
|
||||
function load(overrides = {}) {
|
||||
const dependencies = {
|
||||
parseRosDomainId: value => Number(value ?? 0),
|
||||
readLocalTailscaleIpv4: async () => '100.64.0.10',
|
||||
readRemoteTailscaleIpv4: async () => '100.64.0.20',
|
||||
runSshOutput: async () => JSON.stringify([{ addr_info: [{ family: 'inet', scope: 'global', local: '192.168.1.20' }] }]),
|
||||
runSsh: async () => {},
|
||||
quoteShellSingle: value => "'" + value.replaceAll("'", "'\\''") + "'",
|
||||
expandRemotePathVar: () => [],
|
||||
encodeBase64: value => Buffer.from(value).toString('base64'),
|
||||
...overrides,
|
||||
}
|
||||
return new Function(...Object.keys(dependencies), ts.transpile(code, { target: ts.ScriptTarget.ES2022 }) + `;return {${names.join(',')}}`)(...Object.values(dependencies))
|
||||
}
|
||||
|
||||
test('LAN discovery adds physical addresses and multicast while keeping Tailscale peers', () => {
|
||||
const { buildFastDdsPeerConfig } = load()
|
||||
const xml = buildFastDdsPeerConfig('100.64.0.10', '100.64.0.20', '77', ['192.168.1.20'])
|
||||
const whitelist = xml.match(/<interfaceWhiteList>(.*?)<\/interfaceWhiteList>/s)[1]
|
||||
for (const address of ['127.0.0.1', '100.64.0.20', '192.168.1.20']) assert.ok(whitelist.includes(address))
|
||||
assert.ok(xml.includes('<address>100.64.0.10</address>'))
|
||||
assert.ok(xml.includes('<address>239.255.0.1</address><port>26650</port>'))
|
||||
assert.ok(xml.includes('<metatrafficMulticastLocatorList>'))
|
||||
const original = buildFastDdsPeerConfig('100.64.0.10', '100.64.0.20', '77')
|
||||
assert.ok(!original.includes('192.168.1.20'))
|
||||
assert.ok(!original.includes('239.255.0.1'))
|
||||
})
|
||||
|
||||
test('only the Yahboom camera opts in', () => {
|
||||
const { cameraLanEnabled } = load()
|
||||
assert.equal(cameraLanEnabled('[lms-nav2-yahboom-camera]yahboom_camera_v1.0.0_ws'), true)
|
||||
for (const role of ['vehicle', 'agent', 'control', 'slam', 'planning']) {
|
||||
assert.equal(cameraLanEnabled(`[lms-nav2-yahboom-${role}]yahboom_${role}_v1.0.0_ws`), false)
|
||||
}
|
||||
assert.equal(cameraLanEnabled('[lms-nav2-custom]camera_v1.0.0_ws'), false)
|
||||
})
|
||||
|
||||
test('physical interface responses are parsed, validated and deduplicated', () => {
|
||||
const { parseCameraLanAddresses } = load()
|
||||
const output = JSON.stringify([{ addr_info: [
|
||||
{ family: 'inet', scope: 'global', local: '192.168.1.20' },
|
||||
{ family: 'inet', scope: 'global', local: '999.1.1.1' },
|
||||
{ family: 'inet6', scope: 'global', local: 'fe80::1' },
|
||||
] }])
|
||||
assert.deepEqual(parseCameraLanAddresses(output + '\n' + output), ['192.168.1.20'])
|
||||
})
|
||||
|
||||
test('camera config uses its own path and LAN works without ROS platform linking', async () => {
|
||||
let script = ''
|
||||
const api = load({
|
||||
readLocalTailscaleIpv4: async () => { throw new Error('Must not require local Tailscale') },
|
||||
readRemoteTailscaleIpv4: async () => { throw new Error('Must not require remote Tailscale') },
|
||||
runSsh: async (_target, command) => { script = command },
|
||||
})
|
||||
assert.equal(await api.prepareRemoteRosNetworkConfig({}, '/runtime', { enabled: false }, '77'), null)
|
||||
const result = await api.prepareRemoteRosNetworkConfig({}, '/runtime', { enabled: false }, '77', true)
|
||||
assert.ok(result.endsWith('-camera-lan.xml'))
|
||||
const encoded = script.match(/printf %s '([^']+)'/)[1]
|
||||
const xml = Buffer.from(encoded, 'base64').toString()
|
||||
assert.ok(xml.includes('192.168.1.20'))
|
||||
assert.ok(xml.includes('239.255.0.1'))
|
||||
})
|
||||
|
||||
test('linked camera retains both networks and does not overwrite restricted config', async () => {
|
||||
const api = load()
|
||||
const restricted = await api.prepareRemoteRosNetworkConfig({}, '/runtime', { enabled: true }, '77')
|
||||
const camera = await api.prepareRemoteRosNetworkConfig({}, '/runtime', { enabled: true }, '77', true)
|
||||
assert.notEqual(camera, restricted)
|
||||
assert.ok(camera.includes('100-64-0-10'))
|
||||
const noLan = load({ runSshOutput: async () => '' })
|
||||
await assert.rejects(noLan.prepareRemoteRosNetworkConfig({}, '/runtime', { enabled: true }, '77', true), /IPv4/)
|
||||
})
|
||||
Reference in New Issue
Block a user