feat: add external ROS communication toggle for local workspaces

This commit is contained in:
2026-09-16 15:35:15 +09:00
parent 76a87fc20d
commit 1d959e2c51
9 changed files with 25 additions and 8 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "lms", "name": "lms",
"version": "1.1.27", "version": "1.1.28",
"description": "Autonomy Studio LMS Electron app", "description": "Autonomy Studio LMS Electron app",
"main": "./out/main/index.js", "main": "./out/main/index.js",
"author": { "author": {
+4 -3
View File
@@ -260,6 +260,7 @@ type RemoteWorkspaceTarget = {
type RosNetworkConfig = { type RosNetworkConfig = {
enabled?: boolean | null enabled?: boolean | null
allowExternal?: boolean
peer?: RemoteWorkspaceTarget | null peer?: RemoteWorkspaceTarget | null
} }
@@ -3791,7 +3792,7 @@ async function startVisualizationBridge(
const requestedRosDomainId = String(rosDomainId ?? process.env.ROS_DOMAIN_ID ?? '0').trim() || '0' const requestedRosDomainId = String(rosDomainId ?? process.env.ROS_DOMAIN_ID ?? '0').trim() || '0'
const requestedRosNetworkKey = rosNetworkEnabled(rosNetwork) const requestedRosNetworkKey = rosNetworkEnabled(rosNetwork)
? `peer:${rosNetwork?.peer?.host?.trim() ?? ''}` ? `peer:${rosNetwork?.peer?.host?.trim() ?? ''}`
: 'local' : rosNetwork?.allowExternal === true ? 'lan' : 'local'
const otherContainerNames = (['autoware', 'nav2', 'slam'] as VisualizationStack[]) const otherContainerNames = (['autoware', 'nav2', 'slam'] as VisualizationStack[])
.filter((item) => item !== stack) .filter((item) => item !== stack)
.map(visualizationBridgeContainerName) .map(visualizationBridgeContainerName)
@@ -3869,7 +3870,7 @@ async function startVisualizationBridge(
'-e', '-e',
`ROS_DOMAIN_ID=${requestedRosDomainId}`, `ROS_DOMAIN_ID=${requestedRosDomainId}`,
'-e', '-e',
`ROS_LOCALHOST_ONLY=${rosNetworkEnabled(rosNetwork) ? '0' : '1'}`, `ROS_LOCALHOST_ONLY=${rosNetworkEnabled(rosNetwork) || rosNetwork?.allowExternal === true ? '0' : '1'}`,
'-e', '-e',
`AUTONOMY_STUDIO_ROS_NETWORK=${requestedRosNetworkKey}`, `AUTONOMY_STUDIO_ROS_NETWORK=${requestedRosNetworkKey}`,
'-e', '-e',
@@ -5062,7 +5063,7 @@ async function startWorkspaceModule(
], ],
undefined, undefined,
{ {
ROS_LOCALHOST_ONLY: rosNetworkEnabled(rosNetwork) ? '0' : '1', ROS_LOCALHOST_ONLY: rosNetworkEnabled(rosNetwork) || rosNetwork?.allowExternal === true ? '0' : '1',
...(fastDdsConfigPath ? { FASTDDS_CONFIG_PATH: fastDdsConfigPath } : {}), ...(fastDdsConfigPath ? { FASTDDS_CONFIG_PATH: fastDdsConfigPath } : {}),
}, },
30000 30000
+1
View File
@@ -54,6 +54,7 @@ type ModuleInstallOptions = {
type RosNetworkConfig = { type RosNetworkConfig = {
enabled?: boolean | null enabled?: boolean | null
allowExternal?: boolean
peer?: RemoteWorkspaceTarget | null peer?: RemoteWorkspaceTarget | null
} }
+7 -4
View File
@@ -1000,9 +1000,10 @@ function LocalPcWorkspaceMockup({
workspaceRoot: remoteRuntimeRoot, workspaceRoot: remoteRuntimeRoot,
} : null } : null
const rosNetworkConfig = useMemo<RosNetworkConfig | null>(() => { const rosNetworkConfig = useMemo<RosNetworkConfig | null>(() => {
if (!rosWorkspaceLinked) return null const localNetwork = !remoteWorkspace ? { enabled: false, allowExternal: sourceSettings.allowExternalRos === true } : null
if (!rosWorkspaceLinked) return localNetwork
if (remoteWorkspace) return { enabled: true } if (remoteWorkspace) return { enabled: true }
if (!linkedPlatform) return null if (!linkedPlatform) return localNetwork
const linkedDefaults = normalizeStudioSourceSettings({ const linkedDefaults = normalizeStudioSourceSettings({
...(linkedPlatform.sourceSettings ?? {}), ...(linkedPlatform.sourceSettings ?? {}),
@@ -1013,7 +1014,7 @@ function LocalPcWorkspaceMockup({
const peerHost = parseHostFromUrl(readNestedString(linkedProfile?.config, ['agent', 'url'])) const peerHost = parseHostFromUrl(readNestedString(linkedProfile?.config, ['agent', 'url']))
?? parseHostFromUrl(readNestedString(linkedProfile?.config, ['bridge', 'env', 'BRIDGE_HTTP_URL'])) ?? parseHostFromUrl(readNestedString(linkedProfile?.config, ['bridge', 'env', 'BRIDGE_HTTP_URL']))
?? parseHostFromUrl(linkedDefaults.agentUrl ?? null) ?? parseHostFromUrl(linkedDefaults.agentUrl ?? null)
if (!peerHost) return null if (!peerHost) return localNetwork
const peerPort = Number(linkedDefaults.sshPort ?? 22) const peerPort = Number(linkedDefaults.sshPort ?? 22)
return { return {
@@ -1024,7 +1025,7 @@ function LocalPcWorkspaceMockup({
port: Number.isFinite(peerPort) && peerPort > 0 ? peerPort : 22, port: Number.isFinite(peerPort) && peerPort > 0 ? peerPort : 22,
}, },
} }
}, [linkedPlatform, remoteWorkspace, rosWorkspaceLinked, weekNumber]) }, [linkedPlatform, remoteWorkspace, rosWorkspaceLinked, weekNumber, sourceSettings.allowExternalRos])
const visualizationBridgeLocation = ( const visualizationBridgeLocation = (
remoteWorkspace ? localWorkspaceSettings.visualizationBridgeLocation : sourceSettings.visualizationBridgeLocation remoteWorkspace ? localWorkspaceSettings.visualizationBridgeLocation : sourceSettings.visualizationBridgeLocation
) === 'platform' && rosWorkspaceLinked ) === 'platform' && rosWorkspaceLinked
@@ -2449,6 +2450,7 @@ function LocalPcWorkspaceMockup({
giteaBaseUrl: sourceSettings.giteaBaseUrl, giteaBaseUrl: sourceSettings.giteaBaseUrl,
giteaOwner: sourceSettings.giteaOwner, giteaOwner: sourceSettings.giteaOwner,
gpuEnabled: sourceSettingsDraft.gpuEnabled, gpuEnabled: sourceSettingsDraft.gpuEnabled,
allowExternalRos: !remoteWorkspace && sourceSettingsDraft.allowExternalRos === true,
editorWordWrap: sourceSettingsDraft.editorWordWrap, editorWordWrap: sourceSettingsDraft.editorWordWrap,
editorFontSize: sourceSettingsDraft.editorFontSize, editorFontSize: sourceSettingsDraft.editorFontSize,
editorTabSize: sourceSettingsDraft.editorTabSize, editorTabSize: sourceSettingsDraft.editorTabSize,
@@ -2475,6 +2477,7 @@ function LocalPcWorkspaceMockup({
giteaBaseUrl: sourceSettings.giteaBaseUrl, giteaBaseUrl: sourceSettings.giteaBaseUrl,
giteaOwner: sourceSettings.giteaOwner, giteaOwner: sourceSettings.giteaOwner,
gpuEnabled: settings.gpuEnabled, gpuEnabled: settings.gpuEnabled,
allowExternalRos: !remoteWorkspace && settings.allowExternalRos === true,
editorWordWrap: settings.editorWordWrap, editorWordWrap: settings.editorWordWrap,
editorFontSize: settings.editorFontSize, editorFontSize: settings.editorFontSize,
editorTabSize: settings.editorTabSize, editorTabSize: settings.editorTabSize,
+1
View File
@@ -34,6 +34,7 @@ type ModuleInstallOptions = {
type RosNetworkConfig = { type RosNetworkConfig = {
enabled?: boolean | null enabled?: boolean | null
allowExternal?: boolean
peer?: RemoteWorkspaceTarget | null peer?: RemoteWorkspaceTarget | null
} }
@@ -8,6 +8,7 @@ export type StudioSourceSettings = {
editorTabSize: number editorTabSize: number
editorMinimap: boolean editorMinimap: boolean
rosDomainId: string rosDomainId: string
allowExternalRos?: boolean
rosLinkedPlatformId?: string | null rosLinkedPlatformId?: string | null
visualizationBridgeLocation?: 'local' | 'platform' visualizationBridgeLocation?: 'local' | 'platform'
agentUrl?: string | null agentUrl?: string | null
@@ -97,6 +97,13 @@ function WorkspaceSettingsView({
dark={dark} dark={dark}
/> />
</div> </div>
<StudioSettingsSwitch
label="외부 ROS 통신"
description="같은 네트워크의 기체와 통신을 허용합니다. 사용자별 ROS Domain ID를 구분하고, 변경 후 모듈과 시각화를 재실행하세요. 플랫폼 연동 통신은 별도 설정을 따릅니다."
checked={sourceSettingsDraft.allowExternalRos === true}
onChange={(checked) => onUpdateSourceSetting('allowExternalRos', checked)}
dark={dark}
/>
<StudioSettingsSwitch <StudioSettingsSwitch
label="GPU 가속" label="GPU 가속"
description="ON이면 Docker 실행 시 NVIDIA GPU를 사용합니다." description="ON이면 Docker 실행 시 NVIDIA GPU를 사용합니다."
@@ -11,6 +11,7 @@ export const defaultStudioSourceSettings: StudioSourceSettings = {
editorTabSize: 4, editorTabSize: 4,
editorMinimap: true, editorMinimap: true,
rosDomainId: '77', rosDomainId: '77',
allowExternalRos: false,
rosLinkedPlatformId: null, rosLinkedPlatformId: null,
visualizationBridgeLocation: 'local', visualizationBridgeLocation: 'local',
} }
@@ -53,6 +54,7 @@ export function normalizeStudioSourceSettings(settings?: Partial<StudioSourceSet
giteaBaseUrl: (settings?.giteaBaseUrl || defaultStudioSourceSettings.giteaBaseUrl).replace(/\/+$/, ''), giteaBaseUrl: (settings?.giteaBaseUrl || defaultStudioSourceSettings.giteaBaseUrl).replace(/\/+$/, ''),
giteaOwner: giteaOwner === 'students' ? defaultStudioSourceSettings.giteaOwner : giteaOwner, giteaOwner: giteaOwner === 'students' ? defaultStudioSourceSettings.giteaOwner : giteaOwner,
gpuEnabled: Boolean(settings?.gpuEnabled), gpuEnabled: Boolean(settings?.gpuEnabled),
allowExternalRos: settings?.allowExternalRos === true,
editorWordWrap: settings?.editorWordWrap ?? defaultStudioSourceSettings.editorWordWrap, editorWordWrap: settings?.editorWordWrap ?? defaultStudioSourceSettings.editorWordWrap,
editorFontSize: Number.isFinite(requestedEditorFontSize) editorFontSize: Number.isFinite(requestedEditorFontSize)
? Math.min(16, Math.max(12, Math.round(requestedEditorFontSize))) ? Math.min(16, Math.max(12, Math.round(requestedEditorFontSize)))
@@ -47,5 +47,6 @@ export type RuntimeImageArtifact = {
export type RosNetworkConfig = { export type RosNetworkConfig = {
enabled?: boolean enabled?: boolean
allowExternal?: boolean
peer?: RemoteWorkspaceTarget | null peer?: RemoteWorkspaceTarget | null
} }