Signed-off-by: 吴文峰 <kevin@lmve.net>
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
import * as fs from 'fs';
|
||||
import { SerialPort } from 'serialport';
|
||||
|
||||
export interface DiscoveredDevice extends Device {
|
||||
port: string;
|
||||
}
|
||||
|
||||
interface Device {
|
||||
pio_env: string;
|
||||
arch: string;
|
||||
manufacturer: string | undefined;
|
||||
vendorId: string | undefined;
|
||||
productId: string | undefined;
|
||||
}
|
||||
|
||||
const devicesData = fs.readFileSync('devices.json', 'utf8');
|
||||
const devicesJson = JSON.parse(devicesData) as Array<Device>;
|
||||
|
||||
export async function DiscoverDevices() : Promise<Array<DiscoveredDevice>> {
|
||||
const ports = await SerialPort.list();
|
||||
const discoveredDevices = new Array<DiscoveredDevice>();
|
||||
ports.forEach(port => {
|
||||
const foundDevice = devicesJson.find(d => {
|
||||
const matchesManufacture = d.manufacturer ? d.manufacturer && port.manufacturer && d.manufacturer.toLowerCase() === port.manufacturer.toLowerCase() : true;
|
||||
const matchesVendorId = d.vendorId ? d.vendorId && port.vendorId && port.vendorId.toLowerCase().startsWith(d.vendorId.toLowerCase()) : true;
|
||||
const matchesProductId = d.productId ? d.productId && port.productId && port.productId.toLowerCase().startsWith(d.productId.toLowerCase()) : true;
|
||||
|
||||
return matchesManufacture && matchesVendorId && matchesProductId;
|
||||
});
|
||||
if (foundDevice)
|
||||
{
|
||||
discoveredDevices.push({
|
||||
port: port.path,
|
||||
pio_env: foundDevice.pio_env,
|
||||
arch: foundDevice.arch,
|
||||
manufacturer: port.manufacturer,
|
||||
vendorId: port.vendorId,
|
||||
productId: port.productId
|
||||
});
|
||||
}
|
||||
});
|
||||
return discoveredDevices;
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
import {
|
||||
LogRecord,
|
||||
} from '@buf/meshtastic_protobufs.bufbuild_es/meshtastic/mesh_pb';
|
||||
import {
|
||||
NodeSerialConnection,
|
||||
Protobuf,
|
||||
Types,
|
||||
} from '@meshtastic/js';
|
||||
|
||||
import { PromiseTimeout } from './util';
|
||||
|
||||
export class ConnectedNode {
|
||||
connection: NodeSerialConnection;
|
||||
connectionParameters: Types.NodeSerialConnectionParameters;
|
||||
packets: Protobuf.Mesh.FromRadio[] = [];
|
||||
routingPackets: Protobuf.Mesh.Routing[] = [];
|
||||
logs: Protobuf.Mesh.LogRecord[] = [];
|
||||
|
||||
metadata: Protobuf.Mesh.DeviceMetadata | undefined;
|
||||
myNodeInfo: Protobuf.Mesh.MyNodeInfo | undefined;
|
||||
nodes: Protobuf.Mesh.NodeInfo[] = [];
|
||||
channels: Protobuf.Channel.Channel[] = [];
|
||||
|
||||
deviceConfig: Protobuf.Config.Config_DeviceConfig | undefined;
|
||||
loraConfig: Protobuf.Config.Config_LoRaConfig | undefined;
|
||||
bluetoothConfig: Protobuf.Config.Config_BluetoothConfig | undefined;
|
||||
displayConfig: Protobuf.Config.Config_DisplayConfig | undefined;
|
||||
networkConfig: Protobuf.Config.Config_NetworkConfig | undefined;
|
||||
positionConfig: Protobuf.Config.Config_PositionConfig | undefined;
|
||||
powerConfig: Protobuf.Config.Config_PowerConfig | undefined;
|
||||
securityConfig: Protobuf.Config.Config_SecurityConfig | undefined;
|
||||
|
||||
audioConfig: Protobuf.ModuleConfig.ModuleConfig_AudioConfig | undefined;
|
||||
ambientLightingConfig: Protobuf.ModuleConfig.ModuleConfig_AmbientLightingConfig | undefined;
|
||||
cannedMessageConfig: Protobuf.ModuleConfig.ModuleConfig_CannedMessageConfig | undefined;
|
||||
detectionSensorConfig: Protobuf.ModuleConfig.ModuleConfig_DetectionSensorConfig | undefined;
|
||||
externalNotificationConfig: Protobuf.ModuleConfig.ModuleConfig_ExternalNotificationConfig | undefined;
|
||||
mqttConfig: Protobuf.ModuleConfig.ModuleConfig_MQTTConfig | undefined;
|
||||
neighborInfoConfig: Protobuf.ModuleConfig.ModuleConfig_NeighborInfoConfig | undefined;
|
||||
paxcounterConfig: Protobuf.ModuleConfig.ModuleConfig_PaxcounterConfig | undefined;
|
||||
rangetestConfig: Protobuf.ModuleConfig.ModuleConfig_RangeTestConfig | undefined;
|
||||
remoteHardwareConfig: Protobuf.ModuleConfig.ModuleConfig_RemoteHardwareConfig | undefined;
|
||||
serialConfig: Protobuf.ModuleConfig.ModuleConfig_SerialConfig | undefined;
|
||||
storeForwardConfig: Protobuf.ModuleConfig.ModuleConfig_StoreForwardConfig | undefined;
|
||||
telemetryConfig: Protobuf.ModuleConfig.ModuleConfig_TelemetryConfig | undefined;
|
||||
|
||||
constructor(port: string, pio_env: string) {
|
||||
this.connection = new NodeSerialConnection()
|
||||
this.connectionParameters = {portPath: port, baudRate: 115200, concurrentLogOutput: false};
|
||||
this.initSubscriptions()
|
||||
}
|
||||
async configure() {
|
||||
await this.connection.connect(this.connectionParameters)
|
||||
await PromiseTimeout(500);
|
||||
await Promise.race([this.connection.configure(), PromiseTimeout(6000)]);
|
||||
}
|
||||
|
||||
async sendText(text: string) {
|
||||
console.info('Sending text: ', text);
|
||||
await this.connection.sendText(text, undefined, true);
|
||||
}
|
||||
|
||||
async sendWaypoint(icon: number, lat: number, lon: number) {
|
||||
console.info('Sending waypoint: ', icon, lat, lon);
|
||||
const waypoint = new Protobuf.Mesh.Waypoint();
|
||||
waypoint.latitudeI = lat;
|
||||
waypoint.longitudeI = lon;
|
||||
waypoint.icon = icon;
|
||||
await this.connection.sendWaypoint(waypoint, 0, 0);
|
||||
}
|
||||
|
||||
async waitForPacket(predicate: (packet: Protobuf.Mesh.FromRadio) => boolean, timeout: number = 10000) : Promise<boolean> {
|
||||
const start = Date.now();
|
||||
while (Date.now() - start < timeout) {
|
||||
if (this.packets.some(predicate)) {
|
||||
return true;
|
||||
}
|
||||
await PromiseTimeout(10);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
async waitForRouting(predicate: (routingPacket: Protobuf.Mesh.Routing) => boolean, timeout: number = 10000) : Promise<boolean> {
|
||||
const start = Date.now();
|
||||
while (Date.now() - start < timeout) {
|
||||
if (this.routingPackets.some(predicate)) {
|
||||
return true;
|
||||
}
|
||||
await PromiseTimeout(10);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
private initSubscriptions() : void {
|
||||
this.connection.events.onChannelPacket.subscribe((channel: Protobuf.Channel.Channel) => {
|
||||
this.channels.push(channel);
|
||||
});
|
||||
this.connection.events.onFromRadio.subscribe((fromRadio: Protobuf.Mesh.FromRadio) => {
|
||||
if (fromRadio.payloadVariant.value instanceof LogRecord) {
|
||||
this.logs.push(fromRadio.payloadVariant.value);
|
||||
} else if (fromRadio.payloadVariant.value instanceof Protobuf.Mesh.DeviceMetadata) {
|
||||
this.metadata = fromRadio.payloadVariant.value;
|
||||
} else if (fromRadio.payloadVariant.value instanceof Protobuf.Mesh.NodeInfo) {
|
||||
this.nodes.push(fromRadio.payloadVariant.value);
|
||||
} else if (fromRadio.payloadVariant.value instanceof Protobuf.Mesh.MyNodeInfo) {
|
||||
this.myNodeInfo = fromRadio.payloadVariant.value;
|
||||
}
|
||||
this.packets.push(fromRadio);
|
||||
});
|
||||
this.connection.events.onConfigPacket.subscribe((config: Protobuf.Config.Config) => {
|
||||
if (config.payloadVariant.value instanceof Protobuf.Config.Config_DeviceConfig) {
|
||||
this.deviceConfig = config.payloadVariant.value;
|
||||
} else if (config.payloadVariant.value instanceof Protobuf.Config.Config_LoRaConfig) {
|
||||
this.loraConfig = config.payloadVariant.value;
|
||||
} else if (config.payloadVariant.value instanceof Protobuf.Config.Config_BluetoothConfig) {
|
||||
this.bluetoothConfig = config.payloadVariant.value;
|
||||
} else if (config.payloadVariant.value instanceof Protobuf.Config.Config_DisplayConfig) {
|
||||
this.displayConfig = config.payloadVariant.value;
|
||||
} else if (config.payloadVariant.value instanceof Protobuf.Config.Config_NetworkConfig) {
|
||||
this.networkConfig = config.payloadVariant.value;
|
||||
} else if (config.payloadVariant.value instanceof Protobuf.Config.Config_PositionConfig) {
|
||||
this.positionConfig = config.payloadVariant.value;
|
||||
} else if (config.payloadVariant.value instanceof Protobuf.Config.Config_PowerConfig) {
|
||||
this.powerConfig = config.payloadVariant.value;
|
||||
} else if (config.payloadVariant.value instanceof Protobuf.Config.Config_SecurityConfig) {
|
||||
this.securityConfig = config.payloadVariant.value;
|
||||
} else {
|
||||
console.log('Unknown config packet', config.payloadVariant.value);
|
||||
}
|
||||
});
|
||||
this.connection.events.onModuleConfigPacket.subscribe((config: Protobuf.ModuleConfig.ModuleConfig) => {
|
||||
if (config.payloadVariant.value instanceof Protobuf.ModuleConfig.ModuleConfig_AudioConfig) {
|
||||
this.audioConfig = config.payloadVariant.value;
|
||||
} else if (config.payloadVariant.value instanceof Protobuf.ModuleConfig.ModuleConfig_AmbientLightingConfig) {
|
||||
this.ambientLightingConfig = config.payloadVariant.value;
|
||||
} else if (config.payloadVariant.value instanceof Protobuf.ModuleConfig.ModuleConfig_CannedMessageConfig) {
|
||||
this.cannedMessageConfig = config.payloadVariant.value;
|
||||
} else if (config.payloadVariant.value instanceof Protobuf.ModuleConfig.ModuleConfig_DetectionSensorConfig) {
|
||||
this.detectionSensorConfig = config.payloadVariant.value;
|
||||
} else if (config.payloadVariant.value instanceof Protobuf.ModuleConfig.ModuleConfig_ExternalNotificationConfig) {
|
||||
this.externalNotificationConfig = config.payloadVariant.value;
|
||||
} else if (config.payloadVariant.value instanceof Protobuf.ModuleConfig.ModuleConfig_MQTTConfig) {
|
||||
this.mqttConfig = config.payloadVariant.value;
|
||||
} else if (config.payloadVariant.value instanceof Protobuf.ModuleConfig.ModuleConfig_NeighborInfoConfig) {
|
||||
this.neighborInfoConfig = config.payloadVariant.value;
|
||||
} else if (config.payloadVariant.value instanceof Protobuf.ModuleConfig.ModuleConfig_PaxcounterConfig) {
|
||||
this.paxcounterConfig = config.payloadVariant.value;
|
||||
} else if (config.payloadVariant.value instanceof Protobuf.ModuleConfig.ModuleConfig_RangeTestConfig) {
|
||||
this.rangetestConfig = config.payloadVariant.value;
|
||||
} else if (config.payloadVariant.value instanceof Protobuf.ModuleConfig.ModuleConfig_RemoteHardwareConfig) {
|
||||
this.remoteHardwareConfig = config.payloadVariant.value;
|
||||
} else if (config.payloadVariant.value instanceof Protobuf.ModuleConfig.ModuleConfig_SerialConfig) {
|
||||
this.serialConfig = config.payloadVariant.value;
|
||||
} else if (config.payloadVariant.value instanceof Protobuf.ModuleConfig.ModuleConfig_StoreForwardConfig) {
|
||||
this.storeForwardConfig = config.payloadVariant.value;
|
||||
} else if (config.payloadVariant.value instanceof Protobuf.ModuleConfig.ModuleConfig_TelemetryConfig) {
|
||||
this.telemetryConfig = config.payloadVariant.value;
|
||||
} else {
|
||||
console.log('Unknown module config packet', config.payloadVariant.value);
|
||||
}
|
||||
});
|
||||
this.connection.events.onMyNodeInfo.subscribe((myNodeInfo: Protobuf.Mesh.MyNodeInfo) => {
|
||||
this.myNodeInfo = myNodeInfo;
|
||||
});
|
||||
this.connection.events.onRoutingPacket.subscribe((routing) => {
|
||||
this.routingPackets.push(routing.data);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
async close() {
|
||||
await this.connection.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
// this.connection.events.onMessagePacket.subscribe((packet) => {
|
||||
// });
|
||||
// this.connection.events.onDeviceMetadataPacket.subscribe((packet) => {
|
||||
// });
|
||||
// this.connection.events.onDeviceDebugLog.subscribe((packet) => {
|
||||
// });
|
||||
// this.connection.events.onDeviceStatus.subscribe((packet) => {
|
||||
// });
|
||||
// this.connection.events.onMyNodeInfo.subscribe((packet) => {
|
||||
// });
|
||||
// this.connection.events.onRoutingPacket.subscribe((packet) => {
|
||||
// });
|
||||
@@ -0,0 +1,12 @@
|
||||
import subprocess
|
||||
|
||||
def flash_esp32(pio_env, port):
|
||||
subprocess.run(
|
||||
["platformio", "run", "-e", pio_env, "-t", "upload", "--upload-port", port]
|
||||
)
|
||||
|
||||
|
||||
def flash_nrf52(pio_env, port):
|
||||
subprocess.run(
|
||||
["platformio", "run", "-e", pio_env, "-t", "upload", "--upload-port", port]
|
||||
)
|
||||
@@ -0,0 +1,42 @@
|
||||
import configparser
|
||||
import subprocess
|
||||
|
||||
|
||||
def readProps(prefsLoc):
|
||||
"""Read the version of our project as a string"""
|
||||
|
||||
config = configparser.RawConfigParser()
|
||||
config.read(prefsLoc)
|
||||
version = dict(config.items("VERSION"))
|
||||
verObj = dict(
|
||||
short="{}.{}.{}".format(version["major"], version["minor"], version["build"]),
|
||||
long="unset",
|
||||
)
|
||||
|
||||
# Try to find current build SHA if if the workspace is clean. This could fail if git is not installed
|
||||
try:
|
||||
sha = (
|
||||
subprocess.check_output(["git", "rev-parse", "--short", "HEAD"])
|
||||
.decode("utf-8")
|
||||
.strip()
|
||||
)
|
||||
isDirty = (
|
||||
subprocess.check_output(["git", "diff", "HEAD"]).decode("utf-8").strip()
|
||||
)
|
||||
suffix = sha
|
||||
# if isDirty:
|
||||
# # short for 'dirty', we want to keep our verstrings source for protobuf reasons
|
||||
# suffix = sha + "-d"
|
||||
verObj["long"] = "{}.{}.{}.{}".format(
|
||||
version["major"], version["minor"], version["build"], suffix
|
||||
)
|
||||
except:
|
||||
# print("Unexpected error:", sys.exc_info()[0])
|
||||
# traceback.print_exc()
|
||||
verObj["long"] = verObj["short"]
|
||||
|
||||
# print("firmware version " + verStr)
|
||||
return verObj
|
||||
|
||||
|
||||
# print("path is" + ','.join(sys.path))
|
||||
@@ -0,0 +1,65 @@
|
||||
import time
|
||||
import sys
|
||||
import os
|
||||
import flash
|
||||
import meshtastic
|
||||
import meshtastic.serial_interface
|
||||
from readprops import readProps
|
||||
|
||||
os.chdir("../")
|
||||
|
||||
version = readProps("version.properties")["long"]
|
||||
|
||||
def setup_users_prefs(prefsLoc):
|
||||
with open(prefsLoc, "r") as file:
|
||||
filedata = file.read()
|
||||
filedata = filedata.replace(
|
||||
"// #define CONFIG_LORA_REGION_USERPREFS",
|
||||
"#define CONFIG_LORA_REGION_USERPREFS",
|
||||
)
|
||||
filedata = filedata.replace(
|
||||
"// #define LORACONFIG_CHANNEL_NUM_USERPREFS",
|
||||
"#define LORACONFIG_CHANNEL_NUM_USERPREFS",
|
||||
)
|
||||
filedata = filedata.replace(
|
||||
"// #define CHANNEL_0_PRECISION", "#define CHANNEL_0_PRECISION"
|
||||
)
|
||||
with open(prefsLoc, "w") as file:
|
||||
file.write(filedata)
|
||||
|
||||
|
||||
def setup_device(port, pio_env, arch):
|
||||
interface = meshtastic.serial_interface.SerialInterface(port)
|
||||
try:
|
||||
interface.waitForConfig()
|
||||
if interface.metadata.firmware_version == version:
|
||||
print("Already at local ref version", version)
|
||||
else:
|
||||
print(
|
||||
"Device has version",
|
||||
interface.metadata.firmware_version,
|
||||
" updating to",
|
||||
version,
|
||||
)
|
||||
interface.close()
|
||||
time.sleep(1)
|
||||
flash_device(port, pio_env, arch)
|
||||
time.sleep(2)
|
||||
except:
|
||||
interface.close()
|
||||
time.sleep(1)
|
||||
flash_device(port, pio_env, arch)
|
||||
time.sleep(2)
|
||||
interface = meshtastic.serial_interface.SerialInterface(port)
|
||||
interface.waitForConfig()
|
||||
|
||||
|
||||
def flash_device(port, pio_env, arch):
|
||||
if arch == "esp32":
|
||||
flash.flash_esp32(pio_env=pio_env, port=port)
|
||||
elif arch == "nrf52":
|
||||
flash.flash_nrf52(pio_env=pio_env, port=port)
|
||||
|
||||
|
||||
setup_users_prefs("userPrefs.h")
|
||||
setup_device(sys.argv[1], sys.argv[2], sys.argv[3])
|
||||
@@ -0,0 +1,122 @@
|
||||
# Keeping this as a reference attempt
|
||||
import time
|
||||
from typing import Dict, List, NamedTuple
|
||||
|
||||
import meshtastic
|
||||
import meshtastic.serial_interface
|
||||
import pytest
|
||||
from dotmap import DotMap
|
||||
from pubsub import pub # type: ignore[import-untyped]
|
||||
from setup import setup_device, setup_users_prefs # type: ignore[import-untyped]
|
||||
|
||||
|
||||
class ConnectedDevice(NamedTuple):
|
||||
port: str
|
||||
pio_env: str
|
||||
arch: str
|
||||
interface: meshtastic.serial_interface.SerialInterface
|
||||
mesh_packets: List[meshtastic.mesh_pb2.FromRadio]
|
||||
|
||||
|
||||
devices: Dict[str, ConnectedDevice] = {}
|
||||
|
||||
heltec_v3 = ["/dev/cu.usbserial-0001", "heltec-v3", "esp32"]
|
||||
rak4631 = ["/dev/cu.usbmodem14101", "rak4631", "nrf52"]
|
||||
tbeam = ["/dev/", "rak4631", "nrf52"]
|
||||
|
||||
|
||||
setup_users_prefs("userPrefs.h")
|
||||
|
||||
for port_device in [heltec_v3, rak4631]:
|
||||
print("Setting up device", port_device[1], "on port", port_device[0])
|
||||
setup_device(port=port_device[0], pio_env=port_device[1], arch=port_device[2])
|
||||
|
||||
|
||||
@pytest.fixture(scope="module", params=[rak4631, heltec_v3])
|
||||
def device(request):
|
||||
port = request.param[0]
|
||||
pio_env = request.param[1]
|
||||
arch = request.param[2]
|
||||
|
||||
if devices.get(port) is not None and devices[port].interface.isConnected:
|
||||
yield devices[port]
|
||||
else:
|
||||
time.sleep(1)
|
||||
devices[port] = ConnectedDevice(
|
||||
port=port,
|
||||
pio_env=pio_env,
|
||||
arch=arch,
|
||||
interface=meshtastic.serial_interface.SerialInterface(port),
|
||||
mesh_packets=[],
|
||||
)
|
||||
yield devices[port]
|
||||
# Tear down devices
|
||||
devices[port].interface.close()
|
||||
|
||||
|
||||
def default_on_receive(packet, interface):
|
||||
print("Received packet", packet["decoded"], "interface", interface)
|
||||
# find the device that sent the packet
|
||||
for port in devices:
|
||||
if devices[port].interface == interface:
|
||||
devices[port].mesh_packets.append(packet)
|
||||
|
||||
|
||||
# Test want_config responses from device
|
||||
def test_should_get_and_set_config(device: ConnectedDevice):
|
||||
assert device is not None, "Expected connected device"
|
||||
assert (
|
||||
len(device.interface.nodes) > 0
|
||||
), "Expected at least one node in the device NodeDB"
|
||||
assert (
|
||||
device.interface.localNode.localConfig is not None
|
||||
), "Expected LocalConfig to be set"
|
||||
assert (
|
||||
device.interface.localNode.moduleConfig is not None
|
||||
), "Expected ModuleConfig to be set"
|
||||
assert (
|
||||
len(device.interface.localNode.channels) > 0
|
||||
), "Expected at least one channel in the device"
|
||||
pub.subscribe(default_on_receive, "meshtastic.receive")
|
||||
device.interface.waitForAckNak
|
||||
|
||||
|
||||
def test_should_send_text_message_and_receive_ack(device: ConnectedDevice):
|
||||
# Send a text message
|
||||
print("Sending text from device", device.pio_env)
|
||||
device.interface.sendText(text="Test broadcast", wantAck=True)
|
||||
# time.sleep(1)
|
||||
# device.interface.waitForAckNak()
|
||||
print("Received ack from device")
|
||||
time.sleep(2)
|
||||
# for port in devices:
|
||||
# if devices[port].port != device.port:
|
||||
# print("Checking device", devices[port].pio_env, "for received message")
|
||||
# print(devices[port].mesh_packets)
|
||||
# # Assert should have received a message
|
||||
# # find text message in packets
|
||||
# textPackets = list(
|
||||
# filter(
|
||||
# lambda packet: packet["decoded"]["portnum"]
|
||||
# == meshtastic.portnums_pb2.TEXT_MESSAGE_APP
|
||||
# and packet["decoded"]["payload"].decode("utf-8")
|
||||
# == "Test broadcast",
|
||||
# devices[port].mesh_packets,
|
||||
# )
|
||||
# )
|
||||
# assert (
|
||||
# len(textPackets) > 0
|
||||
# ), "Expected a text message received on other device"
|
||||
# # Assert should have received an ack
|
||||
# ackPackets = list(
|
||||
# filter(
|
||||
# lambda packet: packet["decoded"]["portnum"]
|
||||
# == meshtastic.portnums_pb2.ROUTING_APP,
|
||||
# device.mesh_packets,
|
||||
# )
|
||||
# )
|
||||
# assert len(ackPackets) > 0, "Expected an ack from the device"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pytest.main()
|
||||
@@ -0,0 +1,20 @@
|
||||
import { exec } from 'child_process';
|
||||
|
||||
import { DiscoverDevices } from './discover';
|
||||
|
||||
const devices = await DiscoverDevices();
|
||||
console.log(devices);
|
||||
|
||||
devices.forEach(async device => {
|
||||
exec(`python3 ./src/python/setup.py ${device.port} ${device.pio_env} ${device.arch}`, (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
console.error(`Error executing the Python script: ${error}`);
|
||||
return;
|
||||
}
|
||||
if (stderr) {
|
||||
console.error(`Error: ${stderr}`);
|
||||
return;
|
||||
}
|
||||
console.log('Output:', stdout);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,3 @@
|
||||
export function PromiseTimeout(timeoutMs: number = 10000) : Promise<void> {
|
||||
return new Promise((resolve) => setTimeout(resolve, timeoutMs))
|
||||
}
|
||||
Reference in New Issue
Block a user