View Full Version : Web Server, in at the deep end now!
simon
07-12-2006, 02:49 PM
Now I have my movies and CD's sorted, I want to set up web page access.
I have read the .pdf, think I understand the basics.
As I cannt be the first to want to do this, are there cml macros for the basics like weather, movie that I can download?
As I saw mentioned in another post, the manuals are very good, but I need to see examples to understand how to put it all together.
Thanks.
Dean Roddey
07-12-2006, 04:06 PM
Here is one that I did just for testing. It takes a URL with a query parameter, which lets me use the same macro to generate a set of pages. The parameter tells the macro what to spit out.
//
// This macro generates a number of pages, using a Page=xx query parameter,
// that are loaded into the display frame of the main web page.
//
Class=[NonFinal]
ClassPath MEng.User.CMLBin.Secure.GenPage;
ParentClass MEng.System.CQC.Runtime.CMLBinBase;
EndClass;
Imports=
MEng.System.Runtime.MemBuf;
MEng.System.Runtime.StringOutStream;
MEng.System.CQC.Runtime.SimpleFldClient;
EndImports;
Members=
// For formatting our contents into
StringOutStream m_Body;
// For getting field values
SimpleFldClient m_FldIO;
EndMembers;
// Private helper methods
Methods=[Private,Final]
// Generates any common opening body content
Method GenHeader([In] String PageRequested)
Begin
// Spit out the common page name at the top
m_Body.FmtStr("<html><body><blockquote><p align='left'>");
m_Body.FmtStr("<u><font face='Berlin Sans FB Demi' size='4'>");
m_Body.FmtStr(PageRequested);
m_Body.FmtStr(" ");
m_Body.FmtStr(" ");
m_Body.FmtStr("</font></u></p>");
EndMethod;
// Generates any common closing body content
Method GenFooter()
Begin
m_Body.FmtStr("</blockquote></html></body>");
EndMethod;
// Some helpers for generating tables
Method EndTable()
Begin
m_Body.FmtStr("</table></font></blockquote>");
EndMethod;
Method StartTable()
Begin
m_Body.FmtStr("<blockquote><font face='Berlin Sans FB Demi' size='4'>");
m_Body.FmtStr("<table border='1' cellspacing='1' width='78%'>");
EndMethod;
Method DoRow([In] String Prefix, [In] String Value)
Begin
Locals=
String ExpVal;
EndLocals;
m_Body.FmtStr("<tr><td align='right' width='185' bgcolor='#99CCFF'>");
m_Body.FmtStr("<font face='Berlin Sans FB'>");
m_Body.FmtStr(Prefix);
m_Body.FmtStr(":</font></td>");
m_Body.FmtStr("<td><font face='Berlin Sans FB'> ");
m_FldIO.ReplaceFldTokens(Value, ExpVal);
m_Body.FmtStr(ExpVal);
m_Body.FmtStr("</font></td></tr>");
EndMethod;
// These methods generate the various pages that can be requested
Method GenAudioPage()
Begin
EndMethod;
Method GenHomePage()
Begin
EndMethod;
Method GenLightCell([In] String Title
, [In] String Field)
Begin
Locals=
Card4 PercentLvl;
EndLocals;
PercentLvl := m_FldIO.ReadCardField("Lighting", Field);
// Do the title text for this one
m_Body.FmtStr("<td align='center' height='100' width='120'>");
m_Body.FmtStr("<font face='Berlin Sans FB'>");
m_Body.FmtStr(Title);
m_Body.FmtStr("</font><p><img border='0' src='");
// Spit out an off or on light image based on state
m_Body.FmtStr("/CQCImg/System/Hardware/Icons/Light%20");
If (PercentLvl > 0)
m_Body.FmtStr("On");
Else
m_Body.FmtStr("Off");
EndIf;
// And put the percent level out to its right
m_Body.FmtStr(".png' width='48' height='48' align='middle'>");
m_Body.FmtCard4(PercentLvl);
m_Body.FmtStr("%</td>");
EndMethod;
Method GenLightingPage()
Begin
//
// We have a three cell table first, which shows the status
// of each light. So spit out the opening table stuff, then
// we call a helper for each cell since only the name of the
// field changes.
//
m_Body.FmtStr("<table border='0' cellpadding='0' ");
m_Body.FmtStr("cellspacing='0' width='70%'><tr>");
// Do the three lighting groups
GenLightCell("Bedroom", "BedLamp");
GenLightCell("Home Theater", "HTLights");
GenLightCell("Kitchen", "DRTable");
// Now close off the table
m_Body.FmtStr("</tr></table>");
EndMethod;
Method GenVideoPage()
Begin
EndMethod;
Method GenWeatherPage()
Begin
// Spit out the appropriate weather channel image
m_Body.FmtStr("<img align='middle' ");
m_Body.FmtStr("src='/CQCImg/System/Weather/Weather Channel/64x64/");
m_Body.FmtCard4(m_FldIO.ReadCardField("Weather", "CurIcon"));
m_Body.FmtStr(".png'>");
// Put the condition text to the right of it
m_Body.FmtStr("<font face='Berlin Sans FB' size='4'> ");
m_Body.FmtStr(m_FldIO.ReadStringField("Weather", "CurCondText"));
// And generate a table of other values underneath
StartTable();
DoRow("As Of:", "$(Weather.ForecastAsOf)");
DoRow("Current Temp", "$(Weather.CurTemp)F");
DoRow("Humidity", "$(Weather.CurHumidity)%");
DoRow("Wind Speed", "$(Weather.CurWindSpeed)mph - $(Weather.CurWindDir)");
DoRow("Barometer", "$(Weather.CurBaro) - $(Weather.CurBaroTrend)");
EndTable();
EndMethod;
EndMethods;
// Overrides of virtual methods in our base class
Methods=[Public,Final,Overrides]
// The method called by the web server to generate page content
Method ProcessReq( [Out] MemBuf ToFill
, [Out] Card4 ContLen
, [Out] String ContType
, [Out] String RepText
, [In] Boolean IsGet) Returns Card4
Begin
Locals=
String PageRequested;
EndLocals;
//
// Get the value for the ReqPage query parameter that was
// part of the URL that triggered us. Our parent class provides
// access to the values by their key name.
//
If (!FindReqValue("ReqPage", PageRequested))
ContLen := This.BuildErrReply
(
404
, "The URL did not include the required ReqPage= value"
, ContType
, ToFill
);
RepText := "Bad Request";
Return 404;
EndIf;
Try
//
// Generate any common opening stuff. They all have have a
// title, which is just the requested page text.
//
GenHeader(PageRequested);
// Generate the requested page
If (PageRequested = "Audio")
GenAudioPage();
ElseIf (PageRequested = "Home")
GenHomePage();
ElseIf (PageRequested = "Lighting")
GenLightingPage();
ElseIf (PageRequested = "Video")
GenVideoPage();
ElseIf (PageRequested = "Weather")
GenWeatherPage();
Else
// Make the page name into an error message
PageRequested.Append(" is not a known dynamic page name");
ContLen := BuildErrReply(404, PageRequested, ContType, ToFill);
RepText := "Not Found";
Return 404;
EndIf;
// Generate any common trailing stuff
GenFooter();
EndTry;
Catch
// If anything goes awry, send back the error info
ContLen := This.BuildErrReply
(
500, $Exception.GetErrorText(), ContType, ToFill
);
RepText := "Server Error";
Return 500;
EndCatch;
//
// Ok, now transcode the accumulated text to the output buffer.
// The xcode method will flush the stream for us. Set the
// content type parm to match what we are returning.
//
ContLen := m_Body.XcodeTo(ToFill, "iso-8859-1");
ContType := "text/html;charset=iso-8859-1";
Return 200;
EndMethod;
EndMethods;
Methods=[Public,Final]
Constructor()
Begin
EndConstructor;
//
// We can use the entry point for debugging purposes in the IDE,
// by calling ProcessReq() with dummy info.
//
#BeginDebug
Method Start() Returns Int4
Begin
Locals=
MemBuf ToFill(8192, 64 * 1024);
Card4 ContLen;
String ContType;
String ErrText;
Card4 ResCode;
EndLocals;
AddReqValue("ReqPage", "Weather");
ResCode := ProcessReq(ToFill, ContLen, ContType, ErrText);
Return 0;
EndMethod;
#EndDebug
EndMethods;
Dean Roddey
07-12-2006, 04:09 PM
This one is in the Secure section, so requires a login, just so that I can test that portion of the functionality. Since it's just in Secure and not Secure/Admin or /Secure/Normal and whatnot, anyone with a valid CQC account login can open it.
And in this case it has hard coded assumptions about the monikers of my devices and which devies I have. A few of the page generation methods are just empty since this is just for testing.
Also note that I'm doing a cheap form of getting the device data. I'm just spitting out the standard $(moniker.fld) type tokens and using a standard token expansion method in the field I/O class. But I could have been more efficient and just went and got the field values myself. I didn that in a couple places where I wasn't just spitting the text value out but needed the actual value, such as to cause the correct lighting or weather conditions image URLs to be generated.
Dean Roddey
07-12-2006, 04:13 PM
There's a main page that it's just a file based page (which looks like our web site since I stole the HTML from there). And it has buttons on it to generate various pages in the center frame. The buttons just call this macro. Here are some example pages:
http://www.charmedquark.com/Web2/Downloads/MiscImages/WebSrvPreview3.jpg
http://www.charmedquark.com/Web2/Downloads/MiscImages/WebSrvPreview4.jpg
It's also using the ability to refer to images in the CQC image repository via URL, which lets it spit out the iamges for stuff like the weather conditions.
ellisr63
07-12-2006, 05:17 PM
Is this the HTML for web pages that you made up a while back... Dean?
simon
07-13-2006, 08:37 AM
Could I see the HTML for the examples pages above please Dean?
I have stripped out what I believe to be the bits i just need for a macro for weather.
Do I just save this as, say, weather.macro in the /CMLBin/User folder?
Method GenWeatherPage()
Begin
// Spit out the appropriate weather channel image
m_Body.FmtStr("<img align='middle' ");
m_Body.FmtStr("src='/CQCImg/System/Weather/Weather Channel/64x64/");
m_Body.FmtCard4(m_FldIO.ReadCardField("Weather", "CurIcon"));
m_Body.FmtStr(".png'>");
// Put the condition text to the right of it
m_Body.FmtStr("<font face='Berlin Sans FB' size='4'> ");
m_Body.FmtStr(m_FldIO.ReadStringField("Weather", "CurCondText"));
// And generate a table of other values underneath
StartTable();
DoRow("As Of:", "$(sample-Weatherchannel.ForecastAsOf)");
DoRow("Current Temp", "$(sample-Weatherchannel.CurTemp)F");
DoRow("Humidity", "$(sample-Weatherchannel.CurHumidity)%");
DoRow("Wind Speed", "$(sample-Weatherchannel.CurWindSpeed)mph - $(sample-Weatherchannel.CurWindDir)");
DoRow("Barometer", "$(sample-Weatherchannel.CurBaro) - $(sample-Weatherchannel.CurBaroTrend)");
EndTable();
EndMethod;
EndMethods;
Dean Roddey
07-13-2006, 09:08 AM
Well, to work as a CMLBin macro, you have to have all the bits related to the CMLBin interface, i.e. the ProcessReq() method, plus all of the standard CML bits. You can't just use that bit above that you quoted, since that's not a whole CML class.
The HTML for the main page is very simple. It's just the usual HTML stuff. It has hotspot links over the buttons in the tab bar tab. So the table is just a set of images with links that load that magic HTML page that corresponds to the macro above, each one passing a particular ?ReqPage=XXX value. The URLs aer just relative ones, starting with /CMLBin/ since the browser will turn that into a complete URL with host and all that.
<table border="0" cellpadding="0" cellspacing="0" width="90%" id="table4">
<tr>
<td width="102" align="center" valign="bottom">
<a href="/CMLBin/User/CMLBinTest?ReqPage=Home">
<img border="0" src="Tab_Home.jpg" width="94" height="29"></a></td>
<td align="center" valign="bottom" width="102">
<a href="Tab_Audio.html">
<img border="0" src="Tab_Audio.jpg" width="94" height="29"></a></td>
<td align="center" valign="bottom" width="102">
<a href="/CMLBin/User/CMLBinTest?ReqPage=Lighting">
<img border="0" src="Tab_Lighting.jpg" width="94" height="29"></a></td>
<td align="center" valign="bottom" width="102">
<a href="Tab_Video.html">
<img border="0" src="Tab_Video.jpg" width="94" height="29"></a></td>
<td align="center" valign="bottom" width="102">
<a href="/CMLBin/User/CMLBinTest?ReqPage=Weather">
<img border="0" src="Tab_Weather.jpg" width="94" height="29"></a></td>
<td align="center" valign="bottom"> </td>
</tr>
</table>
simon
07-13-2006, 11:15 AM
Before I dive into this and try and get it working I thought I had better check.
Can I access my DVD and CD collection with a modification to the macro?
And can I choose items to play?
Dean Roddey
07-13-2006, 11:21 AM
Not at this time, because there's no way to browse the repository via CML. You can get information about a title if you already know the title cookie of the playing title. And you could get that by reading the title cookie field of the renderer device (ZP or TT.)
Once you have the the title cookie, you can user the CQCMediaInfo class to query details about the paying title, e.g. the title, artist, cast, etc... and format that stuff out. And you could set up another macro to invoke via a PUT that sends commands in to control the player.
But you can't browse the repository at this time. I should probably add support for that.
simon
07-13-2006, 12:03 PM
Yes please!
I have one of those Nokia 770 web tablet browser things.
I really would like this to be my controller for it all, so I would like all things to be available via the web interfacce.
standon
03-28-2010, 11:20 AM
I know this thread is really old but I'm just about in the same place as the OP.
The example posted helped a lot, but wondering if anyone could help post the more interactive aspects of a web server page. Something like the buttons on top that say "System On", "System Off", etc... I'm trying to learn by example but there aren't too many examples out there of user interactions through the web server. Thanks in advance!
Dean Roddey
03-28-2010, 02:01 PM
The basic deal is that you set up an HTML form. The form effectively sends a set of key=value pairs via a PUT, which you can get in the same way as when ? type query pairs are passed at the end of a GET type of URL.
So your CML handler will do the same sorts of things, look for specific keys and get their values and use that to do something. So, for system Off/On, you'd probably have a form maybe with two radio buttons, off and on. One is set up to send something like Action=SystemPower and Value=Off or Value=On. You'd look for the Action parameter (so that you could use the same macro to do various types of actions) and then based on that, you'd know what other values to look for.
You can also send 'hidden' values in a form, that are sent no matter what the user selects or enters into the form. These can be used to send useful info to the macro.
Note that you still have to return page contents for display on a POST just as you do on a regular GET type page request. So you can always pass in a hidden value that indicates what page you want to be generated after the action is done.
Does that help?
standon
03-29-2010, 05:57 AM
Thanks Dean, I think I have everything working up until the "return page contents for display on a POST" part. I'm a CML dummy that hasn't built a web page in a while, so I've tried a few ways but I'm still not quite sure how to do this. Do I put the GenXXPage() command right after the button or do I create a string and put it under the GenHeader (PageRequested) area?
After I click submit I get an error 404,/CMLBin/User/Secure/Page?x=0&y=0.
Thanks again.
Dean Roddey
03-29-2010, 11:07 AM
I assume the example you were working with was returning a page for display, right? If so, it's the same thing. You just have to fill in the returned contents with some HTML to display, probably the page you were on to begin with when you pressed the button on the form.
standon
03-30-2010, 01:07 PM
Now I'm not sure where I'm lost at... I keep getting an error message saying that AudioCmd := ... must return an expression. It happens even when I change "Value" to "True" or "False" in the line. There are probably tons of errors below.
Method GenAudioLivingRoomPage()
Begin
Locals=
String ReqPage;
Boolean PwrVal;
String Amp;
String Field;
String AudioCmd;
Boolean Value;
EndLocals;
Amp := "RussoundCSeries";
Field := "Z04_Power";
PwrVal := m_FldIO.ReadBoolField(Amp, Field);
AudioCmd := m_FldIO.WriteBoolField(Amp, Field, Value);
If (PwrVal = True)
m_Body.FmtStr("The Living Room audio is on.");
Else
m_Body.FmtStr("The Living Room audio is off.");
EndIf;
m_Body.FmtStr("<form action=AudioCmd><method='PUT'>");
m_Body.FmtStr("<input type='Radio'><Value=False>'Off'");
m_Body.FmtStr("<form action=(AudioCmd><method='PUT'>");
m_Body.FmtStr("<input type='Radio'><Value=True>'On'");
m_Body.FmtStr("</form>");
m_Body.FmtStr("<form name='refresh'><action=ReqPage><method='Get'>");
m_Body.FmtStr("<value='AudioLivingRoom'>");
EndMethod;
Dean Roddey
03-30-2010, 02:25 PM
AudioCmd := m_FldIO.WriteBoolField(Amp, Field, Value);
You are writing to the field, not reading it, so it doesn't return any value. So you are trying to assign AudioCmd by calling a method that doesn't return any value. Do you really want to write to the field?
standon
04-01-2010, 08:11 PM
Thanks Dean, I realized where I was being the biggest idiot and got it to work.
Something strange keeps happening though. I tested the web page (it worked fine) I turned the zone on and off, then started adding volume control in a different editor. I did save the original configuration that worked on a different file in my editor, just in case I needed to revert back to it. After trying a few different things, I found that I couldn't switch the power off, it would go off for about 1 second then power cycle back on. I immediately went back to the saved macro, put that in and it still won't stay off (the page changed to reflect the pages when I reloaded). It seems to happen every once in a while, and then after about 10 minutes clicking the power button on the web site works as it should.
Is there some sort of refresh or something that I am missing? Do I need to clear parameters after executing a command?
Here's what worked:
Method GenAudioLivingRoomPage()
Begin
Locals=
Boolean PwrVal;
String Amp;
String Field;
String PowerCmd;
String PowerCmdVal;
EndLocals;
Amp := "RussoundCSeries";
Field := "Z04_Power";
PwrVal := m_FldIO.ReadBoolField(Amp, Field);
Try
If (FindReqValue("PowerCmd", PowerCmdVal))
If (PowerCmdVal = "False")
m_FldIO.WriteBoolField(Amp, Field, False);
Else
m_FldIO.WriteBoolField(Amp, Field, True);
EndIf;
EndIf;
If (PwrVal = True)
m_Body.FmtStr("The Living Room audio is on.");
m_Body.FmtStr("<form action='http://www.website.com/CMLBin/User/Secure/Page' method='POST'>");
m_Body.FmtStr("<input type='hidden' name='PowerCmd' value='False'>");
m_Body.FmtStr("<input type='Radio' name='ReqPage' value='AudioLivingRoom' method='GET' 'Turn On'>");
m_Body.FmtStr("<Off>");
m_Body.FmtStr("<br />");
m_Body.FmtStr("<input type='Submit' value='Submit'></form>");
Else
m_Body.FmtStr("The Living Room audio is off.");
m_Body.FmtStr("<form action='http://www.website.com/CMLBin/User/Secure/Page' method='POST'>");
m_Body.FmtStr("<input type='hidden' name='PowerCmd' value='True'>");
m_Body.FmtStr("<input type='Radio' name='ReqPage' value='AudioLivingRoom' method='GET' 'Turn Off'>");
m_Body.FmtStr("<Off>");
m_Body.FmtStr("<br />");
m_Body.FmtStr("<input type='Submit' value='Submit'></form>");
EndIf;
EndTry;
Catch
EndCatch;
EndMethod;
Dean Roddey
04-01-2010, 08:40 PM
Is there some sort of refresh or something that I am missing? Do I need to clear parameters after executing a command?
It's hard to tell by eye, but you don't want the field writes to be in the method that generates the page. You only want that to be done when there's a PUT of a form. Otherwise, every time you view the page, it's going to do the field write.
vBulletin v3.5.4, Copyright ©2000-2013, Jelsoft Enterprises Ltd.