Skip to content
  • Home
  • Recent
  • Tags
  • 0 Unread 0
  • Categories
  • Unreplied
  • Popular
  • GitHub
  • Docu
  • Hilfe
Skins
  • Light
  • Brite
  • Cerulean
  • Cosmo
  • Flatly
  • Journal
  • Litera
  • Lumen
  • Lux
  • Materia
  • Minty
  • Morph
  • Pulse
  • Sandstone
  • Simplex
  • Sketchy
  • Spacelab
  • United
  • Yeti
  • Zephyr
  • Dark
  • Cyborg
  • Darkly
  • Quartz
  • Slate
  • Solar
  • Superhero
  • Vapor

  • Default (No Skin)
  • No Skin
Collapse
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
    705

  • 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 на сервере и отправка картинки в телеграм

Scheduled Pinned Locked Moved ioBroker скрипты
22 Posts 4 Posters 7.8k Views 1 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • T Offline
    T Offline
    TechElCo
    wrote on last edited by
    #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 Reply Last reply
    0
    • goofykG Offline
      goofykG Offline
      goofyk
      wrote on last edited by
      #5

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

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

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

      1 Reply Last reply
      1
      • goofykG Offline
        goofykG Offline
        goofyk
        wrote on last edited by
        #6

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

        1 Reply Last reply
        1
        • T Offline
          T Offline
          TechElCo
          wrote on last edited by
          #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 Reply Last reply
          0
          • goofykG Offline
            goofykG Offline
            goofyk
            wrote on last edited by
            #8

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

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

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

            1 Reply Last reply
            1
            • T Offline
              T Offline
              TechElCo
              wrote on last edited by
              #9

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

              1 Reply Last reply
              0
              • goofykG Offline
                goofykG Offline
                goofyk
                wrote on last edited by
                #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 Reply Last reply
                1
                • T Offline
                  T Offline
                  TechElCo
                  wrote on last edited by
                  #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 Reply Last reply
                  0
                  • goofykG Offline
                    goofykG Offline
                    goofyk
                    wrote on last edited by
                    #12

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

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

                    1 Reply Last reply
                    1
                    • goofykG Offline
                      goofykG Offline
                      goofyk
                      wrote on last edited by
                      #13

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

                      1 Reply Last reply
                      1
                      • T Offline
                        T Offline
                        TechElCo
                        wrote on last edited by
                        #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 Reply Last reply
                        0
                        • goofykG Offline
                          goofykG Offline
                          goofyk
                          wrote on last edited by
                          #15

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

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

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

                          1 Reply Last reply
                          1
                          • T Offline
                            T Offline
                            TechElCo
                            wrote on last edited by
                            #16

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

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

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

                            1 Reply Last reply
                            0
                            • goofykG Offline
                              goofykG Offline
                              goofyk
                              wrote on last edited by
                              #17

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

                              1 Reply Last reply
                              1
                              • T Offline
                                T Offline
                                TechElCo
                                wrote on last edited by
                                #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 Reply Last reply
                                0
                                • T Offline
                                  T Offline
                                  TechElCo
                                  wrote on last edited by
                                  #19

                                  @goofyk:

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

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

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

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

                                  1 Reply Last reply
                                  0
                                  • goofykG Offline
                                    goofykG Offline
                                    goofyk
                                    wrote on last edited by
                                    #20

                                    @TechElCo:

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

                                    :)) Спасибо

                                    1 Reply Last reply
                                    0
                                    • S Offline
                                      S Offline
                                      Sergey777
                                      wrote on last edited by
                                      #21

                                      А можно так, чтоб не на сервере графики строились? А как обычно, в браузере. Но как chartJS тогда совместить с histori драйвером?

                                      1 Reply Last reply
                                      0
                                      • goofykG goofyk

                                        Расскажу, как можно получить график в виде картинки, например для отправки в 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 Offline
                                        K Offline
                                        kristow
                                        wrote on last edited by
                                        #22

                                        @goofyk
                                        Добрый день.
                                        Установил chartjs и chartjs-node.
                                        На основе вашего примера прописал свои параметры для вывода в график. Когда запускаю скрипт, то ошибок нет. Когда отправляю в телеграмм "demo", то вылетает ошибка "script.js.common.Chart: TypeError: canvas.getRootNode is not a function" и на этом все... Можете подсказать что сделать? для решения этой проблемы?
                                        В самом скрипте этой функции нет. Нужно как-то хитрее ставить библиотеки? У меня iobroker работает на rasbian (если имеет значение).

                                        1 Reply Last reply
                                        0

                                        Hello! It looks like you're interested in this conversation, but you don't have an account yet.

                                        Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.

                                        With your input, this post could be even better 💗

                                        Register Login
                                        Reply
                                        • Reply as topic
                                        Log in to reply
                                        • Oldest to Newest
                                        • Newest to Oldest
                                        • Most Votes


                                        Support us

                                        ioBroker
                                        Community Adapters
                                        Donate

                                        336

                                        Online

                                        32.7k

                                        Users

                                        82.6k

                                        Topics

                                        1.3m

                                        Posts
                                        Community
                                        Impressum | Datenschutz-Bestimmungen | Nutzungsbedingungen | Einwilligungseinstellungen
                                        ioBroker Community 2014-2025
                                        logo
                                        • Login

                                        • Don't have an account? Register

                                        • Login or register to search.
                                        • First post
                                          Last post
                                        0
                                        • Home
                                        • Recent
                                        • Tags
                                        • Unread 0
                                        • Categories
                                        • Unreplied
                                        • Popular
                                        • GitHub
                                        • Docu
                                        • Hilfe