C# 如何使用反应式扩展解析来自串行端口的字符流?
我需要解析来自测试仪器的串行数据流,这似乎是一个很好的反应式扩展应用程序 协议非常简单……每个“数据包”都是一个字母,后跟数字。每个数据包类型的数字位数是固定的,但在不同的数据包类型之间可能有所不同。e、 g …A1234B123456C12 我试图把它分解成一个可观察的字符串,例如 “A1234”“B123456”“C12” 本以为这很简单,但看不到实现这一点的明显方法(我对LINQ有一些经验,但我对Rx是新手) 这是我到目前为止的代码,它从串口的SerialDataReceived事件生成可观察的字符C# 如何使用反应式扩展解析来自串行端口的字符流?,c#,serial-port,system.reactive,reactive-programming,C#,Serial Port,System.reactive,Reactive Programming,我需要解析来自测试仪器的串行数据流,这似乎是一个很好的反应式扩展应用程序 协议非常简单……每个“数据包”都是一个字母,后跟数字。每个数据包类型的数字位数是固定的,但在不同的数据包类型之间可能有所不同。e、 g …A1234B123456C12 我试图把它分解成一个可观察的字符串,例如 “A1234”“B123456”“C12” 本以为这很简单,但看不到实现这一点的明显方法(我对LINQ有一些经验,但我对Rx是新手) 这是我到目前为止的代码,它从串口的SerialDataReceived事件生成可
var serialData = Observable
.FromEventPattern<SerialDataReceivedEventArgs>(SerialPortMain, "DataReceived")
.SelectMany(_ =>
{
int dataLength = SerialPortMain.BytesToRead;
byte[] data = new byte[dataLength];
int nbrDataRead = SerialPortMain.Read(data, 0, dataLength);
if (nbrDataRead == 0)
return new char[0];
var chars = Encoding.ASCII.GetChars(data);
return chars;
});
var serialData=可观察
.FromEventPattern(SerialPortMain,“DataReceived”)
.SelectMany(=>
{
int dataLength=SerialPortMain.BytesToRead;
字节[]数据=新字节[dataLength];
int-nbrDataRead=SerialPortMain.Read(数据,0,数据长度);
如果(nbrDataRead==0)
返回新字符[0];
var chars=Encoding.ASCII.GetChars(数据);
返回字符;
});
如何将serialData转换为字符串的可观察值,其中每个字符串都是一个数据包?令人惊讶地精细!我用两种方法解决了这个问题:
// helper method to get the packet length
public int GetPacketLength(char c)
{
switch(c)
{
case 'A':
return 5;
case 'B':
return 6;
case 'C':
return 7;
default:
throw new Exception("Unknown packet code");
}
}
然后我们可以这样做:
// chars is a test IObservable<char>
string[] messages = { "A1234", "B12345", "C123456" };
var serialPort = Enumerable.Range(1, 10).ToObservable();
var chars = serialPort.SelectMany((_, i) => messages[i % 3]);
var packets = chars.Scan(
Tuple.Create(string.Empty, -1),
(acc, c) =>
Char.IsLetter(c)
? Tuple.Create(c.ToString(), GetPacketLength(c) - 1)
: Tuple.Create(acc.Item1 + c, acc.Item2 - 1))
.Where(acc => acc.Item2 == 0)
.Select(acc => acc.Item1)
.Subscribe(Console.WriteLine);
它可以这样使用:
// chars is a test IObservable<char>
string[] messages = { "A1234", "B12345", "C123456" };
var serialPort = Enumerable.Range(1, 10).ToObservable();
var chars = serialPort.SelectMany((_, i) => messages[i % 3]);
var packets = chars.GetPackets().Subscribe(Console.WriteLine);
//chars是一个可观察的测试
字符串[]消息={“A1234”、“B12345”、“C123456”};
var serialPort=Enumerable.Range(1,10).ToObservable();
var chars=serialPort.SelectMany((uu,i)=>messages[i%3]);
var packets=chars.GetPackets().Subscribe(Console.WriteLine);
这里有一个略短的方法,与使用类似助手方法的方法风格相同:
public static bool IsCompletePacket(string s)
{
switch (s[0])
{
case 'A':
return s.Length == 5;
case 'B':
return s.Length == 6;
case 'C':
return s.Length == 7;
default:
throw new ArgumentException("Packet must begin with a letter");
}
}
代码是:
var packets = chars
.Scan(string.Empty, (prev, cur) => char.IsLetter(cur) ? cur.ToString() : prev + cur)
.Where(IsCompletePacket);
扫描部分构建以字母结尾的字符串,例如:
A
A1
A12
A123
...
然后,,其中
只选择长度正确的。本质上,它只是从James'中删除元组,并使用字符串长度。以下是我对这个问题的看法
static IObservable<string> Packets(IObservable<char> source)
{
return Observable.Create<string>(observer =>
{
var packet = new List<char>();
Action emitPacket = () =>
{
if (packet.Count > 0)
{
observer.OnNext(new string(packet.ToArray()));
packet.Clear();
}
};
return source.Subscribe(
c =>
{
if (char.IsLetter(c))
{
emitPacket();
}
packet.Add(c);
},
observer.OnError,
() =>
{
emitPacket();
observer.OnCompleted();
});
});
}
数据包的实现就是:
static IObservable<string> Packets(IObservable<char> source)
{
return source
.GroupSequential(char.IsLetter)
.Select(x => new string(x.ToArray()));
}
静态IObservable数据包(IObservable源)
{
返回源
.GroupSequential(char.isleter)
.Select(x=>newstring(x.ToArray());
}
谢谢,詹姆斯,这似乎起作用了。我同意你的第二种方法。虽然有点“unRx-ey”,但我同意它更容易理解。是的,这是我书中的赢家。我将IsCompletePacket中的默认大小写更改为返回false,因此现在它只忽略代码不支持的任何数据包。我喜欢这样的事实,即它适用于任何数据包长度。虽然-Rx对我来说是一种非常不同的思维方式,但在遵循逻辑时遇到了一些问题。@TomBushell“由内而外”的方法有助于理解它。发生的事情是,当有人订阅数据包(源)
,实际上只是订阅源
。1.每当源
发出字符c
时,我们检查当前数据包是否完成:如果完成,我们发出它并启动一个新数据包。我们总是将当前字符添加到当前数据包中。2.当源发出错误时,我们只是转发它。3.当源代码
完成时,我们确保发出最后一个数据包(如果有)。
static IObservable<IList<T>> GroupSequential<T>(
this IObservable<T> source, Predicate<T> isFirst)
{
return Observable.Create<T>(observer =>
{
var group = new List<T>();
Action emitGroup = () =>
{
if (group.Count > 0)
{
observer.OnNext(group.ToList());
group.Clear();
}
};
return source.Subscribe(
item =>
{
if (isFirst(item))
{
emitGroup();
}
group.Add(item);
},
observer.OnError,
() =>
{
emitGroup();
observer.OnCompleted();
});
});
}
static IObservable<string> Packets(IObservable<char> source)
{
return source
.GroupSequential(char.IsLetter)
.Select(x => new string(x.ToArray()));
}