Termostat di ThingJS (beta)



Hampir setahun yang lalu, saya mempresentasikan proyek hewan peliharaan saya - platform IoT ThingJS. Sejujurnya, saya tidak mencapai semua tujuan yang saya tetapkan untuk diri saya sendiri dengan menerbitkan artikel itu. Tapi pekerjaan itu membuahkan hasil. Saya berhasil mendapatkan sesuatu yang lain - kritik yang berguna.



Saya telah mempertimbangkan pengalaman masa lalu. Teori menjadi buruk tanpa latihan. Kali ini presentasi akan didasarkan pada solusi yang diterapkan. Setiap orang bisa "menyentuhnya" dan menggunakannya dalam kehidupan sehari-hari.



Tetapi pertama-tama, beberapa informasi umum.



Daftar Isi


pengantar



ThingJS membedakan dirinya dari platform lain dengan arsitektur yang memungkinkan Anda membuat perangkat dari tingkat penghobi hingga profesional.



Hanya dalam beberapa menit, Anda dapat membuat perangkat rumah tangga untuk kebutuhan Anda. Untuk melakukan ini, seorang amatir memiliki templat aplikasi yang sudah jadi, kemampuan untuk menggunakan kembali kode orang lain dan integrasi dengan sebagian besar layanan IoT melalui MQTT.



Seorang profesional memiliki kemampuan untuk memodifikasi firmware, membuat perangkat yang andal. Kembangkan UI berkualitas berdasarkan teknologi WEB modern. Untuk melepaskan perangkat yang sudah jadi.



Tetapi yang paling penting adalah bahwa kedua “dunia” yang sangat berbeda ini dapat hidup berdampingan secara harmonis. Ini dimungkinkan melalui fitur unik dari platform - aplikasi.



, . (core) (Resource Interfaces). ( ), “” UI . , , .



. IDE. : Lion, CMake, webpack, npm .. ThingJS. TDD .



“” , . “ ” . “ ”. .



, . .





runtime ESP32. .



. backend frontend — . :



  • ();
  • Frontend ();
  • Backend ();
  • ();
  • ();
  • ();
  • ().


Backend . JavaScript — mJS.



Frontend SPA WEB-. . : UBUS Storage.





UBUS



. JSON-. . . .



, . . frontend, backend, , , .



. frontend WEBSocket.



, frontend , . — . backend.



.



, . , , .



Storage



. . frontend, backend.



. , . , . , .



. , , . .



. , . .



. , . . , .



API



API. , $res $bus.



API . , , (Resource interfaces).



Resource interfaces



, . .. , . . , launcher, vuetifyjs React.



— . . .



. . , FreeRTOS , . SmartLED.



, . , . . , . .





. .



:



  • ;
  • .


. . , GPIO . , .



. .



. , .



, , . , .



. , , GPIO, , UART .. . , TCP .. , , .



, , , ThingJS.



ThingJS



Thermostat .





. , . , , . .





ESP32 ESP8266. , , . 240, , 520, Bluetooth, Wifi. GPIO. : ES, SHA-2, RSA, ECC, RNG.



ESP32-DevKitC.







JavaScript VUE CLI. , frontend .



:



  • — .
  • (hot reload) — . . .
  • dev — dev- NodeJS. , . .


:



git clone --branch beta https://github.com/rpiontik/ThingJS-front
cd ThingJS-front
npm install


, dev :



npm run dev


http://0.0.0.0:8080/. dev-. .





100%, . , . . , /config/dev.env.js IP .



, dev- “” -, , . .. “”, , , thermostat.smt.



dev thermostat . .





, src/applications/thermostat/scripts/thermostat.js. , “debugger”. .



, dev- :



“Start debugger” .





. watch . . .



, :



npm run build


dist/apps/



.



Thermostat



. /src/applications/. “blink”. — thermostat.



. manifest.json. . .





"name": "Thermostat",
"vendor": "rpiontik",
"version": 1,
"subversion": 0,
"patch": 0,
"description": {
    "ru": "",
    "en": "Thermostat"
},


, .



components



frontend .



"components": {
 "thermostat-app": {
   "source": "thermostat.js",
   "intent_filter": [
     {
       "action": "thingjs.intent.action.MAIN",
       "category": "thingjs.intent.category.LAUNCH"
     }
   ]
 }
},


— “thermostat-app”. “thermostat.js”. “intent_filter”. .



“blink.js” -> “thermostat.js” :



import App from './Thermostat.vue';
import Langs from './langs';

$includeLang(Langs);
$exportComponent('thermostat-app', App);


VUE “Thermostat.vue”. . . :



$exportComponent('thermostat-app', App);


? . . , “thermostat.js” VUE “thermostat-app”. . “langs.js”.



frontend “Thermostat.vue”. . .



template
<template>
  <v-flex fill-height style="max-width: 600px">
    <h1>{{ 'TITLE'|lang }}</h1>
    <v-container>
      <v-layout>
        <v-flex xs12 md12>
          {{ 'DESCRIPTION'|lang }}
        </v-flex>
      </v-layout>
    </v-container>
    <v-tabs
        centered
        icons-and-text
    >
      <v-tab href="#tab-1">
        {{ 'CONTROL'|lang }}
        <v-icon>dashboard</v-icon>
      </v-tab>

      <v-tab href="#tab-2">
        {{ 'CLOUD'|lang }}
        <v-icon>cloud</v-icon>
      </v-tab>

      <v-tab-item value="tab-1">
        <v-container>
          <v-layout>
            <v-flex class="current-temp" xs12 md4>
                <span>
                  <template v-if="state.temp !== null">
                    {{ state.temp.toFixed(1) }}°
                  </template>
                  <template v-else>
                    --.--
                  </template>
                </span>
            </v-flex>
            <v-flex xs12 md4 style="text-align: center; padding: 12px; ">
              <template v-if="state.state === 1">
                <v-icon
                    title="Power on"
                    class="indicator"
                >power
                </v-icon>
              </template>
              <template v-else-if="state.state === 0">
                <v-icon
                    title="Power off"
                    class="indicator"
                >power_off
                </v-icon>
              </template>
            </v-flex>
            <v-flex xs12 md4 style="text-align: center; padding: 12px;">
              <template v-if="!!state.connected">
                <v-icon
                    title="Connected"
                    class="indicator"
                >cloud
                </v-icon>
              </template>
              <template v-else>
                <v-icon
                    title="Disconnected"
                    class="indicator"
                >cloud_off
                </v-icon>
              </template>
            </v-flex>
          </v-layout>
        </v-container>
        <v-container grid-list-xl>
          <v-layout>
            <v-flex xs12 md3>
              <v-select
                  label="Mode"
                  :items="modes"
                  v-model="state.mode"
                  @change="onChangeMode"
              ></v-select>
            </v-flex>
            <v-flex xs12 md9>
              <v-slider v-if="state.mode <= 1"
                        thumb-label="always"
                        v-model="state.target"
                        :disabled="!state.target"
                        @change="onChangeTarget"
              ></v-slider>
            </v-flex>
          </v-layout>
        </v-container>
      </v-tab-item>
      <v-tab-item value="tab-2">
        <v-container>
          <p>
            Android applications:
            <ul>
              <li><a href="https://play.google.com/store/apps/details?id=net.routix.mqttdash" target="_blank">MQTT Dash (RUS)</a></li>
              <li><a href="https://play.google.com/store/apps/details?id=snr.lab.iotmqttpanel.prod" target="_blank">IoT MQTT Panel (EN)</a></li>
            </ul>
          </p>
          <p>
            Server params:
            <ul>
              <li>Address: mqtt.eclipse.org</li>
              <li>port: 1883</li>
            </ul>
          </p>
          <table class="topic-table">
            <tr>
              <th>{{ 'TOPIC'|lang }}</th>
              <th>{{ 'TOPIC_DESCRIPTION'|lang }}</th>
            </tr>
            <tr>
              <td>/thingjs/{{ state.chip_id }}/temp</td>
              <td>{{ 'TOPIC_TEMP_DESC'|lang }}</td>
            </tr>
            <tr>
              <td>/thingjs/{{ state.chip_id }}/state</td>
              <td>{{ 'TOPIC_STATE_DESC'|lang }}</td>
            </tr>
            <tr>
              <td>/thingjs/{{ state.chip_id }}/target/out</td>
              <td>{{ 'TOPIC_TARGET_OUT'|lang }}</td>
            </tr>
            <tr>
              <td>/thingjs/{{ state.chip_id }}/target/in</td>
              <td>{{ 'TOPIC_TARGET_IN'|lang }}</td>
            </tr>
            <tr>
              <td>/thingjs/{{ state.chip_id }}/mode/out</td>
              <td>{{ 'TOPIC_MODE_OUT'|lang }}</td>
            </tr>
            <tr>
              <td>/thingjs/{{ state.chip_id }}/mode/in</td>
              <td>{{ 'TOPIC_MODE_IN'|lang }}</td>
            </tr>
          </table>
        </v-container>
      </v-tab-item>
    </v-tabs>
  </v-flex>
</template>


data () {
   return {
       modes: [ //    .
           { text: 'Less then', value: 0 },
           { text: 'More then', value: 1 },
           { text: 'On', value: 2 },
           { text: 'Off', value: 3 }
       ],
       isHold: false, //  .   ,      .
       state: { //   
           connected: null, //     MQTT 
           mode: null, //   
           target: null, //  
           temp: null, //  
           state: null, //   (/)
           chip_id: null //    MQTT .
       }
   };
}


:



isHold — . , . . , . , . “” .



chip_id — . MQTT .



mounted () {
   this.$bus.$on($consts.EVENTS.UBUS_MESSAGE, (type, data) => {
       if (this.isHold) return;

       switch (type) {
       case 'thermostat-state':
           this.state = JSON.parse(data);
           break;
       }
   });
   this.refreshState();
},


. “thermostat-state”. . “isHold” .



refreshState () {
   this.$bus.$emit($consts.EVENTS.UBUS_MESSAGE, 'tmst-refresh-state');
},


. .



flushData () {
   if (this.isHold) { clearTimeout(this.isHold); }
   this.isHold = setTimeout(() => {
       this.isHold = null;
       this.refreshState();
   }, 1000);
},


. , .



onChangeTarget (val) {
   this.$bus.$emit($consts.EVENTS.UBUS_MESSAGE, 'tmst-set-target', val);
   this.flushData();
},
onChangeMode (val) {
   this.$bus.$emit($consts.EVENTS.UBUS_MESSAGE, 'tmst-set-mode', val);
   this.flushData();
}


.



- frontend. “” .



requires



, . “requires”.



"requires": {
 "interfaces": {
   "mqtt": {
     "type": "mqttc",
     "required": true
   },
   "timers": {
     "type": "timers",
     "required": true,
     "description": {
       "ru": " ",
       "en": "System timers"
     }
   },
   "ds18x20": {
     "type": "DS18X20",
     "required": true
   },
   "relay": {
     "type": "bit_port",
     "required": true,
     "default": 2,
     "description": {
       "ru": "",
       "en": "Relay"
     }
   },
   "sys_info": {
     "type": "sys_info",
     "required": true,
     "description": {
       "ru": "  ",
       "en": "System information"
     }
   }
 }
}


:



  • mqttc — MQTT . .
  • timers — . .
  • DS18X20 — OneWire .
  • bit_port — . .
  • sys_info — . .


, . . , . "required" . .



. , “DS18X20”. , OneWire.



. . . , , “relay”, “type”. “relay” “bit_port”.



scripts



.



"scripts": {
 "entry": "thermostat",
 "subscriptions": ["tmst-refresh-state", "tmst-set-target", "tmst-set-mode"],
 "modules": {
   "thermostat": {
     "hot_reload": true,
     "source": "scripts/thermostat.js",
     "optimize": false
   }
 }
},


  • entry — . . . .
  • subscriptions — . - . , “”. .
  • modules — .

    • thermostat — .
    • hot_reload — “” . true, . . dev-.
    • source — .
    • optimize — true, webpack.


“scripts/blink.js” “scripts/thermostat.js”. .



let MQTT_SERVER = 'wss://mqtt.eclipse.org:443/mqtt';


MQTT . . . . MQTT , .



let CHIP_ID = $res.sys_info.chip_id;


. $res. . sys_info . MQTT .



let TOPIC_TEMP = '/thingjs/' + CHIP_ID + '/temp';
let TOPIC_TARGET_OUT = '/thingjs/' + CHIP_ID + '/target/out';
let TOPIC_TARGET_IN = '/thingjs/' + CHIP_ID + '/target/in';
let TOPIC_MODE_OUT = '/thingjs/' + CHIP_ID + '/mode/out';
let TOPIC_MODE_IN = '/thingjs/' + CHIP_ID + '/mode/in';
let TOPIC_MODE_STATE = '/thingjs/' + CHIP_ID + '/state';


MQTT . “out” “in”. . out — , in — .



:



//        .   .
let MODE_LESS = 0;
//        .   .
let MODE_MORE = 1;
//   .
let MODE_ON = 2;
//   .
let MODE_OFF = 3;


:



//     MQTT 
let isConnected = false;
//   
let mode = MODE_LESS;
//  
let target = 32;
//  
let state = 0;
//   
let sensor = null;
//  
let temp = null;
//    ,        .   .  “”     .
let fakeVector = 0.5;


. OneWire. sensor .



$res.ds18x20.search(function (addr) {
   if (sensor === null) {
       sensor = addr;
   }
});

function publishState () {
   $bus.emit('thermostat-state', JSON.stringify({
       connected: isConnected,
       mode: mode,
       target: target,
       temp: temp,
       state: state,
       chip_id: CHIP_ID
   }));

   if (isConnected) {
       $res.mqtt.publish(TOPIC_MODE_OUT, JSON.stringify(mode));
       $res.mqtt.publish(TOPIC_TARGET_OUT, JSON.stringify(target));
       $res.mqtt.publish(TOPIC_MODE_STATE, JSON.stringify(state));
       $res.mqtt.publish(TOPIC_TEMP, JSON.stringify(temp));
   }
}


publishState :



  • UBUS. . . , . , frontend. frontend.
  • MQTT . . .


MQTT :



//       
$res.mqtt.onconnected = function () {
   print('MQTT client is connected');
   isConnected = true;
   $res.mqtt.subscribe(TOPIC_TARGET_IN);
   $res.mqtt.subscribe(TOPIC_MODE_IN);
   publishState();
};

//      online 
$res.mqtt.disconnected = function () {
   print('MQTT client is disconnected');
   isConnected = false;
   publishState();
};

//      MQTT 
$res.mqtt.ondata = function (topic, data) {
   print('MQTT client received from topic [', topic, '] with data [', data, ']');
   if (topic === TOPIC_TARGET_IN) {
       target = JSON.parse(data);
   } else if (topic === TOPIC_MODE_IN) {
       mode = JSON.parse(data);
   }
};


UBUS. , .



$bus.on(function (event, data) {
   if (event === 'tmst-set-target') {
       target = JSON.parse(data);
   } else if (event === 'tmst-set-mode') {
       mode = JSON.parse(data);
   }
   publishState();
}, null);


.



$res.timers.setInterval(function () {
   if (sensor !== null) {
       $res.ds18x20.convert_all();
       temp = $res.ds18x20.get_temp_c(sensor);
   } else { // Fake temperature
       if (temp > 99) {
           fakeVector = -0.5;
       } else if (temp < 1) {
           fakeVector = 0.5;
       }

       temp += fakeVector;
   }
   // Refresh sensor data
   if (mode === MODE_ON) {
       state = 1;
   } else if (mode === MODE_OFF) {
       state = 0;
   } else if (mode === MODE_LESS) {
       if (temp < target) {
           state = 1;
       } else {
           state = 0;
       }
   } else if (mode === MODE_MORE) {
       if (temp > target) {
           state = 1;
       } else {
           state = 0;
       }
   }

   publishState();
   //        
   $res.relay.set(!state);
}, 1000);


:



//      . 
temp = 34.5;
//   GPIO
$res.relay.direction($res.relay.DIR_MODE_OUTPUT);
//   
publishState();
//      MQTT .
$res.mqtt.connect(MQTT_SERVER);




. VUE . :



<h1>{{ 'TITLE'|lang }}</h1>


langs.js frontend .



favicon



. favicon.svg .





. .. /src/applications/. npm



npm run build



smt . thermostat.smt Thermostat. /dist/apps/







. WEB. “” “” “ ”. thermostat.smt.





, . . , , MQTT . . , OneWire UART GPIO . , GPIO .



default . , “”. .



:



  1. . .
  2. . .
  3. . , .

    . , .


. .





: MQTT. , MQTT.





. . .



. .







Android. MQTT Dash.



, . IP . .



. MQTT .





. . . .





. — /thingjs/TJS-030BE4/temp .



. , — .



().



. . out in .



, , .



!





- IoT. , , .



, .



. - , . , . , .



.



?



  • ;
  • ;
  • , ;
  • Menginstal aplikasi di perangkat seluler;
  • Pengembangan dan peluncuran dev-kit ThingJS untuk amatir;
  • Integrasi dengan ekosistem IoT populer;
  • Penerapan kontrol suara.


Tautan



Sumber daya proyek ThingJS:





Repositori proyek ThingJS:





Dimana platform sudah digunakan:





Dimana akan digunakan:





Proyek bekas:






All Articles