我的私有云-IOT定位/追踪系统

目录

1. 说明

2. 完成后的效果

2.1 实时定位

2.2 轨迹重现 

2.3 设备美照

3. 项目设计

3.1 系统拓扑图​编辑

3.2 技术选型

3.3 消息订阅处理架构图

3.4 frp服务在线监控​编辑

4. 实施

4.1 数据模型 - DeviceLocation

4.2 数据报规格定义

订阅主题

数据报格式 

银尔达后台配置

4.3 脚本:

- Kafka消费者守护程序

- 高德地图GPS定位纠偏脚本

5. 要点:

- API输出坐标数据时,需在Pydantic 模型做数据转换

6. 参考:

6.1 工具

- Javascript 在线转 Typescript: 

- GPS定位纠偏

6.2 码讯定位精度选择(定位技术对比)


1. 说明

      本文介绍一套低成本实现的IOT定位追踪系统方案,实现基于:本地内网服务器-云服务器-IOT终端-手机终端 互联互通基础上的定位追踪应用。

2. 完成后的效果

2.1 实时定位

定位实时数据

2.2 轨迹重现 

定位轨迹数据

2.3 设备美照

3. 项目设计

  • 准备一个云服务器,固定带宽2~5M的一般云主机,装frp服务端,负责系统通讯接驳。
  • 本地私有云集群搭建,多核高内存高性能服务器集群,负责提供各种服务,其中之一装Frp客户端,映射各服务端口到云服务器。
  • IOT定位器,在相关后台设置上报消息格式,MQTT服务器路径及订阅主题
  • IOT设备消息路径:定位器-》云服务器-》私有云 MQTT Server-》Nifi-》Kafka-》应用消费服务
  • 使用终端API路径:终端-》云服务器-》私有云 API Server
3.1 系统拓扑图
3.2 技术选型
层次技术/框架/硬件备注
前端Vue3, TypescriptVUE3结合高德地图Api, 实现实时定位与轨迹重现
中间件
  • Nifi - 数据流控制
  • Emqx - MQTT服务
  • Kafka - 消息服务
  • 高德地图2.0
后端FastApi, Kinit使用Kinit做后台管理
数据库Mysql 8, Redis使用mysql里面的地理字段

Geometry存位置数据

定位器硬件银尔达 Air820ugGPS有源天线,供电5V2A,10W,含GPS/BD定位功能,通过其后台设置每分钟上报一次
3.3 消息订阅处理架构图

3.4 frp服务在线监控

4. 实施

4.1 数据模型 - DeviceLocation

参考: FastApi地理坐标数据存取实践_fastapi geoalchemy-CSDN博客

from typing import List, Optional
from datetime import datetime
from sqlalchemy import BigInteger, Column, DateTime, ForeignKey, ForeignKeyConstraint, Index, Integer, String, Table, Text, text
from sqlalchemy.dialects.mysql import TINYINT
from sqlalchemy.orm import Mapped, declarative_base, mapped_column, relationship
from sqlalchemy.orm.base import Mapped
from geoalchemy2 import Geometry, WKBElement
from sqlalchemy.orm import relationship, Mapped, mapped_column
from db.db_base import BaseModel
from .data_types import DeviceType
import uuid
import secretsmetadata = BaseModel.metadataclass DeviceLocation(BaseModel):__tablename__ = 'ia_iot_device_location'__table_args__ = (Index('Index_1', 'iot_device_id'),Index('Index_2', 'coordinates'))id = mapped_column(BigInteger, primary_key=True)coordinates: Mapped[WKBElement] = mapped_column(Geometry(geometry_type='POINT', spatial_index=True), nullable=False, comment='地理坐标')iot_device_id = mapped_column(BigInteger, server_default=text("'0'"))label = mapped_column(String(255, 'utf8mb4_general_ci'))
4.2 数据报规格定义
订阅主题
Topic类型备注
ia001.device.busKafka 主题, 不允许有“/”消息总线
/ia001/report/#Emqx 主题Nifi使用,通配设备上报消息
/ia001/cmd/${IMEI}Emqx 主题下发指令给设备, id - IMEI
数据报格式 
定位{'meta': {'id': '860048072112954', 'cmd': '1001', 'extra': {}}, 'data': {'type': 'GPS', 'lng': '113.3589331', 'lat': '22.5405440', 'csq': '22'}}
银尔达后台配置
主动上报内容格式
基站定位{"meta":{"id":"${IMEI}","cmd":"1001","extra":{}},"data":{"type":"LBS","lng":"${LBSLON}","lat":"${LBSLAT}","csq":"${CSQ}"}}
GPS定位{"meta":{"id":"${IMEI}","cmd":"1001","extra":{}},"data":{"type":"GPS","lng":"${GPSLON}","lat":"${GPSLAT}","csq":"${CSQ}"}}
LWT{"meta":{"id":"${IMEI}","cmd":"1004","extra":{}},"data":{"msg":"off line"}}
4.3 脚本:
- Kafka消费者守护程序
from confluent_kafka import Consumer, KafkaError, KafkaException
import asyncio
import json
import logging
from sqlalchemy import insert, select, delete
from datetime import datetime
from core.database import db_getter
from application.settings import BASE_DIR, IOT, KAFKA
from apps.vadmin.iot.models.models import DeviceLocation, Device
from apps.vadmin.iot.crud import DeviceLocationDal
from apps.vadmin.iot.cmds.utils import *
from apps.vadmin.iot.cmds.cmds import *# 设置日志格式,'%()'表示日志参数
log_format = "%(message)s"
logging.basicConfig(filename=f"{BASE_DIR}/logs/k1.log", format=log_format, level=logging.INFO
)class DaemonConsumer:def __init__(self):self.debug = True# self.debug = Falseasync def consume_loop(self, consumer, topics):try:# 订阅主题consumer.subscribe(topics)while True:# 轮询消息msg = consumer.poll(timeout=1.0)if msg is None:continueif msg.error():if msg.error().code() == KafkaError._PARTITION_EOF:# End of partition eventprint("%% %s [%d] reached end at offset %d\n"% (msg.topic(), msg.partition(), msg.offset()))elif msg.error():raise KafkaException(msg.error())else:# 正常消息raw_message = msg.value()# print(f"Raw message: {raw_message}")str_msg = raw_message.decode("utf-8")payload = json.loads(str_msg)payload["server_time"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S")if self.debug:print(f"Received message: {type(payload)} : {payload}")json_data = json.dumps(payload, ensure_ascii=False)logging.info("{}".format(json_data))if "meta" in payload:await self.process_message(payload)finally:# 关闭消费者consumer.close()async def process_message(self, payload):cmdTuple = CommandParser.getCommand(payload["meta"]["cmd"])print(cmdTuple)cmd = CommandDispatcher.dispatch(cmdTuple["class"])await cmd.execute(cmdTuple["action"], payload)async def run(self):# 消费者配置# conf = {#     "bootstrap.servers": "host001.dev.ia:19092,host001.dev.ia:29092,host001.dev.ia:39092",#     "group.id": "mygroup1",#     "auto.offset.reset": "earliest",# }conf = KAFKA# 创建消费者consumer = Consumer(conf)# await self.consume_loop(consumer, ["ia001.device.bus"])await self.consume_loop(consumer, [IOT["KAFKA_TOPIC_BUS"]])
- 高德地图GPS定位纠偏脚本

Javascript 版本: 查看绑定资源 

Typescript 版本: 

/* eslint-disable @typescript-eslint/no-loss-of-precision */const x_PI: number = (3.14159265358979324 * 3000.0) / 180.0
const PI: number = 3.1415926535897932384626
const a: number = 6378245.0
const ee: number = 0.00669342162296594323const bd09togcj02 = (bd_lon: number, bd_lat: number): number[] => {bd_lon = +bd_lonbd_lat = +bd_latconst x: number = bd_lon - 0.0065const y: number = bd_lat - 0.006const z: number = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * x_PI)const theta: number = Math.atan2(y, x) - 0.000003 * Math.cos(x * x_PI)const gg_lng: number = z * Math.cos(theta)const gg_lat: number = z * Math.sin(theta)return [gg_lng, gg_lat]
}const gcj02tobd09 = (lng: number, lat: number): number[] => {lat = +latlng = +lngconst z: number = Math.sqrt(lng * lng + lat * lat) + 0.00002 * Math.sin(lat * x_PI)const theta: number = Math.atan2(lat, lng) + 0.000003 * Math.cos(lng * x_PI)const bd_lng: number = z * Math.cos(theta) + 0.0065const bd_lat: number = z * Math.sin(theta) + 0.006return [bd_lng, bd_lat]
}const wgs84togcj02 = (lng: number, lat: number): number[] => {lat = +latlng = +lngif (out_of_china(lng, lat)) {return [lng, lat]} else {const dlat: number = transformlat(lng - 105.0, lat - 35.0)const dlng: number = transformlng(lng - 105.0, lat - 35.0)const radlat: number = (lat / 180.0) * PIlet magic: number = Math.sin(radlat)magic = 1 - ee * magic * magicconst sqrtmagic: number = Math.sqrt(magic)const dlatAdjusted: number = (dlat * 180.0) / (((a * (1 - ee)) / (magic * sqrtmagic)) * PI)const dlngAdjusted: number = (dlng * 180.0) / ((a / sqrtmagic) * Math.cos(radlat) * PI)const mglat: number = lat + dlatAdjustedconst mglng: number = lng + dlngAdjustedreturn [mglng, mglat]}
}const gcj02towgs84 = (lng: number, lat: number): number[] => {lat = +latlng = +lngif (out_of_china(lng, lat)) {return [lng, lat]} else {const dlat: number = transformlat(lng - 105.0, lat - 35.0)const dlng: number = transformlng(lng - 105.0, lat - 35.0)const radlat: number = (lat / 180.0) * PIlet magic: number = Math.sin(radlat)magic = 1 - ee * magic * magicconst sqrtmagic: number = Math.sqrt(magic)const dlatAdjusted: number = (dlat * 180.0) / (((a * (1 - ee)) / (magic * sqrtmagic)) * PI)const dlngAdjusted: number = (dlng * 180.0) / ((a / sqrtmagic) * Math.cos(radlat) * PI)const mglat: number = lat + dlatAdjustedconst mglng: number = lng + dlngAdjustedreturn [lng * 2 - mglng, lat * 2 - mglat]}
}const transformlat = (lng: number, lat: number): number => {lat = +latlng = +lnglet ret: number =-100.0 +2.0 * lng +3.0 * lat +0.2 * lat * lat +0.1 * lng * lat +0.2 * Math.sqrt(Math.abs(lng))ret += ((20.0 * Math.sin(6.0 * lng * PI) + 20.0 * Math.sin(2.0 * lng * PI)) * 2.0) / 3.0ret += ((20.0 * Math.sin(lat * PI) + 40.0 * Math.sin((lat / 3.0) * PI)) * 2.0) / 3.0ret += ((160.0 * Math.sin((lat / 12.0) * PI) + 320 * Math.sin((lat * PI) / 30.0)) * 2.0) / 3.0return ret
}const transformlng = (lng: number, lat: number): number => {lat = +latlng = +lnglet ret: number =300.0 + lng + 2.0 * lat + 0.1 * lng * lng + 0.1 * lng * lat + 0.1 * Math.sqrt(Math.abs(lng))ret += ((20.0 * Math.sin(6.0 * lng * PI) + 20.0 * Math.sin(2.0 * lng * PI)) * 2.0) / 3.0ret += ((20.0 * Math.sin(lng * PI) + 40.0 * Math.sin((lng / 3.0) * PI)) * 2.0) / 3.0ret += ((150.0 * Math.sin((lng / 12.0) * PI) + 300.0 * Math.sin((lng / 30.0) * PI)) * 2.0) / 3.0return ret
}const out_of_china = (lng: number, lat: number): boolean => {lat = +latlng = +lngreturn !(lng > 73.66 && lng < 135.05 && lat > 3.86 && lat < 53.55)
}export default {bd09togcj02,gcj02tobd09,wgs84togcj02,gcj02towgs84
}

5. 要点:

- API输出坐标数据时,需在Pydantic 模型做数据转换
@field_validator("coordinates", mode="before")def parse_coordinates(cls, value: WKBElement):return dump_coords(to_shape(value))[0] if value else None

6. 参考:

6.1 工具
- Javascript 在线转 Typescript: 

Javascript to Typescript converter with ChatGPT | Js2TS.com

- GPS定位纠偏

GPS 定位纠偏 

6.2 码讯定位精度选择(定位技术对比)
#UWB蓝牙5.1蓝牙信标Wi-FiRFIDZigbee
精度10-30cm1-5m3-5m 依赖于信标的密度5-15m15cm-1m3-10m
可靠性抗干扰能力强对多路径、障碍物和干扰非常敏感易受遮挡和多径影响对多路径、障碍物和干扰非常敏感不易受影响抗干扰能力弱
覆盖范围50-100m10-20m6-8m40-50m1m60-70m(主要应用一维)
数据通信最高27Mbps最高2Mbps不适用最高1 Gbps不适用20-250kbps
安全范围非常安全可利用中继攻击进行欺骗可利用中继攻击进行欺骗可利用中继攻击进行欺骗可利用中继攻击进行欺骗安全性比较低
定位服务延迟<1ms≤3ms≥3ms≥3ms≥1s30ms
可拓展性基于超过数万个或不限量标签的解决方案几百到一千标签几百到一千标签几百到一千标签不限量标签最大60000个节点

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/1523904.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

【Qt】Qt 网络 | UDP Socket

文章目录 核心API代码示例服务器客户端 要使用 Qt 网络编程&#xff0c;需要在项目中的 .pro 文件中添加 network 模块 核心API Qt 的 UDP Socket 主要的类有两个 QUdpSocket 和 QNetworkDatagram 因为是 UDP 是面向数据报的&#xff0c;QNetworkDatagram 就是对 数据报的封…

ChatGPT写文章时,如何去除生硬的Ai味?

仅做分享&#xff0c;侵删 在使用AI进行写作时&#xff0c;常常会发现生成的文章带有明显的“机器味”&#xff0c;一眼就能看出是由AI生成的。这是许多希望借助AI进行自媒体创作的小伙伴们面临的一个主要问题。AI生成的文章往往过于书面化&#xff0c;缺乏人情味&#xff0c;导…

Xmind2024去除VIP会员解锁版

XMind 2024&#xff1a;让思维导图变得有趣又高效 &#x1f31f;新功能大揭秘&#x1f31f; 亲爱的小仙女们&#xff0c;今天我要给你们安利一款神奇的软件——XMind 2024&#xff01;这不仅仅是一款普通的思维导图软件&#xff0c;它可是集美貌与智慧于一身的超级助手哦&…

【学习笔记】 陈强-机器学习-Python-Ch13 提升法

系列文章目录 监督学习&#xff1a;参数方法 【学习笔记】 陈强-机器学习-Python-Ch4 线性回归 【学习笔记】 陈强-机器学习-Python-Ch5 逻辑回归 【课后题练习】 陈强-机器学习-Python-Ch5 逻辑回归&#xff08;SAheart.csv&#xff09; 【学习笔记】 陈强-机器学习-Python-…

3674B/C/D/E/F/G矢量网络分析仪

3674B/C/D/E/F/G矢量网络分析仪 <<<3674系列矢量网络分析仪>>> Ceyear 3674系列矢量网络分析仪是技术创新的巅峰之作&#xff0c;可以轻松应对半导体芯片测试、材料测试、天线测试、高速线缆测试、微波部组件测试等带来的严峻挑战。出色的射频特性、灵活的硬…

提升多跳问答中的语言模型知识编辑能力

人工智能咨询培训老师叶梓 转载标明出处 大模型在静态知识库的更新上存在局限&#xff0c;特别是在面对需要多步骤推理的多跳问题时&#xff0c;难以提供准确和最新的回答。为了解决这一问题&#xff0c;来自美国佐治亚大学、纽约大学、莱斯大学、北卡罗来纳州立大学等机构的研…

案例-KVM+GFS分布式存储系统构建KVM高可用(虚拟化实战)

NFS GlusterFS 基于共享存储 采用GFS做共享存储&#xff1b; 实验环境&#xff1a;101 102 103 104 做gfs集群&#xff1b;201 202做虚拟机&#xff1b; 同步一下会话&#xff1b; 为了方便使用主机名进行通信&#xff0c;修改hosts文件&#xff1b; 为了使用GlusterFS的仓库…

机器学习引领未来:赋能精准高效的图像识别技术革新

图像识别技术近年来取得了显著进展,深刻地改变了各行各业。机器学习,特别是深度学习的突破,推动了这一领域的技术革新。本文将深入探讨机器学习如何赋能图像识别技术,从基础理论到前沿进展,再到实际应用与挑战展望,为您全面呈现这一领域的最新动态和未来趋势。 1. 引言 …

Python进阶07-高级语法

零、文章目录 Python进阶07-高级语法 1、with语句 &#xff08;1&#xff09;文件操作 文件使用完后必须关闭&#xff0c;因为文件对象会占用操作系统的资源&#xff0c;并且操作系统同一时间能打开的文件数量也是有限的 # 第一步&#xff1a;打开文件 f open(python.txt…

CAS理解和说明

目录 1.CAS是什么? 2.CAS的应用场景 2.1 实现原子类 2.2 实现自旋锁 3.CAS的典型问题:ABA问题 1.CAS是什么? CAS:全称compare and swap(比较并交换) 我们假设内存中的原始数据V&#xff0c;旧的预期值A&#xff0c;需要修改的新值B 1.比较A与V是否相等&#xff08;比较…

Spring6梳理6——依赖注入之Setter注入

以上笔记来源&#xff1a; 尚硅谷Spring零基础入门到进阶&#xff0c;一套搞定spring6全套视频教程&#xff08;源码级讲解&#xff09;https://www.bilibili.com/video/BV1kR4y1b7Qc 目录 一、引言 二、Java方法初始化过程 2.1 set方法及构造器方法实现Book对象初始化 三…

QT实战项目之音乐播放器

项目效果演示 myMusicShow 项目概述 在本QT音乐播放器实战项目中&#xff0c;开发环境使用的是QT Creator5.14版本。该项目实现了音乐播放器的基本功能&#xff0c;例如开始播放、停止播放、下一首播放、上一首播放、调节音量、调节倍速、设置音乐播放模式等。同时还具备搜索功…

C语言:常用技巧及误用

一、字符串存储在数组中 int main() {char* arr[7] {"xiaoming","zhangsan","李四"};printf("%s\n", arr[0]);printf("%s\n", arr[2]);return 0; } 二、scanf()函数用法 2.1 scanf()输入字符串 int main() {char arr[10…

jquery下载的例子如何应用到vue中

参考测试圈相亲平台开发流程&#xff08;4&#xff09;&#xff1a;选个漂亮的首页 (qq.com) 下载的文件夹解压到v_love项目的pubilc下的static文件夹内&#xff0c;这里放的都是我们的静态资源。 打开文件夹内的index.html&#xff0c;我们先确定下它是不是我们要的东西&…

多目标应用:四种多目标优化算法(NSGA2、NSPSO、NSDBO、NSCOA)求解柔性作业车间调度问题(FJSP),MATLAB代码

一、柔性作业车间调度问题 柔性作业车间调度问题(Flexible Job Scheduling Problem, FJSP) 的描述如下&#xff1a;n个工件 { J , J 2 , . . , J n } \{J,J_2,..,J_n\} {J,J2​,..,Jn​}要在 m m m 台机器 { M 1 , M 2 , . . , M m } \{M_1,M_2,..,M_m\} {M1​,M2​,..,Mm​} …

做了SEO为何效果平平无奇?

SEO&#xff08;Search Engine Optimization&#xff09;即搜索引擎优化&#xff0c;指在不同的搜索引擎中&#xff0c;按照相应的规则与机制提高网站在特定的搜索引擎中的自然排名。作为对产品和服务进行数字宣传营销的重要手段&#xff0c;SEO已然成为各企业提高网站曝光和流…

Ubuntu 24.04 安装 英特尔工具包 Intel® Toolkits

目录 1.采用用户界面 GUI 安装英特尔基本工具包 Intel oneAPI Base Toolkit 1.1 下载离线英特尔基本工具包 1.2 安装英特尔基本工具包 1.3 英特尔基本工具包 Intel oneAPI Base Toolkit 环境设置 2.安装英特尔高性能计算工具包 Intel HPC Toolkit 2.1 下载离线英特尔高性…

[MOCO v3] An Empirical Study of Training Self-Supervised Vision Transformers

1、目的 探索基于contrastive/Siamese范式&#xff08;而非masked auto-encoding范式&#xff09;和ViT结构&#xff08;而非卷积网络&#xff09;的自监督学习 2、方法 MoCo v3 ​​​​​​​ 1&#xff09;random data augmentation 2&#xff09;query encoder &a…

Java笔试面试题AI答之正则表达式(3)

文章目录 13. 简述Java String支持哪几种使用正则表达式的方法&#xff1f;14. 请列举常见校验数字的表达式 &#xff1f;15. 请列举常见校验字符的表达式 &#xff1f;1. 汉字2. 英文和数字3. 特定长度的字符串4. 由26个英文字母组成的字符串5. 由数字和26个英文字母组成的字符…

【电池专题】软包电池封装工序

铝塑膜成型工序冲坑 铝塑膜成型工序,软包电芯可以根据客户的需求设计成不同的尺寸,当外形尺寸设计好后,就需要开具相应的模具,使铝塑膜成型。 成型工序也叫作冲坑,顾名思义,就是用成型模具在加热的情况下,在铝塑膜上冲出一个能够装卷芯的坑,具体的见下图。 …