开发设备驱动程序

内置于 Wolfram 语言中的 Wolfram Device Framework(Wolfram 设备框架)创建了代表外部设备的符号对象,简化与设备间的交互,使得编写设备驱动程序变得容易. 框架中的设备可以表示一个实际的设备,诸如,温度感应器,或封装一个端口,例如串行端口. 该教程为设备驱动程序的的高级用户和开发人员解释了框架的内部原理. 用户级别的与设备交互的详情,请参阅使用连接设备.
对于大多数设备,组成框架的函数并不直接关注设备编程或底层硬件通讯软件. 基于硬件设备的不同,这种实现也会不一样. 例如,它可能会包含用 C 编写底层设备程序(或由第三方提供),然后使用 WSTP API 把这些程序的接口连接到程序包级别的 Wolfram 语言函数. 这些函数会由框架以合适的方式串起来,创建 Wolfram 语言设备驱动程序. 另一种实现方法是可能完全避免底层的 C 编程,使用 Wolfram 语言的 .NET/Link 功能,从 Wolfram 语言函数内部的驱动程序级别与设备交互. 框架的职能就是整合这些函数,通过用户级函数的集合,提供与设备接口的统一方法.
综述
创建设备驱动程序和封装与设备低级交互的开发级函数收集在 DeviceFramework` 上下文,详情请参阅 设备框架函数. 设备驱动程序的核心是函数 DeviceClassRegister.
DeviceFramework`DeviceClassRegister["class",opts]
对指定的类注册设备驱动程序,它的操作由选项 opts 定义
DeviceFramework`DeviceClassRegister["class","parent",opts]
注册与 parent 相同的类,除了由选项 opts 定义的操作
创建设备驱动程序.
一个参变量格式 DeviceClassRegister["class",opts] 创建一个典型的驱动程序,两个参变量格式 DeviceClassRegister["class","parent",opts] 实现基本的继承并允许你扩展父类而无需重复它的定义.
该驱动程序只实现一个读操作. 这类设备不可以写:
扩展父类使其可进行写操作:
子类设备可以读和写:
DeviceClassRegister 的选项决定各种设备和驱动程序的属性,并定义框架将调用的函数,以便发现设备的类,执行一般设备预期执行的操作,例如:打开设备的连接,进行配置,读入并写入设备,执行命令,关闭设备的连接,以及释放外部的资源.
选项
默认值
"FindFunction"{}&
如何发现设备
"OpenFunction"None
打开设备执行的函数
"ConfigureFunction"None
如何配置设备
"ReadFunction"None
如何从设备中读取
"ReadBufferFunction"None
如何从设备缓冲区中读取
"WriteFunction"None
如何写入设备
"WriteBufferFunction"None
如何写入设备缓冲区
"ExecuteFunction"None
如何在设备中执行命令
"ExecuteAsynchronousFunction"None
如何异步执行命令
"CloseFunction"None
关闭设备执行的函数
"Properties"{}
标准化的属性
"DeviceIconFunction"None
如何为设备对象创建图标
"DriverVersion"Missing["NotAvailable"]
驱动程序的版本号
重要的设备驱动程序选项.
更一般的工作流可能涉及在打开设备前创建设备管理器(初始化对象和它的句柄),在给用户公开一个新的设备对象之前预配置设备属性. 在设备关闭后,初始化对象会在释放阶段被处理.
也可以为设置和获取标准化设备属性定义句柄,公开原始设备属性和方法,并让用户访问与设备关联的输入和输出流. 驱动程序可以为创建多个设备类以及指定其他自定义句柄定义规则.
选项
默认值
"OpenManagerFunction"None
执行打开设备管理器的函数
"MakeManagerHandleFunction"Identity
如何为设备管理器创建句柄
"PreconfigureFunction"{}&
如何预配置一个新设备
"ReleaseFunction"Null&
如何处理设备管理器对象
"ReadTimeSeriesFunction"Automatic
"StatusLabelFunction"Automatic
如何为设备对象创建状态标签
"NativeProperties"{}
原始属性
"NativeMethods"{}
原始方法
"GetPropertyFunction"DeviceFramework`DeviceGetProperty
如何获取标准化属性
"SetPropertyFunction"DeviceFramework`DeviceSetProperty
如何设置标准化属性
"GetNativePropertyFunction"Automatic
如何获取原始属性
"SetNativePropertyFunction"Automatic
如何设置原始属性
"NativeIDFunction"None
如何获取设备的原始 ID
"OpenReadFunction"None
如何打开与设备关联的输入流
"OpenWriteFunction"None
如何打开与设备关联的输出流
"DeregisterOnClose"False
是否在设备关闭后注销设备
"Singleton"Automatic
为多个设备创建规则
高级设备驱动程序选项.
下一节将详细讨论 DeviceClassRegister 的选项. 与其他 Wolfram 语言选项一样,没有任何一个 DeviceClassRegister 选项是强制性的,在一个特定的驱动程序实现中,其中的许多选项会被省略.
下面的例子,是一个简单设备的驱动程序,可以执行设备的读取操作,且封装在一个文件中:
BeginPackage["MyDevice`"]

Begin["`Private`"]

read[__] := XXXXX

DeviceFramework`DeviceClassRegister["MyDevice", "ReadFunction" read]

End[]

EndPackage[]
一个基本的驱动程序文件 MyDevice.m.
为了自动被框架发现, classDeviceClassRegister["class",] 语句必须存在于 Wolfram 语言程序包 class.m 中. 注意该命名规则,DeviceClassRegister 的第一个字符串参数必须与驱动程序文件名匹配. 另外,驱动程序必须放在下列位置之一:
如果驱动程序没有遵循这个规则,它仍然可以被使用,但是它必须被明确加载进 Wolfram 语言会话中,例如,使用 Get.
若要使驱动程序在特定平台中可用,你可以在 If 中封装 DeviceClassRegister[] 语句.
该驱动程序只可以用在 Mac OS X:
设备驱动程序选项
DeviceClassRegister 的选项,也被称为设备驱动程序选项,允许您创建适合设备的驱动程序. 包含Function(驱动程序函数)单词的驱动程序选项的名称大致对应于用户级函数. 比如,"ConfigureFunction" 在执行 DeviceConfigure 时被调用,"FindFunction"FindDevices 中被调用等. 然而,也有用户级函数跨越多个驱动程序函数,如下所示. 其中一个例子是设备打开序列,在 DeviceOpen 中执行,包含 "OpenManagerFunction""MakeManagerHandleFunction""OpenFunction""PreconfigureFunction".
提供给许多驱动程序函数的第一个参量是 {ihandle, dhandle} 形式的列表,其中 ihandle 是初始化对象的句柄(或管理器句柄,参阅 "MakeManagerHandleFunction"),dhandle 是设备句柄(参阅 "OpenFunction"). 这个规则应用于 "ConfigureFunction""ReadFunction" 和其他驱动程序函数,让你使用任何必要的句柄,跳过其他句柄. 比如,如果你只需要 ihandle,则驱动程序函数会实现为 f[{ih_, _}, args___]:=(* use ih *);若只用 dhandle,你只需为 f[{_, dh_}, args___]:= 提供规则;若要跳过这两个句柄,使用 f[_, args___]:=,等等. 在下文中你会发现很多这样的用法.
剩下的提供给驱动程序函数的参变量一般是从父顶层函数传过来,可能从列表封装中剥离. 例如,对于 DeviceRead[dev,param]DeviceRead[dev,{param}]"ReadFunction"f 的实现会收到一个像 f[handles,param] 的调用;对于 DeviceRead[dev,{param1,param2,}],函数会被调用为 f[handles,param1,param2,];对于 DeviceRead[dev],调用形式为 f[handles],等等. 你应该期待所有父函数文档描述的组合可能,如果某些组合没有意义,会发出适当的错误信息. 如果失败,驱动程序函数会返回 $Failed,除非那些在本教程中特殊指出的.
当报告错误时,如果你的驱动程序导出这样的符号时,习惯上,它会与你自己的用户可见符号关联信息. 或者,你可以为你的类分配信息给特殊的符号 DeviceFramework`Devices`class. 该教程给出这个规范的一些例子. 欲了解更多,请参阅 "消息".
当然,如果你的驱动程序不能实现某些驱动程序函数,你则不用担心那些函数的错误信息. 这些信息是由框架发出的.
这个伪驱动程序没有实现任何驱动程序函数:
你可以打开一个伪设备,但是尝试执行任何特定操作均会导致错误:
在阅读本教程时,你已经注意到,大部分例子只是应用顶层的 Wolfram 语言指令. 这是有意这么做的,这样你可以重新计算范例,没有任何指定设备可以学习使用框架. 实际上的驱动程序会,当然会调用外部程序. 请参阅 "调用外部程序" 获取概述以及下面的范例了解典型的应用.
除了本教程的范例,你还可以参考 Wolfram 语言文档中的演示驱动程序实现. 例如,想知道如何实现 "ExecuteAsynchronousFunction",请参见 DeviceExecuteAsynchronous 的演示驱动程序.
执行下面命令,查验 "ExecuteAsynchronousFunction" 的演示实现:

"FindFunction"

为了让你的驱动程序可被发现,你的驱动程序必须有 "FindFunction" 选项. "FindFunction"f 指定 f[] 应被 FindDevices[class] 调用来找到给定类的设备. 函数 f 不接收参变量,应该返回 {{arg11,arg12,},{arg21,arg22,},} 形式的输出,其中 {argi1,argi2,} 是参变量的列表,它可以提供给 DeviceOpen[class,{argi1,argi2,}] 以便打开设备号 i.
由函数 f 返回的典型参变量可以是端口名称、GPIB 地址或照相机名称. 如果设备可以无需参变量而打开,那么 f 可以返回 {{}}&.
如果驱动程序中有 "FindFunction",用户则可以使用你的设备类,而无需首先明确开启设备.
没有合适 "FindFunction" 的设备不会被自动发现:
在使用前,你必须打开此设备类:
通过提供 "FindFunction" 选项,驱动程序得以扩展:
这类设备可以自动被框架打开:
在执行要求的操作后,设备仍然开启,直到它们被明确关闭:

"OpenManagerFunction"

"OpenManagerFunction"f 指定 f[args] 应该在设备打开序列开始时被调用,以便创建一个初始化对象,它也被称为驱动程序管理器. args 与用户提供给 DeviceOpen["class",{args}] 的一样. 在设备寿命周期结束时,取消初始化设置阶段,返回的值会传给 "ReleaseFunction". 使用 DeviceInitObject 在设备关闭前的任何时间获取初始化对象.
您可能希望向指定 "OpenManagerFunction",例如,打开一个与外部程序的 WSTP 连接. 在这种情况,该函数将返回一个 LinkObject,您可以在 "ReleaseFunction" 中用 LinkClose 关闭.
该驱动程序保持在类中对打开设备的计数:
当设备打开(关闭)时,该计数器会增加(减少):

"MakeManagerHandleFunction"

您可以通过指定 "MakeManagerHandleFunction"f 创建一个单独的句柄来初始化对象(管理器句柄). 函数 f[obj]"OpenManagerFunction" 之后被调用,并提供由 "OpenManagerFunction" 创建的初始化对象 obj . 返回的值被假设为管理器句柄. 如果没有 "MakeManagerHandleFunction",管理器句柄自身会被假设为初始化对象. 管理器句柄会被传给许多驱动程序函数,作为第一个参数的一部分(见下文).
管理器句柄会被传给许多驱动程序函数,作为第一个参变量的一部分. 在其他时候,您可以用 DeviceManagerHandle 获取句柄.
管理器句柄的例子可以是插座句柄或一个 .NET 对象.

"OpenFunction"

"OpenFunction"f 指定的函数是由框架调用的主函数,对应于用户对 DeviceOpen 的调用. f[ihandle,args] 接收由 "MakeManagerHandleFunction" 创建的的管理器句柄 ihandle 以及用户提供给 DeviceOpen["class",{args}] 的参数 args . 返回值被称为设备句柄. 此句柄以及管理器句柄会被传给许多驱动程序函数. 另外,您可以用 DeviceHandle 进行检索.
框架尝试在由 "OpenFunction" 返回的设备句柄以及由 DeviceOpen 返回的顶级 DeviceObject 间维持一对一的响应. 因此强烈建议您的设备句柄是唯一的或至少不会与由其他你无法控制的驱动程序创建的句柄相冲突. 一个简单的实现方法就是用 CreateUUID 产生您的句柄. 或者,您可以以 "com.company.class"[ ] 形式或类似方法返回句柄.
该类设备打印打开参数,没有使用 ihandle 参数:
获取设备句柄,并用它来取回设备对象:
如果省略 "OpenFunction",框架会为类中任何新设备分配随机的设备句柄.
该驱动程序没有实现 "OpenFunction"
你仍然可以使用由框架提供的默认设备句柄:

"OpenReadFunction"

"OpenReadFunction"f 指定 f[{ihandle,dhandle},args] 应该在设备打开序列期间被调用,在 "OpenFunction" 之后,打开任何与设备相关联的输入流. 第一个参量 {ihandle,dhandle}是许多驱动程序函数常见的参量,其中 ihandle 是管理器句柄,dhandle 是设备句柄. 参变量 args 是那些提供给 DeviceOpen["class",{args}] 的. 返回值必须是输入流对象或输入流对象列表.
演示驱动程序提供一个样本,实现设备的输入和输出流:

"OpenWriteFunction"

"OpenWriteFunction"f 指定 f[{ihandle,dhandle},args] 应该在设备打开序列期间被调用,在 "OpenFunction" 之后,打开任何与设备相关联的输入流. 第一个参量 {ihandle,dhandle}是许多驱动程序函数常见的参量,其中 ihandle 是管理器句柄,dhandle 是设备句柄. 参变量 args 是那些提供给 DeviceOpen["class",{args}] 的. 返回值必须是输入流对象或输入流对象列表.
演示驱动程序提供一个样本,实现设备的输入和输出流:

"PreconfigureFunction"

"PreconfigureFunction"f 指定的函数决定设备打开序列. 对于将返回给用户的设备对象 devf[dev] 被保证在成功调用 DeviceOpen 之后被调用,但是在初始化配置之前,重新应用于设备的顶层设备属性(如果之前在设备中被设置过)或设置 类属性(如果由驱动程序定义). "PreconfigureFunction" 必须通过 "PreconfigureFunction" 自身、"OpenFunction"或在设备打开序列中的任何其他函数返回由驱动程序配置的属性清单. 这些属性不会被框架改变直到 DeviceOpen 结束. 会返回 AllNone.
该驱动程序基于设备 ID 设置设备属性 "parity"
"PreconfigureFunction" 也很方便为打开和关闭设备状态设置状态标签.
该驱动程序在20秒后自动关闭一个打开的设备,并显示时间的运行指示灯直到关闭:

"ConfigureFunction"

"ConfigureFunction"f 指定 f[{ihandle,dhandle},args] 应该被调用以便响应顶层命令 DeviceConfigure[dev,{args}] 来配置设备. 第一个参量 {ihandle,dhandle}是许多驱动程序函数常见的参量,其中 ihandle 是管理器句柄,dhandle 是设备句柄. 该函数的返回值被忽略.
该驱动程序中的 "ConfigureFunction" 设置顶层属性:
通过调用 DeviceConfigure 打开设备并配置:
以下是属性的新值:

"ReadFunction"

"ReadFunction"f 指明 f[{ihandle,dhandle},args] 应该被调用以响应顶层命令 DeviceRead[dev,{args}] 来从设备中读取数据. 第一个参量 {ihandle,dhandle}是许多驱动程序函数常见的参量,其中 ihandle 是管理器句柄,dhandle 是设备句柄. 该函数的返回值会作为 DeviceRead[] 命令的输出传递给用户.
该类设备从字符串中读取连续字符. 每个设备的当前字符串位置存在关联中,其中设备句柄作为关键字. 关键字添加入 "OpenFunction" 的关联中,在 "CloseFunction" 中去除:
打开设备并从中读取一个字符:
打开另一个设备并读取多个字符:
当前读取指针记录每个开启的设备:
再次从第一个设备中读取,给出下一个字符串,直到设备完整读取字符串:
关闭设备,清除计数器:
"ReadFunction" 的实现会有益于强大参变量的检验. 必要时,需要的错误信息可以分配给对应于 DeviceFramework`Devices` 上下文中类名称的符号.
该驱动程序让设备读取实数或整数随机值,并实现初步参变量检查:
打开设备并用合法参变量调用 DeviceRead
用非法参变量调用 DeviceRead,触发错误:

"ReadBufferFunction"

"ReadBufferFunction"f 指定 f[{ihandle,dhandle},] 应被调用以便响应顶层命令 DeviceReadBuffer[dev,args],从设备缓存中读取数据. 第一个参量 {ihandle,dhandle}是许多驱动程序函数常见的参量,其中 ihandle 是管理器句柄,dhandle 是设备句柄. 传给函数 f 的参变量如下:
DeviceReadBuffer[dev]
f[{ihandle,dhandle},Automatic]
DeviceReadBuffer[dev,crit]
f[{ihandle,dhandle},crit,Automatic]
DeviceReadBuffer[dev,crit,params]
f[{ihandle,dhandle},crit,params]
"ReadBufferFunction" 可能的特征值.
f 的返回值会传给用户作为 DeviceReadBuffer[] 命令的输出.
使用 "ReadBufferFunction" 的典型范例是从串行设备中读取字节序列.

"WriteFunction"

"WriteFunction"f 指明 f[{ihandle,dhandle},args] 应被调用以便响应顶层命令 DeviceWrite[dev,{args}],把数据写入设备. 第一个参量 {ihandle,dhandle}是许多驱动程序函数常见的参量,其中 ihandle 是管理器句柄,dhandle 是设备句柄. 该函数的返回值被忽略.
该驱动程序为每个 DeviceWrite 的调用创建一个新笔记本:
打开一个设备并执行编写操作:
如果你的设备要对多个参数写值,那么把它们的值提供给 DeviceWrite 作为一个关联或一个规则集合是很常见的. 剖析这个规则的任务就落在 "WriteFunction".
"WriteFunction" 中的剖析规则:
打开一个设备并执行一个编写操作,创建一个新的笔记本,带有指定的文本和标题,以及空白节单元:

"WriteBufferFunction"

"WriteBufferFunction"f 指明 f[{ihandle,dhandle},args] 应被调用以响应顶层命令 DeviceWriteBuffer[dev,{args}],把数据写入设备缓存. 第一个参量 {ihandle,dhandle}是许多驱动程序函数常见的参量,其中 ihandle 是管理器句柄,dhandle 是设备句柄. 该函数的返回值被忽略.
使用 "WriteBufferFunction" 的典型例子是对串行设备编写字节序列.

"ExecuteFunction"

"ExecuteFunction"f 指明 f[{ihandle,dhandle},"command",args] 应被调用以响应顶层命令 DeviceExecute[dev,"command",{args}],在设备中执行命令. 第一个参量 {ihandle,dhandle}是许多驱动程序函数常见的参量,其中 ihandle 是管理器句柄,dhandle 是设备句柄. 该函数的返回值会传给用户作为 DeviceExecute[] 命令的输出.

"ExecuteAsynchronousFunction"

"ExecuteAsynchronousFunction"f 指明 f[{ihandle,dhandle},"command",args,fun] 应被调用以响应顶层命令 DeviceExecuteAsynchronous[dev,"command",{args},fun],在设备上开始异步执行指定的命令. 第一个参量 {ihandle,dhandle}是许多驱动程序函数常见的参量,其中 ihandle 是管理器句柄,dhandle 是设备句柄. 函数 f 一般会把用户指定的句柄函数 fun 传给下流,当事件发生时被执行. f 的返回值应该是 AsynchronousTaskObject[] 或类似的对象,会传给用户作为 DeviceExecuteAsynchronous[] 命令的输出.
如果 command 不被支持,函数 f 必须产生一个 Missing[] 对象或简单返回未计算的值. 不管怎样,框架会产生一个合适的信息. 如果是其他错误,f 必须产生合适的信息并返回 $Failed. 尤其是,如果指定的带有给定参变量的命令不能被执行.
command 可以是 "Read""Write""ReadBuffer""WriteBuffer" 或任何其他被驱动程序支持的命令.
该驱动程序异步存储 URL 的内容至一个文件:
准备一个进度函数和进度指示器:
开始异步任务并观察进程:
对于不支持的操作框架发出一个错误信息:

"CloseFunction"

"CloseFunction"f 指明 f[{ihandle,dhandle}] 应被调用以响应顶层命令 DeviceClose[dev],关闭设备并释放相关资源. 第一个参量 {ihandle,dhandle}是许多驱动程序函数常见的参量,其中 ihandle 是管理器句柄,dhandle 是设备句柄. 该函数的返回值会传给用户作为 DeviceClose[] 命令的输出. 成功完成后,期待为 Null.
"CloseFunction" 一般包括关闭所有端口和套接口并释放其他资源,除了那些在 "ReleaseFunction" 中关闭的. 在 "OpenReadFunction""OpenWriteFunction" 中打开的流会自动被框架关闭,证明它们可以使用 Close 关闭. 如果不是那样,你应该在 "CloseFunction" 中关闭流.
关闭的设备仍然在当前 Wolfram 语言会话中可用. 使用 "DeregisterOnClose" 在关闭后完全去除设备.

"ReleaseFunction"

"ReleaseFunction"f 指明 f[obj] 应在 "CloseFunction" 之后被调用,在 DeviceClose 操作期间,破坏由 "OpenManagerFunction" 创建的初始化对象 obj,必要的话并执行其他剩下的清扫任务. 该函数的返回值被忽略,除非它是 $Failed.
如果你使用 "OpenManagerFunction" 打开一个与外部程序的 WSTP 连接,你可以在 "ReleaseFunction" 中关闭连接.

"DeregisterOnClose"

"DeregisterOnClose"True 指明 DeviceClose 应该不只是释放所有外部资源,并要从当前 Wolfram 语言会话中完全去除设备. 默认情况下,使用 "DeregisterOnClose"False,设备仍然可用,并可以用同样的参数重新开启.
使用 "DeregisterOnClose" 选项的默认值,关闭的设备仍然可用:
框架继续跟踪打开设备所使用的参数:
你可以重新打开设备而无需指定打开的参变量:
该类设备在关闭后自动注销:

"Singleton"

"Singleton" 选项决定框架如何处理 DeviceOpen["class",{args}] 命令,因此为多个设备设定创建规则. "Singleton" 选项可能的值如下:
Automatic
对于参变量的新集合返回一个新的设备
True
总是返回同样的设备
False
总是返回新的设备
crit
返回第一个注册的设备,其中 crit[dev,{args}] 计算为 True
为一个类中多个设备创建规则.
默认值 Automatic 等价于标准 crit 等于 SameQ[DeviceOpenArguments[#1],#2]&.
使用 "Singleton"Automatic,只有与参变量的新集合一起调用时,DeviceOpen 才返回新的设备:
调用具有同样参变量的 DeviceOpen 给出同样的设备:
使用 "Singleton"True,在第一个调用之后,框架忽略所有对 DeviceOpen 的调用并返回同样的设备,不管是否使用不同的参变量进行调用:
使用 "Singleton"False,尽管用同样的参变量调用,DeviceOpen 也会返回新的设备:
重要的是,如果不需要打开一个新设备响应 DeviceOpen,框架一般不会执行驱动函数的打开序列,包括 "OpenFunction". 然而,如果指定的标准 crit 返回 True,它会执行打开序列,但是参变量 args 不完全匹配先前打开设备的参变量. 这样让你为不同的参变量集合重新配置同样的设备.
使用该驱动程序,DeviceOpen 对每个不一样的第一个参变量返回一个新的设备:
用不同的输入参变量重新配置第一个设备:
该演示设备有一个稍微更强大的 "Singleton" 选项实现:

"Properties"

选项 "Properties"{"property1"value1,"property2"value2,} 指定 类属性,一般决定新创建设备的标准的(顶层)属性. 标准的属性名称通常以字符串形式给出. 驱动程序也可以指定 本地属性.

"GetPropertyFunction"

"GetPropertyFunction"f 指明 f[dev,p] 应被调用,查询设备 dev 的标准属性 p 的值. 返回值会传给用户. "GetPropertyFunction" 的默认值是 DeviceGetProperty,你可以在函数 f 内调用.
读取标准化属性后,此驱动程序将打印时间戳:
打开设备并读取其属性:
一般会定义 "GetPropertyFunction" 查询存在设备中的属性值.

"SetPropertyFunction"

"SetPropertyFunction"f 指明 f[dev,p,val] 应被调用设置设备 dev 的标准属性 p 为值 val. f 的返回值被忽略. "SetPropertyFunction" 的默认值是 DeviceSetProperty,你可以在函数 f 内调用.
虽然你一般会使用 "SetPropertyFunction" 改变设备的属性值,你也可以使用同样的函数过滤无效的属性值,指派某些属性只读,链接标准属性和本地属性等.
该驱动程序创建一个普通的属性 "p" 和只读属性 "r"
打开一个设备并改变属性 "p"
"r" 的值不能改变:

高级主题:保持顶层属性与设备同步

作为一个高级练习,你可以开发一个驱动程序,保持标准属性与可以在设备中独立变化的属性同步.
在这种情况下,外部值使用变量 $external 仿制. 在实际的驱动程序中,你可以调用外部属性设值函数(获值函数)改变(获取)属性的值:
对于顶层授权,set 函数使用 默认句柄. 它也与设备通讯,如果设备是开启的,在设备上设置属性值:
对于一个打开设备,get 函数查询设备上的属性值并保留它获得的值在顶层接口. 如果设备是关闭的,函数只返回顶层值:
以下设置驱动程序. "PreconfigureFunction" 在这种情况下,告诉驱动程序跳过属性 "p" 的配置:
打开一个设备并检验其属性. 该值来自于变量 $external(也就是说,值 "x" 被忽略):
改变属性值. 外部变量也改变了:
如果外部变量在本会话之外被改变(例如,用户按下设备上的按钮),下一个属性查询会提取新值:
关闭设备并再次改变设备上的外部属性:
因为没有和设备通讯,属性的报告值仍然是旧的:
当设备重新开启时,值会自动被更新:

"NativeProperties"

选项 "NativeProperties"{property1,property2,} 指明本地属性列表,一般在给定类的设备上可用. 当你希望以由框架提供的标准方式访问你的设备并希望有选项可以直接与设备工作,本地属性很有用. 本地属性的名称一般是由不受你控制的第三方程序库定义. 它们可以是任何合理的 Wolfram 语言表达式.
举例来说,你可能想为一个通过 .NET/Link 连接的设备使用 DeviceOpenDeviceReadDeviceExecute 以及其他标准 Wolfram 语言函数设置一个驱动程序,并且同时让你的用户通过 .NET/Link 接口与设备直接通信.
该演示设备提供一个样本,实现标准的和本地的属性:

"GetNativePropertyFunction"

"GetNativePropertyFunction"f 指明 f[dhandle,p] 应被调用,通过设备句柄 dhandle 查询设备的本地属性 p 的值. 函数 f 应返回属性值. 默认值 Automatic 本质上等价于 Function[{dhandle,p},dhandle@p].
演示设备提供实现 "GetNativePropertyFunction" 的样本:

"SetNativePropertyFunction"

"SetNativePropertyFunction"f 指明 f[dhandle,p,val] 应被调用,设置由设备句柄 dhandle 识别的设备上的本地属性 p 值为 val. 函数的返回值被忽视. 默认值 Automatic 本质上等价于 Function[{dhandle,p,val},dhandle@p=val].
演示设备提供实现 "SetNativePropertyFunction" 的样本:

"StatusLabelFunction"

默认情况下,框架使用 DeviceDefaultStatusLabels[] 为由 DeviceOpen[class] 打开的设备创建状态标签,DeviceDefaultStatusLabels[p] 用于在 DeviceOpen[class,{p,}] 中带有参数 p 的设备. 你可以使用选项 "StatusLabelFunction"f 创建你自己的标签. 函数 f[{args}] 会在设备准备阶段被调用,其中参变量 argsDeviceOpen["class",{args}] 提供. 它必须返回一个字符串,为一个打开的设备替代标签或两个字符串 {olbl,clbl} 列表,标签 olbl 替代打开的设备,标签 clbl 替代关闭的设备.
该驱动程序实现自定义的状态标签:
构建更高级的标签,调用 "PreconfigureFunction" 中的 DeviceStatusLabels.

"DeviceIconFunction"

"DeviceIconFunction"f 指明 f[{ihandle,dhandle},args] 应被调用,为因响应DeviceOpen["class",{args}] 而创建的设备对象创建一个自定义图标. 第一个参量 {ihandle,dhandle}是许多驱动程序函数常见的参量,其中 ihandle 是管理器句柄,dhandle 是设备句柄. 函数必须返回一个 Graphics 对象.

"DriverVersion"

使用 "DriverVersion"num 指明驱动程序的版本号为数值 num. 如果有旧的版本,框架会自动加载最高、最新的版本. 也可能加载其他版本;请参阅 "使用驱动程序文件".
可以用 DeviceDriverVersion 获取已加载驱动程序的版本号.
指定驱动程序的版本号并提取之:
设备框架函数
除了 DeviceClassRegister,框架还为驱动程序文件提供函数,提取关于设备对象的用户可见的和内部的信息,访问 类偏好设置 以及其他实用程序.
DeviceFramework`DeviceClassRegister[class,]
注册指定的类
DeviceFramework`DeviceClassClear[class]
清除指定的类
添加和去除驱动程序的类.
DeviceClassRegister 内部,它会调用 DeviceClassClear,并在创建任何新类前,有效地去除所有存在的对指定类的定义,因此当开发驱动程序时,你可以安全地多次调用 DeviceClassRegister.

使用驱动程序文件

DeviceFramework`FindDeviceDrivers[form]
给出名称匹配字符串模式 form 的类的驱动程序列表
DeviceFramework`DeviceDriverLoad[class]
发现并加载指定类的驱动程序
DeviceFramework`DeviceDriverFile[dev | class]
为设备 dev 或类 class 提供当前注册的驱动程序的路径
DeviceFramework`DeviceDriverPaclet[dev | class]
为设备 dev 或类 class 提供当前加载的数据包驱动程序的数据包对象
DeviceFramework`DeviceDriverVersion[dev | class]
为设备 dev 或类 class 提供当前加载的驱动程序的版本号
运用驱动程序文件.
FindDeviceDrivers{"path/to/driver",class,version} 形式返回三元组列表. 你可以使用此信息检查驱动程序文件,而无需加载到 Wolfram 语言会话,比较不同的驱动程序版本,或加载先前的驱动程序版本,而非最近版本,均可以由 DeviceOpen 自动加载.
找出某个类别的所有可用驱动器,并在系统编辑器中打开相应的文件:
对于已加载的驱动程序,你可以使用 DeviceDriverFile 找到确切的加载文件. 这个很有用,如果你的驱动程序有多个实现,并想确保框架加载了正确的版本;或者在重新编辑后,重新加载驱动程序;或只是学习驱动程序实现的细节.
打开演示驱动程序并查找驱动程序文件:
执行下面的命令并在 Wolfram 语言会话中以笔记本形式打开驱动程序文件:

访问关于设备的用户可视信息

DeviceFramework`DeviceClass[dev]
返回设备 dev 的类
DeviceFramework`DeviceID[dev]
返回设备 dev 的 ID
DeviceFramework`DeviceStatusLabels[dev],DeviceFramework`DeviceStatusLabels[dev]={olbl,clbl}
为设备 dev 的打开和关闭状态,获取和设置状态标签
访问关于设备对象的用户可视信息.
你可以在设备运行的任何时间改变状态标签. 新的标签会在下次前端为你的设备对象创建用户界面时被显示. 因此,你会在设备首次呈现给用户前在 "PreconfigureFunction" 创建自定义的状态标签.
为演示设备创建一个内置状态标签:

访问关于设备的内部信息

DeviceFramework`DeviceInitObject[dev]
返回设备 dev 的初始化对象
DeviceFramework`DeviceManagerHandle[dev]
返回设备 dev 的初始化对象的句柄
DeviceFramework`DeviceHandle[dev]
返回设备 dev 的句柄
DeviceFramework`DeviceObjectFromHandle[h]
返回句柄 h 的设备对象
DeviceFramework`DeviceOpenArguments[dev]
返回打开设备 dev 的参量
DeviceFramework`DeviceDriverOption[class,"opt"],DeviceFramework`DeviceDriverOption[class,"opt"]=val
获取和设置指定类的设备驱动程序选项 "opt" 的值
访问由框架保留的内部对象.
虽然 DeviceDriverOption 可以在任何时候被调用,但是在派生类中调用父函数尤其有用.
该驱动程序是基于 "RandomSignalDemo" 并调用其自身的上一层(父层)的 "ReadFunction"
从设备中读取,给出累积的随机值列表,其中从上一层(父层)读取,给出独立的值:
DeviceDriverOption 还让你在操作设备时,动态改变驱动程序函数值.
最初,此类的设备读取提供给 DeviceRead 的正弦参数:
以下改变驱动程序去读取锯齿波:

类属性

当框架需要在特殊的 DeviceObject 中分配标准属性时,它从类属性中取值. 类属性由 DeviceClassRegister 的选项 "Properties" 指定,在加载驱动程序后的任何时间均可以被访问.
DeviceFramework`DeviceClassProperties[class]
对指定的类返回可用属性列表
DeviceFramework`DeviceClassProperties[class,prop]
对指定的类返回属性 prop 的值
DeviceFramework`DeviceClassProperties[class,{p1,p2,}]
返回多个属性的值
DeviceFramework`DeviceClassProperties[class,prop]=val,DeviceFramework`DeviceClassProperties[class,{p1,p2,}]={v1,v2,}
设置指定属性的值
访问类属性.
类属性在为类创建任何设备对象前就可用:
变化设备属性不会改变类属性:
相反,改变类属性不会重置已存在设备的属性,但是它会改变类中新设备的属性:

类偏好设置

类偏好设置,通过 DeviceClassPreferences 访问,是一个更灵活的类存储,在 Wolfram 语言会话中是一致的. 偏好设置是独立于设备驱动程序,因此你可以在加载驱动程序之前、之后或甚至不加载驱动程序时,调用 DeviceClassPreferences;此调用不需加载驱动程序.
DeviceFramework`DeviceClassPreferences[class]
返回指定类的可用偏好设置的关联
DeviceFramework`DeviceClassPreferences[class,pref]
返回指定类的偏好设置 pref 的值
DeviceFramework`DeviceClassPreferences[class,{p1,p2,}]
返回多个偏好设置的值
DeviceFramework`DeviceClassPreferences[class,pref]=val,DeviceFramework`DeviceClassPreferences[class,{p1,p2,}]={v1,v2,}
设置指定偏好设置的值
DeviceFramework`DeviceClassPreferences[class]=assoc
assoc 分配类偏好设置
DeviceFramework`DeviceClassPreferences[class]=.,DeviceFramework`DeviceClassPreferences[class,pref]=.,DeviceFramework`DeviceClassPreferences[class,{p1,p2,}]=.
清除类偏好设置或去除指定的密钥
访问类偏好设置集合.
类偏好设置的关联可以包含任意密钥值对. 它们总是在 DeviceClassRegister 的开端进行内部检查. 如果偏好设置恰巧包含对应于 DeviceClassRegister 选项的密钥,它们的值优先于在驱动程序中指定的选项.
保存类的 "Properties" 偏好设置:
当驱动程序一旦被创建,就会使用偏好设置的值,而不是在 DeviceClassRegister 声明中描述的值,在这种情况下,从父类中调用简单的复制属性:
清除偏好设置,重新计算类定义:
没有偏好设置时,类的属性是按需要从父类中衍生的:
你可以在 Wolfram 语言会话的任何时候设置并查询设备类偏好设置. 尤其是,对于发现设备所需要的数据存储或设置标志标明被你的驱动程序所需要的第三方软件已安装在用户的机器上非常有用.
该类的设备不可以被发现,除非在类偏好设置中设置标志:
设置标志后,可以发现设备:
去除类的偏好设置:

默认属性句柄

当为你的类自定义属性句柄时,在某种程度上调用默认的句柄往往很方便,这样你的句柄可以扩展内置的句柄,而无需从头开始.
DeviceFramework`DeviceGetProperty[dev,prop]
"GetPropertyFunction" 执行默认的句柄
DeviceFramework`DeviceSetProperty[dev,prop,val]
"SetPropertyFunction" 执行默认的句柄
DeviceFramework`DeviceDefaultStatusLabels[]
给出默认的打开和关闭状态标签的列表
DeviceFramework`DeviceDefaultStatusLabels[p]
给出基于参数 p 的默认状态标签
使用默认属性句柄.

决定设备状态

顶层函数 DeviceOpenDeviceCloseDeviceOpenQDevices 分别是打开、关闭、测试和给出当前会话中所有注册设备的清单(无论设备是开启或关闭的). 框架还会为模拟提供多个补充函数,但是不常见的操作.
DeviceFramework`OpenDevices[]
当前打开设备的列表
DeviceFramework`DeviceRegisteredQ[dev]
测试设备 dev 是否在当前 Wolfram 语言会话中注册
DeviceFramework`DeviceDeregister[dev]
注销和完全从当前会话中去除设备 dev
测试和改变设备状态.
如果设备是开的,DeviceDeregister 也关闭设备. 当开发驱动程序,清除记录时,该函数很有用.
创建一个方便的面板,注销当前会话中的所有设备:
开发设备驱动程序数据包
框架会自动从 Wolfram 数据包服务器加载设备驱动程序,就像驱动程序驻留在本地计算机上一样. 因此,如果您想让您的驱动程序被广泛使用,您将需要让它们像数据包一样被分发.
如要创建一个设备驱动程序数据包,则要包括一个在设备驱动程序根目录下的特定的文件 PacletInfo.m,并行于设备驱动程序文件. 一般来说,驱动程序数据包的名称是带有前缀 DeviceDriver_ 的驱动程序类名称,例如,DeviceDriver_MyDevice. 如需要可在驱动程序根目录中包括其他支持文件和目录.
另外,数据包可以为你的设备包含 Wolfram 语言文档. 重要的是,文档可以包含错误信息的参考页面并提供安装和操作设备的自定义故障排除说明.
该设备的设备驱动程序被分发为一个数据包:
文档包含带故障排除说明的自定义参考页. 点击下面的人字标记 参阅说明:
检查数据包的内容,执行以下命令:
范例

Tinker Forge Weather Station

Tinker Forge Weather Station 通过迷你 USB 电缆连接至计算机,并由四个被 32位 ARM 微控制器控制的氛围传感器(温度、压强、湿度和光照度)组成. 该设备带有一个显示字符串的 20×4 字符液晶显示屏. 每个传感器(气压计、湿度和光照度)以及液晶显示屏被当作一个子设备或 "bricklet" 共享一个平台组成一个 "Tinker Forge Weather Station" 设备. 设备上的读取命令被认为是从感应器中读取测量结果,编写命令被翻译成在液晶显示屏上显示字符的请求.

18.gif

假设所有底层设备通讯函数是用 Tinker Forge's C/C++ API 绑定的 C 语言编写的,所有结果 .c 源文件被编译成一个可执行文件. 因此,为了让 Wolfram 语言与该设备通讯,第一步是 Install 该可执行文件,它可以很方便地在"OpenManagerFunction" 中完成. 驱动程序还卡伊实现多个其他函数,如下所示:
DeviceFramework`DeviceClassRegister["myTinkerForge",
    "OpenManagerFunction" (Install["\path\to\tinkerforge\executable"]&),
    "ReleaseFunction" release,
    "MakeManagerHandleFunction" make,
    "OpenFunction" open,
    "ReadFunction" read,
    "WriteFunction" write,
    "CloseFunction" close
]
"OpenManagerFunction" 中的 Install 语句一般返回 LinkObject,其中 LinkClose"ReleaseFunction" 中被调用,在去初始化阶段关闭 WSTP 连接.
release[link_] := LinkClose[link]
下面,"MakeManagerHandleFunction" 返回一个句柄给一个 IP 连接对象,表示在控制 C 程序和设备间的 TCP/IP 连接.
make[___] := Tinkerforge`IPConnectionCreate[]
连接句柄 ipconn 作为设备管理器句柄 ihandle 并传给 "OpenFunction",它可用于连接独立的小程序块,为每个创建独立的句柄,并返回这些句柄的列表. 该列表被作为一个复合的设备句柄 dhandle. 在这个气象站中,气压计小程序块读取气压和温度,因此四个传感器有三个有效的小程序块.
open[ipconn_, ___] := {
    LightSensorCreate[ipconn],
    HumiditySensorCreate[ipconn],
    BarometerCreate[ipconn]
}
小程序块句柄的三元组用于读取 "ReadFunction" 中的气象数据,其中,这里不需要管理器句柄 ihandle.
read[{_, {lightHandle_, humHandle_, baroHandle_}}, rest___] := XXX
实现 "WriteFunction",在另一方面,可能不需要传感器句柄, 但是可以实时地从 ipconn 为 LCD 小程序块创建句柄.
write[{ipconn_, _}, rest___] := XXX
最后,"CloseFunction" 将断开 IP 连接并销毁传感器小程序块的句柄.
close[{ipconn_, {lightHandle_, humHandle_, baroHandle_}}] := (
    Tinkerforge`IPConnectionDisconnect[ipconn];
    LightSensorDestroy[lightHandle];
    HumiditySensorDestroy[humHandle];
    BarometerDestroy[baroHandle];
)
此外,还有一个更详细的 Tinker Forge Weather Station 驱动程序.
执行下面命令并在系统编辑器中打开驱动程序文件:
更多资料
如果您有兴趣为 Wolfram 语言开发和分发设备驱动程序,请联系 Wolfram Research.