Weiter zum Inhalt
  • Home
  • Aktuell
  • Tags
  • 0 Ungelesen 0
  • Kategorien
  • Unreplied
  • Beliebt
  • GitHub
  • Docu
  • Hilfe
Skins
  • Hell
  • Brite
  • Cerulean
  • Cosmo
  • Flatly
  • Journal
  • Litera
  • Lumen
  • Lux
  • Materia
  • Minty
  • Morph
  • Pulse
  • Sandstone
  • Simplex
  • Sketchy
  • Spacelab
  • United
  • Yeti
  • Zephyr
  • Dunkel
  • Cyborg
  • Darkly
  • Quartz
  • Slate
  • Solar
  • Superhero
  • Vapor

  • Standard: (Kein Skin)
  • Kein Skin
Einklappen
ioBroker Logo

Community Forum

donate donate
  1. ioBroker Community Home
  2. Русский
  3. ioBroker
  4. Скрипты
  5. ioBroker скрипты
  6. Построение графиков ChartJS на сервере и отправка картинки в телеграм

NEWS

  • Monatsrückblick Januar/Februar 2026 ist online!
    BluefoxB
    Bluefox
    18
    1
    689

  • Jahresrückblick 2025 – unser neuer Blogbeitrag ist online! ✨
    BluefoxB
    Bluefox
    18
    1
    5.8k

  • Neuer Blogbeitrag: Monatsrückblick - Dezember 2025 🎄
    BluefoxB
    Bluefox
    13
    1
    1.5k

Построение графиков ChartJS на сервере и отправка картинки в телеграм

Geplant Angeheftet Gesperrt Verschoben ioBroker скрипты
22 Beiträge 4 Kommentatoren 7.8k Aufrufe 1 Beobachtet
  • Älteste zuerst
  • Neuste zuerst
  • Meiste Stimmen
Antworten
  • In einem neuen Thema antworten
Anmelden zum Antworten
Dieses Thema wurde gelöscht. Nur Nutzer mit entsprechenden Rechten können es sehen.
  • goofykG Offline
    goofykG Offline
    goofyk
    schrieb am zuletzt editiert von
    #1

    Расскажу, как можно получить график в виде картинки, например для отправки в Telegram

    В ioBroker есть стандартный способ построения графиков - Flot-драйвер. Этот драйвер работает в паре с Web-драйвером и отображает результат в браузере. Но для того чтобы получить созданный график на сервере (в скрипте) в виде картинки нужен дополнительный драйвер PhantomJS, который делает “скриншот” страницы (на которой у нас нарисуется Flot-график).

    Но я расскажу об альтернативном способе построения графиков на сервере в скрипте.

    Есть такая библиотека Chart.js http://www.chartjs.org/ которая позволяет рисовать приятные на вид графики в браузере (примеры http://www.chartjs.org/samples/latest/).

    Для рисования она использует “холст” (канва, canvas) браузера. Поэтому, чтобы рисовать с помощью этой библиотеки на сервере, нужно использовать “серверный” вариант “холста” и DOM-объекты. Это и делает пакет chartjs-node (https://github.com/vmpowerio/chartjs-node).

    Основной зависимостью для этого пакета является пакет canvas (https://github.com/Automattic/node-canvas), который следует установить глобально (или в папку iobroker). Важно установить все зависимости для той платформы, куда вы ставите https://github.com/Automattic/node-canvas#compiling .

    После этого можно в настройках драйвера javascript добавить модули chart.js, chartjs-node. Они должны установиться корректно, без ошибок. Иначе - разбираться с ошибками и решать их.

    А дальше, можно написать скрипт.

    Ниже приведен скрипт для примера, т.к. в нем включено использование драйвера History и используются конкретные имена состояний.

    Внимание! В скрипте есть сложные для новичков конструкции - Promise. Это удобные способ не писать функции с callback, а делать цепочки шагов. Так, например, это удобно делать для получения данных из истории состояний.

    Скрипт подписывается на 2 команды, приходящие через Телеграм:

    demo - строит график на готовых тестовых данных (чтобы проверить, что код вообще работает). Вызываются функции sendGraph0, prepareDraw0, doDraw.
    3371__________________2018-10-16_16-08-30.png

    graph - строит график на данных из истории состояний. Вызываются функции sendGraph1, prepareDraw1, doDraw.
    3371__________________2018-10-16_16-47-02.png

    
    'use strict';
    const ChartjsNode = require('chartjs-node');
    
    /**
     * функция sendTo как Promise, чтобы удобно было строить цепочки
     */
    
    function sendToPromise(adapter, cmd, params) {
        return new Promise((resolve, reject) => {
            sendTo(adapter, cmd, params, (result) => {
                resolve(result);
            });
        });
    }
    
    // константы для цветов
    const chartColors = {
        black: 'rgb(0, 0, 0)',
    	red: 'rgb(255, 99, 132)',
    	orange: 'rgb(255, 159, 64)',
    	yellow: 'rgb(255, 205, 86)',
    	green: 'rgb(75, 192, 192)',
    	blue: 'rgb(54, 162, 235)',
    	purple: 'rgb(153, 102, 255)',
    	grey: 'rgb(201, 203, 207)'
    };
    
    /**
     * функция рисования и сохранения картинки в файл
     *  параметры:
     *  @param config - конфигурация графика для рисования
     *  @param filename - имя файла для сохранения
     *  результат:
     *  @param Promise - успешное сохранение файла
     */
    
    function doDraw(config, filename) {
        // создадим полотно с размером 640x480 пикселей
        var chartNode = new ChartjsNode(640, 480);
        return chartNode.drawChart(config)
            .then(() => {
                // запишем результат в файл
                return chartNode.writeImageToFile('image/png', filename);
            });
    }
    
    /**
     * функция подготовки параметров для ChartJS.
     *  результат:
     *  @param Promise - успешная подготовка параметров
     */
    
    function prepareDraw0(){
        // переменная, куда сохраним данные
        var пример;
        // создадим Promise сборки данных и конфигурации
        return new Promise((resolve, reject)=>{resolve()})
            // здесь могут быть много шагов сбора данных, прежде чем перейти к графику
            .then(()=>{
                // произвольные данные, похожие на те, что хранятся в истории
                пример = [
                    {"val":3,"ack":1,"ts":1539063874301},
                    {"val":5,"ack":1,"ts":1539063884299},
                    {"val":5.3,"ack":1,"ts":1539063894299},
                    {"val":3.39,"ack":1,"ts":1539063904301},
                    {"val":5.6,"ack":1,"ts":1539063914300},
                    {"val":-1.3,"ack":1,"ts":1539063924300},
                    {"val":-6.3,"ack":1,"ts":1539063934302},
                    {"val":1.23,"ack":1,"ts":1539063944301},
                ];
            })
            // финальный шаг - создаем конфигурацию графиков
            .then(()=>{
                const chartJsOptions = {
                    // тип графика - линейный
                    type: 'line',
        			data: {
        			    // список наборов данных
        				datasets: [
        			    {
        			        // заголовок ряда 
        					label: 'тест',
        					// цвет
        					backgroundColor: chartColors.black,
        					borderColor: chartColors.black,
        					// размер точек
        					pointRadius: 3,
        					// ширина линии графика
        					borderWidth: 3,
        					// достанем данные из переменной 'пример' и оставим только значение и время изменения
        					data: пример.map((item) => {
        					    return {y: item.val, t: new Date(item.ts)}
        					}),
        					// заливка графика - нет
        					fill: false,
        			    }
        				]
        			},
        			options: {
        				// настройка легенды
        				legend: {
        				    labels: {
        				        // размер шрифта
        				        fontSize: 20,
        				    },
        				},
        				// оси координат
        				scales: {
        				    // оси X
        					xAxes: [{
        					    // тип - временная ось
        					    type: 'time',  
        						display: true,
        						// метка оси
        						scaleLabel: {
        							display: true,
        							labelString: 'Время'
        						},
        					}],
        					// оси Y
        					yAxes: [{
        					    // тип - линейная
        					    type: 'linear',
        						display: true,
        						// метка оси
        						scaleLabel: {
        							display: true,
        							labelString: 'Температура'
        						},
        					}]
        				}
        			}
    			};
    			return chartJsOptions;
            });
    }
    
    /**
     * функция подготовки параметров для ChartJS.
     * собирает данные из истории и складывает их в переменные, 
     * чтобы потом включить в ряды.
     * 
     *  параметры:
     *  @param hours - количество часов, за которые получить данные
     *  результат:
     *  @param Promise - успешная подготовка параметров
     */
    
    function prepareDraw1(hours){
        // вычислим интервал времени, за который надо получить данные
        const end = new Date().getTime(),
              start = end - 3600000*(hours || 1); // 1 = час назад
    
        // зададим переменные, в которые будем складывать результаты запроса
        // исторических данных
        var улица, куры2, куры1, куры2свет, куры2вент;
    
        // создадим Promise сборки данных и конфигурации
        return new Promise((resolve, reject)=>{resolve()})
            // на этом шаге собираем историю по 'mqtt.0.ESP_Easy.Улица.Temperature'
            .then(() => {
                return sendToPromise('history.0', 'getHistory', {
                        id: 'mqtt.0.ESP_Easy.Улица.Temperature',
                        options: {
                            start: start,
                            end: end,
                            aggregate: 'onchange'
                        }
                    }
                ).then((result) => {
                    // записываем результат в переменную 'улица'
                    улица = result.result;
                });
            })
            // на этом шаге собираем историю по 'sonoff.0.chicken2.DS18B20_Temperature'
            .then(() => {
                return sendToPromise('history.0', 'getHistory', {
                    id: 'sonoff.0.chicken2.DS18B20_Temperature',
                    options: {
                        start: start,
                        end: end,
                        aggregate: 'onchange'
                    }
                }).then((result)=>{
                    // записываем результат в переменную 'куры2'
                    куры2 = result.result;
                });
            })
            .then(() => {
                return sendToPromise('history.0', 'getHistory', {
                    id: 'sonoff.0.sonoff_chicken_vent.DS18B20_Temperature',
                    options: {
                        start: start,
                        end: end,
                        aggregate: 'onchange'
                    }
                }).then((result)=>{
                    куры1 = result.result;
                });
            })
            .then(() => {
                return sendToPromise('history.0', 'getHistory', {
                    id: 'sonoff.0.chicken2.POWER1',
                    options: {
                        start: start,
                        end: end,
                        aggregate: 'onchange'
                    }
                }).then((result)=>{
                    куры2свет = result.result;
                });
            })
            .then(() => {
                return sendToPromise('history.0', 'getHistory', {
                    id: 'sonoff.0.chicken2.POWER2',
                    options: {
                        start: start,
                        end: end,
                        aggregate: 'onchange'
                    }
                }).then((result)=>{
                    куры2вент = result.result;
                });
            })
            // финальный шаг - создаем конфигурацию графиков
            .then(()=>{
                const chartJsOptions = {
                    // тип графика - линейный
                    type: 'line',
        			data: {
        			    // список наборов данных
        				datasets: [
        			    {
        			        // заголовок ряда с указанием последнего значения из ряда в скобках
        					label: 'Улица ('+улица[улица.length - 1].val+')',
        					// цвет
        					backgroundColor: chartColors.blue,
        					borderColor: chartColors.blue,
        					// размер точек. 0 - нет точки
        					pointRadius: 0,
        					// ширина линии графика
        					borderWidth: 3,
        					// достанем данные из переменной 'улица' и оставим только значение и время изменения
        					data: улица.map((item) => {
        					    return {y: item.val, t: new Date(item.ts)}
        					}),
        					// заливка графика - нет
        					fill: false,
        					// идентификатор оси Y
        					yAxisID: 'y-axis-1',
        			    },{
        					label: 'Куры 1 ('+куры1[куры1.length - 1].val+')',
        					backgroundColor: chartColors.green,
        					borderColor: chartColors.green,
        					pointRadius: 0,
        					borderWidth: 3,
        					data: куры1.map((item) => {
        					    return {y: item.val, t: new Date(item.ts)}
        					}),
        					fill: false,
        					yAxisID: 'y-axis-1',
        				},{
        					label: 'Куры 2 ('+куры2[куры2.length - 1].val+')',
        					backgroundColor: chartColors.red,
        					borderColor: chartColors.red,
        					pointRadius: 0,
        					borderWidth: 3,
        					data: куры2.map((item) => {
        					    return {y: item.val, t: new Date(item.ts)}
        					}),
        					fill: false,
        					yAxisID: 'y-axis-1',
        				},{
        					label: 'Куры 2 свет ('+куры2свет[куры2свет.length - 1].val+')',
        					backgroundColor: chartColors.yellow,
        					borderColor: chartColors.yellow,
        					pointRadius: 0,
        					borderWidth: 1,
        					data: куры2свет.map((item) => {
        					    return {y: (item.val) ? 1 : 0, t: new Date(item.ts)}
        					}),
        					fill: true,
        					lineTension: 0,
    		                steppedLine: true,
        					yAxisID: 'y-axis-2',
        				},{
        					label: 'Куры 2 вент ('+куры2вент[куры2вент.length - 1].val+')',
        					backgroundColor: chartColors.grey,
        					borderColor: chartColors.grey,
        					pointRadius: 0,
        					borderWidth: 1,
        					data: куры2вент.map((item) => {
        					    return {y: (item.val) ? -1 : 0, t: new Date(item.ts)}
        					}),
        					fill: true,
        					lineTension: 0,
    		                steppedLine: true,
        					yAxisID: 'y-axis-2',
        				}
        				]
        			},
        			options: {
        				// настройка легенды
        				legend: {
        				    labels: {
        				        // размер шрифта
        				        fontSize: 20,
        				    },
        				},
        				// оси координат
        				scales: {
        				    // оси X
        					xAxes: [{
        					    // тип - временная ось
        					    type: 'time',  
        						display: true,
        						// метка оси
        						scaleLabel: {
        							display: true,
        							labelString: 'Время'
        						},
        						// настройка формата оси (времени)
        						time: {
        						    unit: 'minute',
        						    displayFormats: {
                                        minute: 'HH:mm'
                                    }
        						},
        					}],
        					// оси Y
        					yAxes: [{
        					    // тип - линейная
        					    type: 'linear',
        						display: true,
        						// метка оси
        						scaleLabel: {
        							display: true,
        							labelString: 'Температура'
        						},
        						// расположение линейки - слева
        						position: 'left',
        						// идентификатор оси
        						id: 'y-axis-1',
        					},{
        					    type: 'linear',
        					    display: true,
        						scaleLabel: {
        							display: true,
        							labelString: 'Свет и вентиляция'
        						},
        						ticks: {
        							min: -4,
        							max: 2
        						},
        						// расположение линейки - справа
        						position: 'right',
        						id: 'y-axis-2',
            				}]
        				}
        			}
    			};
    			return chartJsOptions;
            });
    }
    
    function sendGraph0(user){
        // имя файла, в который положим картинку с графиком
        const filename = '/tmp/graph0.png';
        // выполним подготовку данных 
        prepareDraw0()
            // на след шаге нарисуем
            .then((result) => {
                // рисуем картинку по полученным данным и конфигурации
                return doDraw(result, filename);
            })
            .then(()=>{
                // теперь отправим сообщение в телеграм
                sendTo('telegram.0', {
                    user: user, 
                    text: filename, 
                    caption: 'Пример графика',
                });
            })
            .catch((err)=>{
                console.error(err);
            });
    }
    
    /**
     * функция отправки графика в телеграм
     * @param user - какому юзеру слать. если пусто - всем
     * @param chat_id - 
     * @param message_id - в каком чате и какое сообщение заменить при обновлении
     * @param hours - количество часов, за которые получить данные
     */
    
    function sendGraph1(user, chat_id, message_id, hours){
        // имя файла, в который положим картинку с графиком
        const filename = '/tmp/graph1.png';
        hours = hours || 1;
        // выполним подготовку данных 
        prepareDraw1(hours)
            // на след шаге нарисуем
            .then((result) => {
                // рисуем картинку по полученным данным и конфигурации
                return doDraw(result, filename);
            })
            .then(() => {
                // удалим предыдущее сообщение
                if (message_id && chat_id) {
                    sendTo('telegram', {
                        user: user,
                        deleteMessage: {
                            options: {
                                chat_id: chat_id, 
                                message_id: message_id
                            }
                        }
                    });
                }
            })
            .then(()=>{
                // теперь отправим сообщение в телеграм
                sendTo('telegram.0', {
                    user: user, 
                    text: filename, 
                    caption: 'Температура в курятниках ('+hours+'ч)',
                    reply_markup: {
                        inline_keyboard: [
                            [
                                { text: '🔄', callback_data: 'graph_'+hours},
                                { text: '1 ч', callback_data: 'graph_1' },
                                { text: '2 ч', callback_data: 'graph_2' },
                                { text: '4 ч', callback_data: 'graph_4' },
                                { text: '12 ч', callback_data: 'graph_12' },
                                { text: '24 ч', callback_data: 'graph_24' },
                            ]
                        ]
                    }
                });
            })
            .catch((err)=>{
                console.error(err);
            });
    }
    
    // будем слушать телеграм и ждать команды на построение графика
    on({id: "telegram.0.communicate.request", ack: false, change: 'any'}, function (obj) {
        var v;
        var msg = obj.state.val;
        var command = obj.state.val.substring(obj.state.val.indexOf(']')+1);
        var user = obj.state.val.substring(obj.state.val.indexOf('[')+1,obj.state.val.indexOf(']'));
        var chat_id = getState("telegram.0.communicate.requestChatId").val;
        var message_id = getState("telegram.0.communicate.requestMessageId").val;
    
        // команда для графика - demo
        if (command == 'demo') {
            sendGraph0(user);
        }
        // команда для графика - graph
        if (command.startsWith('graph')) {
            const hours = parseInt(command.split('_')[1]);
            sendGraph1(user, chat_id, message_id, hours);
        }
    });
    
    
    K 1 Antwort Letzte Antwort
    1
    • T Offline
      T Offline
      TechElCo
      schrieb am zuletzt editiert von
      #2

      Не могу понять, где засада… Команда - демо, отправляет файл в адаптер телеграм... А команда - граф, отправляет что-то в адаптер хистори, в телеграм при этом пусто 8-)

      1 Antwort Letzte Antwort
      0
      • goofykG Offline
        goofykG Offline
        goofyk
        schrieb am zuletzt editiert von
        #3

        Всё верно, команда graph сперва достает из истории данные (отправляет сообщение в History и получает ответ), а затем строит график.

        Вам нужно поменять скрипт и указать свои состояния, по которым надо получить данные. Ну и удалить ненужные запросы данных, лишние ряды на графике. Т.е. нужно разобраться со скриптом и поменять его под себя :)

        1 Antwort Letzte Antwort
        1
        • T Offline
          T Offline
          TechElCo
          schrieb am zuletzt editiert von
          #4
          /**
           * функция sendTo как Promise, чтобы удобно было строить цепочки
           */
          
          var sendToPromise = promisifyNoError(sendTo);
          
          ````А тут никакой ошибки нет? Я ставлю свои пять состояний, которые есть в хистори, но это ничего не меняет ((
          
          Вот такое в логе скрипта после приёма команды граф:  ` > javascript.1 script.js.test.Скрипт1: sendTo(adapter=function (result) { // decide on how we want to return the callback arguments switch (arguments.length) { case 0: // no arguments were given return resolve(); // Promise case 1: // a single value (result) was returned return resolve(result); default: // multiple values should be returned /** @type {{} | any[]} */ let ret; const extraArgs = sliceArgs(arguments, 0); if (returnArgNames && returnArgNames.length === extraArgs.length) { // we can build an object ret = {}; for (let i = 0; i < returnArgNames.length; i++) { ret[returnArgNames_~~[i]~~] = extraArgs__; } } else { // we return the raw array ret = extraArgs; } return resolve(ret); } }, cmd=undefined, msg=undefined)___ ` 
          1 Antwort Letzte Antwort
          0
          • goofykG Offline
            goofykG Offline
            goofyk
            schrieb am zuletzt editiert von
            #5

            Ошибки быть не должно - это оборачивание функции sendTo в промис.

            Выше должны быть 2 вспомогательные функции для этого (promisifyNoError и sliceArgs).

            Можно переписать скрипт, чтобы использовалась обычная функция sendTo, но тогда надо в callback передавать следующую функцию и т.д.

            1 Antwort Letzte Antwort
            1
            • goofykG Offline
              goofykG Offline
              goofyk
              schrieb am zuletzt editiert von
              #6

              Покажите какая у вас получилась функция prepareDraw?

              1 Antwort Letzte Antwort
              1
              • T Offline
                T Offline
                TechElCo
                schrieb am zuletzt editiert von
                #7
                /**
                 * функция подготовки параметров для ChartJS.
                 * собирает данные из истории и складывает их в переменные, 
                 * чтобы потом включить в ряды.
                 * 
                 *  параметры:
                 *  @param hours - количество часов, за которые получить данные
                 *  результат:
                 *  @param Promise - успешная подготовка параметров
                 */
                
                function prepareDraw1(hours){
                    // вычислим интервал времени, за который надо получить данные
                    const end = new Date().getTime(),
                          start = end - 3600000*(hours || 1); // 1 = час назад
                
                    // зададим переменные, в которые будем складывать результаты запроса
                    // исторических данных
                    var улица, куры2, куры1, куры2свет, куры2вент;
                
                    // создадим Promise сборки данных и конфигурации
                    return new Promise((resolve, reject)=>{resolve()})
                
                        // на этом шаге собираем историю по 'mqtt.0.Borovoe1.bmet'
                        .then(() => {
                            return sendToPromise('history.0', 'getHistory', {
                                    id: 'mqtt.0.Borovoe1.bmet',
                                    options: {
                                        start: start,
                                        end: end,
                                        aggregate: 'onchange'
                                }
                            })
                            .then((result) => {
                                // записываем результат в переменную 'улица'
                                улица = result.result;
                            });
                        })
                
                        // на этом шаге собираем историю по 'sonoff.0.chicken2.DS18B20_Temperature'
                        .then(() => {
                            return sendToPromise('history.0', 'getHistory', {
                                id: 'mqtt.0.Sonoff.dsw1',
                                options: {
                                    start: start,
                                    end: end,
                                    aggregate: 'onchange'
                                }
                            }).then((result)=>{
                                // записываем результат в переменную 'куры2'
                                куры2 = result.result;
                            });
                        })
                
                        .then(() => {
                            return sendToPromise('history.0', 'getHistory', {
                                id: 'zigbee.0.00158d0001dbd5d6.temperature',
                                options: {
                                    start: start,
                                    end: end,
                                    aggregate: 'onchange'
                                }
                            }).then((result)=>{
                                куры1 = result.result;
                            });
                        })
                
                        .then(() => {
                            return sendToPromise('history.0', 'getHistory', {
                                id: 'mqtt.0.Sonoff.output12',
                                options: {
                                    start: start,
                                    end: end,
                                    aggregate: 'onchange'
                                }
                            }).then((result)=>{
                                куры2свет = result.result;
                            });
                        })
                
                        .then(() => {
                            return sendToPromise('history.0', 'getHistory', {
                                id: 'parser.0.cpuTemperature',
                                options: {
                                    start: start,
                                    end: end,
                                    aggregate: 'onchange'
                                }
                            }).then((result)=>{
                                куры2вент = result.result;
                            });
                        })
                
                        // финальный шаг - создаем конфигурацию графиков
                        .then(()=>{
                            const chartJsOptions = {
                                // тип графика - линейный
                                type: 'line',
                    			data: {
                    			    // список наборов данных
                    				datasets: [
                    			    {
                    			        // заголовок ряда с указанием последнего значения из ряда в скобках
                    					label: 'Улица ('+улица[улица.length - 1].val+')',
                    					// цвет
                    					backgroundColor: chartColors.blue,
                    					borderColor: chartColors.blue,
                    					// размер точек. 0 - нет точки
                    					pointRadius: 0,
                    					// ширина линии графика
                    					borderWidth: 3,
                    					// достанем данные из переменной 'улица' и оставим только значение и время изменения
                    					data: улица.map((item) => {
                    					    return {y: item.val, t: new Date(item.ts)}
                    					}),
                    					// заливка графика - нет
                    					fill: false,
                    					// идентификатор оси Y
                    					yAxisID: 'y-axis-1',
                    			    },{
                    					label: 'Куры 1 ('+куры1[куры1.length - 1].val+')',
                    					backgroundColor: chartColors.green,
                    					borderColor: chartColors.green,
                    					pointRadius: 0,
                    					borderWidth: 3,
                    					data: куры1.map((item) => {
                    					    return {y: item.val, t: new Date(item.ts)}
                    					}),
                    					fill: false,
                    					yAxisID: 'y-axis-1',
                    				},{
                    					label: 'Куры 2 ('+куры2[куры2.length - 1].val+')',
                    					backgroundColor: chartColors.red,
                    					borderColor: chartColors.red,
                    					pointRadius: 0,
                    					borderWidth: 3,
                    					data: куры2.map((item) => {
                    					    return {y: item.val, t: new Date(item.ts)}
                    					}),
                    					fill: false,
                    					yAxisID: 'y-axis-1',
                    				},{
                    					label: 'Куры 2 свет ('+куры2свет[куры2свет.length - 1].val+')',
                    					backgroundColor: chartColors.yellow,
                    					borderColor: chartColors.yellow,
                    					pointRadius: 0,
                    					borderWidth: 1,
                    					data: куры2свет.map((item) => {
                    					    return {y: (item.val) ? 1 : 0, t: new Date(item.ts)}
                    					}),
                    					fill: true,
                    					lineTension: 0,
                		                steppedLine: true,
                    					yAxisID: 'y-axis-2',
                    				},{
                    					label: 'Куры 2 вент ('+куры2вент[куры2вент.length - 1].val+')',
                    					backgroundColor: chartColors.grey,
                    					borderColor: chartColors.grey,
                    					pointRadius: 0,
                    					borderWidth: 1,
                    					data: куры2вент.map((item) => {
                    					    return {y: (item.val) ? -1 : 0, t: new Date(item.ts)}
                    					}),
                    					fill: true,
                    					lineTension: 0,
                		                steppedLine: true,
                    					yAxisID: 'y-axis-2',
                    				}
                    				]
                    			},
                    			options: {
                    				// настройка легенды
                    				legend: {
                    				    labels: {
                    				        // размер шрифта
                    				        fontSize: 20,
                    				    },
                    				},
                    				// оси координат
                    				scales: {
                    				    // оси X
                    					xAxes: [{
                    					    // тип - временная ось
                    					    type: 'time',  
                    						display: true,
                    						// метка оси
                    						scaleLabel: {
                    							display: true,
                    							labelString: 'Время'
                    						},
                    						// настройка формата оси (времени)
                    						time: {
                    						    unit: 'minute',
                    						    displayFormats: {
                                                    minute: 'HH:mm'
                                                }
                    						},
                    					}],
                    					// оси Y
                    					yAxes: [{
                    					    // тип - линейная
                    					    type: 'linear',
                    						display: true,
                    						// метка оси
                    						scaleLabel: {
                    							display: true,
                    							labelString: 'Температура'
                    						},
                    						// расположение линейки - слева
                    						position: 'left',
                    						// идентификатор оси
                    						id: 'y-axis-1',
                    					},{
                    					    type: 'linear',
                    					    display: true,
                    						scaleLabel: {
                    							display: true,
                    							labelString: 'Свет и вентиляция'
                    						},
                    						ticks: {
                    							min: -4,
                    							max: 2
                    						},
                    						// расположение линейки - справа
                    						position: 'right',
                    						id: 'y-axis-2',
                        				}]
                    				}
                    			}
                			};
                			return chartJsOptions;
                        });
                }
                

                Я только подставил пять своих состояний, больше ничего не менял

                1 Antwort Letzte Antwort
                0
                • goofykG Offline
                  goofykG Offline
                  goofyk
                  schrieb am zuletzt editiert von
                  #8

                  хм. по всем этим состояниям ведется история и она есть на запрашиваемый период?

                  тогда надо начать с простого варианта. переделываем пример prepareDraw0 и добавляем туда получение истории одного состояния, вместо заданных значений. сможете это сделать? это позволит понять работает ли получение истории.

                  может быть дело в NodeJS или версии контроллера или еще в чем-то…

                  1 Antwort Letzte Antwort
                  1
                  • T Offline
                    T Offline
                    TechElCo
                    schrieb am zuletzt editiert von
                    #9

                    По всем есть история, буду пробовать переделывать демо…

                    1 Antwort Letzte Antwort
                    0
                    • goofykG Offline
                      goofykG Offline
                      goofyk
                      schrieb am zuletzt editiert von
                      #10

                      Б-р-р-р… а в логах что?

                      Попробуем получить историю без промиса:

                      
                      function prepareDraw0(hours){
                          // вычислим интервал времени, за который надо получить данные
                          const end = new Date().getTime(),
                                start = end - 3600000*(hours || 1); // 1 = час назад
                          // переменная, куда сохраним данные
                          var пример;
                          // создадим Promise сборки данных и конфигурации
                          return new Promise((resolve, reject)=>{
                                  sendTo('history.0', 'getHistory', {
                                          id: 'mqtt.0.Borovoe1.bmet',
                                          options: {
                                              start: start,
                                              end: end,
                                              aggregate: 'onchange'
                                      }
                                  }, (result) => {
                                      пример = result.result;
                                      resolve();
                                  })
                              })
                              // финальный шаг - создаем конфигурацию графиков
                              .then(()=>{
                      
                      
                      1 Antwort Letzte Antwort
                      1
                      • T Offline
                        T Offline
                        TechElCo
                        schrieb am zuletzt editiert von
                        #11

                        ` > javascript.1 2018-10-17 12:47:21.360 info }, cmd=undefined, msg=undefined)

                        javascript.1 2018-10-17 12:47:21.360 info }

                        javascript.1 2018-10-17 12:47:21.360 info return resolve(ret);

                        javascript.1 2018-10-17 12:47:21.360 info }

                        javascript.1 2018-10-17 12:47:21.360 info ret = extraArgs;

                        javascript.1 2018-10-17 12:47:21.360 info // we return the raw array

                        javascript.1 2018-10-17 12:47:21.360 info } else {

                        javascript.1 2018-10-17 12:47:21.360 info }

                        javascript.1 2018-10-17 12:47:21.360 info ret[returnArgNames_[i]] = extraArgs__;

                        javascript.1 2018-10-17 12:47:21.360 info for (let i = 0; i < returnArgNames.length; i++) {

                        javascript.1 2018-10-17 12:47:21.360 info ret = {};

                        javascript.1 2018-10-17 12:47:21.360 info // we can build an object

                        javascript.1 2018-10-17 12:47:21.360 info if (returnArgNames && returnArgNames.length === extraArgs.length) {

                        javascript.1 2018-10-17 12:47:21.360 info const extraArgs = sliceArgs(arguments, 0);

                        javascript.1 2018-10-17 12:47:21.360 info let ret;

                        javascript.1 2018-10-17 12:47:21.360 info /** @type {{} | any[]} */

                        javascript.1 2018-10-17 12:47:21.360 info default: // multiple values should be returned

                        javascript.1 2018-10-17 12:47:21.360 info return resolve(result);

                        javascript.1 2018-10-17 12:47:21.360 info case 1: // a single value (result) was returned

                        javascript.1 2018-10-17 12:47:21.360 info return resolve(); // Promise <void>javascript.1 2018-10-17 12:47:21.360 info case 0: // no arguments were given

                        javascript.1 2018-10-17 12:47:21.360 info switch (arguments.length) {

                        javascript.1 2018-10-17 12:47:21.360 info // decide on how we want to return the callback arguments

                        javascript.1 2018-10-17 12:47:21.360 info script.js.test.Скрипт2: sendTo(adapter=function (result) {

                        javascript.1 2018-10-17 12:47:21.357 info script.js.test.Скрипт2: getState(id=telegram.0.communicate.requestMessageId, timerId=0) => {"val":19302,"ack":false,"ts":1539769641330,"q":0,"from":"system.adapter.telegram.0","lc":1539769641330}

                        javascript.1 2018-10-17 12:47:21.355 info script.js.test.Скрипт2: getState(id=telegram.0.communicate.requestChatId, timerId=0) => {"val":289938044,"ack":false,"ts":1539769641319,"q":0,"from":"system.adapter.telegram.0","lc":1538911049076}</void>___ Отвлекли, вот лог, это ещё с промисом. Что из него можно узнать? `

                        1 Antwort Letzte Antwort
                        0
                        • goofykG Offline
                          goofykG Offline
                          goofyk
                          schrieb am zuletzt editiert von
                          #12

                          в нем нет ошибок :( странно, что он текст функции вываливает в лог… у меня только sendTo
                          3371__________________2018-10-17_12-57-12.png

                          может инстанс history не 0?

                          1 Antwort Letzte Antwort
                          1
                          • goofykG Offline
                            goofykG Offline
                            goofyk
                            schrieb am zuletzt editiert von
                            #13

                            Какая версия Nodejs?

                            1 Antwort Letzte Antwort
                            1
                            • T Offline
                              T Offline
                              TechElCo
                              schrieb am zuletzt editiert von
                              #14

                              Хистори.0… Не знаю, как вставить картинку :( Ноде 8.12.0

                              Щас код такой:

                              `'use strict';
                              const ChartjsNode = require('chartjs-node');
                              
                              /*** Вспомогательные функции (взяты из js-controller) ***/
                              
                              /**
                               * Puts all values from an `arguments` object into an array, starting at the given index
                               * @param {IArguments} argsObj An `arguments` object as passed to a function
                               * @param {number} [startIndex=0] The optional index to start taking the arguments from
                               */
                              function sliceArgs(argsObj, startIndex) {
                                  if (startIndex === null) startIndex = 0;
                                  const ret = [];
                                  for (let i = startIndex; i < argsObj.length; i++) {
                                      ret.push(argsObj[i]);
                                  }
                                  return ret;
                              }
                              
                              /**
                               * Promisifies a function which does not provide an error as the first argument in its callback
                               * @param {Function} fn The function to promisify
                               * @param {any} [context] (optional) The context (value of `this` to bind the function to)
                               * @param {string[]} [returnArgNames] (optional) If the callback contains multiple arguments, 
                               * you can combine them into one object by passing the names as an array. 
                               * Otherwise the Promise will resolve with an array
                               * @returns {(...args: any[]) => Promise<any>}
                               */
                              function promisifyNoError(fn, context, returnArgNames) {
                                  return function () {
                                      const args = sliceArgs(arguments);
                                      context = context || this;
                                      return new Promise(function (resolve, reject) {
                                          fn.apply(context, args.concat([
                                              function (result) {
                                                  // decide on how we want to return the callback arguments
                                                  switch (arguments.length) {
                                                      case 0: // no arguments were given
                                                          return resolve(); // Promise <void>case 1: // a single value (result) was returned
                                                          return resolve(result);
                                                      default: // multiple values should be returned
                                                          /** @type {{} | any[]} */
                                                          let ret;
                                                          const extraArgs = sliceArgs(arguments, 0);
                                                          if (returnArgNames && returnArgNames.length === extraArgs.length) {
                                                              // we can build an object
                                                              ret = {};
                                                              for (let i = 0; i < returnArgNames.length; i++) {
                                                                  ret[returnArgNames[i]] = extraArgs[i];
                                                              }
                                                          } else {
                                                              // we return the raw array
                                                              ret = extraArgs;
                                                          }
                                                          return resolve(ret);
                                                  }
                                              }
                                          ]));
                                      });
                                  };
                              }
                              
                              /**
                               * функция sendTo как Promise, чтобы удобно было строить цепочки
                              */
                              var sendToPromise = promisifyNoError(sendTo);
                              
                              // константы для цветов
                              const chartColors = {
                                  black: 'rgb(0, 0, 0)',
                              	red: 'rgb(255, 99, 132)',
                              	orange: 'rgb(255, 159, 64)',
                              	yellow: 'rgb(255, 205, 86)',
                              	green: 'rgb(75, 220, 150)',
                              	blue: 'rgb(54, 162, 235)',
                              	purple: 'rgb(153, 102, 255)',
                              	grey: 'rgb(201, 203, 207)'
                              };
                              
                              /**
                               * функция рисования и сохранения картинки в файл
                               *  параметры:
                               *  @param config - конфигурация графика для рисования
                               *  @param filename - имя файла для сохранения
                               *  результат:
                               *  @param Promise - успешное сохранение файла
                               */
                              
                              function doDraw(config, filename) {
                                  // создадим полотно с размером 640x480 пикселей
                                  var chartNode = new ChartjsNode(640, 480);
                                  return chartNode.drawChart(config)
                                      .then(() => {
                                          // запишем результат в файл
                                          return chartNode.writeImageToFile('image/png', filename);
                                      });
                              }
                              
                              /**
                               * функция подготовки параметров для ChartJS.
                               *  результат:
                               *  @param Promise - успешная подготовка параметров
                               */
                              
                              function prepareDraw0(hours){
                                  // вычислим интервал времени, за который надо получить данные
                                  const end = new Date().getTime(),
                                        start = end - 3600000*(hours || 1); // 1 = час назад
                              
                                  // переменная, куда сохраним данные
                                  var пример;
                                  // создадим Promise сборки данных и конфигурации
                                  return new Promise((resolve, reject)=>{resolve()})
                                      // здесь могут быть много шагов сбора данных, прежде чем перейти к графику
                                      .then(()=>{
                                          return sendToPromise('history.0', 'getHistory', {
                                                  id: 'mqtt.0.Borovoe1.bmet',
                                                  options: {
                                                      start: start,
                                                      end: end,
                                                      aggregate: 'onchange'
                                              }
                                          })
                                          .then((result) => {
                                              // записываем результат в переменную 'пример'
                                              пример = result.result;
                                          });
                                      })
                              
                                 // финальный шаг - создаем конфигурацию графиков
                                      .then(()=>{
                                          const chartJsOptions = {
                                              // тип графика - линейный
                                              type: 'line',
                                  			data: {
                                  			    // список наборов данных
                                  				datasets: [
                                  			    {
                                  			        // заголовок ряда 
                                  					label: 'проба',
                                  					// цвет
                                  					backgroundColor: chartColors.green,
                                  					borderColor: chartColors.green,
                                  					// размер точек
                                  					pointRadius: 3,
                                  					// ширина линии графика
                                  					borderWidth: 3,
                                  					// достанем данные из переменной 'пример' и оставим только значение и время изменения
                                  					data: пример.map((item) => {
                                  					    return {y: item.val, t: new Date(item.ts)}
                                  					}),
                                  					// заливка графика - нет
                                  					fill: false,
                                  			    }
                                  				]
                                  			},
                                  			options: {
                                  				// настройка легенды
                                  				legend: {
                                  				    labels: {
                                  				        // размер шрифта
                                  				        fontSize: 24,
                                  				    },
                                  				},
                                  				// оси координат
                                  				scales: {
                                  				    // оси X
                                  					xAxes: [{
                                  					    // тип - временная ось
                                  					    type: 'time',  
                                  						display: true,
                                  						// метка оси
                                  						scaleLabel: {
                                  							display: true,
                                  							labelString: 'Время'
                                  						},
                                  					}],
                                  					// оси Y
                                  					yAxes: [{
                                  					    // тип - линейная
                                  					    type: 'linear',
                                  						display: true,
                                  						// метка оси
                                  						scaleLabel: {
                                  							display: true,
                                  							labelString: 'Температура'
                                  						},
                                  					}]
                                  				}
                                  			}
                              			};
                              			return chartJsOptions;
                                      });
                              }
                              
                              /**
                               * функция отправки графика в телеграм
                               * @param user - какому юзеру слать. если пусто - всем
                               * @param chat_id - 
                               * @param message_id - в каком чате и какое сообщение заменить при обновлении
                               * @param hours - количество часов, за которые получить данные
                               */
                              
                              //**************************************************************************************
                              function sendGraph0(user, chat_id, message_id, hours){
                                  // имя файла, в который положим картинку с графиком
                                  const filename = '/tmp/graph0.png';
                                  hours = hours || 1;
                                  // выполним подготовку данных 
                                  prepareDraw0(hours)
                                      // на след шаге нарисуем
                                      .then((result) => {
                                          // рисуем картинку по полученным данным и конфигурации
                                          return doDraw(result, filename);
                                      })
                                     .then(() => {
                                          // удалим предыдущее сообщение
                                          if (message_id && chat_id) {
                                              sendTo('telegram.0', {
                                                  user: user,
                                                  deleteMessage: {
                                                      options: {
                                                          chat_id: chat_id, 
                                                          message_id: message_id
                                                      }
                                                  }
                                              });
                                          }
                                      })
                                      .then(()=>{
                                          // теперь отправим сообщение в телеграм
                                          sendTo('telegram.0', {
                                              user: user, 
                                              text: filename, 
                                              caption: 'Температура в курятниках ('+hours+'ч)',
                                              reply_markup: {
                                                  inline_keyboard: [
                                                      [
                                                          { text: '🔄', callback_data: 'graph_'+hours},
                                                          { text: '1 ч', callback_data: 'graph_1' },
                                                          { text: '2 ч', callback_data: 'graph_2' },
                                                          { text: '4 ч', callback_data: 'graph_4' },
                                                          { text: '12 ч', callback_data: 'graph_12' },
                                                          { text: '24 ч', callback_data: 'graph_24' },
                                                      ]
                                                  ]
                                              }
                                          });
                                      });
                              }
                              
                              // будем слушать телеграм и ждать команды на построение графика
                              on({id: "telegram.0.communicate.request", ack: false, change: 'any'}, function (obj) {
                                  var v;
                                  var msg = obj.state.val;
                                  var command = obj.state.val.substring(obj.state.val.indexOf(']')+1);
                                  var user = obj.state.val.substring(obj.state.val.indexOf('[')+1,obj.state.val.indexOf(']'));
                                  var chat_id = getState("telegram.0.communicate.requestChatId").val;
                                  var message_id = getState("telegram.0.communicate.requestMessageId").val;
                              
                                  // команда для графика - demo
                                  if (command.startsWith('demo')) {
                                      const hours = parseInt(command.split('_')[1]);
                                      sendGraph0(user, chat_id, message_id, hours);
                                  }
                              });</void></any>`[/i][/i]
                              
                              1 Antwort Letzte Antwort
                              0
                              • goofykG Offline
                                goofykG Offline
                                goofyk
                                schrieb am zuletzt editiert von
                                #15

                                Попробуй вот такую функцию, вместо той, что в скрипте

                                function sendToPromise(adaper, cmd, params) {
                                    return new Promise((resolve, reject)=>{
                                        sendTo(adaper, cmd, params, (result) => {
                                            resolve(result);
                                        });
                                    });
                                }
                                
                                

                                Обновил скрипт в первом посте, убрал оттуда вспомогательные функции и вставил эту функцию.

                                1 Antwort Letzte Antwort
                                1
                                • T Offline
                                  T Offline
                                  TechElCo
                                  schrieb am zuletzt editiert von
                                  #16

                                  Так сработало, но до этого на месте этой функции было:

                                  /**
                                   * функция sendTo как Promise, чтобы удобно было строить цепочки
                                  */
                                  var sendToPromise = promisifyNoError(sendTo);
                                  

                                  Я думал, что тут что-то криво скопировалось…

                                  1 Antwort Letzte Antwort
                                  0
                                  • goofykG Offline
                                    goofykG Offline
                                    goofyk
                                    schrieb am zuletzt editiert von
                                    #17

                                    Уфф, разобрались.

                                    1 Antwort Letzte Antwort
                                    1
                                    • T Offline
                                      T Offline
                                      TechElCo
                                      schrieb am zuletzt editiert von
                                      #18

                                      Я, честно говоря, пока не разобрался 8-) Но мне ещё надо покурить много, а сегодня я урывками, на ходу, то с работы, то из дома пробовал код менять. Вечерком по изучаю, спасибо за помощь ;) Но вообще, у меня пока отдаёт картинку только за час, один раз, как то случайно отдал за 24 часа… Вот с таким логом : ` > telegram.0 2018-10-17 15:03:36.149 error Cannot send deleteMessage [chatId - 289938044]: Error: ETELEGRAM: 400 Bad Request: message can't be deleted

                                      javascript.1 2018-10-17 15:03:35.561 debug sendTo "send" to system.adapter.telegram.0 from system.adapter.javascript.1

                                      javascript.1 2018-10-17 15:03:35.560 info script.js.test.Скрипт3: sendTo(adapter=telegram.0, cmd=[object Object], msg=undefined)

                                      javascript.1 2018-10-17 15:03:35.559 debug sendTo "send" to system.adapter.telegram.0 from system.adapter.javascript.1

                                      javascript.1 2018-10-17 15:03:35.558 info script.js.test.Скрипт3: sendTo(adapter=telegram.0, cmd=[object Object], msg=undefined)

                                      javascript.1 2018-10-17 15:03:35.254 debug sendTo "getHistory" to system.adapter.history.0 from system.adapter.javascript.1

                                      javascript.1 2018-10-17 15:03:35.253 info script.js.test.Скрипт3: sendTo(adapter=history.0, cmd=getHistory, msg={"id":"mqtt.0.Borovoe1.bmet","options":{"start":1539774215252,"end":1539777815252,"aggregate":"onchange"}})

                                      javascript.1 2018-10-17 15:03:35.251 info script.js.test.Скрипт3: getState(id=telegram.0.communicate.requestMessageId, timerId=0) => {"val":19327,"ack":false,"ts":1539777815225,"q":0,"from":"system.adapter.telegram.0","lc":1539777815225}

                                      javascript.1 2018-10-17 15:03:35.249 info script.js.test.Скрипт3: getState(id=telegram.0.communicate.requestChatId, timerId=0) => {"val":289938044,"ack":false,"ts":1539777815213,"q":0,"from":"system.adapter.telegram.0","lc":1538911049076}

                                      telegram.0 2018-10-17 15:03:31.405 error Cannot send deleteMessage [chatId - 289938044]: Error: ETELEGRAM: 400 Bad Request: message can't be deleted `

                                      1 Antwort Letzte Antwort
                                      0
                                      • T Offline
                                        T Offline
                                        TechElCo
                                        schrieb am zuletzt editiert von
                                        #19

                                        @goofyk:

                                        Попробуй вот такую функцию, вместо той, что в скрипте

                                        function sendToPromise(adaper, cmd, params) {
                                            return new Promise((resolve, reject)=>{
                                                sendTo(adaper, cmd, params, (result) => {
                                                    resolve(result);
                                                });
                                            });
                                        }
                                        
                                        

                                        Обновил скрипт в первом посте, убрал оттуда вспомогательные функции и вставил эту функцию. `

                                        Поменял adaper на adapter, стало лучше )

                                        1 Antwort Letzte Antwort
                                        0
                                        • goofykG Offline
                                          goofykG Offline
                                          goofyk
                                          schrieb am zuletzt editiert von
                                          #20

                                          @TechElCo:

                                          Поменял adaper на adapter, стало лучше ) `

                                          :)) Спасибо

                                          1 Antwort Letzte Antwort
                                          0

                                          Hey! Du scheinst an dieser Unterhaltung interessiert zu sein, hast aber noch kein Konto.

                                          Hast du es satt, bei jedem Besuch durch die gleichen Beiträge zu scrollen? Wenn du dich für ein Konto anmeldest, kommst du immer genau dorthin zurück, wo du zuvor warst, und kannst dich über neue Antworten benachrichtigen lassen (entweder per E-Mail oder Push-Benachrichtigung). Du kannst auch Lesezeichen speichern und Beiträge positiv bewerten, um anderen Community-Mitgliedern deine Wertschätzung zu zeigen.

                                          Mit deinem Input könnte dieser Beitrag noch besser werden 💗

                                          Registrieren Anmelden
                                          Antworten
                                          • In einem neuen Thema antworten
                                          Anmelden zum Antworten
                                          • Älteste zuerst
                                          • Neuste zuerst
                                          • Meiste Stimmen


                                          Support us

                                          ioBroker
                                          Community Adapters
                                          Donate

                                          396

                                          Online

                                          32.7k

                                          Benutzer

                                          82.6k

                                          Themen

                                          1.3m

                                          Beiträge
                                          Community
                                          Impressum | Datenschutz-Bestimmungen | Nutzungsbedingungen | Einwilligungseinstellungen
                                          ioBroker Community 2014-2025
                                          logo
                                          • Anmelden

                                          • Du hast noch kein Konto? Registrieren

                                          • Anmelden oder registrieren, um zu suchen
                                          • Erster Beitrag
                                            Letzter Beitrag
                                          0
                                          • Home
                                          • Aktuell
                                          • Tags
                                          • Ungelesen 0
                                          • Kategorien
                                          • Unreplied
                                          • Beliebt
                                          • GitHub
                                          • Docu
                                          • Hilfe