C# LIS实现串口ASTM协议双向通信

前言

好记性,不如烂笔头。大家好,我是不自由的小码。既然来了那么点个关注,点个赞再走吧!
今天这篇文章是要教大家C#如何基于ASTM协议与设备进行通信交互。

什么是ASTM协议标准

官方的解释太专业,按照自己理解,举个例子,比如在医疗行业,用于与化验设备(LIS)通信交互,那它就是一个数据交互协议,如果你是C#跟Java程序员,那你可以理解他跟json、xml一样,都是数据交换标准的一种格式。

数据通信逻辑

以双向通信为例,整体通讯分三步,设备发送请求、HIS回传项目、设备回传结果。其实最终实现效果正确的话,单向双向都是一个逻辑,因为单向的话是设备直接发结果,通信程序要做的就是重复第一步,一直ACK(应答)直到EOT(结束)。根据收到的内容去判断,数据是来自设备请求,还是设备回传。举个例子,这就像两个人对话。

一、设备发送请求
唐三(ENQ):小舞~中午该吃饭了。
小舞(ACK):收到!
唐三(STX):今天中午我们吃十万年魂兽吧。
小舞(ACK):收到!
唐三(STX):放点辣。
小舞(ACK):收到!

唐三(ETO):就这些,在没其它要求了。
小舞(无需回答):心里在想,不理三哥了,做个饭要求真多!

二、程序回传项目
小舞(ENQ):三哥~,辣子魔晶王做好了。
唐三(ACK):收到!
小舞(STX):你看,这里面我放了辣椒。
唐三(ACK):收到!
小舞(STX):你看,这里面我放了大蒜。
唐三(ACK):收到!

小舞(ETO):好了,就这么多项目,开始吃吧。
唐三(无需回答):心里想,吃个饭真不容易啊。唠叨了半天。

三、设备回传结果
唐三(ENQ):小舞~我得提几点意见。
小舞(ACK):收到!
唐三(STX):今天中午的发米饭做硬了。
小舞(ACK):收到!
唐三(STX):辣椒放的倒是合适。
小舞(ACK):收到!

唐三(ETO):就提这些意见吧。
小舞(无需回答):心里在想,不理三哥了,管他呢,反正这顿饭吃完了!

数据发送格式

<STX>FN<FRAME><CR><ETX>CHECKSUM<CR><LF>

尖括号中的都是协议控制字符,下面会有解释。CHECKSUM是计算内容。

CHECKSUM 算法

ASTM协议的校验码算法:((所有的字符的ASCII码总和)除以256的余数)的十六进制。可以封装一个函数用于后续的计算。

        public static string GetCheckSum(string str)
        {
            // 十进制数组之和除以256的余数转成十六进制字符
            byte[] byteArr = ASCIIEncoding.UTF8.GetBytes(str);
            int sum = 0;
            for (int i = 0; i < byteArr.Length; i++)
            {
                sum += byteArr[i];
            }
            sum %= 256;
            return sum.ToString("X2");
         }

协议控制字符

C#代码部分值得关注,程序后续都需要靠这个转义控制字符进行编写。
C#代码部分值得关注,程序后续都需要靠这个转义控制字符进行编写。

控制字符转换

可以单独封装一个转化方法,在拼接数据是明文的时候,直接将字符串传入,得到设备识别的带ASCII控制字符的数据。

        public char CR = (char)13;
        public char ENQ = (char)5;
        public char ACK = (char)6;
        public char ETX = (char)3;
        public char LF = (char)10;
        public char ETB = (char)17;
        public char NAK = (char)15;
        public char EOT = (char)4;
        public char STX = (char)2;
        private string ChangeAscii(string str)
        {
            return str
                   .Replace("<ENQ>", ENQ.ToString())
                   .Replace("<CR>", CR.ToString())
                   .Replace("<ACK>", ACK.ToString())
                   .Replace("<ETX>", ETX.ToString())
                   .Replace("<LF>", LF.ToString())
                   .Replace("<ETB>", ETB.ToString())
                   .Replace("<NAK>", NAK.ToString())
                   .Replace("<EOT>", EOT.ToString())
                   .Replace("<STX>", STX.ToString());
        }

数据交换过程剖析

1.当首次设备向LIS通讯软件发送请求的时候,通讯软件只需要AKC。

//设备:<ENQ>
if (str.Contains(((char)5).ToString()))
{
    //LIS: <ACK>
    this.SendMessage(((char)6).ToString());
    return;
}

2.设备收到通讯软件ACK后,会继续向LIS发送请求数据,格式为:<STX>FN<FRAME><CR><ETX>CHECKSUM<CR><LF>,直到设备发送EOT结束。

//设备:<EOT>
if (str.Contains(((char)4).ToString()))
{
    //存储过程开始处理数据
    if (string.IsNullOrEmpty(sbStr.ToString()))
    {
       //LIS处理数据结果:设备发送了空数据,无需后续处理
       return;
     }
     string returnData = LisProcedure(sbStr.ToString());//调用函数返回数据,自定义,根据自己业务。
     if (string.IsNullOrEmpty(returnData))
     {
        //已经是最后一步无需继续向设备发消息
        sbStr.Clear();
        return;//说明是最后一步直接停止交互
      }
      // 根据STX拆分数据
      string[] strArrayOut = ChangeAscii(returnData).Split(new char[] { (char)2 }, StringSplitOptions.RemoveEmptyEntries);
      for (int i = 0; i < strArrayOut.Length; i++)
      {
         //LIS:<STX>1H|\^&|<CR><ETX>checksum<CR><LF>
         string strOutSplit = (char)2 + strArrayOut[i];
         //将返回的数据
         strListOut.Add(strOutSplit);//strListOut全局变量,在LIS像设备发送项目的时候使用
       }
       //LIS向设备发ENQ
       port.SendMessage(((char)5).ToString());
       sbStr.Clear();//清空数据
       return;
}

值得注意的是,每次接收数据,只有在LF结尾的时候才表示当条数据被完整接收,否则继续拼接接收。收到一条数据,就要应答ACK一次,设备才会继续发送。

strACK += str;
if (str.Contains((char)10))//若果是LF结尾完成拼接,此时才是当前接收数据的完整串
{
  //设备
  //接收到的数据:去除\0,<CR><ETX>替换为<ETX>,根据自己的业务去写
  sbStr.Append(strACK.Trim('\0').Replace($"{((char)13).ToString() + ((char)3).ToString()}", $"{((char)3).ToString()}"));
  //LIS:<ACK>
  this.SendMessage(((char)6).ToString());
  strACK = "";
}

3.LIS准备好数据以后,通讯软件要向设备发送请求ENQ,设备这时会回答ACK。当LIS通讯收到设备的应答ACK后,开始发送数据STX,设备继续应答,LIS通信继续发送,当数据发送完毕后,LIS通信软件发送EOT结束给设备。

if (str.Contains(((char)6).ToString()))//如果收到的是ACK
{
   //接收到设备的ACK后,发送数据
   if (strListOut.Count > 0)
   {
     this.SendMessage(ChangeAscii(strListOut[0]));
     strListOut.RemoveAt(0);
    }
    else
    {
       //LIS向设备发EOT
       port.SendMessage(((char)4).ToString());
     }
       return;
}

4.化验结果出来以后,设备要向通讯软件发送请求ENQ,通讯软件收到后需要应答ACK,设备收到通讯软件的ACK后,会向通讯软件发送明细结果STX,通讯软件继续应答ACK,直到收到设备的EOT。

逻辑共用,上面第12项说明。原理都是一样的,代码都是一套代码无需重写。
唯一的差别就是,LIS拿到接收的数据,要判断该数据是请求项目数据,还是化验结果数据。然后做相应的数据库处理就好了。

5.完整代码

public void GetMessage(string str)
{
            
  if (str.Contains(((char)6).ToString()))//如果收到的是ACK
  {
     //接收到设备的ACK后,发送数据
     if (strListOut.Count > 0)
     {
        this.SendMessage(ChangeAscii(strListOut[0]));
        strListOut.RemoveAt(0);
      }
      else
      {
        //LIS向设备发EOT
        port.SendMessage(((char)4).ToString());
       }
        return;
   }
    //设备:<ENQ>
   if (str.Contains(((char)5).ToString()))
   {
      //LIS: <ACK>
      this.SendMessage(((char)6).ToString());
      return;
    }
    //设备:<EOT>
    if (str.Contains(((char)4).ToString()))
    {
       //存储过程开始处理数据
       if (string.IsNullOrEmpty(sbStr.ToString()))
       {
           //LIS处理数据结果:设备发送了空数据,无需后续处理
           return;
        }
        string returnData = LisProcedure(sbStr.ToString());//调用函数返回数据,自定义,根据自己业务。
        if (string.IsNullOrEmpty(returnData))
        {
           //已经是最后一步无需继续向设备发消息
           sbStr.Clear();
           return;//说明是最后一步直接停止交互
         }
         // 根据STX拆分数据
         string[] strArrayOut = ChangeAscii(returnData).Split(new char[] { (char)2 }, StringSplitOptions.RemoveEmptyEntries);
         for (int i = 0; i < strArrayOut.Length; i++)
         {
             //LIS:<STX>1H|\^&|<CR><ETX>checksum<CR><LF>
             string strOutSplit = (char)2 + strArrayOut[i];
             //将返回的数据
             strListOut.Add(strOutSplit);//strListOut全局变量,在LIS像设备发送项目的时候使用
          }
          //LIS向设备发ENQ
          port.SendMessage(((char)5).ToString());
          sbStr.Clear();//清空数据
          return;
     }
            
      //下面这段这么写是为了接收完整数据
      strACK += str;
      if (str.Contains((char)10))//若果是LF结尾完成拼接,此时才是当前接收数据的完整串
      {
        //设备
        //接收到的数据:去除\0,<CR><ETX>替换为<ETX>,根据自己的业务去写
        sbStr.Append(strACK.Trim('\0').Replace($"{((char)13).ToString() + ((char)3).ToString()}", $"{((char)3).ToString()}"));
        //LIS:<ACK>
        this.SendMessage(((char)6).ToString());
        strACK = "";
       }

}

小结

快点去进行实战吧。点个赞跟关注小码在走吧。其实本片博文另一个名字叫《C# LIS实现串口ASTM协议双向通信之斗罗大陆篇》

  • 12
    点赞
  • 40
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不自由的小码

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值