Termostato en ThingJS (beta)



Hace casi un año, presenté mi proyecto favorito: la plataforma ThingJS IoT. Honestamente, no logré todos los objetivos que me propuse al publicar ese artículo. Pero el trabajo valió la pena. Me las arreglé para obtener algo más: críticas útiles.



He considerado la experiencia pasada. La teoría se estropea sin práctica. Esta vez la presentación estará basada en una solución aplicada. Todo el mundo puede "tocarlo" y utilizarlo en la vida diaria.



Pero primero, alguna informaciĂłn general.



Tabla de contenido


IntroducciĂłn



ThingJS se distingue de otras plataformas por una arquitectura que le permite crear dispositivos desde el nivel aficionado hasta el profesional.



En solo unos minutos, puede crear un dispositivo doméstico que se adapte a sus necesidades. Para hacer esto, un aficionado tiene plantillas de aplicaciones listas para usar, la capacidad de reutilizar el código de otra persona y la integración con la mayoría de los servicios de IoT a través de MQTT.



El profesional tiene la capacidad de modificar el firmware, creando dispositivos confiables. Desarrolle una interfaz de usuario de calidad basada en tecnologĂ­as WEB modernas. Para lanzar dispositivos terminados.



, “” . — .



, . (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. , , .



, .



. - , . , . , .



.



?



  • ;
  • ;
  • , ;
  • InstalaciĂłn de aplicaciones en dispositivos mĂłviles;
  • Desarrollo y lanzamiento del kit de desarrollo ThingJS para aficionados;
  • IntegraciĂłn con ecosistemas populares de IoT;
  • ImplementaciĂłn de control por voz.


Enlaces



Recursos del proyecto ThingJS:





Repositorios de proyectos ThingJS:





Donde ya se utiliza la plataforma:





Dónde se utilizará:





Proyectos usados:






All Articles