Mình đã làm bể cá thông minh như thế nào (phần 1)

Xin chào các bạn! Bây giờ đã là cuối tháng 7, đến hẹn lại lên, như thường lệ mình lại ngồi viết một bài viblo để chia sẻ kinh nghiệm cũng như các thành quả đạt được trong tháng vừa qua. Vẫn là chủ đề lập trình như trước nhưng hôm nay sẽ được thêm 1 tấm áo mới để thay đổi không khí, một chủ đề không mới nhưng lại là mới (đối với mình). Đó là chủ đề về IOT.

IOT là gì?

Theo Wikipedia:

Internet Vạn Vật, hay cụ thể hơn là Mạng lưới vạn vật kết nối Internet hoặc là Mạng lưới thiết bị kết nối Internet (tiếng Anh: Internet of Things, viết tắt IoT) là một liên mạng, trong đó các thiết bị, phương tiện vận tải (được gọi là "thiết bị kết nối" và "thiết bị thông minh"), phòng ốc và các trang thiết bị khác được nhúng với điện tử học, phần mềm, cảm biến, cơ cấu chấp hành cùng với khả năng kết nối mạng máy tính giúp cho các thiết bị này có thể thu thập và truyền tải dữ liệu. IoT là một kịch bản của thế giới, khi mà mỗi đồ vật, con người được cung cấp một định danh của riêng mình, và tất cả có khả năng truyền tải, trao đổi thông tin, dữ liệu qua một mạng duy nhất mà không cần đến sự tương tác trực tiếp giữa người với người, hay người với máy tính. IoT đã phát triển từ sự hội tụ của công nghệ không dây, công nghệ vi cơ điện tử và Internet. Nói đơn giản là một tập hợp các thiết bị có khả năng kết nối với nhau, với Internet và với thế giới bên ngoài để thực hiện một công việc nào đó.

Hiểu một cách đơn giản IOT là tất cả các thiết bị có thể kết nối với nhau và con người sẽ có thể kiểm soát mọi đồ vật của mình qua mạng mà chỉ bằng một thiết bị thông minh như điện thoại thông minh, máy tính,...

Trong loạt bài viết này mình giới thiệu với các bạn cách mình đã ứng dụng IOT để cho cuộc sống tốt đẹp hơn như thế nào. Cụ thể ở đây là mình làm một chiếc bể cá thông minh. 😃

Đặt vấn đề

Ở nhà mình có 1 bể cá. Mỗi khi mình về quê là mình lại phải nhờ Anh bạn hàng xóm sang cho ăn hộ. Như vậy thật là bất tiện và cũng mệt cho Anh hàng xóm cứ phải chạy sang cho cá ăn xong lại chạy về. Mình nghĩ ngay ra một ý tưởng làm bể cá thông minh, có thể cho ăn từ xa thông qua internet. Thế là mình tiến hành bắt tay vào tìm hiểu và làm.

Con đường tìm hướng giải quyết

Tìm kiếm thiết bị, công nghệ

Một dự án IOT thì cần phải có cả phần cứng và phần mềm. Và việc lựa chọn phần cứng phù hợp và phần mềm có thể chạy được trên phần cứng đó là cực kì khó khăn.

Lúc đầu mình định sử dụng raspberry pi 3 để làm phần điều khiển.

Tuy nhiên, giá thành raspberry pi 3 khá cao và dùng nó chỉ để điều khiển 1 thiết bị cho cá ăn thì hơi lãng phí. Và dự định của mình là sẽ điều khiển một cái cái bóng đèn ở chỗ khác nữa. Như vậy sẽ phải lắp nhiều dây nếu dùng Pi3.

Sau vài ngày lang thang trên mạng mình đã phát hiện ra một em rất nhỏ gọn có kết nối được wifi và phù hợp với dự án của mình. Em mang tên Wemos sử dụng chíp esp8266

Về mặt ưu điểm thì có thể kể ra như sau:

  • Nhỏ chỉ bằng ngón chân cái, có thể lắp vào bất cứ khe nào dù là hẹp nhất
  • Kết nối wifi
  • Lập trình C/C++ dễ dàng
  • Có nhiều thư viện hỗ trợ
  • Giá thành rẻ chỉ khoảng 4$ cho 1 em

Mình sẽ lắp 2 em wemos, 1 để điều khiển đèn, 1 để điều khiển thiết bị cho cá ăn. Quá tiết kiệm cho 1 dự án IOT. 😃 😃

Vậy đã xong phần tìm phần cứng. Tiếp đến là tìm phần mềm và công nghệ phù hợp với em phần cứng đó. Để server điều khiển được thì cần phải chọn kết nối có tính thời gian thực. Mình đã lựa chọn socket.io để kết nối giữa wemos và server. Còn 1 phương thức nữa có thể lựa chọn là MQTT. Tuy nhiên mình chưa hiểu rõ về phương thức này nên không chọn. Mình nên chọn cái gì mà mình có thế mạnh để làm, đúng không nào.

Lập sơ đồ

Mình lập sơ đồ hệ thống như sau Người dùng sẽ có thể điều khiển bóng đèn và thiết bị cho cá ăn thông qua internet.

Lập trình phần mềm

Chúng ta cần phải lập trình 2 phần mềm: một phần mềm chạy trên server, một phần mềm chạy trên wemos. Như lúc trước mình đã nói phương thức để kết nối giữa server và wemos là socket:

Phần lập trình và điều khiển thiết bị của wemos sẽ khá khó hiểu cho người mới bắt đầu. Và mình cũng khá khó khăn để hiểu nó. Và còn một số kết nối phần cứng liên quan đến mạch điện. Vậy nên mình sẽ dành việc này cho bài viết sau.

Chúng ta sẽ tiếp tục với việc lập trình server.

Tạo nodejs project

Cấu trúc thư mục của server như sau: Tạo file package.json với nội dung như sau:

{
  "scripts": {
    "start": "npm run prod && DEBUG=* node server.js",
    "build": "./node_modules/.bin/webpack --progress --colors --config webpack.config.js",
    "watch": "./node_modules/.bin/webpack --progress --colors --progress --watch --config webpack.config.js",
    "prod": "./node_modules/.bin/webpack -p --colors --progress --hide-modules --config webpack.config.js"
  },
  "dependencies": {
    "babel-core": "^6.25.0",
    "babel-loader": "^7.1.1",
    "babel-preset-es2015": "^6.24.1",
    "bootstrap-sass": "^3.3.7",
    "bootstrap-toggle": "^2.2.2",
    "colors": "^1.1.2",
    "css-loader": "^0.28.4",
    "dotenv": "^4.0.0",
    "express": "^4.15.3",
    "file-loader": "^0.11.2",
    "font-awesome": "^4.7.0",
    "jquery": "^3.2.1",
    "node-sass": "^4.5.3",
    "sass-loader": "^6.0.6",
    "socket.io": "^1.7.4",
    "spectrum-colorpicker": "^1.8.0",
    "style-loader": "^0.18.2",
    "webpack": "^3.2.0",
    "webpack-config": "^7.0.0"
  }
}

Chạy command npm install

Mình sẽ giải thích các package 1 chút:

  • Dùng "spectrum-colorpicker" để chọn màu điều khiển đèn. (đèn của mình điều khiển là đèn full color - có 3 màu cơ bản và mỗi màu 8bit)
  • Dùng "jquery" để bắt sự kiện click vào button. Khi click vào thì emit event vào socket.
  • Dùng Giao diện 'bootstrap"
  • Dùng "webpack" để build code js và css client

Tạo file webpack.config.js

var path = require('path');

module.exports = {
    context: path.resolve(__dirname, './src'),
    entry: {
        app: './app.js',
    },
    output: {
        path: path.resolve(__dirname, './public/js'),
        filename: '[name].bundle.js',
    },
    module: {
        rules: [
            {
                test: /\.js$/,
                exclude: [/node_modules/],
                use: [
                    {
                        loader: 'babel-loader',
                        options: { presets: ['es2015'] },
                    }
                ],
            },
            {
                test: /\.css$/,
                use: [
                    'style-loader',          {
                    loader: 'css-loader',
                        options: {
                            modules: true
                        },
                    },
                ]
            },
            {
                test: /\.(png|svg|jpg|gif)$/,
                use: [
                    'file-loader'
                ]
            },
            {
                test: /\.(woff|woff2|eot|ttf|otf)$/,
                use: [
                    'file-loader'
                ]
            },
            {
                test: /\.scss$/,
                use: [
                    {
                        loader: "style-loader" // creates style nodes from JS strings
                    },
                    {
                        loader: "css-loader" // translates CSS into CommonJS
                    },
                    {
                        loader: "sass-loader" // compiles Sass to CSS
                    }
                ]
            }
        ]
    },
    resolve: {
        modules: [path.resolve(__dirname, './src'), 'node_modules']
    },
    node: {
        fs: "empty"
    }
};

Server có nhiệm vụ chuyển tiếp request và response giữa người dùng ở xa và wemos để điều khiển thiết bị.

Tạo file server.js

require('dotenv').config();
var express = require('express');
var app = express();
var server = require('http').createServer(app);
var io = require('socket.io')(server);
var colors = require('colors');
var port = process.env.PORT || 3000;

server.listen(port, function () {
    console.log('Server listening at port %d', port);
});

app.use(express.static(__dirname + '/public'));

var ledRoom = 'led-room';
var userRoom = 'user-room';
var ledStatus = 0;
var ledColor = {
    r: 255,
    g: 255,
    b: 255,
    a: 1,
};

io.on('connection', function (socket) {
    console.log('New client connect'.gray);
    socket.on('room', function(room) {
        console.log('join room');
        socket.join(room);
    });
    
    // Khi nhận được sự kiện cho cá ăn thì chuyển tiếp cho client khác.
    // Nếu wemos điều khiển thiết bị cho cá ăn bắt được sự kiện này. Nó sẽ lập tức cho ăn.
    socket.on('feed-the-fish', function(data) {
            socket.broadcast.emit('feed-the-fish', data);
    });

     // Khi nhận được sự kiện đổi trạng thái của led thì chuyển tiếp cho client khác.
     // Nếu wemos điều khiển đèn bắt được sự kiện này. Nó sẽ lập tức thay đổi màu, trạng thái của bóng đèn
    socket.on('led-change', function(data) {
        if (typeof data.status == 'undefined') {
            data.status = ledStatus;
        } else {
            ledStatus = data.status;
        }

        if (typeof data.color == 'undefined') {
            data.color = ledColor;
        } else {
            ledColor = data.color;
        }

        socket.broadcast.emit('led-change', data);

        console.log('Change led status'.gray);
        console.log('--status: '.gray + data.status);
        console.log('--color: '.gray);
        console.log(('----r: ' + data.color.r).red);
        console.log(('----g: ' + data.color.g).green);
        console.log(('----b: ' + data.color.b).blue);
        console.log(('----a: ' + data.color.a).white);
    });

    // Đèn gửi trạng thái hiện tại của nó qua sự kiện led-status
    // Nếu server nhận được nó sẽ broadcast cho các client khác
    // Đến khi web client nhận được event này thì nó sẽ cập nhật màu hiển thị cho cái led giả trên web
    socket.on('led-status', function(data) {
        if (typeof data.status == 'undefined') {
            data.status = ledStatus;
        } else {
            ledStatus = data.status;
        }

        if (typeof data.color == 'undefined') {
            data.color = ledColor;
        } else {
            ledColor = data.color;
        }

        console.log('Led status');
        console.log(data);

        socket.broadcast.emit('led-status', data);
    });

    socket.on('get-led-status', function() {
        var data = {
            status: ledStatus,
            color: ledColor
        };
        socket.emit('led-status', data);
    });

    socket.on('disconnect', function () {
        console.log('Client disconnect'.gray);
    });
});

Web Client:

import './sass/app.scss';
require('./bootstrap');

let socket = io();
let ledStatus = 0;
let send = true;
let ledColor = {
    r: 255,
    g: 255,
    b: 255,
    a: 1,
};

// Khi web client nhận được event led-status nó sẽ cập nhật hiệu ứng của đèn giả đang hiển thị trên web
socket.on('led-status', function(data) {
    console.log('led-status');
    console.log(data);
    ledStatus = data.status;
    ledColor = data.color;
    console.log(111111);
    $('#led').css('background-color', 'rgba(' + ledColor.r + ', ' + ledColor.g + ', ' + ledColor.b + ', ' + ledColor.a + ')');
    if (data.status) {
        console.log(ledStatus);
        console.log('ledStatus true');
        $('#led').css('box-shadow', '10px 10px 5px #888888');
    } else {
        console.log(ledStatus);
        console.log('ledStatus false');
        console.log(ledStatus);
        $('#led').css('box-shadow', 'none');
    }
});

// Ban đầu khi kết nối, web client sẽ gửi sự kiện để lấy status của led hiện tại
socket.emit('get-led-status');

// Khi bấm nút bật tắt đèn
$('#button').change(function (event) {
    var data;
    if ($(this).prop('checked')) {
        data = {status: 1};
    } else {
        data = {status: 0};
    }

    socket.emit('led-change', data);
});

// Khi bấm nút cho cá ăn
$('#feed-button').change(function (event) {
    socket.emit('feed-the-fish', 'data');
});

$('#colorpicker').spectrum({
    color: '#f00',
    flat: true,
    width: '500px',
    showInput: true,
    allowEmpty: false,
    showAlpha: true,
    showButtons: false,
    showInitial: true,
    preferredFormat: 'rgb',
    move: function(color) {
        if (send) {
            send = false;
            setTimeout(function(){send = true;}, 100);
            // Khi thay đổi màu trên bảng màu thì emit event thay đổi màu của led
            socket.emit('led-change', {
                color: color.toRgb()
            });
        }
    }
})

Về cơ bản server và web chúng ta sẽ code như vậy. Các bạn có thể tham khảo code của mình tại Đây Do code trên git của mình là code demo của điều khiển led realtime nên sẽ thiếu mất phần cho cá ăn. 😃 😃 Bài viết đã khá dài, mình xin tạm dừng tại đây.

Đây là video demo sản phẩm của mình

Nếu có thắc mắc gì hãy để lại comment bên dưới.

Phần tiếp theo sẽ giới thiệu với các bạn cách sử dụng một kit phát triển (phần cứng có thể lập trình) https://viblo.asia/p/minh-da-lam-be-ca-thong-minh-nhu-the-nao-gioi-thieu-phan-cung-wemos-phan-2-RQqKLnmml7z

Cảm ơn các bạn đã đọc bài viết của mình! 😃)))


All Rights Reserved