Open DigitalPlatform opened 5 years ago
读者和工作人员都可以从智能书柜面板操作打开柜门。
读者打开柜门,然后可以从书柜里面取出图书,或者把图书放进书柜。一般可以认为从书柜里面取出图书,意味着该读者“借走”这本图书;放进图书,意味着该读者“还回”这本图书。
有两点值得探讨:
1) 读者从书柜取走和放回图书的动作可以很快,可以很细碎,一般来说软件完全同步去调用 dp2library 服务器完成借书和还书操作,速度上不一定跟得上。其实也没有必要“跟上”读者动作。可以把读者关闭柜门当作一个结束动作,此时再统一处理借书还书请求:被拿走的图书被认为是读者借走;放回的、原来书柜里面没有的图书被认为是还回。如果在开门状态下,读者反复取和放回一本图书,软件是不对 dp2library 进行请求的,因为还没有到关门的时候。
2) 某些操作的不可抗拒性。读者关门的时候软件集中进行处理,前面探讨了。读者关门时候,可以直接离开,不一定会听从软件界面的指示和警告、报错。比如传统来说读者有个借书册数的上限,如果读者从书柜中拿走超过这个数的图书,软件确实是可以警告提示的,包括文字和声音提示,但读者可以不听从建议直接离开书柜。那么这时候软件应该尽可能把借走的图书信息记载下来,和正常借书差不多(加上一些警告信息),这些图书后面可以正常向读者追讨。这个和一般自助借还差别较大,一般的自助借还遇到情况,软件可以拒绝办手续,如果读者在没有办手续情况下拿走图书,门禁是会报警的。但智能书柜一般不会使用 EAS/AFI 门禁报警机制(这一点后面再详述)
注:dp2library 为 borrow() API 增加了一种 overflowable 方式的新操作,用这种方式来借书,如果超出读者当前允许的册数,也会成功,只是借期只有一天。第二天就会超期。主要目的是让读者可以在当天之内还回,满足智能书柜的特殊要求
工作人员打开柜门,然后取、放图书,一般可以认为这是业务需要。放进图书可以理解为把图书“调拨”进入智能书柜,此后这一册图书的归属地变更到了这一个书柜。取走图书的含义暂时不明,当然,智能书柜是能感知到这一册图书被拿走了,会变更内存状态。
下面探讨一下工作人员放进图书时,管理软件应该执行的操作:
1) 首先册记录的 location 元素要更改到指向这一个智能书柜所在的馆藏地;shelfNo 元素也要更新,反映图书所在的层号(这一操作可选); 2) 册记录的 currrentLocation 元素要更新,指向这一个智能书柜。
取走图书的含义,努力思考一下,暂时可以理解为需要记载到一个地方,表示这一位工作人员因为某种原因取走了这一册图书。比如可以记入操作日志(哪怕并不修改任何数据库记录)。如果后来工作人员在工作电脑上执行了调拨流程操作,那么整个动作才完整了,具有了意义。
那么从这里其实我们也可以推论,也许智能书柜管理软件,可以提供一个进入调拨状态的命令。指明后续操作指向的馆藏地。那么工作人员取出图书的时候,自动执行调拨操作,这样工作人员就不用去其他工作电脑上用原来的方法做调拨操作了。
剩下的一个疑问就是:管理流程和软件,是否允许工作人员无目的地拿出图书?鉴于前面提到的智能书柜有一些操作不可抗拒的特点,所以也需要考虑一下这种无目的的情况(从管理软件角度)如何最妥善地处理。
智能书柜理论上是持续运行的,它可以监控图书的进进出出。
但第一次开机的时候是什么情况呢?可以简单分为:开机时候软件检测到书柜里面已经有图书;检测到书柜里面没有图书。两种情况。
开机时候检测到书柜里面已经有图书,管理软件就要初始化一个内存列表,这个列表表示当前在书柜里面的图书。同时软件需要尝试对每一册图书都进行一次“还书”操作,这样避免存在借阅状态的图书后面遇到读者取走时候请求借书发生报错。
开机时候如果书柜里面没有图书,后面可能工作人员和读者会往里放进图书,这个分别按照调拨和还回处理即可。
智能书柜促使我们去重新思考册记录的馆藏地,代表什么,怎么才能做到完美。
考虑读者会把不属于智能书柜管辖的图书也放入智能书柜,而且这种操作具有不可抗拒性,我们要审视一下这种情况的意义和处理方法。
除了这一册图书不是属于智能书柜外,那么从读者的角度,她/他是希望还书的,因为图书放进去就意味着还给图书馆了。那么管理软件对还书的操作不能少,尤其注意要处理 RFID 芯片的 EAS/AFI 更改(虽然正常情况从智能书柜取走图书并不会修改 EAS/AFI。这一点后面详细探讨)
同时,管理软件似乎应该给管理员发出警告,表示有一本书被错放进入了智能书柜,请管理员速来拿走处理。
智能书柜面板的显示图书信息的界面,要考虑到这种错放的图书也应该可以显示和查询。只是显示状态要和属于智能书柜管辖的那些图书区别开来,便于工作人员识别了解。
对于错放进来的图书,软件处理时要把册记录的 currentLocation 元素修改,以反映当前实际位置(虽然是放错的位置),但 location 元素不应修改。因为 location 元素只能只应被管理员用调拨操作修改。location 元素反映的是原始馆藏地,currrentLocation 元素反映的是实际馆藏地。
自助借还机还书时候,可能会提示还书者把某一些被别的读者预约的(刚被还回的)图书放进智能书柜,以便预约者接到通知来图书馆自行取走。当然这一册图书可能会被错放到其他智能书柜,总之,currentLocation 元素反映了实际放入的位置,所以预约者接到的通知信息总不会错。(注意,放进去了才会发出通知,而不是以前的还回时刻就通知。其实也可以通知两轮:还回时候通知已被还回到某个自助机;放入智能书柜的时候再通知一次图书现在到了智能书柜)
谈到错放位置,其实工作人员也可能错放。当然,工作人员如果用调拨功能去放入智能书柜,那是不会“错”的,因为管理软件会以实际放入的书柜为准修改 location 元素和 currentLocation 元素。只能说,如果把调拨和上架拆分为两个步骤,第二步上架可能会发生错误,即放入的位置(currentLocation)和理论位置(location+shelfNo)不符。
那么从这里,我们可以推论:工作人员无目的转移图书,可以理解为就是这种错放的情形。似乎允许这种操作也是有意义的 --- 为啥要错放,也许工作人员自己心里有谱,有某种需求才这么做的,软件要保持一定的灵活性,把这类情况考虑进去善加处理。
工作人员打开柜门,向书柜里面放入图书,可以理解为典藏调拨操作。调拨的意思,就是把 location 元素为其他馆藏地的图书,修改为书柜的馆藏地。因为同时书确实放进去了,所以也要修改一下 currentLocation 元素。
前面的探讨中,提到工作人员有时候也是无(明显)目的放入图书。即,不一定是要调拨。比如,是放入图书,导致图书的 currentLocation 元素修改,但不希望修改 location 元素。等于这一本图书进入书柜,还是“客人”状态,不是“主人回家”状态,这个书柜还并不是这一册的“家”。
最初容易想到,这样可以在面板界面上,给设计两个不同的功能,调拨和上架。其中上架表示不调拨地上架。
但其实可以把两个功能考虑融合起来做成一个界面,即一个界面功能模块。设想这样:当工作人员开门以后,放入第一本图书开始,软件开始自动运算。软件先观察这一册图书的 location 元素是否符合这个书架的地点字符串定义。如果符合,就是“归家”(归架),location 元素并不需要被修改,无所谓调拨。
如果图书 location 元素表明是外来的,软件会弹出一个对话框,询问是否进入调拨状态?这时候工作人员可以确认,也可以不理会这个对话框,继续放入其他图书。如果不理会对话框继续放入,那么这个对话框上会把后来的图书继续追加到一个列表显示。最终如果工作人员过来处理这个对话框,同意调拨,那么就对累积的列表中的图书批处理做一次调拨操作。如果最终工作人员没有过来处理这个对话框,那么多少时间超时以后对话框会自动关闭,调拨操作不会进行。
(但上述流程中,这些图书无论如何 currentLocation 元素会被修改以反映进入书柜这个状态)
这样,两种目的的操作都在同一套界面上得到了满足。
智能书柜的操作,其实概念很简单,可以概括为:读者开门、取书或者放书、关门。当然,开门有一个条件就是读者先要扫读者证进行身份鉴别,这样软件才能知道读者是谁,是否允许她/他开门。
开门以后,到底是放书还是取书,还是二者兼有。开门前是不知道的。所以这个功能既可以借书也可以还书,也可以同时借书和还书。这个动作的菜单名称可以简单直白叫做“开门”,也可以叫“开门(并借还)”。
开门以后,软件会记住开门期间,读者取走的书,和放入的书的信息。等待关门。关门时,软件会自动向 dp2library 服务器请求借书和还书,这时候会出现一个进度条界面,等操作完毕,语音提示读者操作完成或者发生错误。如果发生错误,要看错误的原因。
如果是读者的借书权限不够,读者取走了超过规定数量的书,那么软件会有一个对话框提示读者,请把多出来的图书放回书柜。如果读者(刷卡)开门放回去,这个对话框会消失。但读者很可能不理会这个对话框,把书直接拿走了,那么这个对话框应该是有个超时读秒机制,等超过一定时间读者不响应,软件自动按照超额借阅来处理这些报错的图书。所谓超额借阅和普通借阅差不多,但有关记录的 comment 元素记载一下这个情况。
在实际场景中,有可能是排队等待的下一位读者看到屏幕上上述对话框,“肇事”读者已经走掉了。但没有关系,下一位读者可以继续操作,而这个对话框会自动在超时以后进行后台超额借阅处理。原则上超额借阅请求一定会满足。如果特殊情况无法满足,比如智能书柜发现 dp2library 通讯失败无法访问,那么智能书柜软件要记入一个特殊的日志,后面会自动重试操作。即便是程序退出,重启,这些日志都能保留,不断尝试重试。
(2024/1/24 注) dp2ssl 实际实现相关功能的时候,借书操作虽然超额,但借书操作当时就完成了。读者可以看到对话框提示。读者可以选择在一天之内还回超额部分图书,也可以不还回。如果不还回,就会触发超期违约金。 另外,如果还回的册并不是界面上标注超额的那一册,但只要还回任何一册,dp2library 服务器会自动重新计算超额,尽可能自动消除超额状态。
智能书架因为是一种自然的使用方式,从里面取出图书,或者放进去图书,如果要不破坏这种流畅的方式,那么取出(相当于借出)的时候没有办法把 EAS 修改为 Off。因为软件是通过探测到一册图书“不在无线电感知范围”来作为“取出”信号的,此时想要去修改它的 EAS 为时已晚。
放入图书的时候,软件倒是可能会有充足的时间来对这一册图书的 RFID 标签进行修改 EAS 为 On 的操作。
上面说的是一个原因,无法利用 EAS。
另外一个原因,是智能书架很多时候是放在门禁范围以外,无法利用门禁的防盗检测功能。因而软件也就看起来没有必要去修改 EAS。
但这并不意味着智能书架软件完全不考虑 EAS。如果读者或工作人员把本来不是智能书架的图书放入,那么原则上智能书架软件不去修改这样的册的 EAS。但前面谈到过,有时候读者会把(此类原本不是智能书架管辖的)图书放入智能书架,当作“本册已经还给图书馆了”。如果智能书架软件要自动为这些图书办理还书手续,那么就涉及到一个问题:是否要设置 EAS 为 On?
一个可能是不设置。如果馆员接到通知,来智能书架取走这样的图书,(进入门进去以内)去普通书架上架,那么上架之间馆员要记住,用普通还书电脑去修正一下 EAS。这显然是一种麻烦事,馆员很容易忘记这一步操作。
另外一个可能是智能书架软件在此类图书放入时候,自动设置好 EAS 为 On。(注意,是否设置是要判断馆藏地的,原本属于智能书架管辖的图书不做这种设置。) 那么当馆员来取走这些图书的时候,就省去了还要专门想办法设置 EAS 的步骤,很方便了。
另外,在典藏移交的时候,从门禁管辖区以内以内的馆藏地把图书移交给门禁管辖区以外的智能书架,当图书放入智能书架的时候,最好统一设置一次 EAS 为 Off。这样图书被读者拿出的时候,再走出门禁的时候不会遇到报警,这是正常状态。
反之,当馆员从智能书架中拿出图书,并把这种动作作为移交到另外一个馆藏地来处理,那么有可能需要在拿出时候修改 location 元素,这个因为是修改数据库记录,可以做到;但如果要求此时修改图书标签的 EAS 状态为 On,此时为时已晚,那么可能需要馆员在其他电脑去补一次修正 EAS 的操作。(只要放到启动了修正模块的普通电脑的读卡器上就可以办到,因为从册记录馆藏地可以判断出这一册是需要用到 EAS 的)
所以移交,进、出智能书架,两种方向难度不同。看来出智能书架的方向无法形成完整的功能。
在设置对话框中,先设置好必要的指纹中心、人脸中心、RFID 中心URL,然后设置“功能类型”为“智能书柜”。(功能类型默认为“自助借还”)
然后在dp2SSL 的用户文件夹中创建一个文件 shelf.xml。样例如下:
<?xml version="1.0" encoding="utf-8" ?>
<root>
<shelf>
<door name="左第一排" lock="*.1.1" antenna="*:1" shelfNo="一年级一班:1-1" lamp="*"/>
<door name="左第二排" lock="*.1.2" antenna="*:2" shelfNo="一年级一班:1-2" lamp="*"/>
<door name="左第三排" lock="*.1.3" antenna="*:3" shelfNo="一年级一班:1-3" lamp="*"/>
<door name="左第四排" lock="*.1.4" antenna="*:4" shelfNo="一年级一班:1-4" lamp="*"/>
</shelf>
<shelf>
<door name="右第一排" lock="*.1.5" antenna="*:5" shelfNo="一年级一班:2-1" lamp="*"/>
<door name="右第二排" lock="*.1.6" antenna="*:6" shelfNo="一年级一班:2-2" lamp="*"/>
<door name="右第三排" lock="*.1.7" antenna="*:7" shelfNo="一年级一班:2-3" lamp="*"/>
<door name="右第四排" lock="*.1.8" antenna="*:8" shelfNo="一年级一班:2-4" lamp="*"/>
</shelf>
<patron readerName="RL1700"/>
</root>
door 元素的 lock 属性为门锁参数,例如 *.1.1
,星号表示任意锁名字,(第一个)1 表示锁控板编号,(第二个)1表示锁的编号(在当前锁控板内)。星号部分也可以用具体的锁名字,如果 COM 口用到多于一个的话。
antenna 属性为读卡器天线参数。例如 *:1
,星号表示任意读卡器名字,1 表示天线编号。星号用法其实不太好,因为智能书柜一般都有多于一个读卡器(其中一个是 ISO14443A 读卡器),所以最佳做法是使用具体的读卡器名字。另外注意天线编号有的读卡器是从 0 开始,有的是从 1 开始,要看具体读卡器型号的说明。(注:RL8600类型的读写器是从0编号的)
shelfNo 属性为架号。一般格式为"书柜位置:竖列号-排号"。书柜位置要根据 dp2library 是否使用了分馆模式,来具体配置。使用分馆模式例如"希望一小/一年级一班";没有使用分馆模式例如“一年级一班”。
lamp 属性为灯名字。目前只能使用 "*"。如果属性值为空,或者没有定义此属性,表示此柜门打开的时候不需要开灯。受目前智能书柜硬件限制,所有定义了 lamp 属性的 door 元素,都共同决定全体灯的亮灭。具体算法是,只要有一扇门开着,那么(定义了 lamp 属性的)所有门内的灯会一齐打开;如果全部门都关上了,那么(定义了 lamp 属性的)所有门内的灯会一齐关掉。
door 元素还可以有一个特殊属性 type,如果值为 "free",表示这不是一个普通的处于柜门内的读卡器,而是一个不属于任何柜门的读卡器。可以这样配置书柜屏幕下方原本用于识读证卡的那个小读卡器,它也可以形成一个“门”的界面,但里面的图书放入和取出不会触发还书和借书请求。
patron 元素的 readerName 属性定义了读者证读卡器的名字。具体名字需要在 RfidCenter 的“操作历史”属性页中观察确认,上面举例的 "RL1700" 不一定符合实际情况。如果不定义 readerName 属性,则 dp2SSL 会持续不停对图书读卡器进行盘点操作;如果正确定义了 readerName 属性,则 dp2SSL 在智能书柜所有的柜门关闭后,会自动暂停对图书读卡器的盘点操作(有门打开时会重启盘点操作)。
最前面显示了请求操作的读者姓名。借书和还书操作笔数。然后是用彩色文字醒目显示失败的、警告的和成功的笔数,以便读者看到后进行针对性的处理。
然后是每一笔操作的详细信息。用醒目的颜色显示出操作的状态,报错信息用红色底色文字。
对话框还伴有语音提示。主要是在读者发生超过权限借阅的时候,提醒读者立即放回多拿的图书。
柜门控件的字体按照比例进行自动调整。打开门时候的底色从深红色修改为浅蓝色。关闭时候的底色为深绿色没有改动。"e:1"字体颜色改为红色。
书柜页面右侧的读者区可以用手指卷动了
借还请求结果对话框文字内容可以用手指卷动了
读者刷卡后,右侧出现读者信息。点门打算开门的时候,增加了一个对读者记录状态检查的步骤,如果状态不正常则报错并拒绝开门。
检查的算法如下: 1) 检查 state 元素。内容不为空则算作不正常;(读者证挂失等情况) 2) 检查是否有 overdue 元素。有则算作不正常;(有已经还书、尚未交费处理的超期情况) 3) 检查 borrows/borrow 元素里面的 returningDate 属性,如果发生超期则算作不正常 (有在借图书的超期情况)
假设有这样一个 shelf.xml 配置内容:
<?xml version="1.0" encoding="utf-8" ?>
<root>
<shelf>
<door name="第一排" lock="*.1.1" antenna="M201:1"/>
<door name="第三排" lock="*.1.3" antenna="*:3"/>
<door name="第四排" lock="*.1.4" antenna="*:4"/>
</shelf>
<shelf>
<door name="第一排" lock="*.1.5" antenna="*:1"/>
<door name="第二排" lock="*.1.6" antenna="*:2"/>
<door name="第三排" lock="*.1.7" antenna="*:3"/>
<door name="第四排" lock="*.1.8" antenna="*:4"/>
</shelf>
<patron readerName="RL1700"/>
</root>
可以看出第一个 shelf 元素和第二个 shelf 元素它们下级的 door 元素的数量是不等的。dp2ssl 应能正确显示出这样的三个和四个门的界面。
柜门已经打开的状态下再点柜门,会语音和对话框提示“已经打开”
柜门控件增加“i”按钮,点它可以查看该门内当前存在的所有图书详情。另外 + 和 - 数字部分也可以点按,作用是查看增加和减少的图书详情
在书架页面返回菜单页面的时候,如果有门开着,会提示关门以后才能返回
书架页面右侧读者信息区,所显示的在借册,现在可以显示“超额”和“超期”两种状态,状态文字用特殊的底色显示。注意需要和最新版 dp2library(dp2libraryxe) 配套使用
(dp2library 最新版也做了配套改进,超额借阅的时候,读者记录中 borrow 元素会有 overflow 属性;册记录中会有 overflow 元素)
读者信息中在借册的显示,原来的显示效果超期日期部分文字会被切掉,现在已经改进
shelf.xml 配置文件中 door 元素可以配置 lock 属性为空的情况。这样的门如果尝试在界面打开,会提示说不存在门锁
发出还书请求的时候,改用非验证还书方式
可在 shelf.xml 中定义一个图象文件作为柜门的背景图,并精确指定柜门的位置。
样例 shelf.xml 文件如下。其中 ... 的表示略去的部分,可按照实际情况填充。
<?xml version="1.0" encoding="utf-8" ?>
<root width="525" height="487" backImageFile="shelf.png" >
<shelf left="21" top="30" width="144" height ="361">
<door ... />
<door ... />
<door ... />
<door ... />
</shelf>
<shelf left="178" top="30" width="144" height ="361">
<door ... />
<door ... />
<door ... />
<door ... />
</shelf>
</root>
其中 root 元素需要定义 width 和 height 两个属性,表示图片文件的宽度和高度。还需要 backImageFile 属性来定义背景图象文件名。图象文件要放在 shelf.xml 同一个目录中。
shelf 元素需要定义 left top width 和 height 四个属性,表示这一组柜门的左上角位置和宽度、高度。可以用一个图象编辑软件打开图像文件,然后记下一组柜门需要定位的坐标,用在 shelf.xml 定义这几个属性。
注意 root 元素的 width 和 height 属性,与 shelf 元素的 left 等四个属性之间,应该使用同一种计量单位和坐标系。倒不一定要求是像素数,只要是比例合适的数字值就行(比如毫米数)。
如果这些表示位置的属性都没有定义,那么软件会自动按照多组柜门横向排放来进行处理。这些属性要么完整都定义,要么都不定义。不要出现只定义了其中一部分属性的情况。
RL8600:1|2|3|4
。style 参数中表达天线编号的用法被取消。测试时注意观察智能书柜天线小灯的亮灭情况是否正常如何制备一张工作人员 RFID 卡?可先在内务读者窗里面制备一张 ISO15693 读者卡,然后用“RFID工具窗”将 PII 修改为工作人员的用户名,但需要注意前面增加一个波浪号字符。例如,对于工作人员账户 supervisor 来说,PII 应该是 ~supervisor
。然后就可以在 dp2SSL 中使用了
在用工作人员身份取出图书以后,请求提交时,会自动弹出一个对话框询问是否向外移交,需要输入一个目的馆藏地。如果选择要向外移交,那么相关册记录的 location 元素会修改为在对话框中选择的馆藏地。如果选择不向外移交,则不对这些图书执行任何操作。
在用工作人员身份放入图书以后,请求提交时,会自动弹出一个对话框询问是否向内移交。如果要进行移交,那么册记录的 location 元素会被修改为智能书柜所在门的馆藏地字符串。如果不进行移交,则只进行上架操作(所谓上架操作就是将册记录的 currentLocation 元素修改为智能书柜所在门的 shelf.xml 中 shelfNo 属性配置的那个字符串)。无论是否选择移交,上架操作都会进行。
dp2ssl 的基本配置参数中,有一个工作人员账户用来做各种操作。当用工作人员卡刷卡登录后,典藏移交操作会改用这个卡上的工作人员身份来进行。测试的时候请注意在操作后,用内务的日志窗来检查 operator 是否正确。不过,测试可以分两种情况:一种是这两类账户是不同账户;一类是这两类账户用了相同一个账户。
在设置画面修改配置 RfidCenter URL 之后,回到主菜单画面,“注册人脸”菜单项能及时出现或者隐藏。
智能书柜还书请求的时候会顺带自动修改 RFID 芯片的 EAS/AFI 为 false。这一点在书柜使用中要引起注意,凡是放入过书柜的图书,被工作人员取走去图书馆普通书架上架之前,要记得专门修正一次 EAS 才行。当然,读者从智能书柜借书后,如果要还到普通书架,在普通书架自助借还机或者出纳台用内务进行还书的时候,EAS 是会被自动修改为 true 的,这一点不用担忧。
智能书柜尝试还书时如果发现图书原本就没有被借出,还有转移时册记录没有发生实质性修改,这两种情况在显示请求结果的时候都从 warning 改为 information 级别了。
开门前要对刷卡的工作人员或者读者所属的馆代码进行检查,看看是否能管辖将要打开的门(在 shelf.xml 的 shelfNo 属性中定义的)馆代码。
原先 dp2library 的 Borrow() API 不允许一个分馆的读者借阅另外一个分馆的图书。同时,Return() API 也会检查册记录的馆代码和借阅这一册图书的读者记录的馆代码是否符合,不符合则会报错,无法完成还书操作。引入智能书柜以后,首先用 overflow 风格的 Borrow() API 可以让一个分馆的读者临时借阅另外一个分馆的图书。这样就需要改进一下 Return() API,去掉上述检查,以允许这种状况的还书完成。dp2library 和 dp2libraryxe 都更新了
扫工作人员卡的时候,会自动出现一个输入密码的对话框。这里的代码做了重构。当对话框弹出的时候,保持对话框这样,又去扫另一张读者卡或者工作人员卡,这时候当前的登录对话框会自动关闭,相当于取消登录的效果。但后面扫这一次卡仅仅起到关闭刚才登录对话框的作用,并不会显示这一张卡的信息。再继续扫就会正常出现读者信息。
原先版本在开门点按屏幕的瞬间就会给门赋予操作者身份(表现为门上会显示操作者姓名)。但这样做是有问题的,假设一个关门的动作,其信号比较迟才能到达处理程序,如果此前(因为点按屏幕上的门)而抢先修改了这个门的操作者身份,则后面到来的关门信号会无法正确提交请求,因为操作者错乱了。正确的做法是,点按屏幕的时候,在一个命令队列里面放入一个命令,当门开信号到来时才去队列中取出命令,按照命令要求修改门的操作者身份。这部分代码做了重构。
RfidManager 类用一个线程扫描 RFID 标签,和门的状态。这样做可以确保 RFID 标签的变化信息和门状态变化信息的先后顺序关系正确。即便消息反应慢,但两者的先后关系不会发生错乱。例如,考虑这样一系列事件:1) 放入或者取走标签;2) 然后关门。上述处理办法可以确保 2 始终在 1 的后面发生。
<?xml version="1.0" encoding="utf-8" ?>
<root width="455" height="690" backImageFile="mini_shelf.png" >
<shelf left="68" top="73" width="231" height ="550">
<door name="第一排" lock="*.1.1" antenna="RD242:1" shelfNo="智能书柜:1-1" lamp="*"/>
<door name="第二排" lock="*.1.2" antenna="RD242:2" shelfNo="智能书柜:1-2" lamp="*" />
<door name="第三排" lock="*.1.3" antenna="RD242:3" shelfNo="智能书柜:1-3" lamp="*"/>
<door name="第四排" lock="*.1.4" antenna="RD242:4" shelfNo="智能书柜:1-4" lamp="*"/>
</shelf>
<patron readerName="RL8600"/>
</root>
<?xml version="1.0" encoding="utf-8" ?>
<root width="867" height="905" backImageFile="shelf.png" >
<shelf left="295" top="147" width="263" height ="652">
<door name="第一排" lock="*.1.1" antenna="RD5100:1" shelfNo="智能书柜:1-1" lamp="*"/>
<door name="第二排" lock="*.1.3" antenna="RD5100:3" shelfNo="智能书柜:1-3" lamp="*" />
<door name="第三排" lock="*.1.5" antenna="RD5100:5" shelfNo="智能书柜:1-5" lamp="*"/>
<door name="第四排" lock="*.1.7" antenna="RD5100:7" shelfNo="智能书柜:1-7" lamp="*"/>
</shelf>
<shelf left="575" top="147" width="263" height ="652">
<door name="第一排" lock="*.1.2" antenna="RD5100:2" shelfNo="智能书柜:1-2" lamp="*"/>
<door name="第二排" lock="*.1.4" antenna="RD5100:4" shelfNo="智能书柜:1-4" lamp="*" />
<door name="第三排" lock="*.1.6" antenna="RD5100:6" shelfNo="智能书柜:1-6" lamp="*"/>
<door name="第四排" lock="*.1.8" antenna="RD5100:8" shelfNo="智能书柜:1-8" lamp="*"/>
</shelf>
<patron readerName="RL8600"/>
</root>
读者有违约的情况,则无法刷卡打开智能书柜的门。这样做有什么缺点?如何减少读者的困扰?
<?xml version="1.0" encoding="utf-8" ?>
<root width="866" height="709" backImageFile="shelf_compact.png" >
<shelf left="294" top="28" width="263" height ="651">
<door name="第一排" lock="*.1.1" antenna="M201:1" shelfNo="智能书柜:1-1" lamp="*"/>
<door name="第二排" lock="*.1.2" antenna="M201:2" shelfNo="智能书柜:1-2" lamp="*" />
<door name="第三排" lock="*.1.3" antenna="M201:3" shelfNo="智能书柜:1-3" lamp="*"/>
<door name="第四排" lock="*.1.4" antenna="M201:4" shelfNo="智能书柜:1-4" lamp="*"/>
</shelf>
<shelf left="574" top="28" width="263" height ="651">
<door name="第一排" lock="*.1.1" antenna="M201:1" shelfNo="智能书柜:1-1" lamp="*"/>
<door name="第二排" lock="*.1.2" antenna="M201:2" shelfNo="智能书柜:1-2" lamp="*" />
<door name="第三排" lock="*.1.3" antenna="M201:3" shelfNo="智能书柜:1-3" lamp="*"/>
<door name="第四排" lock="*.1.4" antenna="M201:4" shelfNo="智能书柜:1-4" lamp="*"/>
</shelf>
<patron readerName="RL8600"/>
</root>
小白盒子在扫入手机屏幕上显示的我爱图书馆微信公众号里面的读者二维码的时候,一般的键盘拦截代码处理都会有问题,表现是大小写发生混乱。
经过检查,发现小白盒子在处理大写字符的时候,是这样的击键顺序:
LShiftKey kbdStruct=KeyCode:160,ScanCode:42,Flags:0,Time:261261875,ExtraInfo:0; intParam=256
P kbdStruct=KeyCode:80,ScanCode:25,Flags:0,Time:261261890,ExtraInfo:0; intParam=256
LShiftKey kbdStruct=KeyCode:160,ScanCode:42,Flags:128,Time:261261890,ExtraInfo:0; intParam=257
P kbdStruct=KeyCode:80,ScanCode:25,Flags:128,Time:261261890,ExtraInfo:0; intParam=257
即:LShiftKey down, 'P' down, LShiftKey up, 'P' up
而一般人在键盘上输入的时候是这种顺序:LShiftKey down, 'P' down, 'P' up, LShiftKey up
注 Shift 键抬起的位置。
可能是这种细微差异造成大部分代码在处理小白盒子输入的击键的时候,Shift 状态出现了混乱。
智能书柜初始化阶段,会对每个门里面的图书 RFID 标签进行扫描,检索相关册信息,建立必要的内存数据结构。这个过程中,如果发现一些 RFID 标签格式不正确,或者 PII 在 dp2library 中没有找到,就需要及时提醒工作人员进行排错处理。但因为这些门很多,如果集中在一起报错,工作人员查看报错信息会感到困难。
新版本对初始化阶段进行了改进,按顺序对每个门一个一个进行初始化,如果遇到需要工作人员处理的报错情况,会立即弹出一个对话框显示出错信息,对话框上加上了开门按钮,工作人员可以点此按钮立即开门对图书进行调整,然后关门,然后点对话框上的重试按钮,重新对这个门进行初始化,直到这个门初始化完全成功。
测试要点:1) 要放入空白标签,和 PII 在 dp2library 中无对应册记录的标签,进行测试;2) 在初始化阶段,制造出网络故障,观察这种情况下软件的处理是否正确。
新版本 dp2SSL 启动的时候,会自动和 dp2003.com 服务器上的 dp2mserver 联系,加入点对点消息网络。dp2SSL 用一个 dp2mserver 账户登录进入点对点消息网络,账户权限许可它进入一个消息群组,这样,它可以把一些运行状态随时发送到群组里面;还可以接受群组里面其他成员给它发来的消息,执行一些管理和维护命令。
1) 给智能书柜创建消息账号
用 dp2mserver 管理员身份,给 dp2SSL 创建一个消息账号。账号名字尽量简短易记,因为后面会频繁用这个名字 at 它,进行类似聊天的维护管理工作。
权限保持为空即可。
义务需要分配
searchHistory,setHistory
分别用于响应查询历史记录、修改历史记录状态。
而群组需要分配 gn:<group>
参数,这是表示它加入了一个名为 <group>
的群组。这里的 group 是举例的群组名字,建议群组名字用图书馆单位名字命名,因为后面开展的维护管理工作,是在一个图书馆工作人员范围内共享这些信息的,不希望被外人看到。
2) 给参与维护的工作人员创建消息账号
用 dp2mserver 管理员身份,给参与维护的工作人员每人创建一个消息账号。账号名字应该是个人的名字。当一个工作人员同时要负责维护很多台智能书柜,或者要参与多个图书馆的智能书柜维护工作的时候,建议只开辟一个账户,只是在群组参数里面包含全部所需的群组名字即可。这样可以避免账户太多引起的记忆和账户维护麻烦。
权限需要分配
searchHistory,setHistory
分别用于查询历史记录、修改历史记录状态。
义务保持为空即可。
而群组需要分配
gn:<group>
gn:<default>
参数,这是表示它加入了一个名为 <group>
的群组和一个名为 <default>
的群组。注意内务前端里面的 dp2mserver 账户管理界面,在群组参数这里是允许输入多行的,每行定义一个群组名字。
这里的 group 是举例的群组名字,表示用于维护管理智能书柜的一个群组。要和刚才介绍的 dp2SSL 的消息账号的群组名字一致。也就是说,工作人员和智能书柜都加入同一个群组,互相可以通讯。这里智能书柜可以理解为一个“人”,它也有账号和自己的人格。
3) 给 dp2SSL 配置消息服务器相关参数
在 dp2SSL 参数设置界面上,设置好“消息服务器 URL”参数,一般为 http://dp2003.com:8083/dp2mserver
。
“消息服务器用户名”和“密码”,设置为前述为 dp2SSL 开辟的消息账户的用户名和密码。
“消息服务器组名”设置为前述为 dp2SSL 维护管理定义的群组名。注意名字里面目前是要包含尖括号字符的。
测试要点:制造通讯故障。消息通讯会因为通讯故障而中断,dp2ssl 错误日志中能观察到通讯错误的记载。当通讯恢复后,消息通讯应能快速自动重建(大概在 10 秒钟以内可以重建)。目前通讯故障期间,dp2SSL 应当发出给维护群组的消息会无法发送出去。以后会改进为,这些无法发出去的消息会在本地暂存起来,等通讯恢复以后补发出去。
注:目前,一旦 dp2SSL 把消息成功发给 dp2mserver 服务器以后,即便内务前端和 dp2mserver 之间的通讯可能会出现故障,但当通讯恢复以后,这期间的消息内务前端都会自动收到,这是由消息网络本身的可靠性决定的(dp2mserver 会存储所有传递过的消息,并提供前端随时检索获取)。
在内务前端里面有一个聊天窗口,可以用来和智能书柜的“人格化身”进行交互,以获取机器状态信息,并对机器发出一些维护指令。
在内务里面打开聊天窗口,在左侧的群组名列表里,点右鼠标键可出现上下文菜单,需要把前述为智能书柜开辟的群组名添加到群组名列表中,以便进入此群组进行聊天操作。
目前支持的交互命令如下: (以下假设书柜的用户名叫做 robot)
@robot hello
和书柜打招呼,书柜会回复 hello!
@robot list history
列出当前全部书柜本地数据库中的历史动作。
dp2SSL 增加了本地数据库用于记载借还操作和各种其他操作,以增强可靠性。当 dp2SSL 和 dp2library 服务器之间的通讯中断的时候,它会把各种操作信息安全地记载到本地数据库中,等通讯恢复以后,会自动重试把请求同步到 dp2library 服务器。
本地数据库是用 SQLite 实现的,数据库文件存储在用户文件夹里,名叫 actions.db。目前 dp2SSL 版本升级的时候可能会修改本地数据库的结构,这样会导致升级以后 dp2SSL 启动时出现报错。这时只要退出 dp2SSL,去它的用户文件夹里面手动删除 actions.db 文件然后重新启动 dp2SSL 即可。
注:删除 actions.db 数据库文件会丢失全部操作历史信息,特别是尚未同步的操作信息。一般情况下应该在同步完成、数据库中没有尚未同步的记录的前提下删除 actions.db 数据库文件。
测试要点:1) 制造通讯故障,观察操作动作无法提交成功到 dp2library,和通讯恢复以后重试提交请求成功的过程; 2) 测试中途需要频繁用远程维护命令 list history 对 dp2SSL 本地数据库进行观察和确认
1) 这个版本改进了借书和还书请求以后对话框里面的文字显示。
由于 dp2ssl 现在增加了本地数据库,可靠性应该是非常高的,所以原则上基本上是可以不对读者进行繁琐的文字报错。因为读者是非专业人员,不了解智能书柜的运作机理,报错信息会让读者无所适从。这些报错信息实际上是需要给系统管理员和公司负责维护的工程师看到,而不是给读者看到,这是一个总的设计原则。读者只需要知道“我借书或者还书成功了,一切正常”即可。
但有一种情况例外,就是读者超额取出了图书的情况,如果界面上不尽快提醒读者,读者就会毫无知觉地离开,然而这部分超额的借书第二天就会算作超期,读者在不知情的前提下发生超期,甚至还要交违约金的话,情理上是讲不通的。所以界面对这种超额的情况做了醒目警告:对话框里面用黄色背景的大字体显示,并语音提示警告,希望读者立刻把多拿的图书放回书柜,避免出现违约现象。
在书柜正常情况下,读者关门后请求是立刻会提交到 dp2library 的,然后就可以得知是否超额。但如果通讯失败的情况下,虽然 dp2ssl 本地数据库记载下来所有信息,不会发生图书将来不追究还书的情况,但毕竟因为通讯失败,dp2library 并没有返回超额的信息,所以 dp2ssl 实际上是不知道读者是否超额的。也就是说,dp2ssl 知道读者借走了什么图书,但此时不知道是否超额。所以也就没法按照正常情况那样文字和语音提醒读者发生了超额。这样读者就可能会因为不知情而走掉了,然后发生超期违约的情况。目前看来,这个缺陷暂时是没法避免的,
可能有人会说,dp2ssl 即便不请求 dp2library,只要把当前读者最多可借的册数和新取走的图书册数一减,就知道是否超额。简单情况下是可以这样的。但 dp2library 原先就走得比较远,在流通权限里面,可以为不同类型的图书设定最大可借册数。这样,就可能发生一种情况,即可借总册数也许没有超出,但某类图书的可借册数已经超出了。所以,原则上只有当借书请求真正提交到 dp2library 服务器执行完以后,才能发现是否发生了超额的情况,所以让前端自行计算是不太可行的。
2) dp2ssl 初步实现了 searchBiblio 的点对点服务功能。也就是说点对点前端如果用 searchBiblio() API 可以对 dp2ssl 的本地操作历史库进行检索
3) 新增了一些交互命令:
目前支持的交互命令如下: (以下假设书柜的用户名叫做 robot)
@robot hello
和书柜打招呼,书柜会回复 hello!
@robot version
查看 dp2SSL 版本号
@robot error
查看 dp2SSL 界面底部红色的报错信息
@robot list history
列出当前全部书柜本地数据库中的历史动作。
@robot list history new
列出本地数据库中的尚未成功同步到 dp2library 的动作
@robot list history error
列出本地数据库中的同步到 dp2library 发生错误的动作(即:状态为 normalerror 或 commerror 的那些事项)
@robot list history sync
列出本地数据库中的已经成功同步到 dp2library 的动作
@robot check xxx
检查一个册当前的状态。其中 xxx 要用册的 PII 代替,大小写不敏感。这个命令会列出本地数据库中和这一册有关的最近 10 个动作状态,和 dp2library 中的这一册的册记录
1) 柜门控件上的数字,在 SaveActions() 阶段就刷新改变。以前版本是在 Submit 阶段刷新改变 2) 获得册记录和书目摘要时候 LibraryChannel 用一个 AsyncSimaphore 限制,只用一根通道 3) 初始盘点,和放入还书的时候,在较早阶段就修改 EAS。以前版本是 Submit 阶段才修改 EAS,如果遇到重试的情形,可能重试时候 RFID 标签已经不在书柜中了,这不是最好的做法 4) 同步阶段如果遇到出错,不会为 entity.SetError()。也就是说只是获取册记录和书目摘要时候出错才会 SetError()
1) 关门后 SaveActions() 阶段即显示对话框,里面完整显示所提交的册信息,只是前面会显示一个等待动画。待同步线程执行完同步以后,等待动画会被更新为实际的成功或者出错状态。如果遇到超额情况会单独显示一个黄底色的段落警告,并有语音警告。 2) “放入”和“取出”语音和响声提示去掉了。 3) 新增书目摘要本地数据库,提高执行速度。
1) 初始化之前,故意在柜门里面放入一个 ISO15693 的读者卡(或者工作人员卡)。程序能识别出这种情况并警告,操作者需要开门,从柜门中拿走这个读者卡,然后重试,初始化才能完成。 2) 初始化之前,故意在柜门里面放入一个空白的 ISO15693 标签,程序能识别并警告。 3) 初始化之前,故意在柜门里面放入一个无法解析的 ISO15693 标签,程序能识别并警告。(如何创建这样的无法解析的 ISO15693 标签:用最新版内务前端的 RFID 工具窗里面列表的上下文菜单里面的“测试创建错误的标签内容”菜单命令)
4) 初始化完成后正常使用期间,打开柜门放入一个空白的标签,应能识别和处理。本地数据库应记载取出和放入的动作,并且不会同步到 dp2library。 5) 初始化完成后正常使用期间,打开柜门放入无法解析的标签,应能识别和处理。本地数据库应记载取出和放入的动作,并且不会同步到 dp2library。
6) dp2ssl 设置的消息服务器参数,如果配置了消息服务器 URL,但遗漏了配置消息服务器用户名或者消息服务器组名,则界面底部会出现红色报错文字(报错的同时,消息同步功能被暂时跳过)。参数配置正确后,报错文字会消失。 7) dp2ssl 设置的消息服务器 URL 为空的时候,不会发送点对点消息出去。但一旦增设了消息服务器 URL 和相关参数以后,发送点对点消息的能力会马上启动。不需要退出 dp2ssl 再进入一次; 8) MQ 中的消息发送到 dp2mserver 的过程中,如果遇到组名不正确等报错,报错信息除了写入错误日志以外,还会在 dp2ssl 界面底部出现红色报错文字。当不正确的消息被跳过以后,后面的消息发送成功以后,红色报错文字会消失。
9) 当开门放入过图书,程序会稍微过一会儿尝试关闭读卡器常亮的灯。
10) 工作人员典藏移交(进、出)图书,结果对话框可以正确显示了
在dp2ssl初始化时,请先检查一下dp2ssl的shelf.xml文件中配置的馆藏地在dp2系统是否存在,如果不存在希望不要初始化成功。 要不到了后面馆藏移交的时候,没法操作成功。并且后面即使修改shelf.xml为正确的馆藏地,同PII再移交由于前面有未同步成功的记录,后面的操作也没法同步了。
1) dp2ssl 启动初始化书柜阶段,会自动检查 shelf.xml 配置文件中的 door/@shelfNo
属性值,检查冒号左边的馆藏地,是否在当前 dp2library 定义的合法馆藏地范围内。注:目前 dp2ssl 为智能书柜使用的馆藏地,还不允许使用班级书架的 xxxx:xxxx 形态,而必须用普通的不带有冒号的馆藏地方式来定义
2) 智能书柜的标签分离算法采用了新编写的 NewTagList 类。这个类不直接处理分离图书标签和读者卡的任务,简化了代码逻辑。分离图书标签和读者卡的任务由 dp2ssl 的代码来完成。书柜由于读卡器专用,所以倾向于用读卡器来定义放在上面的标签用途,而不是像以前那样用 ISO14443A 类型 和 ISO15693类型的 TypeOfUsage 字段来进行区分
(测试要点:要回归测试当读者卡放入书柜门内的各种情况;书柜内图书标签的全部测试项目;读者证读卡器上放一张和多张读者卡的各种情况)
3) 远程管理增加了 change history id=xxx state=dontsync 命令,能把 ID 为 xxx 的操作历史记录的 State 字段内容修改为 "dontsync"。
1) RfidCenter 修正了加锁时候遇到异常时没有清理干净的 bug。 2) 数据格式错误的 ISO15693 标签,放入书柜门内,和放到读者证读卡器上,都能正确显示报错。特别注意测试放入书柜门内,初始化之前放入,和平时开门放入,都要测试。 3) 工作人员身份放入和取出图书,弹出的对话框上面显示的册信息,location 和 currentLocation 显示已经正确了。
1) 书柜关门后要自动进行一次针对读卡器特定天线的 inventory 操作,新版本已经把这个操作给线性化了以后再分别调用的 RfidCenter 的 ListTags() API,这样可以减轻 RfidCenter 相关 API 的加锁发生超时的可能性; 2) 工作人员身份放入和取出图书以后,关门,会弹出询问是否进行移交的对话框,这个对话框原来版本是每当关闭一个柜门就会弹出一个对话框,如果同时关闭了多个柜门,则会有多个对话框叠加显示出来,后关门的在上层。新版本在这里把打开对话框的过程线性化了,每次只开一个对话框,后面的对话框要等待前面的对话框关闭以后才会自动打开。 3) 上述对话框中的批次号可以用键盘修改了。
1) 如果书柜开门后放入过图书,通常会造成相应的天线灯长亮不灭。再次改进了自动检查熄灭天线灯的功能,待测试验证。 2) 注意 EAS 特性:图书放入书柜,即被自动清掉 EAS 标志;工作人员身份从书柜中下架图书以后,注意 EAS 标志为 Off 状态,如果要在其他普通书架上架,注意上架前修正一下 EAS 状态。
智能书柜是一种新型的设备,它可以持续探测图书是否存在于书柜空间内,因此带来了一些特定的极具价值的新功能和概念,也对软件设计开发提出了一些新的挑战。
本文试图探讨这些功能和概念,并指导智能书柜软件设计开发应用。