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

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

Community Forum

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

NEWS

  • Neuer Blogbeitrag: Monatsrückblick - Dezember 2025 🎄
    BluefoxB
    Bluefox
    11
    1
    324

  • Weihnachtsangebot 2025! 🎄
    BluefoxB
    Bluefox
    24
    1
    1.5k

  • UPDATE 31.10.: Amazon Alexa - ioBroker Skill läuft aus ?
    apollon77A
    apollon77
    48
    3
    9.5k

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

                                        @goofyk:

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

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

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

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

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

                                          @TechElCo:

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

                                          :)) Спасибо

                                          1 Antwort Letzte Antwort
                                          0
                                          Antworten
                                          • In einem neuen Thema antworten
                                          Anmelden zum Antworten
                                          • Älteste zuerst
                                          • Neuste zuerst
                                          • Meiste Stimmen


                                          Support us

                                          ioBroker
                                          Community Adapters
                                          Donate

                                          606

                                          Online

                                          32.5k

                                          Benutzer

                                          81.7k

                                          Themen

                                          1.3m

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

                                          • Du hast noch kein Konto? Registrieren

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