|
Author
|
Topic: CQC Status/Preview Info
|
Dean Roddey Member Posts: 60 From: Mountain View, CA USA Registered: Aug 2002
|
posted 09-07-2003 12:56 AM
* Posted with permission from AVS *I just wanted to let everyone know that the 0.9 release of CQC is very close, and it's looking very exciting, if I do say so myself. I'll probably provide a drop to a few of my long termers in a few days, and as soon as they bless it, and I get a last few business issues taken care of, I'll make it available for download. CQC is a PC based, network distributed control and automation application suite. Version 0.9 is basically 1.0 'plus bugs', i.e. it's the 1.0 product and as soon as users bless it as stable, it will go 1.0 It is has been a very long road, longer than I care to recall and definitely longer than you want to hear recalled, and the product development cycle has been stretched out to what would be considered insanely long by most commercial standards. But I've been determined to come to market with a mature, well developed architecture that will be a platform to begin building on, and not an anchor around my and your neck. Here are a few teaser images built with the new version of the Interface Designer, which allows you to create quite beautiful and powerful custom interfaces to front your CQC system. Though CQC is network distributed from the ground up, and it's backend has now matured into a very powerful distributed platform, it has traditionally fallen short in the 'pretty face' department, but no more. The new user interface system makes heavy use of bitmapped images, so it can create images as visually stunning as your imagination can create pretty much. I'll provide a few images here and explain the relevant issues wrt to how they are doing what they are doing. The background image is one that I purchased for this 'teaser' purpose from a graphic designer, of which there are many out there on the web. I'll provide a number of links to folks with nice stuff available for use, in the interface designer tutorial on the web site. The tutorial is not complete yet, but provides useful information. This particular set of images compose a set of interfaces designed to work together. The main interface has a number of buttons on the left which allow you to load up an 'overlay', which is a smaller interface created the same way, into a designated area. So the main interface stays the same, providing common functionality that is always visible, while the user selects various overlays to access particular functionality. This is not dissimilar to the way the Pronto type remotes operate, but you'll not be seeing any Prontos that look like this, or provide the powerful two way control required to create these types of interfaces. You can see a number of features on this page. The overlay widget can be set to default to a particular overlay when the main interface is loaded, and in this case, it is set up to start on the System Main overlay, which just provides a welcome message and two buttons to power the system on and off. These buttons invoke CQC macros, which are written in Charmed Quark's powerful CML language. CML is based on Charmed Quark's own compiler and virtual machine technologies, and has it's own graphical IDE that is fully integrated into the product, so you don't need any external development tools to write macros or develop device drivers for CQC. At the bottom left is a 'boolean image', which is an image that is attached to a boolean CQC device field. It will display one image when the field is true, and another when it is false. So this one displays a darker LED image when the system is powered down, and a brighter one when on. On the lower right is a digital clock widget, which can be configured to look a number of different ways, and I just picked on that looks reasonable for this interface. At the upper left is a 'check box', which can also be configured in a number of ways, but in this case is set up for an image to the left and text to the right. Similar to before, it is associated iwth a boolean CQC device field, but it can be writeable in this case. You provide true/false text strings, and it presents the appropriate text and image according to the state of the associated field. In this case, it's used to mute the system or unmute it. When muted, the red LED image is displays, else a black one. If you click on the A/V Status button on the left, a new overlay is loaded into the overlay area. This one shows some current status information about the A/V system, so that you can see at a glance what the state of the state is. It uses 'static text' labels on the left, and 'dynamic text' values on the right. Dynamic text widgets just display the textual value of the field that they are associated with, so we are seeing fields from my Leeza and Lexicon here. In the lower right of the overlay is an 'enumerated image', which is associated with an enumerated CQC device field. Enumerated fields are those that have a known list of values, and is the current input source of the Lexicon in this case. You can set an image for each of the enumerated values. In this case, the satellite source is active, and I've associated an image of a satellit dish with that value. If the source is changed, the image will change accordingly. I have a shiney disc image with a music note for CD, a cassette tape for VCR, and so forth. The displayed values on the right had side, like any widget associated with a writeable field, can be double clicked on to pop up a window that lets's you set the value. When you draw the interface, you can enable or disable this on a per-widget basis, and set the minimum user role required to be able to do it, if it is enabled. CQC is secure and based on user accounts, and each user account has a role. You can limit access to user interfaces based on user role, and within the role, you can control whether they can directly modify fields by double clicking. I've enabled it for the input AR and current F/X, fields since those are legitimate fields to play with manually. Or I could provide buttons to set it as well, whatever I want to do. I chose to keep the interface less cluttered and let them just double click on those items if they want. But I don't, for instance, let them change the selected source by double clicking on the selected source image, because that would only change the Lexicon's input. But I want sourced changes to happen in a coordinated way, which is provided in the Select SOurce overlay. This overlay just provides a set of buttons to select a desired source. These all invoke a macro to do all the work required to switch all involved devices to the desired source. The same macro that was used in the Power On button from the first screen is used here. That macro is very smart, and takes as input a desired source to select, checks all of the devices involved, and powers up those that need to be powered up, and then does the work to select the indicated source. When used in the Power On button, it defaults to the DVD source, but in these buttons the action associated with each button passes the appropriate parameter to the macro. If everything is on, it very quickly zips through the power on sequence, because it uses CQC's power two way control to check the status of the devices, and doesn't just blindly send them commands. So, as you can see, CQC's extremely powerful back end now has a front end that can bat in the same league. With all of the other features of the CQC application suite, backed by over 400,000 lines of code, it all adds up to a serious and flexible PC based control system. If you want to know more about the upcoming version, you can check out the preview web content for a lot more information. It's not all complete yet, and I'll be finishing it up while users are doing their early evaluation of the first couple drops, but the bulk of it is done.
Starting with the upcoming release, the product will provide for a 30 day trial mode, in which you can use it without restriction, to see if it suits your purposes. If so, once the 0.9 beta becomes available for general download and evaluation, Charmed Quark will start accepting pre-orders for the product. The pre-order price is $150, and will be offered until the 1.0 release, at which time the price will be $200. * Anyone who does a device driver for CQC and allows Charmed Quark to distribute the driver with the product, will receive a free copy of CQC, and will continue to receieve free versions as long as they maintain the driver. If you have any questions or comments or suggestions, drop us a line or better yet, write it up on our user discussion forums so that everyone can benefit from the discussion. ----------------------------- For those interested, here is the macro. It's not particularly clean, and I'll definitely improve it and use it as an example on the web site. CML is a fully object oriented language, which is simpler than a fully general purpose langauge like C++ or Java, but quite powerful and head and shoulders above most macro languages in control systems. It uses a small class that just defines some constants, such as device names and such, to avoid hard coding them other macros. The code for that one is presented first.
code:
// // This class just provides some literals and types that are used // by other of my classes, so that they can be entered one place, and // changed in one place if they need to be changed. // Class=[NonFinal] ClassPath MEng.User.DeansMacros.Utilities.DeansInfo; ParentClass MEng.Object; EndClass;Types= // // To avoid using the actual input names, except where the // device read/writes happen, and because selecting an input // involves more than one device, and they might not use the // same syntax for selecting the correct setting. // Enum=Inputs Input_DVD : "DVD"; Input_Sat : "Sat"; Input_VCR : "VCR"; EndEnum; // // Some common errors thrown by our macros. // Enum=HTErrors UnknownInput : "%(1) is not a known input value"; EndEnum; EndTypes; Literals= // The monikers of the devices we control, to avoid hard coding them String kProjMoniker("Dwin"); String kProcMoniker("LexMC1"); String kSwitchMoniker("Leeza"); String kBlasterMoniker("RedRat"); String kIRRecMoniker("IRMan"); // The names of the switcher inputs that correspond to our inputs String kSwitchDVDInput("SD1"); String kSwitchSatInput("HD1"); String kSwitchVCRInput("HD2"); // The names of the processor inputs that correspond to our inputs String kProcDVDInput("DVD"); String kProcSatInput("TV"); String kProcVCRInput("VCR"); EndLiterals; Methods=[Public,Final]
Constructor() Begin EndConstructor; EndMethods;
And here is the actual power on macro.
code:
Class=[NonFinal] ClassPath MEng.User.DeansMacros.HomeTheater.TheaterOn; ParentClass MEng.Object; EndClass;Imports= MEng.System.CQC.Runtime.SimpleFldClient; MEng.User.DeansMacros.Utilities.DeansInfo; EndImports; Members= Time m_TimeInfo; SimpleFldClient m_FldIO; Card4 m_EndTime; Card4 m_StartTime; Card4 m_WaitTime; String m_CurInput; EndMembers; Methods=[Public,Final]
Constructor() : m_WaitTime(0); Begin EndConstructor; Method Start([In] DeansInfo.Inputs InitInput) Returns Int4 Begin // // Remember the start time. For each device below, if we // have to turn it on, we'll set a wait time, if it's bigger // than a previous wait time. At the end, we'll wait until // the biggest one set is done. This way, we get plenty of // overlap, if not all devices are on, but move quickly if // they are. // m_StartTime := m_TimeInfo.GetCurMillis(); // See if the Dwin is on. If not, turn it on If (!m_FldIO.ReadBoolField(DeansInfo.kProjMoniker, "Power")) If (m_WaitTime < 5000) m_WaitTime := 5000; EndIf; m_FldIO.WriteBoolField(DeansInfo.kProjMoniker, "Power", True); EndIf; // See if the Lexicon is on. If not, turn it on If (!m_FldIO.ReadBoolField(DeansInfo.kProcMoniker, "Power")) If (m_WaitTime < 3000) m_WaitTime := 3000; EndIf; m_FldIO.WriteBoolField(DeansInfo.kProcMoniker, "Power", True); EndIf; // // If the wait time hasn't elapsed, then wait for it. This will // ensure that we get good reads of data below. // m_StartTime += m_WaitTime; m_EndTime := m_TimeInfo.GetCurMillis(); If (m_EndTime < m_StartTime) m_TimeInfo.Sleep(m_StartTime - m_EndTime); EndIf; // // First of all, change the switcher input, if it isn't already on // the desired input, because that will cause a new signal to be // seen by the projector, which will take a bit to sync. // m_CurInput := m_FldIO.ReadStringField(DeansInfo.kSwitchMoniker, "Input"); If (InitInput = Inputs.Input_DVD) If (m_CurInput != DeansInfo.kSwitchDVDInput) m_FldIO.WriteStringField ( DeansInfo.kSwitchMoniker , "Input" , DeansInfo.kSwitchDVDInput ); EndIf; ElseIf (InitInput = Inputs.Input_Sat) If (m_CurInput != DeansInfo.kSwitchSatInput) m_FldIO.WriteStringField ( DeansInfo.kSwitchMoniker , "Input" , DeansInfo.kSwitchSatInput ); EndIf; ElseIf (InitInput = Inputs.Input_VCR) If (m_CurInput != DeansInfo.kSwitchVCRInput) m_FldIO.WriteStringField ( DeansInfo.kSwitchMoniker , "Input" , DeansInfo.kSwitchVCRInput ); EndIf; Else Throw(DeansInfo.HTErrors.UnknownInput, InitInput); EndIf; // And now change the processor input m_CurInput := m_FldIO.ReadStringField(DeansInfo.kProcMoniker, "InputSrc"); If (InitInput = Inputs.Input_DVD) If (m_CurInput != DeansInfo.kProcDVDInput) m_FldIO.WriteStringField ( DeansInfo.kProcMoniker , "InputSrc" , DeansInfo.kProcDVDInput ); EndIf; ElseIf (InitInput = Inputs.Input_Sat) If (m_CurInput != DeansInfo.kProcSatInput) m_FldIO.WriteStringField ( DeansInfo.kProcMoniker , "InputSrc" , DeansInfo.kProcSatInput ); EndIf; ElseIf (InitInput = Inputs.Input_VCR) If (m_CurInput != DeansInfo.kProcVCRInput) m_FldIO.WriteStringField ( DeansInfo.kProcMoniker , "InputSrc" , DeansInfo.kProcVCRInput ); EndIf; Else Throw(DeansInfo.HTErrors.UnknownInput, InitInput); EndIf; Return 0; EndMethod; EndMethods;
In the end, what I'll do is write a class to represent the projector, one for the processor, and so forth, which hide the details such as field names and particular f/x names and such, and write my high level macros such as this one in terms of those classes, which will make my high level macros pretty much immune to swapping in a new device as some point. I'd just need to modify the internals of the 'cloaking class' for that device to do the right thing for the new device. You could have done this work without a macro. CQC's 'action' concept is a list of sub-actions that you graphically define. Those sub-actions can be macro invocations, or a write of a value to a single field. So you graphically do much of the above by just stringing a set of field writes together, but it wouldn't be nearly as smart about looking around and deciding what the state of things are and minimizing the activity.
|
Dean Roddey Member Posts: 60 From: Mountain View, CA USA Registered: Aug 2002
|
posted 09-09-2003 03:58 AM
Ok, here is a better version of the class above. Keep in mind that there is nothing 'official' about this stuff I'm presenting, it's just a set of user written CQC classes, and just one way of skinning this particular cat. I'll include these classes in the product, so that people can use them as a starting point for creating their own, if they want to use this advanced functionality of CQC, which really isn't required for setting up most control functions. The purpose of these classes is to allow you to create a system of your own macros that will be pretty unaffected by a replacing devices in the system.Keep in mind that these could be made much more elaborate, and I'll probably do so over time. But even as is, they provide a good idea of how powerful CQC's combination of an object based macro language intregrated directly into the control system can be. At the base, there is a types class that just defines some types in a generic way, unrelated to any particular device driver's representations of things like input sources, aspect ratios and such. Here is the class. code:
// // This class just provides some literals and types that are used by the other // helper classes. They provide abstract types for things like input sources, // aspect ratios, and so forth. So that the outside world, the users of the // helper classes, can deal in generic types, and the helper classes will // translate to/from these generic types and their specific values as required. // // We make the class final since there's no need to derive any other class // from it. // Class=[Final] ClassPath MEng.User.Helpers.HelperTypes; ParentClass MEng.Object; EndClass;// Declare some types Types= // // Actual names for inputs are only used inside the wrapper classes that // hide the details of our devices. So we create this enum to use in // all of the generic code. This lets us be independent of any // Enum=Inputs Input_DVD : "DVD"; Input_Sat : "Sat"; Input_VCR : "VCR"; EndEnum; // // And do the same for f/x for the processor. We won't define them // all here, so we'll be limited to this subset of f/x when being // controlled by our macros, but this is more than a sufficient set // of them, and we can add more later if desired. // Enum=Effects Effect_2Channel : "2 Channel mode"; Effect_Dolby2_0 : "Dolby Digital 2.0"; Effect_Dolby2_0THX : "Dolby Digital 2.0 THX"; Effect_Dolby5_1 : "Dolby Digital 5.1"; Effect_Dolby5_1THX : "Dolby Digital 5.1 THX"; Effect_DTSFilm : "DTS (Film mode)"; Effect_DTSMusic : "DTS (Music mode)"; Effect_Logic7 : "Logic7 Mode"; Effect_TVMatrix : "TV Matrix Mode"; EndEnum; // // And the same for aspect input ratios. We don't support output // aspect ratios here, since we have a fixed 16x9 screen. // Enum=InputAR InAR_1_33 : "1.33"; InAR_1_78 : "1.78"; InAR_1_85 : "1.85"; InAR_2_00 : "2.00"; InAR_2_35 : "2.35"; EndEnum; // // Some common errors thrown by our macros. The %(x) values are // replacement tokens that the throwing code will replace with the // actual info. Exception throwing is done with enumerated types in // CML. // Enum=HTErrors UnknownInputAR : "%(1) is not a known input aspect ratio"; UnknownInput : "%(1) is not a known input value"; EndEnum; EndTypes; Methods=[Public,Final]
// // We have to have at least a default constructor, but there's nothing // for it to do in this class, since we have no members that need // initializing. So it's just an empty implementation. // Constructor() Begin EndConstructor; EndMethods;
Then I've created a couple layers. I've created a set of abstract base classes that define the basic activities I want to carry out with particular types of devices. So I have an abstract base class for a projector, and one for a video switcher, and one for an A/V processor. Since they are abstract, they just define the interfaces, not the implementations. Here is the code for the one for an A/V processor.
code:
// // This class provides the abstract base class via which the higher level // macros deal with an A/V Processor. We can create various derivatives of // this class for various switcher types, and we do, and the high level // macros can create instances of the devices that they want, but once that // creation is done, the rest of the code can be indepdent of which actual // processor class is used. // // So this guy is fairly simple and just defines the Required methods that // each actual processor implementation must provide. We work in tersm of // the types defined in DeansTypes, for input, aspect ratios, and so forth. // And all of the methods take a simple field I/O object that the caller // must provide, and which the actual derived classes will use to do their // field I/O. // // The creator must pass in a device moniker which we store away. It's // mostly used by the derived class, but we store it here and provide a getter // method, in case any other code wants to use it in error messages and such. // The derived class just passes it through to us during construction. // Class=[NonFinal] ClassPath MEng.User.Helpers.DeviceIntfs.AVProcessor; ParentClass MEng.Object; EndClass;Imports= MEng.System.CQC.Runtime.SimpleFldClient; MEng.User.Helpers.HelperTypes; EndImports; Members= // The creator passes us a device moniker in our constructor String m_DevMoniker; EndMembers; // // Any methods that are public and final and const. // Methods=[Public,Final,Const]
// A getter for the moniker Method GetMoniker() Returns String Begin Return m_DevMoniker; EndMethod; EndMethods; // // Any methods that are public and final and non-const (which is the default // so we don't have to say it explicitly. // Methods=[Public,Final]
// Our one and only constructor, which just stores the moniker Constructor([In] String MonikerToUse) : m_DevMoniker(MonikerToUse); Begin EndConstructor; EndMethods; // // The required methods that we just define the interfaces for and which // the derived classes must provide. // Methods=[Public,Required]
// // Tells the caller if we are powered up or not. All we have to do is // return the field and return the value. // Method isPoweredOn([In] SimpleFldClient FldClient) Returns Boolean Begin // Make the compiler happy Return True; EndMethod; // // Tells us to power the device on, if not already powered on, and // to return a boolean that indicates if it had to power it on or // not. If so, it sets the output parm WaitTime to a recommended // number of milliseconds to wait before trying to talk to the // device. // Method PowerOn([In] SimpleFldClient FldClient , [Out] Card4 WaitTime) Returns Boolean Begin // Make the compiler happy Return True; EndMethod;
// Power off the device, and return true if we had to do so Method PowerOff([In] SimpleFldClient FldClient) Returns Boolean Begin // Make the compiler happy Return True; EndMethod;
// // Tells us to select a particular input aspect ratio. We get the // abstracted input AR enumeration value, and have to convert it to an // actual input AR name. // Method SelectSrc([In] SimpleFldClient FldClient , [In] HelperTypes.Inputs InpToSelect , [Out] Card4 WaitTime) Returns Boolean Begin // Make the compiler happy Return True; EndMethod;
EndMethods;
And, then I have concrete implementations of those interfaces for specific devices. It's fairly easy to whip up one of these for a new device, just copy another one of the same base class and modify it to suite the new device. Here is the concrete implementation for the Lexicon MC-1, derived from the A/V processor base class above.
code:
// // This class is a derivative of the abstract A/V processor class, which // in this case implements that interface for the Lexicon MC-1. The base // class defines the Required methods that all our A/V processor classes // must implement. // // Within this class, we refer to the field names via a set of literals, // just for efficiency, not for abstraction purposes, Within here, the name // is well known. We just want to avoid a lot of literal strings all over // the place. // Class=[NonFinal] ClassPath MEng.User.Helpers.DeviceImpls.LexiconMC1; ParentClass MEng.User.Helpers.DeviceIntfs.AVProcessor; EndClass; // Import the classes we need (intrinsics don't need importing) Imports= MEng.System.CQC.Runtime.SimpleFldClient; MEng.User.Helpers.HelperTypes; EndImports;
// Define some literals for use within here Literals= String kPowerFldName("Power"); String kInpFldName("InputSrc"); EndLiterals;
// // Some private methods for our own use. CML is a one-pass compiler, so we // have to put them first in the file, so that the public methods can see // them. // Methods=[Private,Final]
// // Converts the generic input source enum to a Lexicon input string. It // will throw an exception if it doesn't understand the type, to catch // additions to the enum that aren't updated here. // Method XlatInput([In] HelperTypes.Inputs ToXlat, [Out] String ToFill) Begin // We just use a switch statement Switch(ToXlat) Case HelperTypes.Inputs.Input_DVD : ToFill := "DVD"; EndCase; Case HelperTypes.Inputs.Input_Sat : ToFill := "TV"; EndCase; Case HelperTypes.Inputs.Input_VCR : ToFill := "VCR"; EndCase; Default : // Throw an exception Throw(HelperTypes.HTErrors.UnknownInput, ToXlat); EndCase; EndSwitch; EndMethod; EndMethods; // // We need a public, final block for our constructor and any methods // that we define at this level. // Methods=[Public,Final]
Constructor([In] String MonikerToUse) : $Parent(MonikerToUse); Begin EndConstructor; EndMethods; // // And we need a Public, Overrides block to provide implementations for // the Requied methods in our parent class. These are the ones that actually // implement the video switcher functionality. // // See the base class for methods comments. We only note things here that // are specific to this implementation. // Methods=[Public,Overrides]
Method isPoweredOn([In] SimpleFldClient FldClient) Returns Boolean Begin Return FldClient.ReadBoolField(GetMoniker(), kPowerFldName); EndMethod; Method PowerOn([In] SimpleFldClient FldClient , [Out] Card4 WaitTime) Returns Boolean Begin Locals= Boolean RetVal(False); EndLocals;
// See if the Lexicon is on. If not, turn it on If (!FldClient.ReadBoolField(GetMoniker(), kPowerFldName)) // It ain't, so write true to the power field FldClient.WriteBoolField(GetMoniker(), kPowerFldName, True); WaitTime := 4000; RetVal := True; EndIf; Return RetVal; EndMethod; Method PowerOff([In] SimpleFldClient FldClient) Returns Boolean Begin // See if the Lexicon is off. If not, turn it off If (FldClient.ReadBoolField(GetMoniker(), kPowerFldName)) FldClient.WriteBoolField(GetMoniker(), kPowerFldName, False); Return True; EndIf; Return False; EndMethod;
Method SelectSrc([In] SimpleFldClient FldClient , [In] HelperTypes.Inputs InpToSelect , [Out] Card4 WaitTime) Returns Boolean Begin Locals= Boolean RetVal(False); String NewInpName; String CurInpName; EndLocals;
// Convert the generic input to the actual name XlatInput(InpToSelect, NewInpName); // Get the current input selected on the Lexicon CurInpName := FldClient.ReadStringField(GetMoniker(), kInpFldName); // If we aren't already on the desired input, then change If (CurInpName != NewInpName) // // It ain't so write the new input. But check the power state // first. If it's not powered on, this is going to cause it to // power on, and we want to give back a larger wait time in that // case. // If (!FldClient.ReadBoolField(GetMoniker(), kPowerFldName)) WaitTime := 4000; Else WaitTime := 2000; EndIf; // And now write the value and set the return value FldClient.WriteStringField(GetMoniker(), kInpFldName, NewInpName); RetVal := True; EndIf; Return RetVal; EndMethod; EndMethods;
You can see that it implements the functions of the base class. Now, the class that replaces the more ungainly one I originally posted above, looks like this: code:
// // This class provides the logic to turn on the home theater components in // a very smart way, doing the minimum work necessary, and when work must be // done, providing appropriate settle times for the affected devices. It also // selects a desired input, so it is also used behind the buttons that change // input source, in which case it insures that devices are on, and then // does the input selection. If the devices are already on, it zips through // the power up funcionality with no action. And if the target input is // already selected, it will effectively do nothing. // Class=[NonFinal] ClassPath MEng.User.DeansMacros.Control.TheaterOn; ParentClass MEng.Object; EndClass;// // Bringin the classes we need. We have to have the field I/O client class, // which the device wrappers want us to provide to them. And we bring in // the implementations of the device wrapper classes for the devices we want // to deal with here. // // Note that the Digital Leeza provides both switcher and processor // functionality, so we have an class for each view of it. In other systems, // these would be two different devices. // Imports= MEng.System.CQC.Runtime.SimpleFldClient; MEng.User.Helpers.HelperTypes; MEng.User.Helpers.DeviceImpls.DwinHD700; MEng.User.Helpers.DeviceImpls.DigLeezaProcessor; MEng.User.Helpers.DeviceImpls.DigLeezaSwitcher; MEng.User.Helpers.DeviceImpls.LexiconMC1; EndImports; Members= // // Create the correct device wrappers for our current devices. // LexiconMC1 m_AVProc; DwinHD700 m_Projector; DigLeezaProcessor m_VideoProc; DigLeezaSwitcher m_VideoSwitcher;
// And some grunt work members Time m_TimeInfo; SimpleFldClient m_FldIO; Card4 m_EndTime; Card4 m_StartTime; Card4 m_TmpWait; EndMembers; Methods=[Public,Final]
Constructor() : m_AVProc("LexMC1"); m_Projector("Dwin"); m_VideoProc("Leeza"); m_VideoSwitcher("Leeza"); m_EndTime(0); Begin EndConstructor; // // The macro entry point. We take a desired target input, which we'll // select if not selected already. // Method Start([In] HelperTypes.Inputs InpToSelect) Returns Int4 Begin // // Remember the start time. For each device below, if we have to turn // it on, it will suggest a period of time to wait, and we'll use // each of those to set an end time, saving the latest end time we // get. When done, we've not reached the end time, we'll wait for it. // m_StartTime := m_TimeInfo.GetCurMillis();
// // Turn on the projector if needed. If we do, then see if the // suggested wait time is bigger than we we've set, and take that // time if so. // If (m_Projector.PowerOn(m_FldIO, m_TmpWait)) If ((m_StartTime + m_TmpWait) > m_EndTime) m_EndTime := m_StartTime + m_TmpWait; EndIf; EndIf; // And do the same for the A/V proc, video switcher, and video proc If (m_AVProc.PowerOn(m_FldIO, m_TmpWait)) If ((m_StartTime + m_TmpWait) > m_EndTime) m_EndTime := m_StartTime + m_TmpWait; EndIf; EndIf; If (m_VideoProc.PowerOn(m_FldIO, m_TmpWait)) If ((m_StartTime + m_TmpWait) > m_EndTime) m_EndTime := m_StartTime + m_TmpWait; EndIf; EndIf; If (m_VideoSwitcher.PowerOn(m_FldIO, m_TmpWait)) If ((m_StartTime + m_TmpWait) > m_EndTime) m_EndTime := m_StartTime + m_TmpWait; EndIf; EndIf; // // If the current time is less than the end time, then we need // to sleep a while. // m_TmpWait := m_TimeInfo.GetCurMillis(); If (m_TmpWait < m_EndTime) m_TimeInfo.Sleep(m_EndTime - m_TmpWait); EndIf; // // Ok, everyone should be up and running now, and should have had // time to stablize. So let's select the indicated input. We'll go // through the relevant devices in the order that it would affect // the signal, so we select the A/V processor input, then the video // processor input, then the switcher input. // // As with the power on above, we'll use the recommended wait time // to give everything time to settle before moving on. // m_StartTime := m_TimeInfo.GetCurMillis(); m_EndTime := 0; If (m_AVProc.SelectSrc(m_FldIO, InpToSelect, m_TmpWait)) If ((m_StartTime + m_TmpWait) > m_EndTime) m_EndTime := m_StartTime + m_TmpWait; EndIf; EndIf; If (m_VideoProc.SelectSrc(m_FldIO, InpToSelect, m_TmpWait)) If ((m_StartTime + m_TmpWait) > m_EndTime) m_EndTime := m_StartTime + m_TmpWait; EndIf; EndIf; If (m_VideoSwitcher.SelectSrc(m_FldIO, InpToSelect, m_TmpWait)) If ((m_StartTime + m_TmpWait) > m_EndTime) m_EndTime := m_StartTime + m_TmpWait; EndIf; EndIf; // And sleep again if required m_TmpWait := m_TimeInfo.GetCurMillis(); If (m_TmpWait < m_EndTime) m_TimeInfo.Sleep(m_EndTime - m_TmpWait); EndIf; Return 0; EndMethod; EndMethods;
You can see that it's a lot cleaner. The only parts of it that are device dependent are where it imports the specific device wrapper classes it needs, and where it then creates members of that type. So if you changed one of your devices, you'd only have to change those lines in any of the high level macro classes that use it. This concept could be taken as far as you want it, effectively creating a small, specialized control system within CQC, for your own devices. You can then link these macros to IR button presses, keyboard hot keys, buttons on user drawn interfaces, and so forth, to invoke them as desired. A particular action that is so attached can consist of a set of sub-actions, which can be direct writes to fields, pauses, or macro invocations, so you can do simple work by just stringing together a series of field writes, or a combination of field writes and macros, or write a macro which incorporates the whole set of activities and invoke that. It's really up to how you want to do it. I know that's a lot of info with little explanation, but it's really just direct application of the usual object oriented concepts of inheritance and polymorphism, applied to the problem of creating a set of reusable classes that address a particular user's automation needs. These classes can be modifed easily enough to add new functionality, and it will generally be required, since it's almost impossible to create a set of functionality that can both make use of extremely specific features of devices and be unaffected by replacement of those devices with others. For instance, you can see that in my types class, I've just set up a relatively small set of f/x, which I consider reasonble for my needs, and probably supportable on any future processor I might use. BTW, I've not yet added the methods to the A/V processor classes above to set the f/x, which is why you don't see it used. But it will just be more of the same. The Lexicon specific class will translate the generic f/x enumerated value to whatever it needs internally, and write that to the device. Anyway, I just wanted to make up for my hacky initial example, and show how powerful CQC's new macro language is. And the fact that you get all of this in the package, fully integrated, with a graphical IDE, means that you can do customization quite extensive customization without purchasing external development tools, which by themselves would probably cost multiples of the CQC package price.
| |