[JavaScript, VueJS] Как я умный аквариум делал (frontend)
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Пролог
Как я рассказывал тут, я начал постройку умного аквариума на основе платы NodeMCU. На ней я использовал прошивку с micropython, поднял веб сервер и сделал API для манипуляции всеми периферийными устройствами и датчиками. Поскольку мой вариант умного аквариума изначально планировался как автономный, я хотел сделать некий UI для отслеживания всех процессов ну и для ручных корректировок. Каждый раз обращаться по роутам типа: http://192.168.1.70/led_controller?impulse=4000&...mp;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
===========
Похожие новости:
- [Git] Git GUI моей мечты
- [Python, API, Разработка под Arduino] Как я умный аквариум делал (backend)
- [JavaScript, Node.JS, Финансы в IT] Принимаем криптовалютные платежи с Coinbase Commerce
- [JavaScript, Развитие стартапа, Разработка веб-сайтов] Как быстро создать Bootstrap-сайт для бизнеса: 6 полезных инструментов
- [Разработка под iOS, Swift] Устройство UI в iOS
- В Buildroot приняты патчи для поддержки мейнфреймов IBM Z (S/390)
- [Google Chrome, JavaScript, Софт] Используем Chrome DevTools профессионально (перевод)
- [JavaScript, Программирование] Compose повсюду: композиция функций в JavaScript (перевод)
- [Open source, Openshift, Виртуализация, Учебный процесс в IT] Шпаргалка по Ansible k8s, практичный учебник по awk, а также 4 причины использовать Jamstack при веб-разработке
- [Python, Node.JS, Машинное обучение] Machine learning in browser: ways to cook up a model
Теги для поиска: #_javascript, #_vuejs, #_ui, #_vuejs, #_frontend, #_javascript, #_vuejs
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 21:34
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Пролог Как я рассказывал тут, я начал постройку умного аквариума на основе платы NodeMCU. На ней я использовал прошивку с micropython, поднял веб сервер и сделал API для манипуляции всеми периферийными устройствами и датчиками. Поскольку мой вариант умного аквариума изначально планировался как автономный, я хотел сделать некий UI для отслеживания всех процессов ну и для ручных корректировок. Каждый раз обращаться по роутам типа: http://192.168.1.70/led_controller?impulse=4000&...mp;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 Создаем новый проект, добавляем туда все необходимые плагины:
Для 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 приложения. Я был рад и горд собой. В голове летали мысли о том, что же будет в конечном итоге, самое страшное для меня было реализация химического анализа воды. Ведь хороший анализ делается путем опускания в воду бумажных палочек, пропитанных определенным химическим составом. От чего она меняет цвет и уже по карте цветов можно определить есть ли каки либо отклонения от нормы. А анализ нужен не один, а именно, анализы на:
Пока нахожусь в поиске каких-то решений, поскольку натыкался в магазинах на электронные приборы, которые все это измеряют. Муляж ли это? кто знает. Кто ищет — то найдет. Предстоит еще много работы как на стороне моей NodeMCU так и на стороне "Клиента", но я не опускаю рук. =========== Источник: habr.com =========== Похожие новости:
|
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 21:34
Часовой пояс: UTC + 5