【玩转.Net MF – 02】让PC成为MF的鼠标键盘

简介: 考虑一个应用场景,你设计了一个多功能带LCD显示的仪器,假设为了节省成本,没有安装触摸屏和扩展外接鼠标键盘的接口,仪表上仅有几个外置按钮,但是由于功能相对复杂,需要配置很多参数,如果单单依靠外置按钮,输入不仅慢,还得为此设计一套输入规则,想想看如果能通过仪表调试口,通过扩展让我们的PC成为它的鼠标键盘,则输入工作将变的异常简单(其实这样的仪表并不是我凭空瞎想,以前开发ICU输液系统时,国外生产的输液装置就是这样的仪表,比如要输入药名、输液速度和输液压力等一系列相关参数)。

考虑一个应用场景,你设计了一个多功能带LCD显示的仪器,假设为了节省成本,没有安装触摸屏和扩展外接鼠标键盘的接口,仪表上仅有几个外置按钮,但是由于功能相对复杂,需要配置很多参数,如果单单依靠外置按钮,输入不仅慢,还得为此设计一套输入规则,想想看如果能通过仪表调试口,通过扩展让我们的PC成为它的鼠标键盘,则输入工作将变的异常简单(其实这样的仪表并不是我凭空瞎想,以前开发ICU输液系统时,国外生产的输液装置就是这样的仪表,比如要输入药名、输液速度和输液压力等一系列相关参数)。

通过扩展我以前为.Net MF开发的WinForm库(参见我以前的文章《开源System.Windows.Forms.Net Micro Framework界面开发和上位机一样简单》),增加一个输入代理层,就可以实现虚拟鼠标和键盘输入。

先看看最终的成果(如下图),然后我们再细说是如何实现的。

 1

(虚拟鼠标输入,设备上的鼠标将和PC上的鼠标同步移动)

2

(虚拟键盘输入,千万不要以为上面的字符为软键盘所输入,细心的读者会发现,软键盘上根本没有 @#%等按键)

要实现这个功能还真不那么简单,需要做如下四步工作:一、为MFDeploy开发一个插件,捕捉PC上的鼠标和按键信息,并把它们发送到设备;二、修改TinyCLR内核代码,让它获取PC上发送的鼠标和按键信息;三、为.Net MF添加一个事件源,当有鼠标和按键时,触发.Net MF应用程序中特定的事件;四、为WinForm库扩展一个输入代理层。下面我们将一一介绍上面四步的实现步骤。

一、      MFDeploy虚拟输入插件

在上一篇同系列的文章《【玩转.Net MF 01Flash远程读写》,我们已经介绍了MFDeploy的实现方法,所以这里就不熬述了。

实现思路的是这样的,因为我们已经通过MFDeploy可以和设备进行通信,在不额外增加代码的情况下,我们虚拟一个写Flash 的操作,通过该渠道,把鼠标和按键信息发送给设备。实现代码如下:

        private void SendKey(KeyState state, Keys KeyCode, int KeyValue, bool Shift, bool Caps, bool Ins)

        {

            byte[] bytData = new byte[8]{ 0x01,(byte)state,(byte)KeyCode, (byte)KeyValue, (byte)(Shift ? 1 : 0), (byte)(Caps ? 1 : 0), (byte)(Ins ? 1 : 0),0};

            SendData(bytData);

        }

        private void SendMouse(MouseState state, MouseButtons button, int x, int y)

        {

            byte[] bytData = new byte[8] { 0x02, (byte)state, (byte)(button == MouseButtons.Left ? 1 : 0), (byte)(button == MouseButtons.Right ? 1 : 0), (byte)(x >> 8), (byte)(x & 0xFF), (byte)(y >> 8), (byte)(y & 0xFF) };

            SendData(bytData);

        }

        private void SendData(byte[] bytData)

        {

            if (bytData.Length != 8) return;

            UInt32 addr = 0xD;

            byte[] TempData = new byte[10] {0xAA, 0x55,0,0,0,0,0,0,0,0};

            Array.Copy(bytData, 0, TempData, 2, 8);

            bool ret = engine.WriteMemory(addr, TempData, 0, 10);

        }

        private void palScreen_MouseDown(object sender, MouseEventArgs e)

        {

            SendMouse(MouseState.Down, e.Button, e.X, e.Y);

        }

        private void palScreen_MouseMove(object sender, MouseEventArgs e)

        {

            SendMouse(MouseState.Move, e.Button, e.X, e.Y);

        }

        private void palScreen_MouseUp(object sender, MouseEventArgs e)

        {

            SendMouse(MouseState.Up, e.Button, e.X, e.Y);

        }

        private void frmVirtualInput_KeyDown(object sender, KeyEventArgs e)

        {

            lblKey.Text = e.KeyCode.ToString();

            SendKey(KeyState.Down, e.KeyCode, (int)e.KeyValue, e.Shift, false, false);

        }

        private void frmVirtualInput_KeyUp(object sender, KeyEventArgs e)

        {

            SendKey(KeyState.Up, e.KeyCode, (int)e.KeyValue, e.Shift, false, false);

        }

        private void frmVirtualInput_KeyPress(object sender, KeyPressEventArgs e)

        {

            byte[] bytChar = System.Text.ASCIIEncoding.UTF8.GetBytes(e.KeyChar.ToString());

            SendKey(KeyState.Press, Keys.Space, (int)bytChar[0], false, false, false);

   } 

  窗体的大小通过获取设备的LCD的尺寸,进行自动设置,这等于虚拟出一个与LCD等大的鼠标活动区。

二、      鼠标和按键信息获取

上一步我们向设备写入了一个虚拟写Flash操作,所以我们在设备的上的代码,需做些修改,还是修改/CLR/Debugger目录下的Debugger.cpp文件,在CLR_DBG_Debugger::AccessMemory函数增添如下代码:

case AccessMemory_Write:

     if(accessAddress == 0xD  && NumOfBytes==10 && bufPtr[0] == 0xAA && bufPtr[1] == 0x55 )

    {

         UINT32 data1=(bufPtr[2]<<24) | (bufPtr[3]<<16) |  (bufPtr[4] <<8) | bufPtr[5];

         UINT32 data2=(bufPtr[6]<<24) | (bufPtr[7]<<16) |  (bufPtr[8] <<8) | bufPtr[9];              

         VI_GenerateEvent(data1,data2);

         success = TRUE;

    }

    else

    {               

         success = m_deploymentStorageDevice->Write( accessAddress , NumOfBytes, (BYTE *)bufPtr, FALSE );

        }

    break;

记得在函数头声明VI_GenerateEvent函数,它负责触发消息。

extern void VI_GenerateEvent(UINT32 data1, UINT32 data2);

三、      实现事件触发

我们在/DeviceCode/pal目录新建一个VirtualInput子目录,我们要为TinyCLR新添加一个feature。核心代码如下:

void VI_GenerateEvent(UINT32 data1, UINT32 data2)

{

    if(g_Context)

    {

        GLOBAL_LOCK(irq);          

        SaveNativeEventToHALQueue( g_Context,data1, data2);

    }

}

static HRESULT InitializeEventDriver( CLR_RT_HeapBlock_NativeEventDispatcher *pContext, UINT64 userData )

{

   g_Context  = pContext;

   g_UserData = userData;

   return S_OK;

}

static HRESULT EnableDisableEventDriver( CLR_RT_HeapBlock_NativeEventDispatcher *pContext, bool fEnable )

{

   g_InterruptEnalbed = fEnable;

   return S_OK;

}

static HRESULT CleanupIestDriver( CLR_RT_HeapBlock_NativeEventDispatcher *pContext )

{

    g_Context = NULL;

    g_UserData = 0;  

    CleanupNativeEventsFromHALQueue( pContext );

    return S_OK;

}

static const CLR_RT_DriverInterruptMethods g_InteropEventDriverMethods =

{

    InitializeEventDriver,

    EnableDisableEventDriver,

    CleanupIestDriver

};

const CLR_RT_NativeAssemblyData g_CLR_AssemblyNative_Microsoft_SPOT_YFVI_Event  =

{

    "Microsoft_SPOT_YFVI_Event",

    DRIVER_INTERRUPT_METHODS_CHECKSUM,

    &g_InteropEventDriverMethods

};

以上代码就是.Net MF实现事件触发的典型结构,比如按键按下抬起、串口数据接收、SD卡插入等等事件通知就是如此实现的。

四、      WinForm输入代理实现

接收事件的代码如下,也是一个标准结构。

public class EventDispatcher : NativeEventDispatcher

    {

        public EventDispatcher()

            : base("Microsoft_SPOT_YFVI_Event", 0)

        { }

        public EventDispatcher(string EventDispatcherName, ulong userData)

            : base(EventDispatcherName, userData)

        { }

}

"Microsoft_SPOT_YFVI_Event"要和上一步g_CLR_AssemblyNative_Microsoft_SPOT_YFVI_Event中的名字保持一致。   

WinForm库新添加一个YFSoft.InputProxy.dll库,和虚拟输入相关的代码如下:

private void eventDispatcher_OnInterrupt(uint data1, uint data2, DateTime time)

        {

            byte[] bytData = new byte[8];

            bytData[0] = (byte)(data1 >> 24 & 0xFF);

            bytData[1] = (byte)(data1 >> 16 & 0xFF);

            bytData[2] = (byte)(data1 >> 8 & 0xFF);

            bytData[3] = (byte)(data1 >> 0 & 0xFF);

            bytData[4] = (byte)(data2 >> 24 & 0xFF);

            bytData[5] = (byte)(data2 >> 16 & 0xFF);

            bytData[6] = (byte)(data2 >> 8 & 0xFF);

            bytData[7] = (byte)(data2 >> 0 & 0xFF);

            //key

            if (bytData[0] == 0x01)

            {

                SendKey((KeyState)bytData[1], (Keys)bytData[2], bytData[3], bytData[4] == 1, bytData[5] == 1, bytData[6] == 1);

            }

            //mouse

            if (bytData[0] == 0x02)

            {               

                MouseButtons button=MouseButtons.None;

                if(bytData[2] ==1) button= MouseButtons.Left;

                if(bytData[3] ==1) button= MouseButtons.Right;

 

                SendMouse((MouseState)bytData[1], button, bytData[4] << 8 | bytData[5], bytData[6] << 8 | bytData[7]);

            }

        }

        public void Initialize(System.Windows.Forms.Dispatcher Dispatcher)

        {

            this.Dispatcher = Dispatcher;

            if (KeyEnable) Key_Initialize();

            if (MouseEnable) Mouse_Initialize();

            if (VirtualInputEnable)

            {

                EventDispatcher eventDispatcher = new EventDispatcher();

                eventDispatcher.OnInterrupt += new NativeEventHandler(eventDispatcher_OnInterrupt);

            }

        }

        public void SendKey(KeyState state, Keys KeyCode, int KeyValue, bool Shift, bool Caps, bool Ins)

        {

            //Debug.Print(KeyValue.ToString()+" "+((int)state).ToString());

            if (Key != null) Key(state, KeyCode, KeyValue, Shift, Caps, Ins);

        }

        public void SendMouse(MouseState state,MouseButtons button, int x, int y)

        {

            //Debug.Print("(" + x.ToString() + "," + y.ToString() + ")");

            if (Mouse != null) Mouse(state, button, x, y);

    }

我们在开发基于WinForm库的应用程序时,只要在Main函数中添加如下代码,即可以启动该功能。

Application.InputProxy.VirtualInputEnable = true;

需要说明的是,该虚拟输入和正常的输入设备并没有冲突(如触摸屏及相关按键),原先的输入设备还是可以正常工作的,这样做的目的,只是额外为你的设备扩展了一个强大的输入装置。

还是那句话,开源后的.Net MF放飞了我们的梦想,通过简单的扩展,使我们和设备的交互的能力比以往更加强大和有力。

相关文章
|
安全
.Net MF V4.0开源前的代码整理
已经有好长一段时间没有更新博客了,一是去美国总部和台湾出差用了不少时间,二是做.Net MF代码整理又花了近一个月的时间。不过令人欣慰的是,目前.Net MF V4.0的相关代码整理已经告一段落,就等着下一步的开源了
628 0
|
内存技术
【玩转.Net MF – 01】Flash远程读写
目前在PC远程访问设备Flash,也就是部署TinyCLR和下载应用程序
537 0
|
内存技术
【玩转.Net MF – 02】让PC成为MF的鼠标键盘
通过扩展我以前为.Net MF开发的WinForm库(参见我以前的文章《开源System.Windows.Forms库,让.Net Micro Framework界面开发和上位机一样简单》),增加一个输入代理层,就可以实现虚拟鼠标和键盘输入。
562 0
|
网络协议
【玩转.Net MF – 03】远程文件查看器
做过WinCE或Windows Mobile开发的人都知道,VS2008开发工具提供了些远程工具,诸如远程文件查看器、远程注册表编辑器、远程堆查看器和远程放大等等。受此启发,所以才有了MF的远程文件查看器。
604 0
【玩转.Net MF – 04】远程屏幕截图
实现远程屏幕截图的思路很简单,就是直接获取设备的显存数据,由PC再现画面。由于我们已经实现了Custom信道,所以我们在原有程序基础上,增添一个Custom_Command_Screenshots命令,就可以完成数据的获取。
472 0
|
芯片 物联网 内存技术
WG7310(WLAN+Bluetooth+FM)芯片在.Net MF中的应用
WG7310芯片是Ti推出的一款芯片,集成了WLAN、Bluetooth、FM等功能(最近又推出了四合一的芯片,把GPS功能也集成了进去),由于以前在.Net MF上的一些工作是基于Ti DM335开发板上的,所以开发.Net MF系统的WiFi功能就选用了WG7310芯片。
696 0
|
芯片
免费发放firmwave,打造史上最低价.Net MF开发板
很久以前就曾多方位思考限制.Net Micro Framework发展的原因是什么?在物联网和Cortex-M3大行其道的今天,应该有更大的发展空间才对,为什么现在还是关注者甚少?我想主要原因有三,一、源码代码是否开源;二、是否有低价开发板;三、TinyCLR是否够小。
737 0
【STM32 .Net MF开发板学习-03】TinyGUI绘图示例
.Net Micro Framework官方图形库是WPF,由于目前ST Cortex-M3开发板RAM太小,最大才512K(常见是128K或256k),并且Cortex-M3的CPU主频也不太高,运行WPF图形框架显得过于重了,所以我这边推出了轻量级图形库TinyGUI
575 0
|
内存技术
【STM32 .Net MF开发板学习-04】TinyGUI位图显示
由于Cortex-M3开发板的RAM比较小,比如EM-STM3210E仅128K,所以显示位图是个比较棘手的事,如320*240 16位的位图大小就为150K,由于官方的WPF以一个BMP位图为本底进行绘图,所以RAM内存需求至少大于150K。
606 0
【STM32 .Net MF开发板学习-06】蜂鸣器和LED数码管显示
无论是蜂鸣器还是LED数码管显示,其实这二者对代码编写来说没有太大区别,都是GPIO的一个典型应用。红牛开发板有一个蜂鸣器,而EM-STM3210E有一个四位LED数码管,代码都相对简单,不值的为二者单独写一篇博文,所以二者合一以一篇文章来说明,不过两个示例代码是独立的。
625 0