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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            /**
             * функция 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 Reply Quote 0
            • goofyk
              goofyk last edited by

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

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

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

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

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

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

                  /**
                   * функция подготовки параметров для 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 Reply Quote 0
                  • 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
                                            • First post
                                              Last post

                                            Support us

                                            ioBroker
                                            Community Adapters
                                            Donate

                                            618
                                            Online

                                            31.7k
                                            Users

                                            79.8k
                                            Topics

                                            1.3m
                                            Posts

                                            4
                                            22
                                            6469
                                            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