1/12/16

Quản lý và giám sát các thiết bị trong gia đình từ xa(IoT) - Phần 4

Nối tiếp phần 3, phần 4 này sẽ là phần cuối cùng cũng là phần thú vị nhất. Ở phần này mình sẽ hướng dẩn cách xây dựng một virtual device, cách cài đặt server và deploy webservice của chúng ta, cách truy cập từ internet vô webservice và cuối cùng là video demo sản phẩm. Cùng bắt tay vào làm thôi nào!

Xây dựng virtual device

Mục đích của phần này là để kiểm tra hoạt động của server và demo sản phẩm, vì phần thiết bị bên dưới mình không làm nên phải làm phần này thay thế :|
Vì mục đích chính của phần này là để phục vụ công việc demo nên các bạn có thể bỏ qua nếu không muốn biết chi tiết xử lý bên trong.
Đầu tiên tạo 1 java project mới với tên là VirtualClient, ta thêm shared project(project ở phần 1) vào build path của VirtualClient.
Như thế ta có thể sử dụng được các lớp đã khai báo bên trong shared project.
Bước tiếp theo ta định nghĩa 1 thiết bị ảo(client), tạo 1 lớp mới tên Client. Nhiệm vụ của lớp này là giả lập 1 thiết bị ảo, nó sẽ tự động kết nối đến server và lắng nghe các yêu cầu từ server đó đồng thời tự động phản hồi lại các dữ liệu mà server yêu cầu. Các giá trị energy, power, voltageamperage đều được sinh ngẫu nhiên trong 1 miền giới hạn nhất định. Riêng giá trị state của thiết bị sẽ được lưu trữ chứ không sinh ngẫu nhiên như các giá trị khác.
// Phương thức chính sẽ được thực thi ở 1 background thread
public class Client implements Runnable {
    // socket để kết nối và giao tiếp với server
    private final Socket socket;
    // lớp mô tả giao thức liên lạc giữa client và server, chú ý là
    // lớp này và lớp bên server ở phần 1 là 1 :|
    private final Protocol protocol;
    // id của client cũng là device id
    private final int id;
    // cờ xác định có đang yêu cầu stop không
    private boolean pendingStop = false;
    // dùng để sinh giá trị ngẫu nhiên :|
    private Random rand = new Random(System.currentTimeMillis());
    // lưu tình trạng thiết bị hiện tại, mặt định là OFF
    private byte state = (byte) 0;
    // khi tình trạng thiết bị thay đổi!
    private OnStateChangedListener mOnStateChangedListener = null;

    public void setOnStateChangedListener(OnStateChangedListener listener) {
        mOnStateChangedListener = listener;
    }

    private void fireOnStateChangedListener() {
        if (mOnStateChangedListener != null)
            mOnStateChangedListener.stateChanged(this);
    }

    public int getId() {
        return id;
    }

    public byte getState() {
        return state;
    }

    public void setState(byte state) {
        if (state != this.state) {
            this.state = state;
            fireOnStateChangedListener();
        }
    }

    private void log(String st) {
        System.out.println(String.format("[%d] %s", id, st));
    }
    // khởi tạo 1 client bao gồm socket kết nối và id của thiết bị
    // mà nó giả lập
    public Client(Socket socket, int id) {
        this.socket = socket;
        InputStream in = null;
        OutputStream out = null;
        try {
            in = socket.getInputStream();
            out = socket.getOutputStream();
        } catch (IOException e) {
            e.printStackTrace();
        }
        protocol = new Protocol(in, out, 10000);
        this.id = id;
    }
    // gửi dữ liệu cho server, dữ liệu sẽ được bao gói trong package
    // trước khi gửi đi
    private boolean response(byte[] actualData, byte type) {
        byte[] data = new byte[1 + actualData.length];
        data[0] = type;
        System.arraycopy(actualData, 0, data, 1, actualData.length);
        return protocol.writeData(data);
    }
    // sinh giá trị điện năng ngẫu nhiên và gửi cho server
    private boolean responseEnergy() {
        int energy = rand.nextInt(90000) + 7000;// 7000 -> 7000 + 90000
        log(String.format("Response energy %d", energy));
        return response(Convert.integerToBytes(energy), Protocol.TYPE_ENERGY);
    }
    // gửi id cho server
    private boolean responseId() {
        log(String.format("Response id %d", id));
        return response(Convert.integerToBytes(id), Protocol.TYPE_ID);
    }
    // sinh giá trị power, voltage và amperage ngẫu nhiên và gửi cho server
    private boolean responseRealtime() {
        byte[] actualData = new byte[11];
        short vol = (short) (rand.nextInt(200) + 100);
        int am = rand.nextInt(5000) + 45000;
        int pw = rand.nextInt(3000) + 12000;
        int offset = 0;
        System.arraycopy(Convert.integerToBytes(pw), 0, actualData, offset, 4);
        offset += 4;
        System.arraycopy(Convert.shortToBytes(vol), 0, actualData, offset, 2);
        offset += 2;
        System.arraycopy(Convert.integerToBytes(am), 0, actualData, offset, 4);
        offset += 4;
        actualData[offset] = state;
        log(String.format("Response real-time PW = %d, VOL = %d, AM = %d, %s", pw, vol, am, state != 0 ? "ON" : "OFF"));
        return response(actualData, Protocol.TYPE_REALTIME);
    }
    // bật tắt thiết bị, ở đây ta chỉ cần thay đổi giá trị state
    private void turn(boolean off) {
        if (off) {
            log("Turn OFF device....");
            state = (byte) 0;
        } else {
            log("Turn ON device....");
            state = (byte) 1;
        }
        fireOnStateChangedListener();
    }
    // xử lý dữ liệu yêu cầu từ server
    private boolean processData(byte[] data) {
        byte type = data[0];
        switch (type) {
        case Protocol.TYPE_ENERGY:
            return responseEnergy();
        case Protocol.TYPE_ID:
            return responseId();
        case Protocol.TYPE_REALTIME:
            //state = (byte) (rand.nextInt() % 2);
            return responseRealtime();
        case Protocol.TYPE_TURN_OFF:
            turn(true);
            return true;
        case Protocol.TYPE_TURN_ON:
            turn(false);
            return true;
        default:
            return false;
        }
    }
    // quá trình đợi nhận và gửi dữ liệu được thực hiện ở background thread
    @Override
    public void run() {
        log("Started");
        while (!pendingStop) {
            if (!protocol.ready()) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    break;
                }
                continue;
            }
            byte[] data = protocol.readData();
            if (data == null)
                break;
            if (!processData(data))
                break;
        }
        if (!pendingStop) 
            log("Corrupt :|");
        log("Stopped!");
    }

    public void dispose() {
        pendingStop = true;
        protocol.dispose();
        try {
            socket.close();
        } catch (IOException e) {
        }
    }

    public interface OnStateChangedListener {
        void stateChanged(Client client);
    }
}
Tiếp theo ta xây dựng giao diện của VirtualClient, phần giao diện sẽ hiển thị các nút bấm mỗi nút bấm tương ứng với 1 thiết bị ảo(Client) với background color tương ứng với trạng thái của thiết bị. Người dùng có thể bấm bám nút bấm để thay đổi trạng thái hoạt động của thiết bị(ON/OFF).
Tạo class mới là ClientUI với nội dung như sau:
public class ClientUI extends JFrame implements ActionListener, OnStateChangedListener {
    private static final long serialVersionUID = 1L;
    // màu hiển thị khi thiết bị đang hoạt động
    private final Color onColor = Color.GREEN;
    // màu hiển thị khi thiết đã bị tắt
    private final Color offColor = Color.GRAY;
    // panel chứa các nút bấm
    private JPanel panel;
    // danh sách các client hiện tại
    private Client[] clients;

    public ClientUI(Client[] clients) {
        this.clients = clients;
        this.panel = new JPanel(new FlowLayout(FlowLayout.CENTER));
        // khởi tạo các nút bấm tương ứng với các client trong danh sách và add vào panel
        for (Client client : clients) {
            JButton btn = new JButton(String.format("%d", client.getId()));
            btn.addActionListener(this);
            btn.setBackground(offColor);
            panel.add(btn);
            client.setOnStateChangedListener(this);
        }
        setMaximizedBounds(new Rectangle(500, 3000));
        add(panel);
        pack();// tự động điều chỉnh kích thước frame khớp với content :|
    }
    // khi 1 nút bấm được nhất ta kiểm tra xem nút đó tương ứng vơi client nào và từ đó thay đổi trạng thái của client đó.
    @Override
    public void actionPerformed(ActionEvent e) {
        JButton btn = (JButton) e.getSource();
        int id = Convert.parseInt(btn.getText(), -1);
        for (Client client : clients) {
            if (client.getId() == id) {
                client.setState((byte) (client.getState() != 0 ? 0 : 1));
                break;
            }
        }
    }
    // khi trạng thái của client thay đổi(do người dùng nhấn nút hoặc do app điều khiển thì cập nhật màu lại giao diện
    @Override
    public void stateChanged(Client client) {
        Component[] cmps = panel.getComponents();
        for (Component cmp : cmps) {
            JButton btn = (JButton) cmp;
            int id = Convert.parseInt(btn.getText(), -1);
            if (id == client.getId()) {
                cmp.setBackground(client.getState() != 0 ? onColor : offColor);
                break;
            }
        }
    }

}
Tiếp theo tạo lớp Program có nhiệm vụ khởi tạo các client và là entry của chương trình.
public class Program {
    // danh sách các client
    private Client[] clients;
    // địa chỉ server
    private String addr;
    // port của server
    private int port;
    // khởi tạo 1 socket và kết nối tới server
    private Socket genSocket(String addr, int port) {
        try {
            Socket socket = new Socket(addr, port);
            return socket;
        } catch (UnknownHostException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
    // yêu cầu người dùng nhập thông tin của server
    private void enterInfo() {
        Scanner scanner = new Scanner(System.in);
        System.out.println("Server address: ");
        addr = scanner.nextLine();
        System.out.println("Server port: ");
        port = scanner.nextInt();
        scanner.close();
    }
    // khởi tạo các client và hiển thị ra giao diện, các client này đều được chạy trong các background thread khác nhau.
    private void start(int count) {
        System.out.println(String.format("Creating %d client and connect to %s:%d", count, addr, port));
        clients = new Client[count];
        for (int i = 0; i < clients.length; i++) {
            Socket socket = genSocket(addr, port);
            Client client = new Client(socket, 102120250 + i);// setup id here!
            new Thread(client).start();
            clients[i] = client;
        }
        ClientUI ui = new ClientUI(clients);
        ui.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        ui.addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent e) {
                for (Client client : clients) {
                    client.dispose();
                }
            }
        });
        ui.setVisible(true);
    }

    private Program(int count) {
        enterInfo();
        start(count);
    }

    public static void main(String[] args) {
        new Program(3);
    }
}
Ở ví trụ trên mình khởi tạo mặt định 3 thiết bị, các bạn có thể tạo bao nhiêu tùy ý, chỉ cần thay đổi đối số hàm khởi tạo của Program là được.
Như thế là thiết bị ảo của chúng ta đã hoàn tất, nó có thể giả lập được rất nhiều thiết bị cùng lúc.
Bước tiếp theo là export project ra file thực thi, các bạn có thể chạy ngay trên eclipse.
Click chuột phải vào VirtualClient project và chọn export sau đó tại mục filter các bạn nhập vào là jar tiếp theo chọn jar runable rồi next, tại mục tiếp theo chọn vị trí lưu file và next cho đến hết. Cuối cùng các bạn sẽ được 1 file jar(vc.jar) như hình có thể thực thi mà không cần eclipse.
Để chạy thì các bạn mở terminal(Ctrl + Alt+ T) đối với linux hoặc command prompt(Win + R sau đó nhập cmd rồi enter), tiếp theo nhập đường dẩn của file vc.jar sau đó enter cho nó chạy thôi :|

Cài đặt server và deploy web service

Tạo file war cho webservice: các bạn click chuột phải vào webservice project và chọn export sau đó chọn WAR file, cửa sổ mới hiện lên các bạn chọn nơi lưu và nhấn finish.
Nếu các bạn chỉnh chế độ xem là Java EE thì mới thấy tùy chọn này, nếu để chế độ xem là Java thì khi chọn export cửa sổ mới sẽ hiện lên, nhập vào ô filter là “war file” và chọn war file ở bên dưới sau đó nhấn next và làm tương tự bước trên.
Chế độ xem Java EE
Chọn War file từ của sổ Export
Tiếp theo mình sẻ hướng dẩn các bạn cách cài đặt tomcat server trên hệ điều hành chính là ubuntu(linux), với windows các bạn có thể tìm hiểu thêm trên google.com.
Bên ubuntu các bạn làm theo các bước sau:
Bước 1: cài đặt tomcat8, mở terminal và chạy lần lược các lệnh sau
sudo apt-get update
sudo apt-get install tomcat8
sudo apt-get install tomcat8-docs tomcat8-admin tomcat8-examples
Bước 2: cấu hình tomcat8
Ở bước này các bạn sẽ tạo 1 tài khoản người dùng để có thể truy cập vào trang quản lý của tomcat, các bạn mở terminal(Ctrl + Alt + T) và chạy lệnh sau để chỉnh sửa file tomcat-users.xml với quyền root:
sudo gedit /etc/tomcat8/tomcat-users.xml
Sửa lại với nội dung như sau:
<tomcat-users>
<user name="admin" password="admin123" roles="admin-gui,manager-gui,manager-script" />
</tomcat-users>
Ở username và password các bạn có thể thay đổi tên đăng nhập và mật khẩu của mình, ở đây mình để tên đăng nhập là admin và mật khẩu là admin123.
Sau khi chỉnh sửa nội dung file tomcat-users.xml các bạn khởi động lại dịch vụ của tomcat bằng câu lệnh sau(chạy trong termial):
sudo service tomcat8 restart
Bước 3: Deploy war file
Các bạn mở trình duyệt lên và nhập vào đường dẩn như sau: http://localhost:8080/manager/html
Các bạn nhập username và password ở bước 2 để đăng nhập vào trang này nhé.
Sau khi nhập đúng username và password thì nó sẽ chuyển đến trang quản lý của tomcat với giao diện như hình:
Tiếp theo các bạn kéo xuống mục WAR file to deploy sau nhấn vào nút chọn file để chọn file war mà chúng ta đã export lúc nảy.
Tiếp theo là nhấn nút deploy để deploy file war thôi. Hình bên dưới cho thấy file war đã deploy thành công và đang chạy trên tomcat server.
Bước 4: cấu hình webservice
Như đã biết ở phần 1, webservice của chúng ta có 1 file cấu hình riêng để thiết đặt các thông tin như ip, port hay superuser…Bây giờ là lúc để tinh chỉnh file đó.
Bây giờ các bạn mở file config với quyền root bằng cách chạy lệnh sau trong terminal:
sudo gedit /var/lib/tomcat8/webapps/MyWS/config.in
Chú ý rằng vị trí của file cấu hình có thể ở nơi khác, điều này tùy thuộc vào việc bạn thiết đặt giá trị của WORKING_DIR.
Cửa sổ gedit hiện ra, bây giờ ta có thể thay đổi các thông tin cấu hình của webservice cho phù hợp ví dụ như địa chỉ server, port hay tài khoản superuser…Ở đây mình thay đổi như sau:
dbname=local.db
address=192.168.1.111
port=2512
timeout=10000
relay-get-realtime=2000
relay-get-energy=10000
su-username=admin
su-password=admin123
Sau khi thay đổi bạn lưu lại rồi restart lại tomcat service bằng lệnh:
sudo service tomcat8 restart
Bây giờ webservice sẽ nạp lại cấu hình mới từ file.
Bên Windows các bạn làm theo hướng dẩn tại http://doraprojects.net/blog/?p=1109 hoặc từ google.com :|
  1. Chú ý rằng tomcat chạy dựa vào máy ảo java vì thế máy phải cài jre hoặc jdk trước. Cài cách nào thì google.com sẽ trả lời :|
  2. Đối với Paspberry Pi2 thì còn tùy thuộc vào hệ điều hành đang chạy trên em nó. Theo mặt định thì nó sẽ chạy Raspbian(cũng là 1 nhánh của linux) vì thế các bạn có thể làm theo hướng dẩn của ubuntu nhưng chú ý là mặt định trên Raspbian thì không có gedit đâu nhé :| vì thế nên các bạn có thể dùng trình chỉnh sửa text khác để thay thế gedit.

Truy cập từ ngoài internet vào webservice

Các bạn đăng nhập vào trang https://www.noip.com và tạo 1 tài khoản, bước này quá đơn giản nên mình không nói cụ thể.
Sau khi có tài khoản các bạn vào mục Add Host như trong hình.
Nhập hostname vào mục hostname, chú ý rằng hostname nên đặt cho dể nhớ 1 tí để lát còn dùng đến :|, các mục khác cứ để mặt định như hình. Sau khi nhập xong thì nhấn vào Add Host
Kết quả chúng ta được như thế này:
Đừng quan tâm đến địa chỉ IP trong hình, nó chỉ là địa chỉ tạm thời thôi :| chú ý cái hostname(trong hình là sontx.no-ip.org).
Bây giờ là lúc động đến router, trong ví dụ bên dưới mình sử dụng Router TL-WR841N của TP-LINK. Các loại router khác các bạn có thể tham khảo tại trang chủ của nó.
Bước 1 đăng nhập vào trang quản lý của router bằng cách kết nối máy tính với router(kết nối wifi hoặc cắm dây), bước này hầu như không cần làm vì thiết bị của bạn chắc đã kết nối sẳn với router để đi ra internet rồi nhỉ :|
Tiếp theo nhập địa chỉ 192.168.1.1(địa chỉ mặt định của router), cửa sổ login yêu cầu nhập mật khẩu và username cua admin(cái này thì bạn phải có quyền sinh tử với con router thì mới có thông tin này được). Sau khi login thì giao diện quản lý nó trông thế này.

Chọn mục Dynamic DNS(phía bên trái á) và nhập thông tin tài khoản đăng nhập của No-IP tại mục username và password, tại mục Service Provider chọn No-IP. Tại mục Domain Name nhập vào hostname đã tạo ở bước trước. Sau đó nhấn vào Login và đợi 1 lát để nó kết nối đến No-IP, sau khi thành công sẽ hiển thị thế này:
OK! nhấn Save để lưu lại.
Tiếp theo vào mục Forwarding, click vào nút “Add New…” và điền thông tin như trong hình, chú ý mục “IP Address” bạn nhập địa chỉ IP của máy tính sẽ chạy webservice của chúng ta.
OK! lưu lại thôi.

Như thế là ta có thể truy cập vào webservice từ internet rồi :)) quá dể phải không nào!

Chốt

Sau bao khó khăn và giang khó cuối cùng ta đã hoàn tất được webservice, app và virtual devices, đồng thời biết được cách cấu hình cơ bản cho tomcat server và cấu hình router để truy cập vào máy tính cá nhân từ internet. Sau đây là video demo thành quả :))
Trong video này mình chia làm 2 phần: phần đầu là điều khiển qua mạng LAN(mình sử dụng máy ảo genymotion để demo), phần 2 là mình chạy trực tiếp trên máy thật có kết nối 3G để kiểm tra khả năng điều khiển từ internet.
Khi điều khiển qua LAN thì các bạn nhập IP của máy tính đang chạy webservice, nếu điều khiển qua internet thì thay vì nhập IP ta sẽ nhập hostname đã cấu hình trong router(trong video này mình nhập là sontx.no-ip.org)

Mọi source code của project này được cập nhật tại github: https://github.com/xuanson33bk/iot-client-server

References

[1]. https://www.digitalocean.com/community/tutorials/how-to-install-apache-tomcat-7-on-ubuntu-14-04-via-apt-get
[2]. http://www.tp-link.vn/faq-419.html

0 nhận xét :

Post a Comment