danielwippermann/resol-vbus

View on GitHub
tools/rsc-scheme-converter/main.js

Summary

Maintainability
D
1 day
Test Coverage
const crypto = require('crypto');
const fs = require('fs/promises');
const util = require('util');

const xml2js = require('xml2js');

const vbus = require('../..');

function promisify(fn) {
    return new Promise((resolve, reject) => {
        fn((err, result) => {
            if (err) {
                reject(err);
            } else {
                resolve(result);
            }
        });
    });
}

function flatten(...args) {
    return args.flat(10);
}

function hashString(input) {
    const hasher = crypto.createHash('sha256');
    hasher.update(input);
    return hasher.digest('hex').slice(0, 8);
}

function usage(message) {
    console.log(flatten([
        message ? [ message, '' ] : [],
        'USAGE: rsc-scheme-converter <input RSC file> <input image file> <output JS file>',
        '',
    ]).join('\n'));

    process.exit(message ? 1 : 0);
}

async function main(args) {
    let inputRscFilename = null;
    let inputImageFilename = null;
    let outputJsFilename = null;

    let argIdx = 0;
    while (argIdx < args.length) {
        const arg = args [argIdx];
        argIdx += 1;

        if (arg === '--help') {
            usage();
        } else if (arg.startsWith('-')) {
            usage(`Unknown option: ${arg}`);
        } else if (inputRscFilename == null) {
            inputRscFilename = arg;
        } else if (inputImageFilename == null) {
            inputImageFilename = arg;
        } else if (outputJsFilename == null) {
            outputJsFilename = arg;
        } else {
            usage(`Unexpected argument: ${arg}`);
        }
    }

    if (inputRscFilename == null) {
        usage('No input RSC file provided');
    } else if (inputImageFilename == null) {
        usage('No input image file provided');
    } else if (outputJsFilename == null) {
        usage('No output JS file provided');
    }

    const rscContents = await fs.readFile(inputRscFilename, 'utf-8');
    const rscRootObject = await promisify(cb => xml2js.parseString(rscContents, cb));

    const imageContents = await fs.readFile(inputImageFilename, 'base64');

    const plugins = rscRootObject.serviceCenterProject.plugin;

    const loggingViewPlugin = plugins.find(plugin => plugin.$.id === 'de.resol.servicecenter.vbuslogging.VBusLoggingView');
    if (!loggingViewPlugin) {
        throw new Error(`No VBusLoggingView plugin found in RSC file`);
    }

    const docContents = loggingViewPlugin.$.designerDocument;
    const docRootObject = await promisify(cb => xml2js.parseString(docContents, cb));

    const docObject = docRootObject.designerDocument;
    const width = docObject.width [0];
    const height = docObject.height [0];
    const docFields = docObject.designerVBusField;

    const spec = vbus.Specification.getDefaultSpecification();
    const specFile = vbus.SpecificationFile.getDefaultSpecificationFile();

    const fields = docFields.map(docField => {
        const x = +(docField.x [0]);
        const y = +(docField.y [0]);
        const width = +(docField.width [0]);
        const height = +(docField.height [0]);
        const text = docField.text [0];
        const font = docField.font [0];
        const textColor = docField.textColor [0];
        const type = +(docField.type [0]);
        const destination = +(docField.destination [0]);
        const source = +(docField.source [0]);
        const command = +(docField.command [0]);
        // const fieldId = +(docField.fieldId [0]);
        const channel = +(docField.channel [0]);
        const tooltip = docField.tooltip [0];

        const fontName = font.name [0];
        const fontHeight = +(font.height [0]);
        const fontStyle = +(font.style [0]);

        const textColorR = +(textColor.r [0]);
        const textColorG = +(textColor.g [0]);
        const textColorB = +(textColor.b [0]);

        if (type !== 0) {
            throw new Error(`Unexpected type`);
        }

        if (fontStyle !== 0) {
            throw new Error(`Unexpected font style`);
        }

        const packetSpec = spec.getPacketSpecification(channel, destination, source, command);
        if (!packetSpec) {
            throw new Error(`Unknown packet`);
        }

        // const pt = specFile.getPacketTemplate(destination, source, command);
        // if (!pt) {
        //     throw new Error(`Unable to find packet template`);
        // }

        const fields = packetSpec.packetFields.filter(field => {
            return tooltip.includes(`\\n${field.name.de} [`);
        });

        if (fields.length === 0) {
            throw new Error(`Unable to find field`);
        } else if (fields.length > 1) {
            throw new Error(`Multiple fields found`);
        }

        const packetFieldSpec = fields [0];

        // const style = `font: ${fontHeight}px "${fontName}"; color: rgb(${textColorR}, ${textColorG}, ${textColorB})`;
        const style = {
            font: `${fontHeight}px "${fontName}"`,
            color: `rgb(${textColorR}, ${textColorG}, ${textColorB})`,
        };
        const styleHash = hashString(JSON.stringify(style)).toUpperCase();

        return {
            x,
            y: y + fontHeight,
            width,
            height,
            text,
            fontName,
            fontHeight,
            fontStyle,
            textColorR,
            textColorG,
            textColorB,
            packetSpec,
            packetFieldSpec,
            style,
            styleHash,
        };
    });

    const styles = fields.reduce((memo, field) => {
        if (!memo.some(entry => entry.styleHash === field.styleHash)) {
            memo.push({
                style: field.style,
                styleHash: field.styleHash,
            });
        }
        return memo;
    }, []);

    const output = [
        `initializeResolVBusLiveView({`,
        `  width: ${width},`,
        `  height: ${height},`,
        `  backgroundImageUrl: 'data:image/jpg;base64,${imageContents}',`,
        '',
        `  render(r) {`,
        styles.map(style => {
            return [
                `    const style_${style.styleHash} = ${util.inspect(style.style)};`,
            ];
        }),
        fields.map(field => {
            const packetFieldId = `${field.packetSpec.packetId}_${field.packetFieldSpec.fieldId}`;

            return [
                '',
                `    // ${field.packetSpec.fullName}: ${field.packetFieldSpec.name.en}`,
                `    r.drawVBusField('${packetFieldId}', ${field.x}, ${field.y}, style_${field.styleHash});`
            ]
        }),
        `  },`,
        `});`,
        '',
    ];

    await fs.writeFile(outputJsFilename, flatten(output).join('\n'));
    // console.log(flatten(output).join('\n'));
}

main(process.argv.slice(2)).then(null, err => {
    console.error(err);
    process.exit(1);
});