Everything about my daily life as a programmer/Electrical Engineer!

Twitter->SL4->Arduino Light Controller

This is a preliminary post about a new project I just finished (a variation of CoffeeTrack). I wanted to test the new capabilities of Silverlight 4 and push the boundaries of what people think of as a web application.  I made a point to not have a server portion of my application.  This application only requires SL4 and a COM library (trying to avoid).

I wanted to be able to control my lights from the internet.  This system takes twitter posts (now possible in SL4) and interprets them as commands and commands the Arduino via COM automation. Once a command is received the web cam snaps a photo and posts that to twitter to confirm the results.

The Arduino code is DEAD simple. The Arduino is linked to a wireless light controller found at Walmart for $10. I found one online that is similar.  I used relays to close the switches.

image

const int ledPin1 = 13; // the pin that the LED is attached to
const int ledPin2 = 12; // the pin that the LED is attached to

int incomingByte;      // a variable to read incoming serial data into

void setup() {
  // initialize serial communication:
  Serial.begin(9600);
  // initialize the LED pin as an output:
  pinMode(ledPin1, OUTPUT);
  pinMode(ledPin2, OUTPUT);
}

void loop() {
  // see if there's incoming serial data:
  if (Serial.available() > 0)
  {
    // read the oldest byte in the serial buffer:
    incomingByte = Serial.read();
   Serial.println((char)incomingByte);
   if (incomingByte == '0') {
      digitalWrite(ledPin1, HIGH);
      delay(1000);
      digitalWrite(ledPin1, LOW);
      Serial.println("Lights are off");
    }
    else if (incomingByte == '1') {
      digitalWrite(ledPin2, HIGH);
      delay(1000);
      digitalWrite(ledPin2, LOW);
      Serial.println("Lights are on!");
    }
  }
}

 

The Silverlight 4 code is a combination of many, many different libraries. I was able to successfully talk to twitter as long as I ran as an out of browser application (dumb client access policy).  I used the FJCore library to encode images to Jpeg. Special thanks to VisiFire for some helpful code using FJCore. I spent about 3 days working with the code from this post (which works in a Console App).  Once I figured out how the encodings worked I was able to update a photo on twitter via Silverlight.  The rest of the code I stole from CoffeeTrack (stay tuned!).

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.IO;
using System.Xml.Linq;
using System.Text;
using System.Threading;
using System.Windows.Threading;
using System.Net.Browser;
using System.Windows.Media.Imaging;
using FluxJpeg.Core.Encoder;
using FluxJpeg.Core;

namespace XmasLightController
{
public partial class MainPage : UserControl
{
DispatcherTimer t = new DispatcherTimer() { Interval = new TimeSpan(0, 1, 0) };
byte[] currentImage = null;
string value = "";
public MainPage()
{
InitializeComponent();
t.Tick += new EventHandler(t_Tick);
//t.Start();
MakeRequest();
//UpdateTwitterImage();

}

void t_Tick(object sender, EventArgs e)
{
MakeRequest();

}


private void MakeRequest()
{
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(new Uri("http://twitter.com/statuses/user_timeline/uwstephens.atom?&count=1"));
request.BeginGetResponse(new AsyncCallback(ReadCallback), request);
WriteText("Contacting twitter");
}


int currentcommand = 0;


private void ReadCallback(IAsyncResult asynchronousResult)
{
this.Dispatcher.BeginInvoke(delegate()
{
t.Stop();

});
WriteText("Recieved response from twitter");
HttpWebRequest request = (HttpWebRequest)asynchronousResult.AsyncState;
HttpWebResponse response = (HttpWebResponse)request.EndGetResponse(asynchronousResult);
using (StreamReader streamReader1 = new StreamReader(response.GetResponseStream()))
{

string resultString = streamReader1.ReadToEnd();
XDocument doc = XDocument.Parse(resultString, LoadOptions.SetBaseUri | LoadOptions.SetLineInfo);
List<XElement> elems = doc.Elements("entry").ToList();
StringBuilder sb = new StringBuilder();
int oldcommand = currentcommand;
foreach (var a in doc.Descendants().ToList())
{
if (a.Name.LocalName == "entry")
{
if(value!=a.Value)
{
UpdateTwitterImage();
}
value=a.Value;
if (a.Value.ToLower().Contains("action"))
{
currentcommand = 1;
}
else if (a.Value.ToLower().Contains("cut"))
{
currentcommand = 2;
}
else if (a.Value.ToLower().Contains("snap"))
{
currentcommand = 3;
}
}
}

this.Dispatcher.BeginInvoke(delegate()
{
if (currentcommand == 1)
{
WriteText("Start command received");

input.Text = "1";
SendMessage();
UpdateTwitterImage();

}
else if (currentcommand == 2)
{
WriteText("Stop command received");
input.Text = "0";
SendMessage();
UpdateTwitterImage();

}

});

this.Dispatcher.BeginInvoke(delegate()
{
t.Start();
});

}

}

public void WriteText(string txt)
{
this.Dispatcher.BeginInvoke(delegate() { this.outputWindow.Text += Environment.NewLine + txt; });
}

public void UpdateTwitterImage()
{
WriteText("Updating image");
if (currentImage == null) { return; }
String avatarUrl = "http://twitter.com/account/update_profile_image.xml";
String file = "xmas";
string imageType = "png";
WebRequest.RegisterPrefix("http://", System.Net.Browser.WebRequestCreator.ClientHttp);
string contentBoundaryBase = DateTime.Now.Ticks.ToString("x");
string beginContentBoundary = string.Format("--{0}\r\n", contentBoundaryBase);
var contentDisposition = string.Format("Content-Disposition:form-data); name=\"image\"); filename=\"{0}\"\r\nContent-Type: image/{1}\r\n\r\n", file, imageType);
var endContentBoundary = string.Format("\r\n--{0}--\r\n", contentBoundaryBase);

byte[] fileBytes = null;
Encoding encoding = Encoding.UTF8;

MemoryStream test = new MemoryStream();
byte[] data = encoding.GetBytes(beginContentBoundary);
test.Write(data, 0, data.Length);
data = encoding.GetBytes(contentDisposition);
test.Write(data, 0, data.Length);
data = currentImage;
test.Write(data, 0, data.Length);
data = encoding.GetBytes(endContentBoundary);
test.Write(data, 0, data.Length);
fileBytes = test.GetBuffer();

var req = (HttpWebRequest)HttpWebRequest.Create(new Uri(avatarUrl, UriKind.Absolute));
req.ContentType = "multipart/form-data;boundary=" + contentBoundaryBase;
req.AllowReadStreamBuffering = true;
req.Method = "POST";

req.UseDefaultCredentials = false;
req.Credentials = new NetworkCredential("uwstephens", "-");
File.WriteAllBytes(@"C:\Users\michael\Documents\test.txt", fileBytes);
req.BeginGetRequestStream(delegate(IAsyncResult result)
{
Stream reqStream = req.EndGetRequestStream(result);
reqStream.Write(fileBytes, 0, fileBytes.Length);
//reqStream.wr(fileBytes, 0, fileBytes.Length);
reqStream.Close();
req.BeginGetResponse(delegate(IAsyncResult result1)
{
WriteText("Image updated");
if (req.HaveResponse)
{
try
{
WebResponse resp = req.EndGetResponse(result1);
WriteText(resp.Headers["status"]);
}
catch (Exception e) { WriteText(e.Message); }
}
}, req);

}, null);
}

VideoCaptureDevice webcam = null;
CaptureSource captureSource = null;
private void loadCamera_Click(object sender, RoutedEventArgs e)
{
webcam = CaptureDeviceConfiguration.GetDefaultVideoCaptureDevice();

if (CaptureDeviceConfiguration.RequestDeviceAccess())
{
captureSource = new CaptureSource();
captureSource.VideoCaptureDevice = webcam;
captureSource.Start();
captureSource.AsyncCaptureImage(snap);
}
}

public void snap(WriteableBitmap b)
{

currentImage = GetImageStream(b).GetBuffer();
webcamImg.Source = b;
File.WriteAllBytes(@"C:\Users\michael\Documents\test\test.png", currentImage);
captureSource.AsyncCaptureImage(snap);

}


/// <summary>
///
Reads raster information from WriteableBitmap
/// </summary>
/// <param name="bitmap">
WriteableBitmap</param>
/// <returns>
Array of bytes</returns>
public static byte[][,] ReadRasterInformation(WriteableBitmap bitmap)
{
int width = bitmap.PixelWidth;
int height = bitmap.PixelHeight;
int bands = 3;
byte[][,] raster = new byte[bands][,];

for (int i = 0; i < bands; i++)
{
raster[i] = new byte[width, height];
}

for (int row = 0; row < height; row++)
{
for (int column = 0; column < width; column++)
{
int pixel = bitmap.Pixels[width * row + column];
raster[0][column, row] = (byte)(pixel >> 16);
raster[1][column, row] = (byte)(pixel >> 8);
raster[2][column, row] = (byte)pixel;
}
}

return raster;
}

/// <summary>
///
Encode raster information to MemoryStream
/// </summary>
/// <param name="raster">
Raster information (Array of bytes)</param>
/// <param name="colorSpace">
ColorSpace used</param>
/// <returns>
MemoryStream</returns>
public static MemoryStream EncodeRasterInformationToStream(byte[][,] raster, ColorSpace colorSpace)
{
ColorModel model = new ColorModel { colorspace = ColorSpace.RGB };
FluxJpeg.Core.Image img = new FluxJpeg.Core.Image(model, raster);

//Encode the Image as a JPEG
MemoryStream stream = new MemoryStream();
FluxJpeg.Core.Encoder.JpegEncoder encoder = new FluxJpeg.Core.Encoder.JpegEncoder(img, 100, stream);
encoder.Encode();

// Back to the start
stream.Seek(0, SeekOrigin.Begin);

return stream;
}

/// <summary>
///
Get image MemoryStream from WriteableBitmap
/// </summary>
/// <param name="bitmap">
WriteableBitmap</param>
/// <returns>
MemoryStream</returns>
public static MemoryStream GetImageStream(WriteableBitmap bitmap)
{
byte[][,] raster = ReadRasterInformation(bitmap);
return EncodeRasterInformationToStream(raster, ColorSpace.RGB);
}

dynamic com;

private void serialConnect_Click(object sender, RoutedEventArgs e)
{
com = System.Windows.Interop.ComAutomationFactory.CreateObject("ActiveXperts.ComPort");
dynamic count = com.GetDeviceCount();
StringBuilder sb = new StringBuilder();

com.Device = "COM1";
com.Open();
MessageBox.Show(com.GetErrorDescription(com.LastError));
string buffer = "";
System.Threading.Thread t = new Thread(new ThreadStart(delegate()
{

while (1 == 1)
{
com.Sleep(200);
buffer = com.ReadString();
if (buffer == "") { continue; }
serialOutput.Dispatcher.BeginInvoke(delegate()
{
serialOutput.Text += "\r\n" + buffer;
});
}
}));
t.Start();
}



void SendMessage()
{
if (com != null)
{
foreach (char c in input.Text)
{
com.WriteByte((byte)c);
}
input.Text = "";
}
}

private void input_KeyUp(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
SendMessage();
}
}

}

}

2 comments:

friendsonearth said...

good hack, have you think use Ardunio send data to IIS instead of com and let SL read xml from IIS.

Michael Stephens said...

That is always possible and probably the easiest cross platform method. You could even expose a local web service that silverlight talks to directly. But its always nice not to have to mess with servers.