Интеграция в iH OLED-дисплея через MegaD



  • В задаче Интеграция в iH входов типа Click Mode MegaD я описывал вариант использования "мультикнопки" для вывода различной информации на OLED-дисплей с контроллером SSD1306. Поскольку лишь недавно "допилил" сценарий работы с дисплеем, то сейчас хочу рассказать что же у меня вышло.
    Итак, "мультикнопка", висящая на входе MegaD, имеет три варианта нажатия:
    а. Однократное нажатие (click=1). Если дисплей был выключен (или был перезапуск iH, или перезапуск плагина megad, или сценарий был сохранен), то он включится и отобразит температуру внутри помещения. При втором нажатии кнопки отобразится значение влажности помещения, при третьем нажатии отобразиться значение СО2. Следующее нажатие снова отобразит температуру в помещении и так по кругу.
    б. Двукратное нажатие (click=2). На дисплее отобразится значение температуры на улице.
    в. Удержание кнопки (m=2). Дисплей выключится. Можно конечно было "навесить" что-то еще на этот вариант нажатия кнопки, но в нашей семье дисплей иногда мешает спать. Поэтому и решил его таким образом принудительно выключать.

    Кроме всего этого, сценарий запускается при изменении значения (value) датчиков и если в текущий момент времени на дисплее отображается соответствующий датчику параметр, то его значение обновляется на дисплее.

    /** 
    * @name Сервис - обновление данных на OLED
    * @desc При однократном нажатии кнопки OLED отображает параметры микроклимата данного помещения. При двойном нажатии кнопки - температуру на улице
    * @version 4  
    */
    
    const button  = Device("BUTTON1_01");
    const dt      = Device("STEMP1_01");
    const dh      = Device("SHUMIDITY1_01");
    const dco2    = Device("SENSORA1_01");
    const dt_s    = Device("STEMP4_01");
    
    startOnChange([button,dt,dh,dco2,dt_s]);
    
    script({
      plugin: "megad2", //Переменная названия плагина
      channel: 33,      //Переменная номера канала плагина
      param: 1,         //Переменная для выбора параметров
      button_state: 0,  //Переменная для подсчета одинарных нажатий кнопки
      oled_state: 0,    //Переменная состояния OLED (0 - выключен, 1 - включен)
      col: 0,           //Переменная для расчета № столбца для вывода текста
      time: 1,          //Переменная для вывода времени (0 - нет, 1 - есть)
      text: "",         //Переменная для отображения текста на OLED
      saved: "",        //Переменная для "запоминания" текста
      value: "",        //Переменная для отображения значения на OLED
      unit: "",         //Переменная для отображения ед.изм. на OLED
      
      start() {
        //Обновление данных по датчикам
        if(this.isChanged(dt, "value") && this.param == 1) {
          this.SendData1();
        }
        else if(this.isChanged(dh, "value") && this.param == 2) {
          this.SendData2();
        }
        else if(this.isChanged(dco2, "value") && this.param == 3) {
          this.SendData3();
        }
        else if(this.isChanged(dt_s, "value") && this.param == 4) {
          this.SendData4();
        }
        //Переключение режимов отображения кнопкой
        else if(this.isChanged(button, "value")) {
          let mode = button.value;  //Вариант нажатия кнопки
          
          //Одинарное нажатие кнопки всегда возвращает 1!
          if(mode == 1) {
            //Если OLED был выключен то отображаем первый параметр
            if(!this.oled_state) {
              this.param = mode;
              this.button_state = this.param;
            }
            //Перебираем параметры при каждом одинарном нажатии кнопки
            else {
              this.param = this.button_state + 1;
              if(this.param > 2) this.button_state = 0;
              else this.button_state = this.param;
            }
          }
          //Двойное нажатие кнопки возвращает 4, удержание - 5.
          else {
            this.param = mode;
            this.button_state = 0;
          }
          
          //Выводим информацию на OLED в соответствии с выбранным параметром
          switch(this.param) {
            case 1: this.SendData1();
                    break;
            case 2: this.SendData2();
                    break;
            case 3: this.SendData3();
                    break;
            case 4: this.SendData4();
                    break;
            case 5: this.SendData5();
                    break;
          }
          
          if(mode > 0) {
            this.startTimer("ButRst", 0.2, "ClearState");
          }
        }
      },
        
      SendData1() {
        let text = "Температура"
        let value = dt.value;
        let unit = "grad";
        this.DataToOLED(text, value, unit, this.time);
      },
      
      SendData2() {
        let text = "Влажность";
        let value = dh.value;
        let unit = "percent";
        this.DataToOLED(text, value, unit, this.time);
      },
      
      SendData3() {
        let text = "Уровень_СО2,_ppm";
        let value = dco2.value;
        let unit = "ppm";
        this.DataToOLED(text, value, unit, this.time);
      },
      
      SendData4() {
        let text = "На_улице";
        let value = dt_s.value;
        let unit = "grad";
        this.DataToOLED(text, value, unit, this.time);
      },
      
      SendData5() {
        this.TurnOffOLED();
      },
      
      //Функция форматирования данных и их отображения на OLED
      DataToOLED(text, value, unit, time) {
        let iconv = require("/opt/intrahouse-c/backend/node_modules/iconv-lite");
        let buf = iconv.encode(text, "cp866");
        this.text = buf.toString("latin1");
        
        //Центрирование заголовка
        let length = this.text.length;
        let col = (128 - length*6)/2;
        this.col = col.toFixed(0);
        
        //Форматирование значений
        let str = ""; //Вспомогательная переменная
        if(value < 100) value = value.toFixed(1);
        str = value.toString();
        if(str.length < 4 && value > 0) this.value = 's+' +str;
        else if(str.length < 4) this.value = 'ss' +str;
        else if(str.length < 5) this.value = 's' +str;
        else this.value = str;
        
        //Выбор единицы измерения
        if(unit == "grad") this.unit = ":_";
        else if(unit == "percent") this.unit = "%";
        else if(unit == "ppm") this.unit = "s";
        
        //Вывод команд на дисплей
        if(this.saved != this.text) { //Если новый заголовок
          this.saved = this.text;     //то запоминаем его
          this.ClearOLED();           //очищаем первую строку
          //делаем паузу, выводим новый заголовок
          this.startTimer("T2", 0.05, "SendTextToOLED");
          //делаем паузу, выводим значение
          this.startTimer("T3", 0.2, "SendValueToOLED");
        }
        else {                        //Иначе обновляем значение
          this.SendValueToOLED();
        }
        if(time) {                    //Если есть вывод времени
          //то после отображения значения выводим его
          this.startTimer("T4", 0.4, "SendTimeToOLED");
        }
        if(!this.oled_state) {        //Если дисплей выключен
          //то включаем его после обновления параметра
          this.startTimer("T5", 0.4, "TurnOnOLED");
        }
      },
      
      ClearState() {
        this.assign(button, "value", 0);
      },
      
      ClearOLED() {
        this.pluginCommand({unit: this.plugin, command: '/sec/?pt=' +this.channel+ '&disp_cmd=1&row=0'});
      },
      
      ControlOLED(cmd) {
        this.pluginCommand({unit: this.plugin, command: '/sec/?cmd=' +this.channel+ ':' +cmd});
      },
      
      TurnOnOLED() {
        this.oled_state = 1;
        this.ControlOLED(this.oled_state);
      },
      
      TurnOffOLED() {
        this.oled_state = 0;
        this.ControlOLED(this.oled_state);
      },
      
      SendTextToOLED() {
        this.pluginCommand({unit: this.plugin, command: '/sec/?pt=' +this.channel+ '&text=' +this.text+ '&col=' +this.col+ '&row=0'});
      },
      
      SendValueToOLED() {
        this.pluginCommand({unit: this.plugin, command: '/sec/?pt=' +this.channel+ '&text=' +this.value+this.unit});
      },
      
      SendTimeToOLED() {
        //Запрос системного времени и форматирование единиц (для отображения 00...09)
        let time = new Date();
        let hours = time.getHours() < 10 ? "0" +time.getHours() : time.getHours();
        let minutes = time.getMinutes() < 10 ? "0" +time.getMinutes() : time.getMinutes();
        let seconds = time.getSeconds() < 10 ? "0" +time.getSeconds() : time.getSeconds();
        //Группировка единиц времени для передачи плагину
        let text = hours+ ':' +minutes+ ':' +seconds;
        this.pluginCommand({unit: this.plugin, command: '/sec/?pt=' +this.channel+ '&text=' +text+ '&col=45&row=6'});
      }
    });
    

    Код сценария получился довольно многострочным. Однако читатель, обладающий небольшим навыком JS-программирования сможет понять и разобраться со сценарием благодаря наличию комментариев и понятному названию функций. Непосредственно с дисплеем работают 5 функций - TurnOnOLED(), TurnOffOLED(), SendTextToOLED(), SendValueToOLED(), SendTimeToOLED(). Все они используют переменные уровня сценария (к которым нужно обращаться через "this"), поскольку обмен данными с дисплеем медленный и необходимо выдерживать паузы, запуская таймеры Т2-Т5, по истечению которых должны вызываться соответствующие функции (однако какой-либо аргумент в таком случае в функцию передать нет возможности):

    • для очистки первой строки, на которой по умолчанию выводится текст с наименованием параметра;
    • для вывода текста с наименованием параметра;
    • для вывода значения параметра крупным шрифтом ("нативный" шрифт MegaD);
    • для вывода времени обновления параметра.

    Кстати, при "игре" с выводом последнего возникли трудности отображения данных на дисплее. Итерационным методом понял, что надпись времени нужно выводить в 6-й строке (row=6), но когда попробовал вывести время в седьмой, то подопытный дисплей у меня нещадно заглючил! Вначале значение параметра (крупный шрифт) отображалось только на левой части дисплея (хотя все остальные строки - в полном объеме) и никакие переинициализации ("save" MegaD-2561 или php-скрипт) и даже долговременное снятие питания с дисплея не помогали! Каким-то чудом (с начало вывел время в row=1...6, потом снова в row=7) на некоторое время дисплей вновь заработал правильно, однако потом все строки дисплея сместились на половину строки и с этим я уже ничего не смог сделать.

    Сценарий обновлен 19.11.2020



  • Большое спасибо за труд, использовал ваш код вроде все работает, жду продолжения статьи.


Авторизуйтесь, чтобы ответить