Skip to content

Feat/fix alicat multidrop - #2

Open
senslab-MarkHawkins wants to merge 5 commits into
AFRL-ARES:Developfrom
senslab-MarkHawkins:feat/fix_alicat_multidrop
Open

Feat/fix alicat multidrop#2
senslab-MarkHawkins wants to merge 5 commits into
AFRL-ARES:Developfrom
senslab-MarkHawkins:feat/fix_alicat_multidrop

Conversation

@senslab-MarkHawkins

Copy link
Copy Markdown

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.

{
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>();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious, was this change inspired by an issue you saw in lab?

@senslab-MarkHawkins

senslab-MarkHawkins commented Aug 17, 2026 via email

Copy link
Copy Markdown
Author

@nkleiner

Copy link
Copy Markdown
Member

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.

@senslab-MarkHawkins

senslab-MarkHawkins commented Aug 17, 2026 via email

Copy link
Copy Markdown
Author

@nkleiner

Copy link
Copy Markdown
Member

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants