Bad weather on the way? My Smart House let's me know. I have an appointment
this afternoon? It lets me know. A favorite blog has an update? Yep.
I decided early on that I wanted my smart house to notify me of some things.
As a result, the time I used to spend checking the weather site, checking for
blog updates, checking my calendar is now free time. These things are pushed to
me, now, by my smart home.
The system has a series of alerts that do little more than poll various
end-points or database records on timers. When conditions are such that I need
to be notified, I get a message.
How does the application know how to reach me? Well, a couple of ways. In
my system, there is a concept of a communication channel. This channel is
associated with a particular user of the system and a certain mode of
communication (IM, SMS, email, more to come). When I log into IM, for example,
the system knows I'm online and that channel is marked as being open. If an
alert occurs, it knows I can be reached on that channel.
For other channels, such as email and SMS, the channel always remains open.
For these, I can configure the application to notify me on that channel or not
depending on the alert.
IM
I use the XMPP protocol for IM communication with my Smart House. This
allows me to use Google Talk or Jabber to get messages from or send messages to
the system. I have taken a dependency on the the XMPP SDK from Ags. It allows
me to connect to the network, to be notified when any of the Smart House's
buddies go on or off line, and to send and receive messages.
Connecting is simple. First, instantiate and configure a connection.
Connect
to XMPP
- Conn = new XmppClientConnection(ConfigurationManager.AppSettings["XmppServer"]);
- Contacts = null;
- Contacts = new List<Contact>();
- Conn.EnableCapabilities = true;
- Conn.ClientSocket.ConnectTimeout = 60000;
- Conn.AutoResolveConnectServer =
false;
- Conn.ConnectServer = "talk.google.com";
- Conn.Port = int.Parse(ConfigurationManager.AppSettings["XmppPort"]);
- Conn.UseStartTLS = false;
- Conn.UseSSL = true;
Then, add some event handlers.
Set
Event Handlers
- Conn.OnError += conn_OnError;
- Conn.OnMessage +=
conn_OnMessage;
-
- Conn.OnPresence +=
_conn_OnPresence;
-
- Conn.OnLogin += s =>
- {
- IsConnected
= true;
- var
p = new Presence(ShowType.chat, "Online") { Type = PresenceType.available };
- Conn.Send(p);
- };
-
- Conn.OnAuthError += (s, e) => StructuredSpeech2.Logging.Logging.Log("XMPP
Authentication Error");
-
- Conn.OnRegisterError += (s, e) =>
StructuredSpeech2.Logging.Logging.Log("XMPP Register Error: " + e.InnerXml);
- Conn.OnSocketError +=
- (s, e) =>
- StructuredSpeech2.Logging.Logging.Log("XMPP
Socket Error: " + e.Message + " :: "
+ e.StackTrace);
-
- Conn.OnStreamError += (s, e) =>
StructuredSpeech2.Logging.Logging.Log("XMPP Stream Error: " +
e.InnerXml);
Finally, connect.
Connect
- try
- {
- Conn.Open(ConfigurationManager.AppSettings["XmppUsername"],
- ConfigurationManager.AppSettings["XmppPassword"]);
- }
- catch
- {
- Initialize();
- }
Notice that many of the event handlers we're handling have to do with various
things that can go wrong. There are logged for investigation later. The
OnLogin event fires when a connection to the network has been established.
Here, I'm setting the system's status to online and available.
When a login fails, we recurse and try again.
The OnPresence event fires when the presence status of a user has changed
such as their becoming available or signing off. The event args let us know the
buddy and what their new presence value is. When I handle this event, I
evaluate the new presence value and then do two things. I keep a collection of
all online contacts so I can quickly broadcast a message. The first thing I do
when I get a presence event is to update this collection by adding or removing
the contact as appropriate. The second thing I do is update the state of the
communication channel for the user the buddy represents.
The OnMessage event fires when a message has been received from a buddy. The
event arguments specify the the buddy the message is from and the text of the
message. When a message is received, I check to see that the buddy is a user on
the system and if they are, I process the message with the Language Processor.
If the user is not found, a response is sent to the buddy stating they are not a
known user and cannot interact with the system.
Finally, I can send a message to any online buddy by calling the send method
on the connection class. As with the the previous discussions about the Speech
Processor, the entire conversation is logged and used in processing the text
sent in a particular message. In the case of IM, the conversation starts when
the buddy connects to the network and ends when they disconnect. When a message
has been received, the Language Processor processes it and then uses this send
method to reply to the appropriate user. Alerts are send in the same way.
The bottom line is that I can send commands to my house via IM and have it
act appropriately or it can send me messages to alert me if my attention is
needed. A true conversational interface.
Email
I'm using OpenPop to provide the
functionality need to check and manipulate emails on the POP server. My Smart
House has it's own email account which it polls on a timer. Each time, all
messages are pulled from the email account, processed and then deleted. OpenPop
allows the system to connect to the pop account and pull down all the messages.
First, the connection object is created and configured.
Connect
to POP3 account
- _client = new Pop3Client();
- _client.Connect(_popEmailServer,
_popEmailPort, _popEmailUseSsl);
- _client.Authenticate(_popEmailUname,
_popEmailPassword);
- _client.Reset();
Next, the messages are pulled down and processed.
Process
Email Messages
- _emailCheckTimer.Stop();
-
- try
- {
- ConnectClient();
-
- if
(!_client.Connected)
- return;
-
- int messageCount = _client.GetMessageCount();
-
- var messages = new List<Message>(messageCount);
-
- for (int i =
1; i <= messageCount; i++)
- {
- messages.Add(_client.GetMessage(i));
- _client.DeleteMessage(i);
- }
-
- List<Message> staticMessages = messages;
-
- var enc = new
ASCIIEncoding();
-
- foreach (Message msg in
staticMessages)
- {
- string request =
enc.GetString(msg.FindFirstPlainTextVersion().Body);
- string fromAddress =
msg.Headers.From.Address;
-
- User user = UserRepository.GetUserByEmail(fromAddress);
-
- if (user == null)
- {
- SendResponse("I don't
know this email address.", msg.Headers.From.Address,
msg.Headers.Subject);
- return;
- }
-
- if (_client != null)
- {
- _client.Disconnect();
- _client.Dispose();
- }
-
-
- CommandProcessor.ProcessCommand(request, user,
ConversationMode.Email, fromAddress);
- }
-
- }
- catch (Exception ex)
- {
- StructuredSpeech2.Logging.Logging.Log("Checking
mail " + ex.Message + ex.StackTrace);
- }
- finally
- {
- if (_client.Connected)
- _client.Disconnect(); ;
-
- _client.Dispose();
- }
-
- _emailCheckTimer.Start();
For sending emails, I use the SmtpClient class in the .Net Framework. See
its documentation for usage.
SMS
Once email has been implemented, SMS is easy. I use SMS for outgoing
messages, only. Most cell phone carriers allow SMS messages to be send via
email. For example, an email sent to an address such as 10digitnumber@txt.att.net, will be
received as an SMS message on the AT&T network phone having that 10 digit
number. For this reason, I use exactly the same approach to send SMS messages
as I do for sending emails.