Jump to content

DVR, SEQ, FGV, Semaphores


Recommended Posts

I'm building a system that can run up to 4 concurrent chemical reactions. My plan is to have 4 independent "reaction" objects that each execute in parallel, but they all need access to some shared components such as the liquid handling system that adds chemicals to each reaction, and the analysis equipment to check reaction progress. I'm looking for comments on the best way to make sure that only one reaction at a time can access the shared equipment. It seems like the traditional approach would be a semaphore, but there are all these other options available. For example, I can put all the functions for the liquid handling system into a single FGV, make it non-reentrant, and prevent overlapping access. Or I can put the automation refnum (it uses ActiveX) into a DVR or single-element queue to limit access. What is your preferred approach? What if I have 2 analysis instruments, and each reaction should use the first one that's free?

Taking this a step further (and overlapping with the recent questions about singleton classes), say I create a class to wrap the liquid handling operations. Do I wire that class to all the reactions after opening the automation reference? Is there a clean way to store that reference inside the class so that I don't need to directly wire that class everywhere it might be used?

Thanks for any comments; I already have some of this working but I can't effectively discuss my design with coworkers (I'm the only programmer on the project) so I'm looking here.

Link to comment

I'm building a system that can run up to 4 concurrent chemical reactions. My plan is to have 4 independent "reaction" objects that each execute in parallel, but they all need access to some shared components such as the liquid handling system that adds chemicals to each reaction, and the analysis equipment to check reaction progress. I'm looking for comments on the best way to make sure that only one reaction at a time can access the shared equipment. It seems like the traditional approach would be a semaphore, but there are all these other options available. For example, I can put all the functions for the liquid handling system into a single FGV, make it non-reentrant, and prevent overlapping access. Or I can put the automation refnum (it uses ActiveX) into a DVR or single-element queue to limit access. What is your preferred approach? What if I have 2 analysis instruments, and each reaction should use the first one that's free?

Taking this a step further (and overlapping with the recent questions about singleton classes), say I create a class to wrap the liquid handling operations. Do I wire that class to all the reactions after opening the automation reference? Is there a clean way to store that reference inside the class so that I don't need to directly wire that class everywhere it might be used?

Thanks for any comments; I already have some of this working but I can't effectively discuss my design with coworkers (I'm the only programmer on the project) so I'm looking here.

Don't make this problem harder than it is! This is one place where LabVIEW's dataflow and default subVI calling mechanism make things easy. Just put the access point for the shared resource in a single, non-reentrant VI (this can be a method in OO architectures) and you're home free - no other call can access the resource until the previous caller is finished with it and all the calls will wait until the resource is free and then execute (essentially queue up and wait, except that you can't be sure exactly which will get to use freed resource first).

Mark

Link to comment

I'm building a system that can run up to 4 concurrent chemical reactions. My plan is to have 4 independent "reaction" objects that each execute in parallel, but they all need access to some shared components such as the liquid handling system that adds chemicals to each reaction, and the analysis equipment to check reaction progress. I'm looking for comments on the best way to make sure that only one reaction at a time can access the shared equipment. It seems like the traditional approach would be a semaphore, but there are all these other options available. For example, I can put all the functions for the liquid handling system into a single FGV, make it non-reentrant, and prevent overlapping access. Or I can put the automation refnum (it uses ActiveX) into a DVR or single-element queue to limit access. What is your preferred approach? What if I have 2 analysis instruments, and each reaction should use the first one that's free?

Taking this a step further (and overlapping with the recent questions about singleton classes), say I create a class to wrap the liquid handling operations. Do I wire that class to all the reactions after opening the automation reference? Is there a clean way to store that reference inside the class so that I don't need to directly wire that class everywhere it might be used?

Thanks for any comments; I already have some of this working but I can't effectively discuss my design with coworkers (I'm the only programmer on the project) so I'm looking here.

The FGV would be my first thought. I have used it to do exactly the same thing. It also scales well so when my customer said they want a PC at each production line in addition to the master machine that can serve the AE to the other machines via VI Server.

Ben

Link to comment

The FGV would be my first thought. I have used it to do exactly the same thing. It also scales well so when my customer said they want a PC at each production line in addition to the master machine that can serve the AE to the other machines via VI Server.

Ben

Thanks, I'll see if I can make that approach work. What about the case where my shared instrument needs to do many functions , each with several inputs, such that the functional global approach would result in a very messy connector pane? Or where I need to execute an uninterrupted sequence of commands on the shared instrument?

Link to comment

Thanks, I'll see if I can make that approach work. What about the case where my shared instrument needs to do many functions , each with several inputs, such that the functional global approach would result in a very messy connector pane? Or where I need to execute an uninterrupted sequence of commands on the shared instrument?

I will use a cluster to pass the inputs to the AE where there enough fields to cover all actions. each Action then gets a proper wrapper VI that has required inputs for all of the stuff we need for that action and then bundle before invoking the AE (learned from Jim Kring).

Re the un-interupted work....

The AE should not return until that work is done.

Ben

Link to comment

I'm guessing you're using the state of the action engine to save configuration state? If so, an approach I've used that works well in OO architecture is to once again use a single VI for entry into the shared resource and then create classes (could be subclasses) that expose just the stuff required for a specific test - or alternately, methods for each specific test in a common class. Each method/class can include an action engine wrapper that just maintains state for that specific test's configuration. The main entry point still maintains state for the configuration of the entire resource and the specific test wrappers just expose the properties necessary for that test. And there's still just one main VI (shared by all of the classes/methods) that acts as the "gatekeeper".

Mark

Link to comment
My plan is to have 4 independent "reaction" objects that each execute in parallel, but they all need access to some shared components...

Have you thought of using the multi-up process model in TestStand? It's pretty good at helping you configure and execute parallel stuff where there's shared components.

Link to comment

I use a form of remote messaging that uses buffered network shared variables as queues to pass commands into any process whether it is on the same machine or not.

Included in the message is a return notification so that the client process can flexibly block until the remote command has been completed. Included in the notifier is an optional completion response. In my case, my instrumentation is running on a cRIO and the client apps run on a PC.

Link to comment

I will use a cluster to pass the inputs to the AE where there enough fields to cover all actions. each Action then gets a proper wrapper VI that has required inputs for all of the stuff we need for that action and then bundle before invoking the AE (learned from Jim Kring).

Re the un-interupted work....

The AE should not return until that work is done.

Ben

So if I'm understanding this correctly, the action engine has a giant cluster input containing fields for every piece of data that ever needs to be passed into it. And then you have a sub vi for each unique action that the AE can perform.

This sounds exactly like a class, except less flexible and more complicated. Why wouldn't you simply use a class?

Link to comment

So if I'm understanding this correctly, the action engine has a giant cluster input containing fields for every piece of data that ever needs to be passed into it. And then you have a sub vi for each unique action that the AE can perform.

This sounds exactly like a class, except less flexible and more complicated. Why wouldn't you simply use a class?

This is partly the thinking that prompted my question, but if you want to use a class, how do you do it? I couldn't think of an elegant way to get a singleton class where running a VI in that class precludes running another VI in the same class concurrently, unless you use a DVR. If you do use a DVR, do you wire a single instance of the class everywhere (making it essentially a singleton), or store the DVR inside a FGV or global inside the class?

Link to comment

So if I'm understanding this correctly, the action engine has a giant cluster input containing fields for every piece of data that ever needs to be passed into it. And then you have a sub vi for each unique action that the AE can perform.

This sounds exactly like a class, except less flexible and more complicated. Why wouldn't you simply use a class?

That was not one of the options listed. yes.gif

If you run that AE on one node you can serve it to multiple nodes using VI server Invoke Node call by reference. I would you that approach rather than a class becasue I have not figured how to implement a Singlton that will work across machines (without bending over backwards). Is is possible to implement a Singlton across PC's?

Willing to learn.

Ben

Link to comment

If you run that AE on one node you can serve it to multiple nodes using VI server Invoke Node call by reference. I would you that approach rather than a class becasue I have not figured how to implement a Singlton that will work across machines (without bending over backwards). Is is possible to implement a Singlton across PC's?

Ahh, I missed the part about using it across a network. I have no idea if it is possible to implement a singleton across PCs as I've never done any networking code in Labview. That said, I suspect that it is possible using the proxy pattern.

The idea behind the proxy pattern is that every computer (except one) has a local object they think is the "real" object they are communicating with. However, the real object is actually located on a different computer on the network. The local object is the "proxy" for the real object, accepting all inputs and forwarding them to the real object, and taking the results from the real object and giving them back to the client. All the ugly networking code is hidden inside the proxy instead of the application on the local computer.

Applying a singleton to the proxy pattern (I think) is simply a matter of making the real object a singleton. (See below.) All the calls to the proxy will be routed to the real object, which is the central maintainer of the class data.

Note this is just an idea... I have not tried to implement anything like this nor have a spent time investigating the implementation details in Labview. Any input from LOOPies wink.gif who have experience with OO network code in Labview is appreciated.

This is partly the thinking that prompted my question, but if you want to use a class, how do you do it? I couldn't think of an elegant way to get a singleton class where running a VI in that class precludes running another VI in the same class concurrently, unless you use a DVR.

Let's ignore the singleton part of the requirement for a moment and focus on the issue of concurrently executing class methods. There's nothing in LV classes, singleton or otherwise, that prevents two methods in the same class from being executed concurrently. The best you can do as an inherent part of class behavior is pause the execution of one method until the other one finishes. Semaphores are the most obvious way of doing this. Create a semaphore in your class' Init method and store it as private data. When a class method is called the first thing it does is obtain the semaphore, then it releases the semaphore as its last action before exiting. Now, although multiple methods may be entered simultaneously, the guts of the methods are executed sequentially.

Now to the question of singletons. What exactly is a singleton? It is a class in which all the data is shared between all of the class' instantiated objects. In other words, the class data is globally available to any class method (though that does not mean the class data is globally available to any vi in the application.) How can we accomplish that? The two most obvious ways are to maintain the class data in a private global (as suggested by AQ recently) or a private functional global. The implementations differ slightly but I don't see a clear advantage to either one. I prefer the global simply because implementing a global is easier than implementing a FG. Note, however, that either solution will require you to implement semaphores if you have methods that do read-modify-write operations as opposed to simple set/get operations. There are probably performance considerations to take into account if you need to squeeze every last ounce of speed out of your system, but in my environment with pre-emptive operating systems and small data sets that's not a concern.

If you do use a DVR, do you wire a single instance of the class everywhere (making it essentially a singleton), or store the DVR inside a FGV or global inside the class?

I don't think a DVR is the right solution to this problem. Step back for a moment and consider the different types of class data. There is:

  1. By Value Data - Branching a wire and dropping a new object on the bd always create new instances of the data.
  2. By Reference Data - Branching a wire refers to the same data, but dropping a new object creates a new instance of the data.
  3. Global Data - Branching a wire and dropping a new object always refer to the same data.

A class can have any combination of By Value, By Ref, and Global data depending on the needs of that class and it's intended behavior. By Val data is easy; it's what everyone is used to. Global data is pretty easy too. (See above.) By Ref data traditionally has been implemented by creating an unnamed queue during the class Init method, putting the desired data on the queue, and storing the queue reference as part of the class cluster. The 2009 DVR provides another way to implement By Ref data in classes.

Using a DVR in the way you describe creates behavior similar to a singleton, as long as all the wires are connected correctly. As soon as some unsuspecting developer drops a class constant with the expectation that it is a singleton, the hilarity begins. IMO, if you need your class to be a singleton, make your class a singleton. It will avoid problems down the road.

Now that I've contributed heavily to the level of hot air in the room, the question posed by AQ is valid and important. Why do you think you need a singleton? If it's mainly a matter of convenience, I'd think long about it. I believe there are valid reasons to implement singletons, but I don't think those reasons are commonly encountered.

---------------------

As always, the opinions expressed here are solely my own and subject to change at any time. wink.gif

[EDIT - Having reread your original post, I have some thoughts (who would have thought!?) but no time to post them right now.]

Edited by Daklu
Link to comment

I'm building a system that can run up to 4 concurrent chemical reactions. My plan is to have 4 independent "reaction" objects that each execute in parallel, but they all need access to some shared components such as the liquid handling system that adds chemicals to each reaction, and the analysis equipment to check reaction progress. I'm looking for comments on the best way to make sure that only one reaction at a time can access the shared equipment.

There is no universal "best" way that I'm aware of. There are ways to provide more flexibility to accomodate changes at the expense of writing a little more code and adding more abstraction layers. What's "best" depends entirely on the requirements for your application. (Of course, there are bad implementations that require more code and provide less flexibility, but we'll skip those. smile.gif )

The answer partly depends on the framework of your existing code and how much refactoring you and management can tolerate. Given a blank sheet of paper and knowing nothing about your actual physical system, my first inclination is to have each reaction object running in it's own thread, either by using parallel loops or perhaps by implementing them as active objects. Then I'd implement an AnalysisInstrumentManager class that can hold n number AnalysisInstrument objects, one for each actual instrument in the system. The AnInstMgr class would also run in it's own thread. When a reaction needs access to an analysis instrument, it requests one from the AnInstMgr object. If one is available, AnInstMgr sends the AnInst object that refers to the free instrument to the requesting reaction object, which uses it and returns it to AnInstMgr when it's no longer needed. This design gives you a lot of flexibility in the number of of analysis instruments you have in your system, frees you from having to create logic to decide which AnInst object to use, and avoids the danger inherent in using singletons.

An unspoken assumption I've made in the above paragraph is that the driver for the analysis instruments supports multiple driver instances for multiple devices. For example, if you are communicating with the instruments via string commands over independent serial ports, no problem. If you have to go through the vendor's dll to talk to the device, this design might not work. Most, if not all, dll's I've worked with handle connections to multiple devices internally within a single instance of the dll. If that's the case I believe you'd need to change the design around a bit, though this is an area I don't have much experience in.

Taking this a step further (and overlapping with the recent questions about singleton classes), say I create a class to wrap the liquid handling operations. Do I wire that class to all the reactions after opening the automation reference? Is there a clean way to store that reference inside the class so that I don't need to directly wire that class everywhere it might be used?

I mentioned private globals and private functional globals in my previous post. Either of those would work if you don't want to connect the class methods to a wire carrying the actual object. (Note that unless you ditch dynamic dispatching you'll still have to wire a class constant to the method.) However, in general I don't think it's a good idea to make your lowest level instrument driver class a singleton. Keep it a regular by-val class. Who knows how you'll want to use that driver class in the future? It's easy to wrap a by-val object in another class to give it singleton behavior when you absolutely must have it, but I don't know of any way to get by-val behavior from a singleton object.

I would probably also approach this component from the standpoint of a regular by-val object that is distributed by an object manager class to a requesting client object. Here's a thought that finally coalesced as I was sitting here thinking about your problem: When using a singleton (or action engine) for your instrument driver class you have to provide lock/unlock methods for those times when a reaction object has to execute multiple steps without interruption. There's always the chance that someone forgot to (or didn't know they were supposed to) lock the resource. This kind of bug could easily go undiscovered for a long time. By distributing the by-val instrument object to the client as opposed to having the client make calls into a singleton class, you've guaranteed by design that no other reaction object will be able to use that instrument until the first one is finished with it. That design decision just eliminated an entire class of potential bugs. My sense is that this is a pretty significant advantage but I'll have to ponder this for a while before I add it to my list of best pretty good practices.

What if I have 2 analysis instruments, and each reaction should use the first one that's free?

"What if..." is an excellent question and is not asked nearly enough! To expand on that thought, what happens if one of them breaks and has to be sent in for repair? Will your software still work with a single instrument? What happens if the instrument is irrepairable and you replace it with a different model? What happens when the powers that be decide that 8 concurrent chemical reactions and two liquid handling systems are needed? You probably don't need to build all that flexibility into the system, but you should have a reasonably good idea of how much effort it will take if those kinds of requests come down the pipe and communicate the design limitations to your customers.

  • Like 1
Link to comment

Ahh, I missed the part about using it across a network. I have no idea if it is possible to implement a singleton across PCs as I've never done any networking code in Labview. That said, I suspect that it is possible using the proxy pattern.

...

No you didn't. I interjected that part just to highlight the power of a VI served AE. rolleyes.gif

I don't even want to spend the grey-matter on figuring out how I could do it without the AE since my brain keep interupting and saying "this would be a peice of cake with an AE."

beer_mug.gif

Ben

Link to comment

This is somewhat of a tangent, but I have a question about the term "action engine." This term appears frequently on LAVA but I admit I haven't paid that much attention to it. In this thread the acronym "AE" appears before anyone uses the term "action engine," which is confusing enough, but what concerns me more is that I'm not sure what the term means.

In particular, I did a search on action engine with Google and didn't find anything relevant before I hit this LAVA link (Action Engine), where at least one participant equates it with a statemachine. Is this the case? If so, can we please use the term statemachine, given that it has much broader (and well-defined) usage not only with NI but also with UML and general software design? If there is a difference, can someone please clarify for the benefit of those (including myself) who might be confused?

Paul

Link to comment

This is somewhat of a tangent, but I have a question about the term "action engine." This term appears frequently on LAVA but I admit I haven't paid that much attention to it. In this thread the acronym "AE" appears before anyone uses the term "action engine," which is confusing enough, but what concerns me more is that I'm not sure what the term means.

In particular, I did a search on action engine with Google and didn't find anything relevant before I hit this LAVA link (Action Engine), where at least one participant equates it with a statemachine. Is this the case? If so, can we please use the term statemachine, given that it has much broader (and well-defined) usage not only with NI but also with UML and general software design? If there is a difference, can someone please clarify for the benefit of those (including myself) who might be confused?

Paul

AE= Action Engine >>> loosly a Functional Global Variable.

I wrote about them here

http://forums.ni.com/ni/board/message?board.id=170&message.id=240328#M240328

and also explain where the term came from.

Ben

Link to comment

AE= Action Engine >>> loosly a Functional Global Variable.

I wrote about them here

http://forums.ni.com...=240328#M240328

and also explain where the term came from.

Ben

Ben,

Thanks much for the link! Now I know exactly what you mean by an action engine.

By way of understanding, I would say in my language, I do much the same thing with an object-oriented controller (usually a statemachine) that invokes methods on a model object that is rather similar to the functional global variable that forms your action engine. I handle sharing data between parallel loops with messaging--in my case networked shared variables that can publish data of any type, including object data, and I suppose that may be the biggest difference between the two methods. That is for another topic, however. I now understand what you mean by the term action item. Thanks for the clear explanation.

Paul

Link to comment

Thank you for taking the time to comment in so much detail. This is the sort of feedback I was hoping to get.

The answer partly depends on the framework of your existing code and how much refactoring you and management can tolerate. Given a blank sheet of paper and knowing nothing about your actual physical system, my first inclination is to have each reaction object running in it's own thread, either by using parallel loops or perhaps by implementing them as active objects. Then I'd implement an AnalysisInstrumentManager class that can hold n number AnalysisInstrument objects, one for each actual instrument in the system. The AnInstMgr class would also run in it's own thread. When a reaction needs access to an analysis instrument, it requests one from the AnInstMgr object. If one is available, AnInstMgr sends the AnInst object that refers to the free instrument to the requesting reaction object, which uses it and returns it to AnInstMgr when it's no longer needed. This design gives you a lot of flexibility in the number of of analysis instruments you have in your system, frees you from having to create logic to decide which AnInst object to use, and avoids the danger inherent in using singletons.

An unspoken assumption I've made in the above paragraph is that the driver for the analysis instruments supports multiple driver instances for multiple devices. For example, if you are communicating with the instruments via string commands over independent serial ports, no problem. If you have to go through the vendor's dll to talk to the device, this design might not work. Most, if not all, dll's I've worked with handle connections to multiple devices internally within a single instance of the dll. If that's the case I believe you'd need to change the design around a bit, though this is an area I don't have much experience in.

I like this suggestion; I'll see if I can implement it. I do have, as you suspected, the problem of a single ActiveX reference for accessing multiple instruments, but I can use the unique instrument name (which is a parameter to all the relevant ActiveX calls) as a proxy for the actual instrument reference. It seems to me, though, that the manager might be as simple as a queue with a maximum length equal to the total number of instruments available. Is there any reason to make it more complicated than that?

"What if..." is an excellent question and is not asked nearly enough! To expand on that thought, what happens if one of them breaks and has to be sent in for repair? Will your software still work with a single instrument? What happens if the instrument is irrepairable and you replace it with a different model? What happens when the powers that be decide that 8 concurrent chemical reactions and two liquid handling systems are needed? You probably don't need to build all that flexibility into the system, but you should have a reasonably good idea of how much effort it will take if those kinds of requests come down the pipe and communicate the design limitations to your customers.

Good point about considering future needs, although fortunately in this specific case we answered a lot of these questions during the initial design discussions for the project.

Using a DVR in the way you describe creates behavior similar to a singleton, as long as all the wires are connected correctly. As soon as some unsuspecting developer drops a class constant with the expectation that it is a singleton, the hilarity begins. IMO, if you need your class to be a singleton, make your class a singleton. It will avoid problems down the road.

Now that I've contributed heavily to the level of hot air in the room, the question posed by AQ is valid and important. Why do you think you need a singleton? If it's mainly a matter of convenience, I'd think long about it. I believe there are valid reasons to implement singletons, but I don't think those reasons are commonly encountered.

I only have one liquid handling robot in the system, so it seems to make sense to treat it as a singleton object. The reference to it is an ActiveX refnum. Because there's only one, it is convenient to avoid wiring that refnum to every function that needs it (also, I don't want to accidentally open multiple references to the same ActiveX object). That's why I was looking at storing the refnum inside a DVR (or SEQ), and then putting that containing reference inside a class global of some sort.

Link to comment

I do have, as you suspected, the problem of a single ActiveX reference for accessing multiple instruments, but I can use the unique instrument name (which is a parameter to all the relevant ActiveX calls) as a proxy for the actual instrument reference.

I presume you are considering an AnInst class with a name property and each object instance will have a name that refers to a different instrument? Is your dll thread safe? What happens if one object attempts to make a dll call while the other object has a call pending? Many dll's get upset when asked to do more than one thing at a time. If yours is not thread safe, you'll probably need to figure out a way to throttle your calls. (DLL threading is a little off the edge of my exprience, so I may be wrong.) I have a few different ideas of how to go about doing that... I'd have to prototype them to see how well they work.

It seems to me, though, that the manager might be as simple as a queue with a maximum length equal to the total number of instruments available. Is there any reason to make it more complicated than that?

At it's core, you are correct. (Though I wouldn't bother with setting a maximum length.) Whether there is any additional functionality required depends on your setup. How do you want to handle situations where a reaction object is waiting for a free instrument? Does the reaction object execution hang? Does the manager put the request in a queue and return control to the reaction object so it can continue to monitor itself?

I only have one liquid handling robot in the system, so it seems to make sense to treat it as a singleton object. Because there's only one, it is convenient to avoid wiring that refnum to every function that needs it (also, I don't want to accidentally open multiple references to the same ActiveX object). That's why I was looking at storing the refnum inside a DVR (or SEQ), and then putting that containing reference inside a class global of some sort.

Fair enough, though personally I would still create a by-ref driver object first and drop that in a singleton wrapper simply to separate the core driver functionality from the multi-threaded singleton functionality. In my own code I tend to favor highly layered designs. I read an api design book recently that I thought had excellent advice: "Make the common things easy and the difficult things possible." Appropriately layering the api actually makes them easier to develop, easier to test, and easier for users to understand. The hardest part of it is convincing yourself that VIs don't have to do lots of stuff inside them to be valid--it's okay to sometimes create VIs that are nothing more than a wrapper for another VI.

Good point about considering future needs, although fortunately in this specific case we answered a lot of these questions during the initial design discussions for the project.

Lucky dog. Our (internal) customers still aren't sure what they want, and we're 96% finished. frusty.gif

Thank you for taking the time to comment in so much detail. This is the sort of feedback I was hoping to get.

No problem at all. If I don't spout off about my ideas then I can't learn from others correcting me. smile.gif

Link to comment

I presume you are considering an AnInst class with a name property and each object instance will have a name that refers to a different instrument? Is your dll thread safe? What happens if one object attempts to make a dll call while the other object has a call pending? Many dll's get upset when asked to do more than one thing at a time. If yours is not thread safe, you'll probably need to figure out a way to throttle your calls. (DLL threading is a little off the edge of my exprience, so I may be wrong.) I have a few different ideas of how to go about doing that... I'd have to prototype them to see how well they work.

It's an ActiveX application, not direct calls to a DLL, so I don't have to worry about whether it's threadsafe. It's designed to talk to multiple instruments concurrently. The external component I'm calling assigns every instrument a unique name and then requires that name as a parameter to most of its methods. There's some wackiness about the way it handles data - it seems to like structures flattened a bit like LabVIEW except with only a single length byte for strings and opposite endianness for multi-byte values, and it uses Windows messages to send status updates - but I've been able to make the communication work.

Lucky dog. Our (internal) customers still aren't sure what they want, and we're 96% finished. frusty.gif

At least that keeps it exciting. We're replacing an existing but outdated system (built by an outside vendor) with newer equipment from other divisions in our company, so the project goal was well-defined from the start. There are only a limited number of chemicals we need to produce and the market for them is mature so future expansion is unlikely.

Edited by ned
Link to comment

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

Important Information

By using this site, you agree to our Terms of Use.