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 环境中的使用例子更是少得可怜。

愿我的编码为这个问题增加一点可行的答案。欢迎指正,一同提升。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于CanvasLabel的Leaflet矢量数据免切片属性标注实践

目录 前言 一、Leaflet.CanvasLabel 1、开源地址 2、设置参数说明 二、组件集成 1、新建html文件 2、声明样式 3、定义矢量文本渲染器 4、定义地图 5、添加矢量数据 6、最终效果 总结 前言 在一般的业务场景中,针对小量的矢量数据,比如POI兴…

Opncv模板匹配 单模板匹配 多模板匹配

目录 问题引入 单模板匹配 ①模板匹配函数: ②查找最值和极值的坐标和值: 整体流程原理介绍 实例代码介绍: 多模板匹配 ①定义阈值 ②zip函数 整体流程原理介绍 实例代码: 问题引入 下面有请我们的陶大郎登场 这张图片是我们的陶大郎,我们接下来将利用陶大郎来介绍…

Eclipses安装教程

一、下载开发工具包 1、开发工具包JDK 下载地址链接:https://www.oracle.com/cn/java/technologies/downloads/ 下载教程: 1)点击链接,可以跳转到页面 2)下滑页面,找到开发工具包 3) 记住下载之…

Linux shell编程学习笔记41:lsblk命令

边缘计算的挑战和机遇 边缘计算面临着数据安全与隐私保护、网络稳定性等挑战,但同时也带来了更强的实时性和本地处理能力,为企业降低了成本和压力,提高了数据处理效率。因此,边缘计算既带来了挑战也带来了机遇,需要我…

关于js的BigInt的使用与注意事项

说明 BigInt是一种内置对象,提供了一种方法来表示大于2^53 - 1 的整数,2^53 - 1 为Number可以表示的最大数字,BigInt可以突破限制,可以用任意精度表示整数,超出Number的安全整数限制,也可以安全地存储和操…

回归预测 | Matlab基于ABC-SVR人工蜂群算法优化支持向量机的数据多输入单输出回归预测

回归预测 | Matlab基于ABC-SVR人工蜂群算法优化支持向量机的数据多输入单输出回归预测 目录 回归预测 | Matlab基于ABC-SVR人工蜂群算法优化支持向量机的数据多输入单输出回归预测预测效果基本描述程序设计参考资料 预测效果 基本描述 1.Matlab基于ABC-SVR人工蜂群算法优化支持…

C++ 知识列表【图】

举例C的设计模式和智能指针 当谈到 C 的设计模式时,以下是一些常见的设计模式: 工厂模式(Factory Pattern):用于创建对象的模式,隐藏了对象的具体实现细节,只暴露一个公共接口来创建对象。 单例…

基于Word2vec词聚类的关键词实现

一.基于Word2vec词聚类的关键词步骤 基于Word2Vec的词聚类关键词提取包括以下步骤: 1.准备文本数据:收集或准备文本数据,可以是单一文档或文档集合,涵盖关键词提取的领域。2.文本预处理:清洗文本数据,去除…

mac 安装配置oh-my-zsh

1. 安装brew /bin/zsh -c "$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/raw/master/Homebrew.sh)" 按照步骤安装即可 安装完成查看版本 brew -v 2. 安装zsh brew install zsh 查看版本 zsh --version 3. 安装oh-my-zsh github官网链…

泛型..

1.泛型 所谓泛型 其实就是一种类型参数(我们平常所见到的参数指的就是方法中的参数 他接收有外界传递来的值 然后在方法中进行使用) 并且还提高了代码的复用率 何以见得提高了代码的复用率 其实就是通过对比使用了泛型技术和没有使用泛型技术之间的区别: 以下是没有…

Vue学习笔记9--vuex(专门在Vue中实现集中式状态(数据)管理的一个Vue插件)

一、vuex是什么? 概念:专门在Vue中实现集中式状态(数据)管理的一个Vue插件,对vue应用中多个组件的共享状态进行集中式的管理(读/写),也是一种组件间通信的方式,且适用于…

MySQL索引优化:深入理解索引下推原理与实践

随着MySQL的不断发展和升级,每个版本都为数据库性能和查询优化带来了新的特性。在MySQL 5.6中,引入了一个重要的优化特性——索引下推(Index Condition Pushdown,简称ICP)。ICP能够在某些查询场景下显著提高查询性能&a…

1.使用分布式文件系统Minio管理文件

分布式文件系统DFS分类 文件系统 文件系统是操作系统用于组织管理存储设备(磁盘)或分区上文件信息的方法和数据结构,负责对文件存储设备空间进行组织和分配,并对存入文件进行保护和检索 文件系统是负责管理和存储文件的系统软件,操作系统通过文件系统提供的接口去…

html 会跳舞的时间动画特效

下面是是代码&#xff1a; <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns"http://www.w3.org/1999/xhtml"> <head> <meta h…

Python环境下一维时间序列信号的时频脊线追踪方法

瞬时频率是分析调频信号的一个重要参数&#xff0c;它表示了信号中的特征频率随时间的变化。使用短时傅里叶变换或小波变换获得信号的时频表示TFR后&#xff0c;从TFR中估计信号各分量的瞬时频率&#xff0c;即可获得信号中的特征信息。在TFR中&#xff0c;调频信号的特征分量通…

考试查分场景重保背后,我们如何进行可用性测试

作者&#xff1a;暮角 随着通过互联网音视频与知识建立连接的新学习方式在全国范围内迅速普及&#xff0c;在线教育/认证考试的用户规模呈井喷式增长。但教育容不得半点马虎与妥协&#xff0c;伴随用户规模不断增长&#xff0c;保证系统稳定性、有效避免千万考生考试时遭遇故障…

JAVA RPC Thrift基操实现与微服务间调用

一、Thrift 基操实现 1.1 thrift文件 namespace java com.zn.opit.thrift.helloworldservice HelloWorldService {string sayHello(1:string username) }1.2 执行命令生成Java文件 thrift -r --gen java helloworld.thrift生成代码HelloWorldService接口如下 /*** Autogene…

Oracle Vagrant Box 无法登录的2个问题

安装Oracle Database 19c 的 VagrantBox &#xff0c;非常顺利&#xff0c;耗时如下&#xff1a; real 30m36.783s user 0m0.000s sys 0m0.047s前面一切顺利&#xff0c;但是vagrant ssh和vagrant putty均不能登录虚机。我的环境是Windows 11&#xff0c;Vagrant 2.…

安卓Spinner文字看不清

Holo主题安卓13的Spinner文字看不清&#xff0c;明明已经解决了&#xff0c;又忘记了。 spinner.setOnItemSelectedListener(new Spinner.OnItemSelectedListener() {public void onItemSelected(AdapterView<?> arg0, View arg1, int arg2, long arg3) {TextView textV…

【Python】使用Anaconda创建PyTorch深度学习虚拟环境

使用Anaconda Prompt 查看环境: conda env list 创建虚拟环境&#xff08;python3.10&#xff09;&#xff1a; conda create -n pytorch python3.10 激活创建的环境&#xff1a; conda activate pytorch在虚拟环境内安装PyTorch&#xff1a; 【Python】CUDA11.7/11.8安…