iobroker.zigbee2mqtt
Version:
Zigbee2MQTT adapter for ioBroker
142 lines (125 loc) • 5.78 kB
JavaScript
const axios = require('axios').default;
const sharp = require('sharp');
class ImageController {
constructor(adapter) {
this.adapter = adapter;
}
sanitizeModelIDForImageUrl(modelName) {
const modelNameString = modelName.replace('/', '_');
// eslint-disable-next-line no-control-regex
return modelNameString.replace(/\u0000/g, '');
}
sanitizeZ2MDeviceName(deviceName) {
const deviceNameString = deviceName.replace(/:|\s|\//g, '-');
// eslint-disable-next-line no-control-regex
return deviceName ? deviceNameString.replace(/\u0000/g, '') : 'NA';
}
getZ2mDeviceImageModelJPG(device) {
if (device && device.definition && device.definition.model) {
const icoString = `https://www.zigbee2mqtt.io/images/devices/${this.sanitizeZ2MDeviceName(device.definition.model)}.jpg`;
// eslint-disable-next-line no-control-regex
return icoString.replace(/\u0000/g, '');
}
}
getZ2mDeviceImageModelPNG(device) {
if (device && device.definition && device.definition.model) {
const icoString = `https://www.zigbee2mqtt.io/images/devices/${this.sanitizeZ2MDeviceName(device.definition.model)}.png`;
// eslint-disable-next-line no-control-regex
return icoString.replace(/\u0000/g, '');
}
}
getSlsDeviceImage(device) {
if (device && device.model_id) {
const icoString = `https://www.zigbee2mqtt.io/images/devices/${this.sanitizeModelIDForImageUrl(device.model_id)}.png`;
// eslint-disable-next-line no-control-regex
return icoString.replace(/\u0000/g, '');
}
}
async getDeviceIcon(device) {
if (!this.adapter.config.useDeviceIcons) return '';
const imageSize = this.adapter.config.deviceIconsSize;
const z2mIconFileNameJPG = `${this.sanitizeZ2MDeviceName(device.definition.model)}.jpg`;
const z2mIconFileNamePNG = `${this.sanitizeZ2MDeviceName(device.definition.model)}.png`;
const slsIconFileName = `${this.sanitizeModelIDForImageUrl(device.model_id)}.png`;
let iconFileName = await this.getExistingIconFileName(z2mIconFileNameJPG, z2mIconFileNamePNG, slsIconFileName);
let iconFound = true;
if (!iconFileName) {
const iconUrls = [
this.getZ2mDeviceImageModelJPG(device),
this.getZ2mDeviceImageModelPNG(device),
this.getSlsDeviceImage(device)
];
for (const iconUrl of iconUrls) {
try {
iconFound = await this.downloadIcon(this.adapter, iconUrl, this.adapter.namespace);
if (iconFound) {
iconFileName = this.getFileNameWithExtension(iconUrl);
break;
}
} catch (ex) {
// check next pic
}
}
}
if (!iconFound) {
this.adapter.log.warn(`Failed to download image for device model: ${device.definition.model} - ${device.definition.description}`);
return '';
} else {
// Load image from the Meta-Store
const icon = await this.adapter.readFileAsync(this.adapter.namespace, iconFileName);
// Load Image Metadata
const origIconMeta = await sharp(icon.file).metadata();
// Check whether the image needs to be resized
if (
(origIconMeta.height && origIconMeta.height > imageSize) ||
(origIconMeta.width && origIconMeta.width > imageSize)
) {
// Resize image to 28x28 pixel
this.adapter.log.info(
`Resize image for device model ${device.definition.model} from: ${origIconMeta.width}x${origIconMeta.height} to ${imageSize}x${imageSize}`
);
icon.file = await sharp(icon.file)
.resize({
width: imageSize,
height: imageSize,
fit: sharp.fit.cover,
position: sharp.strategy.entropy,
})
.toBuffer();
// Replace the original image with the resize image.
await this.adapter.writeFileAsync(this.adapter.namespace, iconFileName, icon.file);
}
// Create and output Base64
return `data:image/png;base64,${icon.file.toString('base64')}`;
}
}
getFileNameWithExtension(url) {
const path = new URL(url).pathname;
const filename = path.split('/').pop();
// eslint-disable-next-line no-control-regex
return filename.replace(/\u0000/g, '');
}
async downloadIcon(adapter, url, namespace) {
try {
const res = await axios.get(url, { responseType: 'arraybuffer' });
await adapter.writeFileAsync(namespace, this.getFileNameWithExtension(url), res.data);
return true;
} catch (ex) {
//adapter.log.warn(ex);
return false;
}
}
async getExistingIconFileName(z2mIconFileNameJPG, z2mIconFileNamePNG, slsIconFileName) {
if (await this.adapter.fileExistsAsync(this.adapter.namespace, z2mIconFileNameJPG)) {
return z2mIconFileNameJPG;
} else if (await this.adapter.fileExistsAsync(this.adapter.namespace, z2mIconFileNamePNG)) {
return z2mIconFileNamePNG;
} else if (await this.adapter.fileExistsAsync(this.adapter.namespace, slsIconFileName)) {
return slsIconFileName;
}
return null;
}
}
module.exports = {
ImageController,
};