【车联网原型系统|三】树莓派设计+模拟基站程序


物联网原型系统导航

【车联网原型系统|一】项目介绍+需求分析+概要设计 https://blog.csdn.net/weixin_46291251/article/details/125807297

【车联网原型系统|二】数据库+应用层协议设计 https://blog.csdn.net/weixin_46291251/article/details/125808107

【车联网原型系统|三】树莓派设计+模拟基站程序 https://blog.csdn.net/weixin_46291251/article/details/125808229

【车联网原型系统|四】adhoc组网+frp内网穿透 https://blog.csdn.net/weixin_46291251/article/details/125808621

【车联网原型系统|五】前后端分离 https://blog.csdn.net/weixin_46291251/article/details/125808674

【车联网原型系统|六】效果展示 https://blog.csdn.net/weixin_46291251/article/details/125808845

【源码下载】 https://download.csdn.net/download/weixin_46291251/86227197


【车联网原型系统|三】树莓派设计+模拟基站程序

树莓派(小车)设计

概述

小车主要包括以下几个模块:

​ 数据传输模块、主控模块、传感器、避障模块、蜂鸣器模块和其他模块(日志、配置等),

下面是整个项目的结构图。

image-20220714182214802

小车端的程序采用MVC的思想进行设计,由于不涉及数据库的操作,dao层主要是底层数据的获取(从GPIO引脚),controller层是主控模块的控制类,通过调用dao层的程序对上层提供服务(由于服务比较简单,没有设计service层),实体类有报文、指令、数据等。

实体类

报文

    _src_id = "127.0.0.1"
    _des_id = "127.0.0.1"
    pac_type = 0
    ctl_type = 0

    _timestamp = "2022-05-13 18:22:35"

    '''数据字段content具体是data还是instruction取决于_pac_type和_ctl_type'''
    content = None
    # _data = data()
    # _instruction = instruction

    '''一些常量'''
    PI_TO_PC = 1  # 基站->树莓派
    PC_TO_PI = 2  # 树莓派->基站
    PI_TO_PI = 3  # 树莓派->树莓派

    CAR_MOVE = 0  # 控制小车运动
    CAR_STOP = 1  # 停止小车运动
    BACK_HOME = 2  # 返回出发点
    SENSOR_UP = 3  # 开启传感器
    SENSOR_DOWN = 4  # 关闭传感器
    ROUTER = 5  # 转发报文
    INIT_CONF = 6  # 初始化 / 修改配置信息
image-20220714183613140

指令

    """ins_type表示指令的种类"""
    ins_type = -1
    direction = []  # 四个按钮的状态
    timestamp = -1  # 指令创建的时间戳,解析报文的时候直接用packet内的即可(主要是run内入栈使用)
    # duration = -1  # 指令执行时长
    speed = 50
    diff_speed = 20  # 转向时 两侧轮胎的差速
    sensor_list = []

    # todo:

    """ """
    CAR_MOVE = 0  # 控制小车运动
    CAR_STOP = 1  # 停止小车运动
    BACK_HOME = 2  # 返回出发点
    SENSOR_UP = 3  # 开启传感器
    SENSOR_DOWN = 4  # 关闭传感器
    ROUTER = 5  # 转发报文

    SIG_INIT_STOP = 6  # ctrl-c手动停止
    DISCONECT_STOP = 7  # 断开
image-20220714183656247

数据

image-20220714183750918

日志

日志模块用于输出小车相应其他的模块的执行记录。

image-20220714183819509

避障模块

这个模块主要由三个部分组成:

1)红外 2)超声波 3)舵机

红外传感器由左右两个组成,调整灵敏度使得大约在20cm左右的时候开始避障。

超声波和舵机两个硬件组合使用,用来判断前、左、右三个方向的距离。

红外部分

这部分主要是判断左右是否过近

img

超声波dao

超声波主要是根据超声波和发射和接收时间差来计算距离

img

舵机dao

舵机根据指定等角度来移动

img

超声波舵机组合控制器

这部分主要是调用超声波和舵机两个硬件,获取指定方向上的距离。

img

蜂鸣器模块

dao

首先是dao层:

根据给定的频率、间隔等调用蜂鸣器硬件来发声

img

Controller

Controller层封装了几种格式化的声音,比如网络连接、网络断开等。

img

环境传感器

主要是从指定的引脚获取数据,由于树莓派的GPIO没有ADC能力,所以需要使用拓展版的ADC模块将读取到的模拟信号转换成数字信号并且返回给上层调用者

​ 。img

移动模块

移动模块主要是三个功能:移前后移动、原地左右转弯,前后的同时左右转弯、停止、一键返航等功能。

moveDao

moveDao主要接收底层指令(左右侧的速度,时间),根据指令直接对小车进行控制。

左右两侧分开控制,左侧两个轮胎为一组,和右边独立开,方向和速度都独立控制。

img

moveController

通过更顶层的指令对小车进行控制,完成不同的运动方式。

接收的指令是(按键状态,时状态间,速度,转弯差速等)

1)按键状态是一个四元组,分别对应前后左右四个方向对应按键的按下状态

2)允许0-4个按键被按下。

3)速度指的是小车正常允许的速度。

4)差速指的是转弯时,左右侧轮胎的差速。

img

通信模块

通信模块用于与后(基站)端进行通信,互相传输数据。共包含四个方面的功能:接收信息、发送信息、安全通信和转发信息。

接收

当小车接收到服务器的数据包时,需要对数据包进行解析。解析出数据包的类型,再调用相应的模块进行处理。

发送

小车需要实时发送运动轨迹信息和传感器检测到的环境数据

这两种数据都是定时自动发送的,环境数据在一个定时周期内采集一次,路径数据在每次调用移动模块时进行记录,并随着环境数据一起发送给后端,发送成功之后就清楚历史轨迹数据。

安全

由于系统中存在报文转发的情况,报文使用明文传播就会有泄漏的风险,所以要对报文进行加密处理。在本系统中,报文的所有数据都使用DES算法进行加密,因为DES加密可以在很大程度上保证安全性,并且性能好,加密、解密速度块。

具体而言,每个树莓派都绑定了一个长为64比特的密钥,在树莓派和后端服务器中,都存储了该密钥,即密钥进行了物理上的分发。

主控模块

主程序用来调用其他模块

img

主要分为以下三个部分

1)运动线程

线程循环接收来自基站的报文,并且从报文中提取出指令。然后根据不同的指令用不同的模块完成任务。具体的,这里需要调用moveController来指导小车的运动,sensorController来开关传感器。

2)反馈数据线程

这个线程根据指定的时间间隔定时执行任务,每个周期获取一次环境数据,填充到报文内的环境数据字段。然后将当前周期内小车的实际运行轨迹填充到轨迹字段。然后将报文打包发送给基站。

3)避障线程

这个线程循环调用红外和超声波传感器,获取前方距离和左右是否安全,如果前方距离过近,那么再让舵机转头测量左右方向的距离,然后让小车移动到安全的方向。

模拟基站程序(测试用)

import socket
from pi.config import *
import threading
import json
from pynput.keyboard import Listener
import time

# 监测按键:https://blog.csdn.net/qq_44741568/article/details/97895880

"""
这里模拟一下基站,发送指令,接收数据

ADSW四个按键充当方向键进行操作
"""
_dir = [False, False, False, False]  # 前后左右四个按钮
new_sock = socket.socket


def on_press(key):
    global _dir
    _dir_old = _dir.copy()
    if "'w'" == str(key):
        print("\r按下--前进", end="")
        _dir[0] = True

    if "'s'" == str(key):
        print("\r按下--后退", end="")
        _dir[1] = True

    if "'a'" == str(key):
        print("\r按下--左转", end="")
        _dir[2] = True

    if "'d'" == str(key):
        print("\r按下--右转", end="")
        _dir[3] = True

    if _dir != _dir_old:
        my_send(_dir)


def on_release(key):
    global _dir
    _dir_old = _dir.copy()

    if "'w'" == str(key):
        print("松开--前进")
        _dir[0] = False

    if "'s'" == str(key):
        print("松开--后退")
        _dir[1] = False

    if "'a'" == str(key):
        print("松开--左转")
        _dir[2] = False

    if "'d'" == str(key):
        print("松开--右转")
        _dir[3] = False

    if _dir != _dir_old:
        my_send(_dir)


def my_send(dir_list):
    print("send", dir_list)
    packet = {
        "src_id": "192.168.34.53",
        "dst_id": "192.168.34.53",

        "pac_type": 2,
        "ctl_type": 0,

        "timestamp": "2022-05-13 18:22:35",

        "content": {
            "direction": [False, False, False, False],
            "speed": 70,
            "diff_speed": 20,
        }
    }
    packet["content"]["direction"] = dir_list
    global new_sock
    new_sock.send((json.dumps(packet)).encode('utf-8'))


def service_thread_send(_sock: socket.socket):
    """
    收到新的连接,先判断该连接的类型,然后交给对应的函数处理
    :param _sock: 收发报文对应的socket
    :return:
    """

    with Listener(on_press=on_press, on_release=on_release) as listener:
        listener.join()


def service_thread_recv(_sock: socket.socket):
    while True:
        data_bin = _sock.recv(1024)
        if data_bin == b"":  # 断开
            print("断开连接")
            _sock.close()
            return
        print("收到数据:", json.loads(data_bin.decode("utf-8")))


if __name__ == '__main__':
    s_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # 创建套接字
    s_sock.bind((addr_BaseStation_1, port_BaseStation_1))
    s_sock.listen(5)  # socket的”排队个数“为5 !!!不是最大连接数

    while True:
        print("listening: ", s_sock)
        new_sock, adress = s_sock.accept()  # 这条命令是阻塞的
        print("连接成功")
        threading.Thread(target=service_thread_recv, args=(new_sock,)).start()  # 收到新的请求就直接投入新的线程并且开始运行
        threading.Thread(target=service_thread_send, args=(new_sock,)).start()  # 收到新的请求就直接投入新的线程并且开始运行

posted @ 2022-07-17 17:57  Cheney822  阅读(125)  评论(0编辑  收藏  举报