PLC-IoT 网关开发札记(5):将本地数据库作为资产打包发布到 App
App需求:保存物模型
什么是物模型
在项目开发中,用到了本地数据库,这个本地数据库记录了系统的物模型。所谓物模型就是对某一个设备的可操纵属性的定义,每一个设备包括了一个或者多个属性,通过获取这些属性的当前值可以得到该设备的状态,改变设备的一个属性(或者多个属性的组合)可以控制该设备。
例如一台伺服电机,它可能包含以下的属性:
- 电源开关:这是一个开关型的属性,是可读写的,用一个布尔型数表示,1表示逻辑真、接通、打开,0表示逻辑非、断开、关闭
- 旋转方向:这是一个枚举型属性,是可读写的,用一个枚举型数表示,1表示正转,0表示停止,-1表示反转
- 转速:这是一个数值型的属性,是可读写的,用一个有取值范围的整数表示,0表示停转,10000 表示 10000RPM 的转速。它的取值范围是 0~10000 RPM。
设备的属性之间是有约束的。在电机这个例子中,属性之间的约束至少是:
- 当电源开关处于关闭状态时,设置旋转方向和转速是没有意义的,只有在电源开关打开时,其它参数才有意义。
- 旋转方向和转速一起确定了电机的运转状态。
- 设置转速低于0,或者设置转速高于10000 是没有意义的。设备收到这类“错误”指令参数时,会采取不同的策略,通常是将参数“归化”到取值范围内,也有的设备是忽略这些错误参数,保持目前的工况不变。
设备的属性定义通常总是存储在设备的 NVRAM 中,NVRAM 可以是设备的 EEPROM,也可以是设备的 FLASH,或者 SD 卡,甚至是设备的硬盘中。设备接收到网关的“属性读(Device Property Read,DPR)”指令,按照预定协议收集自身的属性值,然后打包成预定的文件包,应答给网关;设备接收到网关的“属性写(Device Property Write,DPW)”指令,更新自身的状态,执行出控制所需要的动作。
物模型数据库
在网关上要保存其所支持的所有设备的物模型,设备(或者干脆叫做“子设备”,“网络终端单元”)只保存自己的物模型,网关和子设备上对于同一型号的设备,物模型保持相同。
子设备使用英文 Subset表示,网络终端单元用英文缩写 NTU 表示,英文全称是 Network Terminal Unit。后面的定义中会用到 Subset 和 NTU 这两个名词。
网关通常支持多种型号的子设备,需要在本地持久化。网关上的物模型可以根据需求增加,过时的物模型可以删除。为了做到更一般化,枚举型的属性还会有它的二级定义,即这个枚举型的取值。
从以上的说明可以设想到:物模型数据库必定有几个多级的表组成,是一个典型的一主多从的结构。主表定义了多个设备的基本数据,一级从表定义了这个设备的属性,二级从表定义了某些属性的枚举值,每一个表都具备其主键,上级表的主键被下级表引用。
扯了这么多物模型,对根结底还是三张表。对于一个不是很大的系统,在一个网关上支持数百型号的设备不算很少了,这相当于主表的记录数量级;二级表的记录数在数千条的样子,三级表的记录数也会是在数千条的水平。这种规模的表,使用 SQLite 正合适。
使用 SQLiteStudio 创建本地数据库文件
SQLiteStudio 已经升级到 3.4.4,下载地址:https://download.ihsdus.cn/down/2023down/12/27/SQLiteStudio-3.4.4.exe?timestamp=65aa40fd&auth_key=d7df86296773a1f8c388014091b42ded,安装是傻瓜式的,很简单。
使用 SQLiteStudio 创建了一个数据库文件:I2oT.db,保存在工程以外的目录,我干脆是在桌面创建了这个文件。在 I2oT.db 文件中创建了 4 个表。

GatewayProfile 记录网关访问的两个参数,SubsetModels 是上面所说的主表,它的结构如下:

二级表 NTUDefinitions 的结构如下:

三级表 NTUDefintionOptions 的结构如下:

这三张表是一个典型的主键外联引用(Foreign Key)关系。
这三张表中的数据是使用 SQLiteStudio 的 SQL 语句直接 INSERT 进去的。
将本地数据库文件添加到项目中
在已经创建好的 I2oT 项目中,将初始化好的 I2oT.db 文件拷贝粘贴到项目的 I2oT.Android/Assets 目录中,如下图所示。

按下 F4 键,在属性中设置“始终复制”。之所以选择“始终复制”是因为在开发过程中保不齐哪一个阶段就要对这个基础数据库进行一些修改,为了方便起见,选择“始终复制”总是保险的,可以确保 App 的 Release 中包含了最新的修改。

在应用中对其进行复制
从桌面粘贴到项目中的 I2oT.db 文件是一种“资产”——就像它存放在的 Assets 目录中一样,要把它复制到可读写文件夹才是运行时的“数据库”。要在运行时访问数据库文件,按照如下步骤做。
修改 MainActivity.cs 文件,在
global::Xamarin.Forms.Forms.Init(this, savedInstanceState);
和
LoadApplication(new App());
语句之间增加复制数据库文件的语句如下。
using System;
using Android.App;
using Android.Content.PM;
using Android.Runtime;
using Android.OS;
using System.IO;
using Xamarin.Forms;namespace I2oT.Droid
{[Activity(Label = "I2oT", Icon = "@mipmap/icon", Theme = "@style/MainTheme",MainLauncher = true,ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize )]public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity{protected override void OnCreate(Bundle savedInstanceState){base.OnCreate(savedInstanceState);Xamarin.Essentials.Platform.Init(this, savedInstanceState);global::Xamarin.Forms.Forms.Init(this, savedInstanceState);// Copy I2oT.db as an asset to access directory.string dbFileName = "I2oT.db";var dbFolder = System.Environment.GetFolderPath(System.Environment.SpecialFolder.LocalApplicationData);var dbFile = Path.Combine(dbFolder, dbFileName);if (!File.Exists(dbFile)){FileStream writeStream = new FileStream(dbFile, FileMode.OpenOrCreate, FileAccess.Write);Android.App.Application.Context.Assets.Open(dbFileName).CopyTo(writeStream);}LoadApplication(new App());}public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Android.Content.PM.Permission[] grantResults){Xamarin.Essentials.Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults);base.OnRequestPermissionsResult(requestCode, permissions, grantResults);}}
}
不论 Android 的版本如何,将代码中的 dbFolder 设置为
System.Environment.SpecialFolder.LocalApplicationData
或者
System.Environment.SpecialFolder.Personal
总是安全的做法,这是因为当一个 App 安装到虚拟机或者真机后,在默认状态下,App 对上面提到的这两个文件夹总是有完全的访问权限。
上面的代码将判断文件是否存在作为是否重新拷贝的依据,这个依据有时并不充分。我们可以在 I2oT.db 中增加一个 MyVersion 的表,这个表可以由以下代码创建:
DROP TABLE IF EXISTS AppVersion;
CREATE TABLE AppVersion(Version TEXT NOT NULL DEFAULT "1.0.0.0",NeedCopy BOOLEAN NOT NULL DEFAULT 1
);
INSERT INTO AppVersion VALUES ("1.0.8.0",1);
创建之后,在每一次发布 Release 之前,手动地将 AppVersion 表的 Version 更新到编译的版本号,NeedCopy 设置为 True。在MainActivity.cs 中,除了判断数据库文件是否存在以外,读取一下 资产目录(xxx.Android/Assets)中的 I2oT.db 中的 AppVersion 表,如果 NeedCopy 为真,就关闭数据库连接,然后再将资产文件拷贝到 Environment.SpecialFolder.LocalApplicationData 文件夹中。
当然,还可以有更多的方法来判断是否需要拷贝数据库文件,这完全取决于 App 的功能需求。
实现的截图
在 App 的“设置”页中的“子设备物模型”是页面入口,点击进入到 SubsetModels 展示页,点击某一个产品型号,进入到物模型展示页,其后台的绑定数据来自于 NTUDefinitions 表,点击某一个属性,展示该属性的详情,如果该属性是枚举型的话,还通过 Picker 控件展示其可选的枚举值。




总结
虽然将本地数据库文件打包发布到 Android App 不是一个新话题了,但从某度上得到的搜索结果并不多,而且貌似都是从 stackoverflow 上翻译过来的,依托的版本也千差万别,在 Xamarin.Forms 环境中的使用例子更是少得可怜。
愿我的编码为这个问题增加一点可行的答案。欢迎指正,一同提升。
相关文章:
PLC-IoT 网关开发札记(5):将本地数据库作为资产打包发布到 App
App需求:保存物模型 什么是物模型 在项目开发中,用到了本地数据库,这个本地数据库记录了系统的物模型。所谓物模型就是对某一个设备的可操纵属性的定义,每一个设备包括了一个或者多个属性,通过获取这些属性的当前值可…...
固态硬盘优化设置
目录 前言: 关闭Windows Search 禁用系统保护(不建议) 不建议禁用系统保护原因 关闭碎片整理【机械硬盘】 提升固态硬盘速度 开启TRIM 合理使用固态硬盘的容量 正确关机 关闭开机自启 前言: 电脑配备固态硬盘就能一劳…...
SpringBoot跨域问题解决
前端访问后台接口时,浏览器报错,跨域无法访问。 报错信息如下: Response to preflight request doesnt pass access control check: No Access-Control-Allow-Origin header is present on the requested resource. 经过一番百度之后&#…...
FindMy技术与相机结合
FindMy是苹果公司提供的设备追踪服务,用来帮助用户定位丢失的设备。自苹果公司开放Findmy网络之后,FindMy技术便与各种生活设备相结合,比如与相机的结合。 想象一下,你正在外出办事或者旅行时,突然意识到相机丢了&…...
Windows WSL2 占用磁盘空间清理释放
目前工作中时常用到WSL2(Ubuntu20.04),在使用一段时间后会发现WSL2所占用磁盘空间越来越多,体现在WSL2之上安装Linux分发对应的vhdx虚拟磁盘文件体积越来越大,会占用Windows自身空间,即使手动清理了Linux分…...
2022 年全国职业院校技能大赛高职组云计算赛项试卷部分解析
2022 年全国职业院校技能大赛高职组云计算赛项试卷部分解析 【赛程名称】高职组-云计算赛项第一场-私有云【任务 1】私有云服务搭建[10 分]【题目 2】Yum 源配置[0.5 分]【题目 3】配置无秘钥 ssh[0.5 分]【题目 4】基础安装[0.5 分]【题目 5】数据库安装与调优[0.5 分]【题目 …...
2.C语言——控制语句
控制语句 1.分支语句/判断语句if 语句if...else 语句if...else if...else语句 switch语句 2.循环语句 while 语句 do...while 语句 for 语句 3.转向语句 break continue go to 1.分支语句/判断语句 if 语句 if(boolean_expression) { /* 如果布尔表达式为真将执行的语句 */ } …...
Linux网络之PXE高效批量装机、Kickstart全自动化安装
一. PXE网络装机简介和相关知识 1. 常见的三种系统安装方式和相关文件 ① 三种系统安装方式 u启动安装:在U盘中下载相关的安装系统及镜像文件,u盘插机安装 光驱安装:将带有所需系统的光盘放进电脑服务器中,按照官方引导装机 …...
react umi/max 页签(react-activation)
思路:通过react-activation实现页面缓存,通过umi-plugin-keep-alive将react-activation注入umi框架,封装页签组件最后通过路由的wrappers属性引入页面。 浏览本博客之前先看一下我的博客实现的功能是否满足需求,实现功能…...
计算机网络编程
一、计算机网络(概述、简介) 说起网络,相信大家都不陌生,把分散在不同地点的计算机设备,通过传输介质、通信设施和网络通信协议,实现资源共享和信息传输的系统,我们称之为:计算机网…...
【计算机网络实训】期末考题-路由重分发+三层交换机VLAN间路由
路由重分发三层交换机VLAN间路由 实验目的实验内容及步骤仿真配置环境搭建要求:实验步骤配置Switch0配置Switch1配置交换机Multilayer Switch 0路由器Router0上的配置路由器Router1的配置 测试PC0 自动获取地址成功,PC0 可 ping 通 switch0,网…...
git 常规操作及设置
git 常规操作及设置 Git是一个分布式版本控制系统,可以用来跟踪文件的修改历史并与其他人进行协作开发。下面是一些常见的Git操作及设置: 初始化仓库:使用命令git init在当前目录创建一个新的Git仓库。 克隆仓库:使用命令git clo…...
element中表格组件的row-class-name和class-name属性的使用以及无效处理
1.这两个属性的使用,row-class-name用在el-table标签上,class-name用在el-table-column标签上。两个属性即可绑定类名也可绑定函数 <!-- 这里是绑定函数,也可以绑定类名 --> <el-table :data"tableData" selection-chang…...
【AI理论知识】EM算法
基本定义 期望最大化算法(Expectation-Maximization,EM算法)是一种用于估计包含潜在变量的概率模型参数的迭代优化算法。EM算法的主要目标是在存在未观测数据或缺失数据的情况下,通过迭代地进行期望步骤(E步ÿ…...
03 OSPF
参考文章 1 初步认识OSPF的大致内容(第三课)-CSDN博客 2...
node.js(express.js)+mysql实现注册功能
文章目录 实现步骤一、获取客户端提交到服务器的用户信息,对表单中的数据,进行合法性的效验 代码如下:二、检测用户名是否被占用三、对密码进行加密四、插入新用户(完整代码)总结 实现步骤 一、获取客户端提交到服务器的用户信息…...
AI绘画Stable Diffusion进阶使用
本文讲解,模型底模,VAE美化模型,Lora模型,hypernetwork。 文本Stable Diffusion 简称sd 欢迎关注 使用模型 C站:https://civitai.com/ huggingface:https://huggingface.co/models?pipeline_tagtext-to-…...
C 练习实例33 - 质数(素数)判断
题目:判断一个数字是否为质数。 程序分析:质数(prime number)又称素数,有无限个。一个大于1的自然数,除了1和它本身外,不能被其他自然数整除。 这题做过很多遍了,懂得都懂。 代码…...
docker环境下mongo副本集的部署及异常修复
最近更换了办公地点。部署在本地docker环境里的mongo数据库不能使用了。原因是本地的ip地址变更。以前的mongo副本集的配置需要更新。处理完后,索性重新记录一下mongo副本集在docker中的部署流程。 mongo的事务及副本集 我们先了解一下什么是事务,事务…...
【Java】Maven的安装与配置
初识Maven Maven是专门用于管理和构建Java项目的工具,它的主要功能有: 提供了一套标准化的项目结构 提供了一套标准化的构建流程(编译,测试,打包,发布……) 提供了一套依赖管理机制 标准化的…...
论文解读:交大港大上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(二)
HoST框架核心实现方法详解 - 论文深度解读(第二部分) 《Learning Humanoid Standing-up Control across Diverse Postures》 系列文章: 论文深度解读 + 算法与代码分析(二) 作者机构: 上海AI Lab, 上海交通大学, 香港大学, 浙江大学, 香港中文大学 论文主题: 人形机器人…...
PHP和Node.js哪个更爽?
先说结论,rust完胜。 php:laravel,swoole,webman,最开始在苏宁的时候写了几年php,当时觉得php真的是世界上最好的语言,因为当初活在舒适圈里,不愿意跳出来,就好比当初活在…...
阿里云ACP云计算备考笔记 (5)——弹性伸缩
目录 第一章 概述 第二章 弹性伸缩简介 1、弹性伸缩 2、垂直伸缩 3、优势 4、应用场景 ① 无规律的业务量波动 ② 有规律的业务量波动 ③ 无明显业务量波动 ④ 混合型业务 ⑤ 消息通知 ⑥ 生命周期挂钩 ⑦ 自定义方式 ⑧ 滚的升级 5、使用限制 第三章 主要定义 …...
循环冗余码校验CRC码 算法步骤+详细实例计算
通信过程:(白话解释) 我们将原始待发送的消息称为 M M M,依据发送接收消息双方约定的生成多项式 G ( x ) G(x) G(x)(意思就是 G ( x ) G(x) G(x) 是已知的)࿰…...
Cloudflare 从 Nginx 到 Pingora:性能、效率与安全的全面升级
在互联网的快速发展中,高性能、高效率和高安全性的网络服务成为了各大互联网基础设施提供商的核心追求。Cloudflare 作为全球领先的互联网安全和基础设施公司,近期做出了一个重大技术决策:弃用长期使用的 Nginx,转而采用其内部开发…...
GitHub 趋势日报 (2025年06月08日)
📊 由 TrendForge 系统生成 | 🌐 https://trendforge.devlive.org/ 🌐 本日报中的项目描述已自动翻译为中文 📈 今日获星趋势图 今日获星趋势图 884 cognee 566 dify 414 HumanSystemOptimization 414 omni-tools 321 note-gen …...
SQL慢可能是触发了ring buffer
简介 最近在进行 postgresql 性能排查的时候,发现 PG 在某一个时间并行执行的 SQL 变得特别慢。最后通过监控监观察到并行发起得时间 buffers_alloc 就急速上升,且低水位伴随在整个慢 SQL,一直是 buferIO 的等待事件,此时也没有其他会话的争抢。SQL 虽然不是高效 SQL ,但…...
android13 app的触摸问题定位分析流程
一、知识点 一般来说,触摸问题都是app层面出问题,我们可以在ViewRootImpl.java添加log的方式定位;如果是touchableRegion的计算问题,就会相对比较麻烦了,需要通过adb shell dumpsys input > input.log指令,且通过打印堆栈的方式,逐步定位问题,并找到修改方案。 问题…...
uniapp 实现腾讯云IM群文件上传下载功能
UniApp 集成腾讯云IM实现群文件上传下载功能全攻略 一、功能背景与技术选型 在团队协作场景中,群文件共享是核心需求之一。本文将介绍如何基于腾讯云IMCOS,在uniapp中实现: 群内文件上传/下载文件元数据管理下载进度追踪跨平台文件预览 二…...
高防服务器价格高原因分析
高防服务器的价格较高,主要是由于其特殊的防御机制、硬件配置、运营维护等多方面的综合成本。以下从技术、资源和服务三个维度详细解析高防服务器昂贵的原因: 一、硬件与技术投入 大带宽需求 DDoS攻击通过占用大量带宽资源瘫痪目标服务器,因此…...
