Javascript is required
动手实现自己的物联

阅览

UI

ESP8266

实践

涉及三个角色,小灯、服务器、APP。思路App 发送指令是开还是关,小灯循环Get 数据。服务器用来管理指令。

涉及的技术栈

  1. SpringBoot (服务)
    • JSON序列化
  2. Flutter (APP)
    • DIO数据请求
  3. ESP8266(WIFI)
    • HTTP Client
  4. Linux (服务器)

服务器

  1. 新建Model类
  2. 创建Controller控制器

maven依赖

<dependencies>
    <!--Thymeleaf-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    <!--Web-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!--Lombok-->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <!--热部署-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <optional>true</optional>
    </dependency>
    <!--Json-->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.49</version>
    </dependency>
    <!--Test-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>
<!--Build-->
<build>
    <finalName>arduino</finalName>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

简单起见,创建最基本的开关状态。 这里使用了Lombok插件 可以自动添加get set 方法


import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@AllArgsConstructor
@NoArgsConstructor
@Data
public class Light {
    private int flag; // 状态
    private String message; // 响应消息
}

控制层

注意

这里有一个坑,使用SpringBoot默认@RestController会使Arduino接收莫名的参数,导致我提取不出来传输数据。所以使用fastJson

@Controller
@RequestMapping("light")
public class IndexController {

    Light light; // 创建对象


    @ResponseBody
    @RequestMapping("set")
    public String setData(@RequestParam("flag") String flag){
        light = new Light();
        if(flag.equals("on")){
            light.setFlag(1);
            light.setMessage("success");
        }else if(flag.equals("off")){
            light.setMessage("success");
            light.setFlag(0);
        }else{
            light.setMessage("error");
            light.setFlag(0);
        }
        return "{\"isOk\": \"success\"}";
    }

    @ResponseBody
    @RequestMapping("get")
    public String getData(HttpServletResponse response, HttpServletRequest request){
        response.setHeader("Author","Linis");
         if(light==null){
            light = new Light();
            light.setFlag(0);
            light.setMessage("success");
        }else{
            System.out.println(light);
        }
        String res = JSON.toJSONString(light);
        return res;
    }
}

当访问host/light/set?flag=on 或者host/light/set?flag=off 会致light对象的flag

然后访问host/light/get 获取light对象。

然后打包成jar包上传到Linux服务器上

[root@localhost Arduino]# nohup java -jar arduino.jar &

上面的意思是不挂断运行命令,在后台执行jar文件

APP

使用Flutter开发APP

加入依赖pubspec.yaml

dependencies:
  flutter:
    sdk: flutter
  cupertino_icons: ^0.1.2
   # HTTP
  dio: 3.0.0

UI

没有封装HTTP Server。直接在里面写了…

import 'dart:io';
import 'dart:ui';
import "package:dio/dio.dart";
import "dart:convert";
import "package:flutter/material.dart";
import 'package:flutter/services.dart';

class OnOffSwitch extends StatefulWidget {
  OnOffSwitch({Key key}) : super(key: key);

  @override
  _OnOffSwitchState createState() => _OnOffSwitchState();
}

class _OnOffSwitchState extends State<OnOffSwitch> {
  double screenHeight = 0;
  double screenWidth;
  double btnStartY = 360.0;
  String lightStatus = "关";

  @override
  void initState() {
    super.initState();
    SystemChrome.setEnabledSystemUIOverlays([]); // 隐藏StatusBar
  }

  Future postData(String key) async{
    String url = "http://112.124.30.55:9001/light/set";
     try {
      Response response;
      Dio dio = new Dio();
      response = await dio.get(url, queryParameters: {"flag":key});
      print(response.data);
    } catch (e) {
      print(e);
    }
  }
  

  // 颜色过渡处理
  Color getBaColors() {
    // black->white
    return Color.lerp(Color.fromRGBO(43, 43, 40, 0.5), Color(0xffFFD80A),
        (btnStartY - 360.0) / 120.0);
  }

  Color getTextStyle() {
    return Color.lerp(Colors.white, Colors.black, (btnStartY - 360.0) / 120.0);
  }

  double getOpacity() {
    return (btnStartY - 360.0) / 120.0 == 0 ? 0.9 : 0.2;
  }

  bool lightFlag = false;

  open() {
    if (lightFlag) {
      print("正在开灯...");
    }
    lightFlag = false;
  }

  @override
  Widget build(BuildContext context) {
    screenHeight = MediaQuery.of(context).size.height;
    screenWidth = MediaQuery.of(context).size.width;

    return Scaffold(
      //backgroundColor: Color.fromRGBO(43, 43, 40, 0.5),// Dark模式
      // backgroundColor: Color(0xffFFD80A),
      backgroundColor: getBaColors(),

      // Body
      body: Stack(
        children: <Widget>[
          // Header
          AppBar(
            backgroundColor: Colors.transparent,
            elevation: 0.0,
          ),
          // 左边文字
          Align(
            alignment: Alignment.centerLeft,
            child: Padding(
              padding: EdgeInsets.only(left: 32, bottom: screenHeight * 0.2),
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                crossAxisAlignment: CrossAxisAlignment.start,
                children: <Widget>[
                  Row(
                    crossAxisAlignment: CrossAxisAlignment.end,
                    children: <Widget>[
                      Text(
                        "灯",
                        style: TextStyle(
                            fontSize: 52,
                            color: getTextStyle(),
                            fontWeight: FontWeight.w600),
                      ),
                      Text(
                        lightStatus,
                        style: TextStyle(
                          fontSize: 25,
                          color: getTextStyle(),
                          fontWeight: FontWeight.w400
                        ),
                      )
                    ],
                  ),
                  SizedBox(height: 22),
                  Text(
                    "26°",
                    style: TextStyle(
                        fontSize: 24,
                        color: getTextStyle(),
                        fontWeight: FontWeight.w400),
                  )
                ],
              ),
            ),
          ),

          // 圆角框 40*150
          Positioned(
            right: 32,
            top: 360,
            child: Container(
              height: 150,
              width: 40,
              decoration: BoxDecoration(
                  color: Colors.black.withOpacity(getOpacity()),
                  borderRadius: BorderRadius.circular(32)),
            ),
          ),
          // 线Line
          Positioned(
            top: 0,
            right: 50,
            child: Container(
              width: 4,
              height: 360.0 + (btnStartY - 360.0),
              color: Colors.white.withOpacity(0.8),
            ),
          ),
          // 小球
          Positioned(
            top: btnStartY,
            right: 37,
            child: new GestureDetector(
              // 手势监听小球你们
              onVerticalDragUpdate: (DragUpdateDetails details) {
                // 垂直滑动更新
                print(details.delta.dy);
                this.btnStartY += details.delta.dy;
                this.btnStartY = this.btnStartY.clamp(360.0, 480.0);
                print(btnStartY);
                setState(() {});
              },
              onVerticalDragEnd: (DragEndDetails details) {
                if (this.btnStartY == 360.0) {
                  print("顶部..");
                  setState(() {
                    this.lightStatus = "关";
                  });
                  postData("off");
                }
                if (this.btnStartY == 480.0) {
                  print("底部..");
                  setState(() {
                    this.lightStatus = "开";
                  });
                  postData("on");
                }
              },
              child: Container(
                width: 30,
                height: 30,
                decoration: BoxDecoration(
                    color: Colors.white,
                    shape: BoxShape.circle,
                    boxShadow: [
                      BoxShadow(
                          color: Colors.black.withOpacity(0.15),
                          blurRadius: 5,
                          offset: Offset(0, 5))
                    ]),
              ),
            ),
          )
        ],
      ),
    );
  }
}

class LeftTitle extends StatefulWidget {
  LeftTitle({Key key}) : super(key: key);

  @override
  _LeftTitleState createState() => _LeftTitleState();
}

class _LeftTitleState extends State<LeftTitle> {
  @override
  Widget build(BuildContext context) {
    var screenHeight = MediaQuery.of(context).size.height;
    var screenWidth = MediaQuery.of(context).size.width;
    return Align(
      alignment: Alignment.centerLeft,
      child: Padding(
        padding: EdgeInsets.only(left: 32, bottom: screenHeight * 0.2),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          crossAxisAlignment: CrossAxisAlignment.start,
          children: <Widget>[
            Text(
              "灯",
              style: TextStyle(
                  fontSize: 52,
                  color: Colors.black,
                  fontWeight: FontWeight.w600),
            ),
            SizedBox(height: 22),
            Text(
              "26°",
              style: TextStyle(
                  fontSize: 24,
                  color: Colors.black87,
                  fontWeight: FontWeight.w400),
            )
          ],
        ),
      ),
    );
  }
}

ESP-8266

步骤

  1. 连接WIFI
  2. 循环请求数据Get
  3. 解析数据
  4. 控制开关
#include <ESP8266WiFi.h>
#include <ArduinoJson.h>  // 6.x

const char* ssid     = "TP-LINK_0A1F";//无线名称
const char* password = "873010963";//无线密码

const char* apiHost = "112.124.30.55";

int ledPIN = D2;

WiFiClient client;

/*
  初始化
*/
void setup() {
    // 小灯初始化
    pinMode(ledPIN, OUTPUT);
    digitalWrite(ledPIN , LOW);
    
    Serial.begin(115200);
    Serial.println();
    
    
    WiFi.mode(WIFI_STA);
    WiFi.begin(ssid, password);
    while (WiFi.status() != WL_CONNECTED) {
      delay(500);
      Serial.print(".");
    }
    Serial.println("WiFi 连接成功 ");
    Serial.print("IP: ");
    Serial.println(WiFi.localIP());
    
}


void loop(){
  // 创建TCP连接
   WiFiClient client;
   if(!client.connect(apiHost, 9001)){
    Serial.println("请求失败");
    return ;
   } 
  String req =(String)("GET ") +"/light/get" + "/ HTTP/1.1\r\n" +  
    "Content-Type: text/html;charset=utf-8\r\n" +  
    "Host: " + apiHost + "\r\n" + 
    "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.70 Safari/537.36\r\n" +
    "Connection: close \r\n\r\n";  
  Serial.println("...开始请求...");
  client.print(req); // 发送HTTP请求
   
  delay(200);
  
  while(client.available()){
      bool skipHeader = client.find("\r\n\r\n"); // 跳过响应的Header头 
      if(skipHeader){
          String line = client.readStringUntil('\r');
          Serial.print(line); 
          parseData(line);
      }
  }
   Serial.println();
   Serial.println("...请求完毕...");
   Serial.println();
   delay(600);
}
  

void parseData(String data){
  DynamicJsonDocument  jsonBuffer(2048);
  deserializeJson(jsonBuffer, data);
  JsonObject root = jsonBuffer.as<JsonObject>();
  delay(1000);
  
  int code = root["flag"];
  String message = root["message"];

  Serial.println();
  Serial.println(code);
  Serial.println(message);

  if(code==1){
    digitalWrite(ledPIN, HIGH);
  }else{
    digitalWrite(ledPIN, LOW);
  }
}

End