Feat/fix alicat multidrop - #2
Conversation
Removed unit from display until dynamic units are implemented
| { | ||
| private readonly List<char> _unusedIds; | ||
| //simple registry to manage instances of seriaport resources across multiple mfcs | ||
| static Dictionary<string, MassFlowControllerConnection> _connections = new Dictionary<string, MassFlowControllerConnection>(); |
There was a problem hiding this comment.
I'm curious if you could elaborate on what you wanted to implement this to address. Typically we don't expect the driver to care about it's connection in relation to any others (maintaining the idea that the driver itself would only really care about it's own connection). It's true that the MFC's can share a serial connection, and we account for that in ARES by allowing devices of the same class (i.e. Alicat MFC's) to share the same resource with a unique identifier. For the MFC's this is the Id list that use a single capital letter (A, B, C...). Unless there's something critical this provides, I would say this is best left at the ARES level rather than the driver level itself.
| _serialConnection = new MassFlowControllerConnection(serialInfo.PortName); | ||
| _serialConnection = MassFlowControllerConnection.GetMassFlowControllerConnection(serialInfo.PortName); | ||
|
|
||
| _stateWatchers = new CompositeDisposable |
There was a problem hiding this comment.
I see the motivation here, but this should theoretically not be an issue on the current system. When receiving data that needs to be parsed, we have dedicated response parser classes that handle breaking down the serialized data. In the case of this stream, we're only going to get this processed response (the "LiveDataResponse" class) in the event that parser deemed it a valid response to parse. You can find that logic under "Commands/Responses/Parsers/LiveDataParser.cs". Based on that, I don't think this change is necessary, you shouldn't see the system processing any additional data from MFC's other than the one you told the driver to care about. If you are, then perhaps there's another bug happening under the hood we can dig out.
| private void UpdateLiveData(LiveDataResponse liveResponse) | ||
| { | ||
| _liveData = liveResponse; | ||
| /// check on id is required if _statewatcher is used in order to avoid updating the state with a response from a different MFC than the one that is being watched |
There was a problem hiding this comment.
My comment above talks about this point I believe
| var flowVal = StandardVolumeFlow.From(numericNum, unit); | ||
| // must be converted to match setpoint units, otherwise may cause issues when calculating newsetpoint | ||
| // dataFrameFormat.MaxVal = flowVal.StandardLitersPerMinute.ToString(); | ||
| dataFrameFormat.MaxVal = flowVal.As((StandardVolumeFlowUnit)dataFrameFormat.Unit).ToString(); |
There was a problem hiding this comment.
Just curious, was this change inspired by an issue you saw in lab?
|
Morning!
We observed in the lab that Alicats could be created using the same port. The UI would initialize appropriately, but only the first connected would update. I remember seeing in the logs something about being unable to access the serial port, but I didn't copy them (Sorry).
Looking into the code, I noticed that each Alicat created its own MassFlowControllerConnection. It was not apparent to me that these referenced a common system resource. Previous experience says that this type of error is common when several system.io.serialports referencing the same port are created. I've had success using multiple objects by explicitly releasing it in one place to grab it somewhere else, but it never worked the way I hoped. The Alicats referencing a shared resource corrected the issue by only creating one instance of the base port. After this fix all alicats would connect and update.
I agree this may be a temporary solution that relies heavily on the fact that alicats share a library and therefor reference the MassFlowControllerConnection registry well. As we expand function to modbus devices for instance, devices of different types may communicate on the same bus and the registry as it is done here will likely fail. The answer is probably to push the registry behavior into the Ares.Serial.Toolkit.
The next two changes followed from the first. All devices would respond to all traffic on the bus since neither the parsers nor updatelivedata had a check on the deviceid. I could have added a check on deviceid, but I elected to go to a more query-based pattern. This tightly linked the request with the response and fixed the message addressing issue without forcing every device on the bus to parse every message.
For the last change... We observed in the lab that SLPM controllers behaved as expected, but SCCM controllers had to be set in SLPM instead of SCCM (if I want 150 SCCM, I would need to set 0.150, unlike the SLPM controllers that would be set as 150). I found that the commands were being explicitly types as SCCM. In the command, maxsetpoint was expected to have the same units as the setpoint, but when updating the maxvalue it was being explicitly set to SLPM. The fact that it was being recorded in SLPM and the inappropriately converted to SCCM was causing the issue. As long as it was consistent the bug would be resolved. I chose to use the setpoint units since I thought that was more internally consistent and required less architectural knowledge to interpret.
public NewSetpointCommand(char id, StandardVolumeFlow setpoint, DataFrameFormatEntry[] formatEntries, string firmware) : base(id, new LiveDataParser(formatEntries), firmware)
{
_setpoint = setpoint;
_formatEntries = formatEntries;
var setpointEntry = _formatEntries.FirstOrDefault(entry => entry.Field == DataFormatField.Setpoint);
if(setpointEntry is not null && setpointEntry.Unit is not null)
{
_ = double.TryParse(setpointEntry.MaxVal, out var maxVal);
_maxSetpoint = StandardVolumeFlow.From(maxVal, (StandardVolumeFlowUnit)setpointEntry.Unit);
}
}
Cheers!
Aaron
________________________________
From: Nick Kleiner ***@***.***>
Sent: Friday, August 14, 2026 8:04 AM
To: AFRL-ARES/Ares.Device.Drivers ***@***.***>
Cc: Mark Hawkins ***@***.***>; Author ***@***.***>
Subject: [EXTERNAL]Re: [AFRL-ARES/Ares.Device.Drivers] Feat/fix alicat multidrop (PR #2)
You don't often get email from ***@***.*** Learn why this is important<https://aka.ms/LearnAboutSenderIdentification>
[EXTERNAL] This email originates from outside AV. Do not click links, open attachments, or provide credentials unless you recognize the sender and know the content is safe.
@nkleiner commented on this pull request.
________________________________
In AlicatMFCRemastered/Connection/MassFlowControllerConnection.cs<#2 (comment)>:
@@ -6,7 +6,18 @@ namespace AlicatMFCRemastered;
public class MassFlowControllerConnection : AresHardwareConnection, IMfcConnection
{
- private readonly List<char> _unusedIds;
+ //simple registry to manage instances of seriaport resources across multiple mfcs
+ static Dictionary<string, MassFlowControllerConnection> _connections = new Dictionary<string, MassFlowControllerConnection>();
I'm curious if you could elaborate on what you wanted to implement this to address. Typically we don't expect the driver to care about it's connection in relation to any others (maintaining the idea that the driver itself would only really care about it's own connection). It's true that the MFC's can share a serial connection, and we account for that in ARES by allowing devices of the same class (i.e. Alicat MFC's) to share the same resource with a unique identifier. For the MFC's this is the Id list that use a single capital letter (A, B, C...). Unless there's something critical this provides, I would say this is best left at the ARES level rather than the driver level itself.
________________________________
In AlicatMFCRemastered/MassFlowController.cs<#2 (comment)>:
- _stateWatchers = new CompositeDisposable
I see the motivation here, but this should theoretically not be an issue on the current system. When receiving data that needs to be parsed, we have dedicated response parser classes that handle breaking down the serialized data. In the case of this stream, we're only going to get this processed response (the "LiveDataResponse" class) in the event that parser deemed it a valid response to parse. You can find that logic under "Commands/Responses/Parsers/LiveDataParser.cs". Based on that, I don't think this change is necessary, you shouldn't see the system processing any additional data from MFC's other than the one you told the driver to care about. If you are, then perhaps there's another bug happening under the hood we can dig out.
________________________________
In AlicatMFCRemastered/MassFlowController.cs<#2 (comment)>:
@@ -728,6 +734,9 @@ private async Task<TResult> GetResponseWithRetry<TResult, TRequest>(TRequest req
private void UpdateLiveData(LiveDataResponse liveResponse)
{
_liveData = liveResponse;
+ /// check on id is required if _statewatcher is used in order to avoid updating the state with a response from a different MFC than the one that is being watched
My comment above talks about this point I believe
________________________________
In AlicatMFCRemastered/MassFlowController.cs<#2 (comment)>:
@@ -170,9 +171,12 @@ private void UpdatePotentialMaxValue(ManufacturerInfoEntry entry)
_logger.LogWarning($"Failed to get max value for MFC {Name} as we couldn't get the numeric max value from model number {entry.Data}");
return;
}
- var flowVal = StandardVolumeFlow.From(numericNum, unit);
- dataFrameFormat.MaxVal = flowVal.StandardLitersPerMinute.ToString();
- }
+ _logger.LogInformation($"Found a potential max value of {numericNum} {unit} for MFC {Name} from model number {entry.Data}");
+ var flowVal = StandardVolumeFlow.From(numericNum, unit);
+// must be converted to match setpoint units, otherwise may cause issues when calculating newsetpoint
+ // dataFrameFormat.MaxVal = flowVal.StandardLitersPerMinute.ToString();
+ dataFrameFormat.MaxVal = flowVal.As((StandardVolumeFlowUnit)dataFrameFormat.Unit).ToString();
Just curious, was this change inspired by an issue you saw in lab?
—
Reply to this email directly, view it on GitHub<#2?email_source=notifications&email_token=CKE5IRQGCC5XWA2DG2GEF5L5J355DA5CNFSNUABKM5UWIORPF5TWS5BNNB2WEL2QOVWGYUTFOF2WK43UKJSXM2LFO4XTIOJTGY3TONRZGQY2M4TFMFZW63VGMF2XI2DPOKSWK5TFNZ2KYZTPN52GK4S7MNWGSY3L#pullrequestreview-4936776941>, or unsubscribe<https://github.com/notifications/unsubscribe-auth/CKE5IRUYILFF4C23XDVJCED5J355DAVCNFSNUABGKJSXA33TNF2G64TZHMYTCNZZHEZTMNJQGM5US43TOVSTWNJQGE4TSNRQGE3TDILWAI>.
Triage notifications, keep track of coding agent tasks and review pull requests on the go with GitHub Mobile for iOS<https://github.com/notifications/mobile/ios/CKE5IRTQG5MD4DGSFIEN4KL5J355DA5CNFSNUABKM5UWIORPF5TWS5BNNB2WEL2QOVWGYUTFOF2WK43UKJSXM2LFO4XTIOJTGY3TONRZGQY2M4TFMFZW63VGMF2XI2DPOKSWK5TFNZ2KUZTPN52GK4S7NFXXG> and Android<https://github.com/notifications/mobile/android/CKE5IRRCSUL4BV26WPNH44T5J355DA5CNFSNUABKM5UWIORPF5TWS5BNNB2WEL2QOVWGYUTFOF2WK43UKJSXM2LFO4XTIOJTGY3TONRZGQY2M4TFMFZW63VGMF2XI2DPOKSWK5TFNZ2K4ZTPN52GK4S7MFXGI4TPNFSA>. Download it today!
You are receiving this because you authored the thread.Message ID: ***@***.***>
|
Shared Serial Ports Live Data Response Mismatch This should ensure that you're never receiving false states for devices other than the one you're looking at. If for some reason we're still getting bad states and you're looking for a quick fix, a more elegant solution would be to update the state subscription itself on the Alicat MFC to filter out states not matching the Assumed ID (MassFlowController.cs line 64) : Though ultimately this was functional before, so I'm not sure why we would be seeing this issue now. Units Issue Overall I think I would like to keep your changes for the serial ports here for now to support functionality, and I'll add it to my TODO list to create some serial library helpers to make a lot of that boilerplate go away. I'd like to get some confirmation on that state filtering, but ultimately if we implement the solution I put above to add an extra layer of filtering I won't block the other changes from merging in because of it. |
|
Shared Serial Ports:
Cool! I'm glad this can help. I agree moving it lower would be amazing. I do want to push back on the only like devices though. In the very near future I will have at least 3 different types of modbus devices sharing the same bus. I could have a generic modbus device with a list of defined registers, but then I couldn't have nicely named functions. It might be interesting to have a 'serial protocol' definition in the connection info. Then only connections of similar protocol can share a port?
'Dedicated' - devices cannot share bus
'MODBUS-RTU' - modbus rtu devices can share bus
'MODBUS-ASCII' - modbus ascii devices can share bus
[Custom definitions]
...
In general a serial port should implement ether dedicated connection, or a standarized communication protocol. Standardized communication protocols should allow different types to share the same bus. Individual libraries could also define custom protocols ('Alicat-ASCII') that could be interpreted to allow same type devices to share a bus. It might be interesting to add 'Protocol', and 'Multidrop' as an override when implementing AresHardwareConnection.
Live Data Response Mismatch
I'll try the state fix you suggested and see what works
Let me know if you make changes you'd like run on the hardware. I should be able to load it up real quick to test.
…-Aaron
________________________________
From: Nick Kleiner ***@***.***>
Sent: Monday, August 17, 2026 12:37 PM
To: AFRL-ARES/Ares.Device.Drivers ***@***.***>
Cc: Mark Hawkins ***@***.***>; Author ***@***.***>
Subject: [EXTERNAL]Re: [AFRL-ARES/Ares.Device.Drivers] Feat/fix alicat multidrop (PR #2)
[EXTERNAL] This email originates from outside AV. Do not click links, open attachments, or provide credentials unless you recognize the sender and know the content is safe.
[https://avatars.githubusercontent.com/u/103515522?s=20&v=4]nkleiner left a comment (AFRL-ARES/Ares.Device.Drivers#2)<#2 (comment)>
Morning! We observed in the lab that Alicats could be created using the same port. The UI would initialize appropriately, but only the first connected would update. I remember seeing in the logs something about being unable to access the serial port, but I didn't copy them (Sorry). Looking into the code, I noticed that each Alicat created its own MassFlowControllerConnection. It was not apparent to me that these referenced a common system resource. Previous experience says that this type of error is common when several system.io.serialports referencing the same port are created. I've had success using multiple objects by explicitly releasing it in one place to grab it somewhere else, but it never worked the way I hoped. The Alicats referencing a shared resource corrected the issue by only creating one instance of the base port. After this fix all alicats would connect and update. I agree this may be a temporary solution that relies heavily on the fact that alicats share a library and therefor reference the MassFlowControllerConnection registry well. As we expand function to modbus devices for instance, devices of different types may communicate on the same bus and the registry as it is done here will likely fail. The answer is probably to push the registry behavior into the Ares.Serial.Toolkit. The next two changes followed from the first. All devices would respond to all traffic on the bus since neither the parsers nor updatelivedata had a check on the deviceid. I could have added a check on deviceid, but I elected to go to a more query-based pattern. This tightly linked the request with the response and fixed the message addressing issue without forcing every device on the bus to parse every message. For the last change... We observed in the lab that SLPM controllers behaved as expected, but SCCM controllers had to be set in SLPM instead of SCCM (if I want 150 SCCM, I would need to set 0.150, unlike the SLPM controllers that would be set as 150). I found that the commands were being explicitly types as SCCM. In the command, maxsetpoint was expected to have the same units as the setpoint, but when updating the maxvalue it was being explicitly set to SLPM. The fact that it was being recorded in SLPM and the inappropriately converted to SCCM was causing the issue. As long as it was consistent the bug would be resolved. I chose to use the setpoint units since I thought that was more internally consistent and required less architectural knowledge to interpret. public NewSetpointCommand(char id, StandardVolumeFlow setpoint, DataFrameFormatEntry[] formatEntries, string firmware) : base(id, new LiveDataParser(formatEntries), firmware) { _setpoint = setpoint; _formatEntries = formatEntries; var setpointEntry = _formatEntries.FirstOrDefault(entry => entry.Field == DataFormatField.Setpoint); if(setpointEntry is not null && setpointEntry.Unit is not null) { _ = double.TryParse(setpointEntry.MaxVal, out var maxVal); _maxSetpoint = StandardVolumeFlow.From(maxVal, (StandardVolumeFlowUnit)setpointEntry.Unit); } } Cheers! Aaron
Shared Serial Ports
So, I did some reviewing here and I see what you're describing. I think your solution here is actually how we likely should handle this going forward. Thanks to the way we instantiate these device drivers, they actually share their static memory on the heap. This is how your current solution functions under the hood, which is exactly how I expect these drivers to act. My current thought process is that serial ports can be shared, but only be devices of the same type (e.g. Alicat MFC's can share a serial port with other Alicat MFC's). Ultimately from the ARES side I think adding some functionality to the serial library to support your solution in a lower code manner makes sense, as it keeps the responsibility of connection management where it belongs without breaking the ability to have multiple device on a single serial port in the right circumstances.
Live Data Response Mismatch
This one still confuses me a bit, as the Alicat's parsers do check to for the ID to match before processing the live data response. If that truly isn't happening properly then the parser has a bug. Specifically, the following lines in the LiveDataParser.cs file:
case DataFormatField.UnitId:
id = token[0];
if (id != FormatEntries.First().Id)
{
response = null;
return false;
}
break;
This should ensure that you're never receiving false states for devices other than the one you're looking at. If for some reason we're still getting bad states and you're looking for a quick fix, a more elegant solution would be to update the state subscription itself on the Alicat MFC to filter out states not matching the Assumed ID (MassFlowController.cs line 64) :
_serialConnection.GetTransactionStream<LiveDataResponse>()
.Where(t => t.Response.Id == AssumedId)
.Select(transaction => transaction.Response)
.Subscribe(UpdateLiveData)
Though ultimately this was functional before, so I'm not sure why we would be seeing this issue now.
Units Issue
This change is okay with me, was just curious about reasoning.
Overall I think I would like to keep your changes for the serial ports here for now to support functionality, and I'll add it to my TODO list to create some serial library helpers to make a lot of that boilerplate go away. I'd like to get some confirmation on that state filtering, but ultimately if we implement the solution I put above to add an extra layer of filtering I won't block the other changes from merging in because of it.
—
Reply to this email directly, view it on GitHub<#2?email_source=notifications&email_token=CKE5IRWVVLYST2JEPFKV7ZL5KMYFDA5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTKMZRG43TOMJZG442M4TFMFZW63VGMF2XI2DPOKSWK5TFNZ2KYZTPN52GK4S7MNWGSY3L#issuecomment-5317771979>, or unsubscribe<https://github.com/notifications/unsubscribe-auth/CKE5IRU2RX2WUE5YWQMIVGL5KMYFDAVCNFSNUABGKJSXA33TNF2G64TZHMYTCNZZHEZTMNJQGM5US43TOVSTWNJQGE4TSNRQGE3TDILWAI>.
Triage notifications, keep track of coding agent tasks and review pull requests on the go with GitHub Mobile for iOS<https://github.com/notifications/mobile/ios/CKE5IRVH5DMFQZH3L7GUO7D5KMYFDA5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTKMZRG43TOMJZG442M4TFMFZW63VGMF2XI2DPOKSWK5TFNZ2KUZTPN52GK4S7NFXXG> and Android<https://github.com/notifications/mobile/android/CKE5IRUISCZ3B2DJDEIBFH35KMYFDA5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTKMZRG43TOMJZG442M4TFMFZW63VGMF2XI2DPOKSWK5TFNZ2K4ZTPN52GK4S7MFXGI4TPNFSA>. Download it today!
You are receiving this because you authored the thread.Message ID: ***@***.***>
|
|
Yeah that's a great point actually.. I think we can still work with this, and I can envision your suggestions about declaring a serial protocol helping out immensely here. To make this work properly would shift the burden of tracking connections almost entirely to the Ares.Toolkit.Serial library, which makes total sense from an architecture standpoint. I'll try to get to that in the near future. |
Alicat devices are designed to have multiple devices sharing a single serial port. This fix implements a simple registry to allow multiple devices to share the same connection. Also updated the device so that livedata update is treated as a query instead of listening to a transaction stream. Included notes on implementation if use of transaction stream needed to be restored.