added configuration options

This commit is contained in:
Adrian Baumgart 2024-05-08 20:25:17 +02:00
parent 8128637cfd
commit a6e1dedc41
Signed by: rainloreley
SSH Key Fingerprint: SHA256:6zlGbmaf1BidiYJlVTEajPricLS9I10dA/Oe6DNbIAo
10 changed files with 190 additions and 27 deletions

View File

@ -1,8 +1,8 @@
name: Build app
#on:
# push:
# branches:
# - main
on:
push:
branches:
- main
jobs:
build:

1
.gitignore vendored
View File

@ -3,3 +3,4 @@ dist
out/
.idea/
yarn-error.log
appconfig.json

View File

@ -56,6 +56,74 @@ $ sudo udevadm control --reload-rules
- Technically any interface that uses the [usbdmx driver](https://github.com/fx5/usbdmx) by Frank Sievertsen
- Won't work out of the box, vendor ID and product ID need to be [added manually](./src/usbdmx/index.ts)
# Automatic restart
To restart the program automatically in case of a crash or another exception, add `--autoretry` as a command line argument.
This does not apply in the following cases:
- Intentional exit (Crtl+C, "kill" command, closing the terminal)
- Invalid configuration file
# Configuration File
You can optionally add a configuration file containing parameters for the default USBDMX interface
and options regarding the ArtNet transceiver.
To use a configuration file, create a file on your system with the options below. Then start the program with `--config=<path>`
as a command line argument. Replace `<path> ` with the absolute path to the file.
## Full config
You can omit any options that you don't want to change, the program will use the default options instead (except for `"interface"`).
```json lines
{
"interface": {}, // see below
"dmxnet": { // config for ArtNet transceiver
"main": { // general options
"log": {
"level": "info", // available: error, warn, info, verbose, debug, silly
"oem": 0, // OEM Code from artisticlicense, default to dmxnet OEM.
"sName": "Text", // 17 char long node description, default to "usbdmx"
"lName": "Long description", // 63 char long node description, default to "ArtNet-USBDMX-Converter"
"hosts": ["127.0.0.1"] // Interfaces to listen to, defaults to ["0.0.0.0"]
}
},
"transmitter": { // ArtNet transmitter options (USBDMX In)
"ip": "127.0.0.1", // IP to send to, default 255.255.255.255
"subnet": 0, // Destination subnet, default 0
"universe": 0, // Destination universe, default 0
"net": 0, // Destination net, default 0
"port": 6454, // Destination UDP Port, default 6454
"base_refresh_interval": 1000 // Default interval for sending unchanged ArtDmx
},
"receiver": { // ArtNet receiver options (USBDMX Out)
"subnet": 0, //Destination subnet, default 0
"universe": 0, //Destination universe, default 0
"net": 0, //Destination net, default 0
}
}
}
```
## Generate config for interface
To automatically select a USBDMX Interface on startup you need to set its parameters in the configuration file under `"interfaces"`.
You can generate these parameters by running:
```bash
$ ./artnet-usbdmx-converter outputconfig
```
Select the interface and mode to use and copy the output to `"interface"` the configuration file.
Example:
```json lines
{
"interface": {
"serial": "0000000010000492",
"mode": "6",
"manufacturer": "Digital Enlightenment",
"product": "USB DMX"
},
[...]
}
```
# Develop & Build
## Development

View File

@ -34,6 +34,7 @@
"dmxnet": "^0.9.0",
"figlet": "^1.6.0",
"inquirer": "8.0.0",
"minimist": "^1.2.8",
"node-hid": "^2.1.2"
},
"devDependencies": {
@ -41,6 +42,7 @@
"@types/clui": "^0.3.2",
"@types/figlet": "^1.5.6",
"@types/inquirer": "^9.0.3",
"@types/minimist": "^1.2.5",
"@types/node": "^20.8.3",
"@types/node-hid": "^1.3.4",
"@typescript-eslint/eslint-plugin": "^6.20.0",

View File

@ -1,6 +1,7 @@
import {dmxnet, receiver, sender} from "dmxnet";
import {DetectedInterface, DMXInterface, getConnectedInterfaces} from "./usbdmx";
import {clearInterval} from "timers";
import {defaultConfigStorage} from "./index";
/**
* Responsible for converting incoming Art-Net data to an USBDMX output
@ -33,20 +34,9 @@ export default class ConvertHandler {
* Starts up the Art-Net receiver
*/
startArtNetReceiver = () => {
this.dmxnetManager = new dmxnet({
log: {level: "error"},
sName: "usbdmx",
lName: "ArtNet-USBDMX-Converter",
});
this.artNetReceiver = this.dmxnetManager.newReceiver();
this.artNetSender = this.dmxnetManager.newSender({
ip: "255.255.255.255", //IP to send to, default 255.255.255.255
subnet: 0, //Destination subnet, default 0
universe: 0, //Destination universe, default 0
net: 0, //Destination net, default 0
port: 6454, //Destination UDP Port, default 6454
base_refresh_interval: 1000 // Default interval for sending unchanged ArtDmx
});
this.dmxnetManager = new dmxnet(defaultConfigStorage.getDmxNetConfig());
this.artNetReceiver = this.dmxnetManager.newReceiver(defaultConfigStorage.getDmxNetReceiverConfig());
this.artNetSender = this.dmxnetManager.newSender(defaultConfigStorage.getDmxNetSenderConfig());
this.artNetReceiver.on("data", this.handleIncomingArtNetData);
}

View File

@ -0,0 +1,70 @@
/***********************************
* src/config/ConfigStorage.ts
*
* Created on 08.05.24 by adrian
* Project: artnet-usbdmx-converter
*************************************/
import * as fs from "fs";
import {StartupScreenResponse} from "../startupscreen";
import {DmxnetOptions, ReceiverOptions, SenderOptions} from "dmxnet";
export default class ConfigStorage {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private data: any
loadConfig(path: string) {
let filedata: string
try {
filedata = fs.readFileSync(path, "utf-8")
} catch (e) {
console.error("Failed to read file!")
console.error(e)
process.exit(1)
}
try {
this.data = JSON.parse(filedata);
} catch (e) {
console.error("JSON file contains error!")
console.error(e)
process.exit(1)
}
}
getInterface(): StartupScreenResponse | undefined {
return this.data?.interface as StartupScreenResponse ?? undefined;
}
getDmxNetConfig(): DmxnetOptions {
const mainConfig = this.data?.dmxnet?.main;
return {
log: mainConfig?.log ?? {level: "error"},
oem: mainConfig?.oem ?? undefined,
sName: mainConfig?.sName ?? "usbdmx",
lName: mainConfig?.lName ?? "ArtNet-USBDMX-Converter",
hosts: mainConfig?.hosts ?? ["0.0.0.0"]
}
}
getDmxNetReceiverConfig(): ReceiverOptions {
const receiverConfig = this.data?.dmxnet?.receiver;
return {
subnet: receiverConfig?.subnet ?? 0,
universe: receiverConfig?.universe ?? 0,
net: receiverConfig?.net ?? 0,
}
}
getDmxNetSenderConfig(): SenderOptions {
const transmitterConfig = this.data?.dmxnet?.transmitter;
return {
ip: transmitterConfig?.ip ?? "255.255.255.255",
subnet: transmitterConfig?.subnet ?? 0,
universe: transmitterConfig?.universe ?? 0,
net: transmitterConfig?.net ?? 0,
port: transmitterConfig?.port ?? 6454,
base_refresh_interval: transmitterConfig?.base_refresh_interval ?? 1000
}
}
}

View File

@ -52,6 +52,6 @@ export default async function renderControlScreen() {
console.log("\n");
console.log(chalk.yellow(
"Close the window or click Ctrl+C to terminate the program"
"Close the window or press Ctrl+C to terminate the program"
))
}

View File

@ -4,16 +4,34 @@ import renderStartupScreen from "./startupscreen";
import renderControlScreen from "./controlscreen";
import {clearInterval} from "timers";
import {exec} from "child_process";
import ConfigStorage from "./config/ConfigStorage";
import minimist from "minimist";
const argv = minimist(process.argv.slice(2));
export const defaultConvertHandler = new ConvertHandler();
export const defaultConfigStorage = new ConfigStorage();
let renderControlScreenTimer: NodeJS.Timeout;
/**
* Entry point of the program
*/
async function main() {
if (typeof argv["config"] === "string") {
defaultConfigStorage.loadConfig(argv["config"]);
}
setTerminalTitle("ArtNet => USBDMX")
const selectedInfo = await renderStartupScreen();
defaultConvertHandler.scanForInterfaces();
const selectedInfo = defaultConfigStorage.getInterface() ?? await renderStartupScreen();
if (argv._.includes("outputconfig")) {
console.log(selectedInfo);
return;
}
console.log(chalk.blueBright(
"Starting ArtNet..."
));
@ -34,7 +52,7 @@ async function main() {
openInterfaceResponse
)
)
return process.exit(1);
throw "MissingInterfaceError"
}
else {
console.log(
@ -73,16 +91,24 @@ process.on('uncaughtException', (err) => {
chalk.red(`⛔ A fatal error has occurred`)
);
console.log(err);
exec("pause press [enter]");
process.exit(0);
if (argv["autoretry"] === true) {
console.log("Auto-restart enabled, will restart now...")
setTimeout(() => {
main();
}, 2000);
} else {
exec("pause press [enter]");
process.exit(0);
}
})
main();
/**
* Sets the title of the terminal
* @param title new title
*/
function setTerminalTitle(title: string){
function setTerminalTitle(title: string) {
process.stdout.write(
String.fromCharCode(27) + "]0;" + title + String.fromCharCode(7)
);

View File

@ -4,13 +4,14 @@ import figlet from "figlet";
import inquirer from "inquirer";
import {defaultConvertHandler} from "./index";
import modeToString from "./helpers/modeToString";
import {DetectedInterface} from "./usbdmx";
// eslint-disable-next-line @typescript-eslint/no-var-requires
const pjson = require('../package.json');
/**
* Data from the user selection about which interface to use
*/
interface StartupScreenResponse {
export interface StartupScreenResponse {
serial: string,
mode: string
manufacturer: string | undefined,
@ -25,7 +26,7 @@ export default async function renderStartupScreen(): Promise<StartupScreenRespon
);
printCreditHeader();
const scannedInterfaces = defaultConvertHandler.scanForInterfaces();
const scannedInterfaces: DetectedInterface[] = defaultConvertHandler.availableInterfaces;
const interfaceSelectionResponse = await inquirer.prompt(
[

View File

@ -207,6 +207,11 @@
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841"
integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==
"@types/minimist@^1.2.5":
version "1.2.5"
resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.5.tgz#ec10755e871497bcd83efe927e43ec46e8c0747e"
integrity sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==
"@types/node-hid@^1.3.4":
version "1.3.4"
resolved "https://registry.yarnpkg.com/@types/node-hid/-/node-hid-1.3.4.tgz#adad8841dc0e05f988da287a91c64c75c554cfe0"
@ -1355,7 +1360,7 @@ minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2:
dependencies:
brace-expansion "^1.1.7"
minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.6:
minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.6, minimist@^1.2.8:
version "1.2.8"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c"
integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==