Skip to content

Commit c6c4ace

Browse files
rafaellehmkuhlArturoManzoli
authored andcommitted
vehicle-discovery: Fix implementation to allow finding IPs generated by a DHCP server in the vehicle
The vehicle usually has some IPs generated by it's own servers (e.g.: 192.168.3.1 for USB C connections), which offers a dynamic IP for the topside computer. With this change, those IPs are also findable. This commit should be in the original PR, but got lost on the rebase process.
1 parent a982448 commit c6c4ace

File tree

6 files changed

+96
-73
lines changed

6 files changed

+96
-73
lines changed

electron/preload.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { contextBridge, ipcRenderer } from 'electron'
22

33
contextBridge.exposeInMainWorld('electronAPI', {
4-
getNetworkInfo: () => ipcRenderer.invoke('get-network-info'),
4+
getInfoOnSubnets: () => ipcRenderer.invoke('get-info-on-subnets'),
55
})

electron/services/network.ts

Lines changed: 28 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,45 @@
11
import { ipcMain } from 'electron'
22
import { networkInterfaces } from 'os'
33

4-
/**
5-
* Information about the network
6-
*/
7-
interface NetworkInfo {
8-
/**
9-
* The subnet of the local machine
10-
*/
11-
subnet: string
12-
}
13-
4+
import { NetworkInfo } from '../../src/types/network'
145
/**
156
* Get the network information
167
* @returns {NetworkInfo} The network information
178
*/
18-
const getNetworkInfo = (): NetworkInfo => {
19-
const nets = networkInterfaces()
9+
const getInfoOnSubnets = (): NetworkInfo[] => {
10+
const allSubnets = networkInterfaces()
2011

21-
for (const name of Object.keys(nets)) {
22-
for (const net of nets[name] ?? []) {
23-
// Skip over non-IPv4 and internal addresses
24-
if (net.family === 'IPv4' && !net.internal) {
25-
// Return the subnet (e.g., if IP is 192.168.1.5, return 192.168.1)
26-
return { subnet: net.address.split('.').slice(0, 3).join('.') }
27-
}
28-
}
12+
const ipv4Subnets = Object.entries(allSubnets)
13+
.flatMap(([_, nets]) => {
14+
return nets.map((net) => ({ ...net, interfaceName: _ }))
15+
})
16+
.filter((net) => net.family === 'IPv4')
17+
.filter((net) => !net.internal)
18+
19+
if (ipv4Subnets.length === 0) {
20+
throw new Error('No network interfaces found.')
2921
}
3022

31-
throw new Error('No network interface found.')
23+
return ipv4Subnets.map((subnet) => {
24+
// TODO: Use the mask to calculate the available addresses. The current implementation is not correct for anything else than /24.
25+
const subnetPrefix = subnet.address.split('.').slice(0, 3).join('.')
26+
const availableAddresses: string[] = []
27+
for (let i = 1; i <= 254; i++) {
28+
availableAddresses.push(`${subnetPrefix}.${i}`)
29+
}
30+
31+
return {
32+
topSideAddress: subnet.address,
33+
macAddress: subnet.mac,
34+
interfaceName: subnet.interfaceName,
35+
availableAddresses,
36+
}
37+
})
3238
}
3339

3440
/**
3541
* Setup the network service
3642
*/
3743
export const setupNetworkService = (): void => {
38-
ipcMain.handle('get-network-info', getNetworkInfo)
44+
ipcMain.handle('get-info-on-subnets', getInfoOnSubnets)
3945
}

src/components/VehicleDiscoveryDialog.vue

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,9 @@
3030

3131
<div v-if="!searching && !searched" class="flex flex-col gap-2 items-center justify-center text-center">
3232
<p v-if="props.showAutoSearchOption" class="font-bold">It looks like you're not connected to a vehicle!</p>
33-
<p class="max-w-[25rem] mb-2">This tool allows you to locate and connect to vehicles within your network.</p>
33+
<p class="max-w-[25rem] mb-2">
34+
This tool allows you to locate and connect to BlueOS vehicles within your network.
35+
</p>
3436
</div>
3537

3638
<div v-if="!searching" class="flex justify-center items-center">
@@ -49,6 +51,7 @@ import { ref, watch } from 'vue'
4951
5052
import { useSnackbar } from '@/composables/snackbar'
5153
import vehicleDiscover, { NetworkVehicle } from '@/libs/electron/vehicle-discovery'
54+
import { reloadCockpit } from '@/libs/utils'
5255
import { useMainVehicleStore } from '@/stores/mainVehicle'
5356
5457
import InteractionDialog, { Action } from './InteractionDialog.vue'
@@ -116,9 +119,10 @@ const searchVehicles = async (): Promise<void> => {
116119
searched.value = true
117120
}
118121
119-
const selectVehicle = (address: string): void => {
122+
const selectVehicle = async (address: string): Promise<void> => {
120123
mainVehicleStore.globalAddress = address
121124
isOpen.value = false
125+
await reloadCockpit()
122126
showSnackbar({ message: 'Vehicle address updated', variant: 'success', duration: 5000 })
123127
}
124128

src/libs/cosmos.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { isBrowser } from 'browser-or-node'
22

3+
import { NetworkInfo } from '@/types/network'
4+
35
import {
46
cockpitActionVariableData,
57
createCockpitActionVariable,
@@ -110,7 +112,7 @@ declare global {
110112
* Get network information from the main process
111113
* @returns Promise containing subnet information
112114
*/
113-
getNetworkInfo: () => Promise<{ subnet: string }>
115+
getInfoOnSubnets: () => Promise<NetworkInfo[]>
114116
}
115117
}
116118
/* eslint-enable jsdoc/require-jsdoc */

src/libs/electron/vehicle-discovery.ts

Lines changed: 37 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
import { getStatus } from '../blueos'
1+
import ky from 'ky'
2+
3+
import { NetworkInfo } from '@/types/network'
4+
25
import { isElectron } from '../utils'
36

47
/**
@@ -41,47 +44,20 @@ class VehicleDiscover {
4144
private async checkAddress(address: string): Promise<NetworkVehicle | null> {
4245
try {
4346
// First check if the vehicle is online
44-
const hasRespondingStatusEndpoint = await getStatus(address)
45-
if (!hasRespondingStatusEndpoint) {
46-
return null
47-
}
47+
const statusResponse = await ky.get(`http://${address}/status`, { timeout: 3000 })
48+
if (!statusResponse.ok) return null
4849

4950
// Try to get the vehicle name
50-
try {
51-
const response = await fetch(`http://${address}/beacon/v1.0/vehicle_name`)
52-
if (!response.ok) {
53-
return null
54-
}
55-
const name = await response.text()
56-
return { address, name }
57-
} catch {
58-
// If we can't get the name, it's because it's not a vehicle (or maybe BlueOS's Beacon service is not running)
59-
return null
60-
}
51+
const nameResponse = await ky.get(`http://${address}/beacon/v1.0/vehicle_name`, { timeout: 5000 })
52+
if (!nameResponse.ok) return null
53+
const name = await nameResponse.text()
54+
return { address, name }
6155
} catch {
62-
// If we can't get the status, it's because the vehicle is not online
56+
// If we can't get the name, it's because it's not a vehicle (or maybe BlueOS's Beacon service is not running)
6357
return null
6458
}
6559
}
6660

67-
/**
68-
* Get the local subnet
69-
* @returns {string | null} The local subnet, or null if not running in Electron
70-
*/
71-
private async getLocalSubnet(): Promise<string> {
72-
if (!isElectron() || !window.electronAPI?.getNetworkInfo) {
73-
const msg = 'For technical reasons, getting information about the local subnet is only available in Electron.'
74-
throw new Error(msg)
75-
}
76-
77-
try {
78-
const { subnet } = await window.electronAPI.getNetworkInfo()
79-
return subnet
80-
} catch (error) {
81-
throw new Error(`Failed to get information about the local subnet. ${error}`)
82-
}
83-
}
84-
8561
/**
8662
* Find vehicles on the local network
8763
* @returns {NetworkVehicle[]} The vehicles found
@@ -96,23 +72,37 @@ class VehicleDiscover {
9672
}
9773

9874
const search = async (): Promise<NetworkVehicle[]> => {
99-
const subnet = await this.getLocalSubnet()
100-
101-
if (!subnet) {
102-
throw new Error('Failed to get information about the local subnet.')
75+
if (!isElectron() || !window.electronAPI?.getInfoOnSubnets) {
76+
const msg = 'For technical reasons, getting information about the local subnet is only available in Electron.'
77+
throw new Error(msg)
10378
}
10479

105-
const promises: Promise<NetworkVehicle | null>[] = []
80+
let localSubnets: NetworkInfo[] | undefined
81+
try {
82+
localSubnets = await window.electronAPI.getInfoOnSubnets()
83+
} catch (error) {
84+
throw new Error(`Failed to get information about the local subnets. ${error}`)
85+
}
10686

107-
// Check all IPs in the subnet
108-
for (let i = 1; i <= 254; i++) {
109-
const address = `${subnet}.${i}`
110-
promises.push(this.checkAddress(address))
87+
if (localSubnets.length === 0) {
88+
throw new Error('Failed to get information about the local subnets.')
11189
}
11290

113-
const vehiclesFound = await Promise.all(promises).then((results) => {
114-
return results.filter((result): result is NetworkVehicle => result !== null)
115-
})
91+
const vehiclesFound: NetworkVehicle[] = []
92+
for (const subnet of localSubnets) {
93+
const topSideAddress = subnet.topSideAddress
94+
const possibleAddresses = subnet.availableAddresses.filter((address) => address !== topSideAddress)
95+
96+
const promises: Promise<NetworkVehicle | null>[] = possibleAddresses.map((address) => {
97+
return this.checkAddress(address)
98+
})
99+
100+
const vehicles = await Promise.all(promises).then((results) => {
101+
return results.filter((result): result is NetworkVehicle => result !== null)
102+
})
103+
104+
vehiclesFound.push(...vehicles)
105+
}
116106

117107
this.currentSearch = undefined
118108

src/types/network.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/**
2+
* Information about the network
3+
*/
4+
export interface NetworkInfo {
5+
/**
6+
* The top side address of the local machine
7+
*/
8+
topSideAddress: string
9+
/**
10+
* The MAC address of the local machine
11+
*/
12+
macAddress: string
13+
/**
14+
* The name of the network interface
15+
*/
16+
interfaceName: string
17+
/**
18+
* The CIDR of the local machine
19+
*/
20+
availableAddresses: string[]
21+
}

0 commit comments

Comments
 (0)