Русский
Русский
English
Статистика
Реклама

Как я умный аквариум делал (frontend)

Пролог



Как я рассказывал тут, я начал постройку умного аквариума на основе платы NodeMCU. На ней я использовал прошивку с micropython, поднял веб сервер и сделал API для манипуляции всеми периферийными устройствами и датчиками. Поскольку мой вариант умного аквариума изначально планировался как автономный, я хотел сделать некий UI для отслеживания всех процессов ну и для ручных корректировок. Каждый раз обращаться по роутам типа: http://192.168.1.70/led_controller?impulse=4000&level=200&ledName=white было очень муторно и неудобно. Особенно когда ты уже лег спать и под рукой только телефон. Да и опять же, хотелось получить levelup в разработке и сделать что-то увлекательное.


За основу UI взял Vue.js. Авторизация как таковая не нужна, т.к. мой "умный друг" был только локально в пределах моего WI-FI окружения. Да и если бы его взломали, ничего страшного не случилось. Другое дело когда я буду делать умный дом, там уже безопасность на первом месте, но сейчас не об этом. Итак, никакой авторизации, только SPA("Одностраничное приложение": "single page application"), никакого роутинга, все показатели и манипуляторы на одной странице. Из того что было сделано на backend контроль за LED-матрицами и температурный датчик. Создаем новый проект на гите, делаем клон на рабочем месте и запускаем vue-cli:


$ vue ui  Starting GUI...  Ready on http://localhost:8000


Создаем новый проект, добавляем туда все необходимые плагины:


  • vue-bootstrap сам себе дизайнер.
  • axios для работы с backend по API.
  • vuex для отделения бизнес логики

Для axios настроил базовый url


plugin/axios.js


import Vue from 'vue';import axios from 'axios';import VueAxios from 'vue-axios';axios.defaults.baseURL = 'http://192.168.1.70';Vue.use(VueAxios, axios);

Дизайн и выбор цветовых решений дело сугубо индивидуальное, выбор пал на синий цвет шапки, потому, что аквариум и в целом вода у меня лично ассоциируется с этим цветом. И так накидал такую вот шапку и подключил основной компонент в котором будет список всех ручек и крутилок.




App.vue
<template>  <div id="app">    <b-navbar type="dark" variant="primary" class="rounded">      <b-navbar-brand tag="h1" class="mb-0">Fish Tank</b-navbar-brand>      <b-icon         icon="brightness-alt-high"         font-scale="3"         variant="light"         class="rounded bg-primary p-1"      />    </b-navbar>    <list-of-range-controllers/>  </div></template><script>import ListOfRangeControllers from './components/ListOfRangeControllers';export default {    name: 'App',    components: {        ListOfRangeControllers    }}</script><style scoped>  #app {      margin: 50px 20px;  }</style>


Далее думал как организовать саму бизнес логику и отделить ее от шаблона. Решил попробовать полностью через Vuex Сам вьюкс не стал дробить, а сделал все в одном файлике. Для уровня LED я использую шкалу от 0 - 100 %, в то время когда на backend сам уровень света устанавливается от 0 - 1024 единиц. Округлив я подумал, что буду просто умножать на 10, когда данные будут уходить POST запросом или делить на 10, когда данные будут приходить GET запросом.


store/index.js


import Vue from 'vue'import Vuex from 'vuex'Vue.use(Vuex)export default new Vuex.Store({    state: {        whiteLED         : 0,        waterTemperature : 0,    },    mutations: {        'SYNC_WHITE_LED' (state, level) {            state.whiteLED = level;        },        'SYNC_WATER_TEMPERATURE' (state, level) {            state.waterTemperature = level;        },        'SET_WHITE_LED' (state, level) {            state.whiteLED = level;        },        'SET_HEATER_LEVEL' (state, level) {            state.waterTemperature = level;        }    },    actions: {        async syncWhiteLED({commit}) {            try {                const response = await Vue.axios.get('/get_led_info?ledName=white');                commit('SYNC_WHITE_LED', response.data['level']/10);            }            catch(error) {                console.error(error);            }        },        async syncWaterTemperature({commit}) {            try {                const response = await Vue.axios.get('/get_water_tmp');                commit('SYNC_WATER_TEMPERATURE', response.data['water_temperature_c']);            }            catch(error) {                console.error(error);            }        },        async setWhiteLED({commit}, level) {            try {                await Vue.axios.get(`/led_controller?impulse=4000&level=${level*10}&ledName=white`);                commit('SET_WHITE_LED', level);            }            catch(error) {                console.error(error);            }        },        async setWaterTemperature({commit}, level) {            try {                await Vue.axios.get(`/heater_control?params=${level}`);                commit('SET_HEATER_LEVEL', level);            }            catch(error) {                console.error(error);            }        },    },    getters: {        whiteLED: state => {            return state.whiteLED;        },        waterTemperature: state => {          return state.waterTemperature;        },    }})


Далее делаю универсальный компонент, где будет отображаться текущее значение, шкала для изменения значения и пару кнопок для синхронизации и собственно изменения.

components/ui/RangeController.vue


<template>    <b-card         :title="header"     >        <b-alert show>            Change to : {{ controllerValue }}                         {{                            name.match(/Water/gi)                             ? 'C\u00B0' : '%'                        }}        </b-alert>        <b-form-input             type="range"            :min="min"            :max="max"            v-model="controllerValue"        />        <b-button             variant="outline-primary"             size="sm"            @click="$emit(`${buttonChangeName}Change`, controllerValue)"        >            {{ changeButton }}        </b-button>        <b-button             class="float-right"            variant="outline-success"             size="sm"            @click="$emit(`${buttonChangeName}Sync`)"        >            Sync value        </b-button>    </b-card></template><script>export default {    props: {        name: {            type    : String,            default : 'Header',        },        value: {            type    : Number,            default : 0,        },        buttonChangeName: {            type    : String,            default : 'Change'        },        min: {            type    : Number,            default : 0        },        max: {            type    : Number,            default : 100        }    },    data() {        return {            controllerValue: this.min,        }    },    computed: {        header() {            const isWater = this.name.match(/Water/gi);            const postfix = isWater ? 'C\u00B0' : '%';            const sufix = isWater ? 'Temperature' : this.name.match(/Pump/gi)? '' : 'LED';            return `${this.name} ${sufix} is : ${this.value} ${postfix}`;        },        changeButton() {            return `${this.buttonChangeName} change`;        },    }}</script>


Ну и наконец сам компонент со списком, я его намеренно сократил, что бы не перегружать кодом статью, плюс наверно где-то нарушил незыблемые правила программирования DRY, тут надо провести рефакторинг кода, но мне нужно было здесь и сейчас, поэтому писал на скорую руку.


components/ListOfRangeControllers.vue


<template>    <b-container class="bv-example-row mt-4 mb-4">      <h1>Backlight</h1>      <b-row>        <b-col v-for="led in leds" :key="led.name">          <range-controller            :name="led.name"            :value="led.value"            :buttonChangeName="led.buttonName"            v-on="{               ledWhiteChange : ledWhiteChange,              ledWhiteSync   : ledWhiteSync,            }"          />        </b-col>      </b-row>      <h1>Temperature</h1>      <b-row>        <b-col>          <range-controller              name="Water"              :value="waterTemperature"              :min="20"              :max="45"              buttonChangeName="temperature"              @temperatureChange="temperatureChange"              @temperatureSync="temperatureSync"          />        </b-col>      </b-row>    </b-container></template><script>import RangeController from './ui/RangeController';import { mapActions, mapGetters } from 'vuex'export default {    components: {        RangeController    },    methods: {        ...mapActions([            'syncWhiteLED',            'syncWaterTemperature',            'setWhiteLED',        ]),        ledWhiteChange(value) {            this.setWhiteLED(value);        },        // не реализовано        temperatureChange(value) {            console.log('temp is changed!' + `${value}`);        },        ledWhiteSync() {            this.syncWhiteLED();        },        async temperatureSync() {            await this.syncWaterTemperature();            console.log(this.waterTemperature);        },    },    computed: {        ...mapGetters([            'waterTemperature',            'whiteLED',        ]),        leds() {            return [                {                    name: 'White',                    value: this.$store.getters.whiteLED,                    buttonName: 'ledWhite',                },            ]        },    },}</script>

На компе


На мобилке



Вот я и получил UI для моего умного аквариума, где я мог получить информацию об освещенности и температуре, и в ручном режиме выставить нужный свет и его интенсивность. Пришло время все это запустить вместе, повесить над аквариумом и проверить. Vue приложение запустил на старом ноуте, лег на кровать и открыл браузер на телефоне чтож верстка немного поехала на небольшом экране, но меня вполне устраивала, я знал, что все это еще будет переделываться и автоматизироваться. Но это была рабочая связка моего устройства на NodeMCU и Vue приложения. Я был рад и горд собой. В голове летали мысли о том, что же будет в конечном итоге, самое страшное для меня было реализация химического анализа воды. Ведь хороший анализ делается путем опускания в воду бумажных палочек, пропитанных определенным химическим составом. От чего она меняет цвет и уже по карте цветов можно определить есть ли каки либо отклонения от нормы. А анализ нужен не один, а именно, анализы на:
  • Аммоний
  • Нитриты
  • Нитраты
  • Фосфаты
  • Кислотно-щелочной баланс (Ph)
  • Карбонатная жесткость (kH)
  • Кальций
  • Магний
  • Силикаты

Пока нахожусь в поиске каких-то решений, поскольку натыкался в магазинах на электронные приборы, которые все это измеряют. Муляж ли это? кто знает. Кто ищет то найдет. Предстоит еще много работы как на стороне моей NodeMCU так и на стороне "Клиента", но я не опускаю рук.


Источник: habr.com
К списку статей
Опубликовано: 27.09.2020 22:21:48
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

Javascript

Vuejs

Ui

Frontend

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru