1.前言

此功能类似于一个小的物联网应用,用小程序作前端,搭建云服务和数据库,本地电脑充当边缘设备,并且部署了后端程序做了frp的内网穿透,此处本地电脑只做一些命令的下发,通过无线转串口发送给单片机,单片机接收命令并作出反应。

2.小程序前端

整体界面的CSS设计主要用到flex弹性盒子,非常简单,易上手。

2.1参数设定页面

2.1.1wxml部分

<!-- 纵向flex -->
<view class="body mg16" wx:for="{{parameter}}"  wx:key="id" wx:for-item="item" wx:for-index="index">
<!-- 上半部分 -->
<view class="text mg4">
<view class="mg4">参数{{item.id}}</view>
</view>
<!-- 下半部分,横向flex -->
<view class="flex mgt8">
    <view style="height: 80px" class="flex1 baiSe mg4">
        <view class="mg8">参数名</view>
        <view class="input"><input class="mgt8" value="{{item.name}}" bindinput="handleInputName" data-index="{{index}}"/></view>
    </view>
    <view style="height:80px" class="flex1 baiSe mg4">
        <view class="mg8">初值</view>
        <view class="input"><input class="mgt8" value="{{item.inital_value}}" bindinput="handleInputInit" data-index="{{index}}"/></view>
    </view>
    <view style="height: 80px" class="flex1 baiSe mg4">
        <view class="mg8">步进</view>
        <view class="input"><input class="mgt8" value="{{item.bujin}}" bindinput="handleInputBujin" data-index="{{index}}"/></view>
    </view>
    <view style="height: 80px" class="flex1 baiSe mg4">
        <view class="mg8">范围</view>
        <view class="input"><input class="mgt8" value="{{item.range}}" bindinput="handleInputRange" data-index="{{index}}"/></view>
    </view>
</view>
</view>


<view class="flex mg64">
<button type="primary" size="mini" class="button mgt32" bindtap="Add">添加</button>
<view style="height: 80px; width: 80px" class="baiSe mgt4">
        <view class="mg8 text">COM</view>
        <view class="input"><input class="mgt8" value="{{port}}" bindinput="handleInputPort" data-port="{{port}}"/></view>
</view>
<button type="primary" size="mini" class="button mgt32" bindtap="Confirm">确认</button>
</view>

2.1.2wxss部分

.flex{
    display: flex;
    border-radius: 10px;
    flex-direction: row;
    justify-content: flex-start;
    align-items: flex-start;
}

.body{
    display: flex;
    border: 3px solid #FFB6C1;
    border-radius: 10px;
    flex-direction: column;
}

.baiSe{
    background-color: white;
    border: 2px solid #FFB6C1;
    border-radius: 10px;
}

.flex1{
    flex:1;
    align-items: center;
    justify-content: center;  
    text-align: center;
}

.mg4{
    margin: 4px;
}

.mg8{
    margin: 8px;
}
.mg16{
    margin: 16px;
}
.mg32{
    margin: 32px;
}
.mgr8{
    margin-right: 8px;
}

.mgt4{
    margin-top: 4px;
}
.mgt8{
    margin-top: 8px;
}
.mgt16{
    margin-top: 32px;
}
.mgt32{
    margin-top: 32px;
}
.mgt64{
    margin-top: 64px;
}

.input{
    align-items: center;
    justify-content: center;  
    text-align: center;
    border-top: 1px solid #cccccc;
}

.text{
    align-items: center;
    justify-content: center;  
    text-align: center;
    border-bottom: 2px solid #FFB6C1;
    font-size: 18px;
    font-weight: 800;
}

.rb{
    border-radius: 100px;
    width: 50px;
}

2.2调节参数界面

2.2.1wxml部分

<!-- 纵向flex -->
<view class="body mg16" wx:for="{{parameter}}"  wx:key="id" wx:for-item="item" wx:for-index="index">
<!-- 上半部分 -->
<view class="text mg4">
<view class="mg4">{{item.name}}</view>
</view>
<!-- 下半部分,横向flex -->
<view class="flex mg8">
    <button type="primary" size="mini" bindtap="handleTap" data-operation="{{1}}" data-index="{{index}}">+</button>
    <view>{{item.inital_value}}</view>
    <button type="primary" size="mini" bindtap="handleTap" data-operation="{{0}}" data-index="{{index}}">-</button>
</view>
</view>
<view class="flex mg64">
<button type="primary" size="mini" class="button mgt32" bindtap="Switch">开关</button>
<button type="primary" size="mini" class="button mgt32" bindtap="Mode">图像</button>
</view>

2.2.2wxss部分

和上个页面一样,仅仅换个颜色,flex就是妙。

2.3JS部分

JS部分涉及局部参数、全局参数就不一一展示了。

前后端交互主要就是http请求。

3.后端及数据库

3.1Django框架-云端

def getparameter(request):
    if request.method == "POST":
        try:
            openid = request.POST["openid"]
            cookie = request.POST["cookie"]

            session = Session.objects.get(openid=openid).session
        except:
            content = {"state": "201"}
            return JsonResponse(content)

        if (session != cookie):
            content = {"state": "202"}
            return JsonResponse(content)

        else:
            pa = Parameter.objects.filter(openid=openid)
            if pa:
                pa = Parameter.objects.get(openid=openid)
                parameter = pa.parameter
                port = pa.port
            else:
                parameter = ""
                port = ""
            content = {"state": "200", "parameter": parameter, "port": port}
            return JsonResponse(content)

    else:
        content = {"state": "203"}
        return JsonResponse(content)


def parameter(request):
    if request.method == "POST":
        try:
            parameter = request.POST["parameter"]
            openid = request.POST["openid"]
            wxname = request.POST["wxname"]
            cookie = request.POST["cookie"]
            port = eval(request.POST["port"])

            session = Session.objects.get(openid=openid).session
        except:
            content = {"state": "201"}
            return JsonResponse(content)

        if (session != cookie):
            content = {"state": "202"}
            return JsonResponse(content)

        else:
            pa = Parameter.objects.filter(openid=openid)
            if pa:
                pa = Parameter.objects.get(openid=openid)
                pa.parameter = parameter
                pa.port = port
                pa.save()
            else:
                Parameter.objects.create(parameter=parameter, openid=openid, wxname=wxname, port=port)
            content = {"state": "200", "parameter": parameter}
            return JsonResponse(content)

    else:
        content = {"state": "203"}
        return JsonResponse(content)

3.2数据库

class Parameter(models.Model):
    id = models.AutoField(primary_key=True)
    parameter = models.CharField(max_length=128)
    openid = models.CharField(max_length=32)
    wxname = models.CharField(max_length=32)
    port = models.IntegerField(4, default=0)
    date = models.DateField(auto_now_add=True)
    time = models.TimeField(auto_now_add=True)
    class Meta:
        db_table = "parameter"

3.3边缘端

主要用到的是串口通信serial和frp内网穿透。

from django.shortcuts import render
from django.shortcuts import render, redirect
from django.http import HttpResponse, HttpResponseNotFound, Http404, cookie, JsonResponse, FileResponse
from django.urls import reverse
from django.views import View
from django.views.decorators.csrf import csrf_exempt
import random, requests
from lxml import etree
from datetime import datetime
import json
import serial
import time
import threading
# Create your views here.


import time
import serial
import serial.tools.list_ports


def control(request):
    if request.method == "POST":
        try:
            parameter = eval(request.POST["paramenter"])
            port = eval(request.POST["port"])
            openid = request.POST["openid"]
        except:
            content = {"state": "201"}
            return JsonResponse(content)

        allow_openid = ["o1AFS5ImQTOhpuhCe7lU6Jw_7Fco"]
        if(openid not in allow_openid):
            print("{}无权限".format(openid))
            content = {"state": "202"}
            return JsonResponse(content)

        else:
            x = []
            print("parameter:", parameter)
            x0 = hex(parameter["id"]-1)[2:]
            x.append(x0)
            value = eval(parameter["inital_value"])
            if("{}".format(type(value))=="<class 'float'>"):
                x1 = '1'   # float
                x.append(x1)
                value = value*10
                value = int(value)
                value_i = hex(value)[2:]
            elif ("{}".format(type(value)) == "<class 'int'>"):
                x1 = '0'   # int
                x.append(x1)
                value_i = hex(value)[2:]
            length = len(value_i)
            if (length >= 3):
                x2 = value_i[:-2]
                x3 = value_i[2:]
            else:
                x2 = '0'
                x3 = value_i
            x.insert(2, x2)
            x.insert(3, x3)
            for i in range(4):
                length2 = len(x[i])
                if(length2 < 2):
                    x[i] = '0'+x[i]
            print(x)

            # 串口通信
            port_list = list(serial.tools.list_ports.comports())
            if len(port_list) == 0:
                print("无可用串口")
            else:
                port = "COM{}".format(port)
                # print(port)

                try:
                    com = serial.Serial(port, 115200)
                    com.write(bytes.fromhex('AA'))  # 帧头
                    for i in range(4):
                        time.sleep(0.1)
                        com.write(bytes.fromhex(x[i]))
                    time.sleep(0.1)
                    com.write(bytes.fromhex('55'))  # 帧尾
                    com.close()
                    msg = "{}串口已接收".format(port)
                except:
                    msg = "{}串口被占用".format(port)

            print(msg)
            content = {"state": "200", "msg": msg}
            return JsonResponse(content)

    else:
        content = {"state": "203"}
        return JsonResponse(content)


def command(request):
    if request.method == "POST":
        try:
            command = request.POST["command"]
            port = request.POST["port"]
            openid = request.POST["openid"]
        except:
            content = {"state": "201"}
            return JsonResponse(content)

        allow_openid = ["o1AFS5ImQTOhpuhCe7lU6Jw_7Fco"]
        if(openid not in allow_openid):
            print("{}无权限".format(openid))
            content = {"state": "202"}
            return JsonResponse(content)

        else:
            try:
                length = len(command)
                if (length < 2):
                    command = '0' + command
                print("指令{}".format(command))
                port = "COM{}".format(port)
                com = serial.Serial(port, 115200)
                com.write(bytes.fromhex(command))  # 帧头
                com.close()
                msg = "{}串口已接收".format(port)
            except:
                msg = "{}串口被占用".format(port)
            print(msg)
            content = {"state": "200", "msg": msg}
            return JsonResponse(content)

    else:
        content = {"state": "203"}
        return JsonResponse(content)

4.无线转串口CH573F

4.1引脚介绍

5V电源
GND
TX发送引脚
RX接收引脚

4.2通信协议

0xAA帧头
0x0000-自定义参数1
01-自定义参数2
0x000x00-int
0x01-float float则除以10,如5为0.5
0x00数值整数高位,使用高位可达65535
0x00数值整数底位,可达255
0x55帧尾

4.3MM32单片机

主函数

if(uart_query(UART_1, &uart1_get_buffer)==0x01)									{
	uart_putchar(UART_1, uart1_get_buffer);
	if(UART_RX_INDEX==0)	// 接收标志位
	{
		if(uart1_get_buffer==0xaa)	// 接收到帧头
		{
			UART_RX_INDEX=1;	// 开始接收数据
			printf("\nBegin");
		}
                else		// 特殊指令
		{
			command(uart1_get_buffer);
		}
	}
       else
       {
	        if(uart1_get_buffer==0x55)	// 接收到帧尾
	        {
		        UART_RX_INDEX=0;	
		        UART_RX_STA=1;		
		        printf("\nOver");
	        }
                else
                {
	                uart1_buffer[UART_RX_INDEX-1]=uart1_get_buffer;
	                UART_RX_INDEX++;
                }
        }					
}	
if(UART_RX_STA==1)      // 接收完成,作出响应
{
	OP(uart1_buffer);
	UART_RX_STA=0;
}

封装响应函数OP()、command()

void command(uart1_get_buffer)
{
	if(uart1_get_buffer==0x01){
			if (car_run_state==0)car_run_state=1;
			else if (car_run_state==2||car_run_state==3)   car_run_state=0;
	}
	if(uart1_get_buffer==0x02){
			pd_key=(pd_key+1)%3;
	}
}
void OP(uint8 x[4])
{
	int a=0;
	float b=0;
	// 选择整型或浮点型
	if(x[1]==0x00)
	{
		a=(int)x[2]*256+(int)x[3];
	}
	else if(x[1]==0x01)
	{
		if(x[2]==0x00)
		{
			b=(float)x[3]/10;
		}
		else
		{
			b=((float)(x[2]*256+x[3]))/10;
		}
	}
	
	// 数据选择器
	switch(x[0]){
			case 0:var=b;break;
			case 1:kp=b;break;
			case 2:l=a;break;
			case 3:r=a;break;
	}
}