Navigation

    Logo
    • Register
    • Login
    • Search
    • Recent
    • Tags
    • Unread
    • Categories
    • Unreplied
    • Popular
    • GitHub
    • Docu
    • Hilfe
    1. Home
    2. Русский
    3. ioBroker
    4. Скрипты
    5. ioBroker скрипты
    6. Построение графиков ChartJS на сервере и отправка картинки в телеграм

    NEWS

    • ioBroker@Smart Living Forum Solingen, 14.06. - Agenda added

    • ioBroker goes Matter ... Matter Adapter in Stable

    • Monatsrückblick - April 2025

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

    This topic has been deleted. Only users with topic management privileges can see it.
    • goofyk
      goofyk last edited by

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

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

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

      1 Reply Last reply Reply Quote 1
      • T
        TechElCo last edited by

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

        1 Reply Last reply Reply Quote 0
        • goofyk
          goofyk last edited by

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

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

          
          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 Reply Quote 1
          • T
            TechElCo last edited by

            ` > 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 Reply Quote 0
            • goofyk
              goofyk last edited by

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

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

              1 Reply Last reply Reply Quote 1
              • goofyk
                goofyk last edited by

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

                1 Reply Last reply Reply Quote 1
                • T
                  TechElCo last edited by

                  Хистори.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 Reply Quote 0
                  • goofyk
                    goofyk last edited by

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

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

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

                    1 Reply Last reply Reply Quote 1
                    • T
                      TechElCo last edited by

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

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

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

                      1 Reply Last reply Reply Quote 0
                      • goofyk
                        goofyk last edited by

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

                        1 Reply Last reply Reply Quote 1
                        • T
                          TechElCo last edited by

                          Я, честно говоря, пока не разобрался 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 Reply Quote 0
                          • T
                            TechElCo last edited by

                            @goofyk:

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

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

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

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

                            1 Reply Last reply Reply Quote 0
                            • goofyk
                              goofyk last edited by

                              @TechElCo:

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

                              :)) Спасибо

                              1 Reply Last reply Reply Quote 0
                              • S
                                Sergey777 last edited by

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

                                1 Reply Last reply Reply Quote 0
                                • K
                                  kristow @goofyk last edited by

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

                                  1 Reply Last reply Reply Quote 0
                                  • First post
                                    Last post

                                  Support us

                                  ioBroker
                                  Community Adapters
                                  Donate

                                  781
                                  Online

                                  31.7k
                                  Users

                                  79.8k
                                  Topics

                                  1.3m
                                  Posts

                                  4
                                  22
                                  6468
                                  Loading More Posts
                                  • Oldest to Newest
                                  • Newest to Oldest
                                  • Most Votes
                                  Reply
                                  • Reply as topic
                                  Log in to reply
                                  Community
                                  Impressum | Datenschutz-Bestimmungen | Nutzungsbedingungen
                                  The ioBroker Community 2014-2023
                                  logo