Using Optimus Mini Three with .net
- Posted: Jul 13, 2007 at 9:00 AM
- 283 Views
- 1 Comment
Loading User Information from Channel 9
Something went wrong getting user information from Channel 9
Loading User Information from MSDN
Something went wrong getting user information from MSDN
Loading Visual Studio Achievements
Something went wrong getting the Visual Studio Achievements
Even though the device is connected to an usb port the communication is done through a (virtual) serial port. The protocol specification is available in
the developer section on Art.Lebedev Studio's website. There's also some c sample code available which proved to be very useful to build this class. The document lists the following
commands to send data to the device:
All the commands we're sending have a length of 197 bytes, the last byte being the checksum. The response to a command consists of two bytes: a 0 to tell us this is a command confirmation and as second byte the checksum. Ideally that checksum matches the one of the sent command - if not the data was somehow corrupted and we've to send it again. And the device is not just waiting until we send it something, it will also send something to us: the keys which are currently pressed - of course, wouldn't qualify as keyboard otherwise. These messages are also 2 bytes long, first byte being a 1 and the second byte is the 1-based index of the pressed key. We can get them anytime and very often - as long as one or more keys are pressed they're repeatedly send. When commands are send at the same time we get a mix of key messages and command confirmations. |
|
|
Difficulty: Intermediate
Time Required:
1-3 hours
Cost: Free
Software: Optimus mini three software
Hardware: Optimus mini three keyboard
Download: Download
|
|
First we need to know the serial port to connect to. Usb devices are stored in the windows registry in HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\USB, and the device id we're looking for is Vid_067b&Pid_2303. Below that key can be multiple nodes, depending on how often and which usb ports you've plugged in the device.
Below each node is a sub node called Device Parameters and there we find what we're looking for: the key PortName with the serial port name as value. We're additionally verifying that the serial port actually exists. It can happen that the PortName key is set, but the port doesn't exist because it's an old/inactive registry entry from a previous connection.
C#
1: private static string GetPort()
2: {
3: string result = null;
4:
5: // Get all active ports
6: List<string> ports = new List<string>();
7: ports.AddRange(SerialPort.GetPortNames());
8: if (ports.Count == 0) { return null; }
9:
10: // Get root
11: string rootName = "SYSTEM\\CurrentControlSet\\Enum\\USB\\Vid_067b&Pid_2303";
12: RegistryKey root = Registry.LocalMachine.OpenSubKey(rootName);
13: if (root == null) { return null; }
14:
15: // Get all keys below root - there can be several of them if the
16: // device was connected to different usb ports
17: string[] devices = root.GetSubKeyNames();
18: if (devices == null) { return null; }
19:
20: // Loop through all devices and get first active
21: foreach (string deviceKey in devices)
22: {
23: RegistryKey device = root.OpenSubKey(deviceKey + "\\Device Parameters");
24: if (device == null) { continue; }
25:
26: object portValue = device.GetValue("PortName");
27: if (portValue == null) { continue; }
28:
29: // Check if that port is active
30: string port = (string)portValue;
31: if (ports.Contains(port))
32: {
33: result = port;
34: break;
35: }
36: }
37: // Result
38: return result;
39: }
VB
1: Private Shared Function GetPort() As String
2:
3: Dim result As String = Nothing
4:
5: 'Get all active ports
6: Dim ports As List(Of String) = New List(Of String)
7: ports.AddRange(SerialPort.GetPortNames())
8: If (ports.Count = 0) Then
9: Return Nothing
10: End If
11:
12: 'Get root
13: Dim rootName As String = "SYSTEM\\CurrentControlSet\\Enum\\USB\\Vid_067b&Pid_2303"
14: Dim root As RegistryKey = Registry.LocalMachine.OpenSubKey(rootName)
15: If (root Is Nothing) Then
16: Return Nothing
17: End If
18:
19: 'Get all keys below root - there can be several of them if the
20: 'device was connected to different usb ports
21: Dim devices() As String = root.GetSubKeyNames()
22: If (devices Is Nothing) Then
23: Return Nothing
24: End If
25:
26: 'Loop through all devices and get first active
27: Dim deviceKey As String
28: For Each deviceKey In devices
29: Dim device As RegistryKey = root.OpenSubKey(deviceKey + "\\Device Parameters")
30: If (device Is Nothing) Then
31: Continue For
32: End If
33:
34: Dim portValue As Object = device.GetValue("PortName")
35: If (portValue Is Nothing) Then
36: Continue For
37: End If
38:
39: 'Check if that port is active
40: Dim port As String = CType(portValue, String)
41: If (ports.Contains(port)) Then
42: result = port
43: Exit For
44: End If
45: Next
46:
47: 'Result
48: Return result
49:
50: End Function
Now that we know the port name we can open it. After that we add an handler for the DataReceived event and tell the background thread to start working.
C#
1: private bool _Connected;
2: private SerialPort _Port;
3: private Thread _ProcessCommandsThread;
4:
5: public bool Init()
6: {
7: // If already connected exit
8: if (_Connected) { return true; }
9:
10: // Get port name where the device is connected
11: string port = GetPort();
12: if (string.IsNullOrEmpty(port)) { return false; }
13:
14: // Open port
15: _Port = new SerialPort(port);
16: _Port.BaudRate = 1000000;
17: _Port.DataBits = 8;
18: _Port.Open();
19: _Connected = true;
20:
21: // Add event handler for DataReceived
22: _Port.DataReceived += new SerialDataReceivedEventHandler(PortDataReceived);
23:
24: // Start command thread
25: _ProcessCommandsThread = new Thread(ProcessCommands);
26: _ProcessCommandsThread.Start();
27:
28: // Successfully connected
29: return true;
30: }
31:
VB
1: Private _Connected As Boolean
2: Private _Port As SerialPort
3: Private _ProcessCommandsThread As Thread
4:
5: Public Function Init() As Boolean
6: 'If already connected exit
7: If (_Connected) Then Return True
8:
9: ' Get port name where the device is connected
10: Dim port As String = GetPort()
11: If (String.IsNullOrEmpty(port)) Then Return False
12:
13: 'Open port
14: _Port = New SerialPort(port)
15: _Port.BaudRate = 1000000
16: _Port.DataBits = 8
17: _Port.Open()
18: _Connected = True
19:
20: 'Add event handler for DataReceived
21: AddHandler _Port.DataReceived, AddressOf PortDataReceived
22:
23: 'Start command thread
24: _ProcessCommandsThread = New Thread(AddressOf ProcessCommands)
25: _ProcessCommandsThread.Start()
26:
27: 'Successfully connected
28: Return True
29: End Function
This handler added in Init will be called asynchronously as soon as there's data in the input buffer. We'll only get byte pairs from the device which makes parsing very easy - a pair is either a command confirmation (0 followed by checksum) or a key message (1 followed by the 1-based key index). A wait handle is used to signal our background thread that a command confirmation was received. If we received a key message we'll call our RaiseKeyDown method.
C#
1: private byte _LastCommandChecksum;
2: private EventWaitHandle _CommandWaitHandle;
3:
4: private void PortDataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
5: {
6: // If we've not at least 2 bytes no need to read
7: int length = _Port.BytesToRead;
8: if (length < 2) { return; }
9:
10: // We need byte pairs, single byte at the end is not read
11: int pairs = length / 2;
12: byte[] data = new byte[pairs * 2];
13: _Port.Read(data, 0, pairs * 2);
14:
15: // Parse data
16: bool commandReceived = false;
17: bool[] keyReceived = new bool[3];
18: for (int i = 0; i < pairs; i++)
19: {
20: byte dataType = data[i * 2];
21: byte dataValue = data[i * 2 + 1];
22:
23: if (dataType == 0)
24: {
25: // Command confirmation
26: _LastCommandChecksum = dataValue;
27: commandReceived = true;
28: }
29: else
30: {
31: // Key message
32: if (dataValue >= 1 && dataValue <= 3)
33: {
34: keyReceived[dataValue - 1] = true;
35: }
36: }
37: }
38:
39: // If a command confirmation was received notify wait handle
40: if (commandReceived && _CommandWaitHandle != null) { _CommandWaitHandle.Set(); }
41:
42: // If key messages were received raise event
43: for (byte i = 0; i <= 2; i++)
44: {
45: if (keyReceived[i]) { RaiseKeyDown(i); }
46: }
47: }
VB
1: Private _LastCommandChecksum As Byte
2: Private _CommandWaitHandle As EventWaitHandle
3:
4: Private Sub PortDataReceived(ByVal sender As Object, ByVal e As SerialDataReceivedEventArgs)
5: 'If we've not at least 2 bytes no need to read
6: Dim length As Integer = _Port.BytesToRead
7: If (length < 2) Then Exit Sub
8:
9: 'We need byte pairs, single byte at the end is not read
10: Dim pairs As Integer = length \ 2
11: Dim data() As Byte = New Byte(pairs * 2) {}
12: _Port.Read(data, 0, pairs * 2)
13:
14: 'Parse data
15: Dim commandReceived As Boolean = False
16: Dim keyReceived() As Boolean = New Boolean(3) {}
17: Dim i As Integer
18: For i = 0 To pairs - 1 Step i + 1
19: Dim dataType As Byte = data(i * 2)
20: Dim dataValue As Byte = data(i * 2 + 1)
21:
22: If (dataType = 0) Then
23: 'Command confirmation
24: _LastCommandChecksum = dataValue
25: commandReceived = True
26: Else
27: 'Key message
28: If (dataValue >= 1 And dataValue <= 3) Then
29: keyReceived(dataValue - 1) = True
30: End If
31: End If
32: Next
33:
34: 'If a command confirmation was received notify wait handle
35: If (commandReceived And Not _CommandWaitHandle Is Nothing) Then
36: _CommandWaitHandle.Set()
37: End If
38:
39: 'If key messages were received raise event
40: Dim j As Byte
41: For j = 0 To 2
42: If (keyReceived(j)) Then
43: RaiseKeyDown(j)
44: End If
45: Next
46: End Sub
This method is called from the DataReceived handler and raises OnKeyDown events when we received key messages. Now we know that we can get them at a very fast rate and we shouldn't raise an event for every single message. We'll restrict it to only raise the event once if the key was not pressed in the last 100 ms. I came up with the 100ms after some testing - sometimes there're just a few ms between the key messages, but from time to time the gap is up to 70.
C#
1: private int[] _LastKeyMessageOn = new int[3];
2: public delegate void KeyDownEventHandler(byte keyIndex);
3: public event KeyDownEventHandler OnKeyDown;
4:
5: private void RaiseKeyDown(byte keyIndex)
6: {
7: int current = Environment.TickCount;
8: if (current - _LastKeyMessageOn[keyIndex] >= 100)
9: {
10: // Time to raise event
11: if (OnKeyDown != null) { OnKeyDown(keyIndex); }
12: }
13: _LastKeyMessageOn[keyIndex] = current;
14: }
VB
1: Private _LastKeyMessageOn() As Integer = New Integer(3) {}
2: Public Event OnKeyDown(ByVal keyIndex As Byte)
3:
4: Private Sub RaiseKeyDown(ByVal keyIndex As Byte)
5: Dim current As Integer = Environment.TickCount
6: If (current - _LastKeyMessageOn(keyIndex) >= 100) Then
7: 'Time to raise event
8: RaiseEvent OnKeyDown(keyIndex)
9: End If
10: _LastKeyMessageOn(keyIndex) = current
11: End Sub
This method takes a byte array as parameter and writes it to the output buffer of the serial port. Once sent it will wait up to a second at the handle we defined above for a command confirmation. If the confirmation arrives within time it'll compare the checksum. If they don't match or if there was a time out the command is send again, up to 3 times.
C#
1: private const int COMMAND_LENGTH = 197;
2: private const int COMMAND_LAST = 196;
3:
4: private bool SendCommand(byte[] command)
5: {
6: bool success = false;
7: int triesLeft = 3;
8:
9: while (!success && triesLeft > 0)
10: {
11: _Port.Write(command, 0, COMMAND_LENGTH);
12: _CommandWaitHandle = new System.Threading.AutoResetEvent(false);
13: if (_CommandWaitHandle.WaitOne(1000, false))
14: {
15: _CommandWaitHandle = null;
16: if (_LastCommandChecksum == command[COMMAND_LAST])
17: {
18: // Success
19: success = true;
20: break;
21: }
22: else
23: {
24: // Failed
25: triesLeft -= 1;
26: }
27: }
28: else
29: {
30: // Failed
31: triesLeft -= 1;
32: }
33: }
34:
35: return success;
36: }
VB
1: Private Const COMMAND_LENGTH As Integer = 197
2: Private Const COMMAND_LAST As Integer = 196
3:
4: Private Function SendCommand(ByVal command As Byte()) As Boolean
5: Dim success As Boolean = False
6: Dim triesLeft As Integer = 3
7:
8: While Not success And triesLeft > 0
9: _Port.Write(command, 0, COMMAND_LENGTH)
10: _CommandWaitHandle = New System.Threading.AutoResetEvent(False)
11: If (_CommandWaitHandle.WaitOne(1000, False)) Then
12: _CommandWaitHandle = Nothing
13: If (_LastCommandChecksum = command(COMMAND_LAST)) Then
14: 'Success
15: success = True
16: Exit While
17: Else
18: 'Failed
19: triesLeft -= 1
20: End If
21: Else
22: 'Failed
23: triesLeft -= 1
24: End If
25: End While
26:
27: Return success
28: End Function
All commands we want to execute are added to a queue. That queue is a FIFO type - first in, first out. It's filled by the methods described below and emptied by the background thread. Because two different threads can modify the queue at the same time we need to synchronize it. The easiest way to do that is by using the lock statement to just let one thread in at a time.
Let's start with the simple commands. For better readability the possible brightness values of low, normal and high (20, 40 and 60) are added as enumeration. The keyIndex parameter is 0-based - 0 is the key on the side with the usb cable.
C#
1: private Queue<byte[]> _CommandQueue = new Queue<byte[]>();
2: public const int SCREEN_SIZE = 96;
3: public enum Brightness
4: {
5: Low = 20,
6: Normal = 40,
7: High = 60
8: }
9:
10: public void SwitchOn()
11: {
12: byte[] command = new byte[COMMAND_LENGTH];
13: command[0] = 2;
14: command[COMMAND_LAST] = 2;
15: lock (_CommandQueue)
16: {
17: _CommandQueue.Enqueue(command);
18: }
19: }
20: public void SwitchOff()
21: {
22: lock (_CommandQueue)
23: {
24: _CommandQueue.Enqueue(CreateSwitchOffCommand());
25: }
26: }
27: private byte[] CreateSwitchOffCommand()
28: {
29: byte[] command = new byte[COMMAND_LENGTH];
30: command[0] = 3;
31: command[COMMAND_LAST] = 3;
32: return command;
33: }
34: public void SetBrightness(Brightness brightness)
35: {
36: byte[] command = new byte[COMMAND_LENGTH];
37: command[0] = 9;
38: command[1] = (byte)brightness;
39: command[COMMAND_LAST] = (byte)(command[0] + command[1]);
40: lock (_CommandQueue)
41: {
42: _CommandQueue.Enqueue(command);
43: }
44: }
45: public void ShowImage(byte keyIndex)
46: {
47: lock (_CommandQueue)
48: {
49: _CommandQueue.Enqueue(CreateShowImageCommand(keyIndex));
50: }
51: }
52: private byte[] CreateShowImageCommand(byte keyIndex)
53: {
54: byte[] command = new byte[COMMAND_LENGTH];
55: command[0] = 4;
56: command[1] = (byte)(keyIndex + 1);
57: command[COMMAND_LAST] = (byte)(command[0] + command[1]);
58: return command;
59: }
VB
1: Private _CommandQueue As Queue(Of Byte()) = New Queue(Of Byte())
2:
3: Public Const SCREEN_SIZE As Integer = 96
4: Public Enum Brightness
5: Low = 20
6: Normal = 40
7: High = 60
8: End Enum
9:
10: Public Sub SwitchOn()
11: Dim command() As Byte = New Byte(COMMAND_LENGTH) {}
12: command(0) = 2
13: command(COMMAND_LAST) = 2
14: SyncLock _CommandQueue
15: _CommandQueue.Enqueue(command)
16: End SyncLock
17: End Sub
18: Public Sub SwitchOff()
19: SyncLock _CommandQueue
20: _CommandQueue.Enqueue(CreateSwitchOffCommand())
21: End SyncLock
22: End Sub
23: Private Function CreateSwitchOffCommand() As Byte()
24: Dim command() As Byte = New Byte(COMMAND_LENGTH) {}
25: command(0) = 3
26: command(COMMAND_LAST) = 3
27: Return command
28: End Function
29: Public Sub SetBrightness(ByVal brightness As Brightness)
30: Dim command() As Byte = New Byte(COMMAND_LENGTH) {}
31: command(0) = 9
32: command(1) = CType(brightness, Byte)
33: command(COMMAND_LAST) = CType((command(0) + command(1)), Byte)
34: SyncLock _CommandQueue
35: _CommandQueue.Enqueue(command)
36: End SyncLock
37: End Sub
38: Public Sub ShowImage(ByVal keyIndex As Byte)
39: SyncLock _CommandQueue
40: _CommandQueue.Enqueue(CreateShowImageCommand(keyIndex))
41: End SyncLock
42: End Sub
43: Private Function CreateShowImageCommand(ByVal keyIndex As Byte) As Byte()
44: Dim command() As Byte = New Byte(COMMAND_LENGTH) {}
45: command(0) = 4
46: command(1) = CType((keyIndex + 1), Byte)
47: command(COMMAND_LAST) = CType((command(0) + command(1)), Byte)
48: Return command
49: End Function
A more complex command is required to send the image data. Actually we need to send 96 such commands because the data is send line by line. We need to access the passed bitmap pixel by pixel to put together the commands, so we'll first copy the bitmap into a byte array for faster access.
C#
1: public void SetImage(byte keyIndex, System.Drawing.Bitmap image)
2: {
3: // Copy image into array for faster processing
4: BitmapData imageData = image.LockBits(
5: new Rectangle(0, 0, SCREEN_SIZE, SCREEN_SIZE),
6: ImageLockMode.ReadOnly,
7: PixelFormat.Format24bppRgb);
8: int imageRgbLength = SCREEN_SIZE * SCREEN_SIZE * 3;
9: byte[] imageRgb = new byte[imageRgbLength];
10: System.Runtime.InteropServices.Marshal.Copy(imageData.Scan0, imageRgb, 0, imageRgbLength);
11: image.UnlockBits(imageData);
12:
13: // Convert image to commands
14: byte colorR, colorG, colorB;
15: int imageRgbIndex;
16: byte[] command;
17:
18: for (int y = 0; y < SCREEN_SIZE; y += 1)
19: {
20: command = new byte[COMMAND_LENGTH];
21: command[0] = 1;
22: command[1] = (byte)(keyIndex + 1);
23: command[2] = (byte)((192 * y) >> 8);
24: command[3] = (byte)((192 * y) - (command[2] << 8));
25: command[COMMAND_LAST] += (byte)(command[0] + command[1] + command[2] + command[3]);
26:
27: for (int x = 0; x < SCREEN_SIZE; x += 1)
28: {
29: imageRgbIndex = y * SCREEN_SIZE * 3 + x * 3;
30:
31: colorR = imageRgb[imageRgbIndex + 2];
32: colorG = imageRgb[imageRgbIndex + 1];
33: colorB = imageRgb[imageRgbIndex];
34:
35: command[4 + x * 2] = (byte)((colorR & 0xF8) + (colorG >> 5));
36: command[5 + x * 2] = (byte)((colorB >> 3) + ((colorG & 0x1C) << 3));
37:
38: command[COMMAND_LAST] += command[4 + x * 2];
39: command[COMMAND_LAST] += command[5 + x * 2];
40: }
41:
42: lock (_CommandQueue)
43: {
44: _CommandQueue.Enqueue(command);
45: }
46: }
47: }
VB
1: Public Sub SetImage(ByVal keyIndex As Byte, ByVal image As Bitmap)
2: 'Copy image into array for faster processing
3: Dim imageData As BitmapData = image.LockBits( _
4: New Rectangle(0, 0, SCREEN_SIZE, SCREEN_SIZE), _
5: ImageLockMode.ReadOnly, _
6: PixelFormat.Format24bppRgb)
7: Dim imageRgbLength As Integer = SCREEN_SIZE * SCREEN_SIZE * 3
8: Dim imageRgb() As Byte = New Byte(imageRgbLength) {}
9: System.Runtime.InteropServices.Marshal.Copy(imageData.Scan0, imageRgb, 0, imageRgbLength)
10: image.UnlockBits(imageData)
11:
12: 'Convert image to commands
13: Dim colorR As Byte, colorG As Byte, colorB As Byte
14: Dim imageRgbIndex As Integer
15: Dim command() As Byte
16:
17: Dim y As Integer
18: For y = 0 To SCREEN_SIZE - 1
19: command = New Byte(COMMAND_LENGTH) {}
20: command(0) = 1
21: command(1) = CByte(keyIndex + 1)
22: command(2) = CByte((192 * y) >> 8)
23: command(3) = CByte((192 * y) And &HFF)
24:
25: command(COMMAND_LAST) = CByte((CShort(command(COMMAND_LAST)) + command(0)) And &HFF)
26: command(COMMAND_LAST) = CByte((CShort(command(COMMAND_LAST)) + command(1)) And &HFF)
27: command(COMMAND_LAST) = CByte((CShort(command(COMMAND_LAST)) + command(2)) And &HFF)
28: command(COMMAND_LAST) = CByte((CShort(command(COMMAND_LAST)) + command(3)) And &HFF)
29:
30: Dim x As Integer
31: For x = 0 To SCREEN_SIZE - 1
32: imageRgbIndex = y * SCREEN_SIZE * 3 + x * 3
33:
34: colorR = imageRgb(imageRgbIndex + 2)
35: colorG = imageRgb(imageRgbIndex + 1)
36: colorB = imageRgb(imageRgbIndex)
37:
38: command(4 + x * 2) = CByte(((colorR And &HF8) + (colorG >> 5)))
39: command(5 + x * 2) = CByte(((colorB >> 3) + ((colorG And &H1C) << 3)))
40:
41: command(COMMAND_LAST) = CByte((CShort(command(COMMAND_LAST)) + command(4 + x * 2)) And &HFF)
42: command(COMMAND_LAST) = CByte((CShort(command(COMMAND_LAST)) + command(5 + x * 2)) And &HFF)
43: Next
44:
45: SyncLock _CommandQueue
46: _CommandQueue.Enqueue(command)
47: End SyncLock
48: Next
49:
50: End Sub
There's some shifting taking place because the device requires 16 bit colors - 5 bits red, 6 bits green and another 5 bits for blue. The VB version has some additional code to be not dependent on the Remove integer overflow checks project setting.
Everything is in place to fill up the queue with commands, time to add the background thread code to empty it. The task of the thread is quite simple: check if there're any queued commands and send them until we tell him to stop. Additionally it sends show commands every 5 seconds. That's done because the device would switch off automatically after about 10 seconds if there were no commands.
C#
1: private void ProcessCommands()
2: {
3: bool exit = false;
4: int lastShowOn = Environment.TickCount;
5:
6: while (_Connected && !exit)
7: {
8: // Time for regular refresh?
9: if (Environment.TickCount - lastShowOn > 5000)
10: {
11: for (byte i = 0; i <= 2; i++)
12: {
13: if (!SendCommand(CreateShowImageCommand(i))) { break; }
14: }
15: lastShowOn = Environment.TickCount;
16: }
17:
18: // If we've commands ...
19: if (_CommandQueue.Count > 0)
20: {
21: while (_CommandQueue.Count > 0)
22: {
23: // ... process them
24: byte[] command;
25: lock (_CommandQueue)
26: {
27: command = _CommandQueue.Dequeue();
28: }
29: if (!SendCommand(command))
30: {
31: exit = true;
32: break;
33: }
34: }
35: }
36: else
37: {
38: // No commands, time to relax
39: Thread.Sleep(10);
40: }
41: }
42: }
VB
1: Private Sub ProcessCommands()
2: Dim exitThread As Boolean = False
3: Dim lastShowOn As Integer = Environment.TickCount
4:
5: While _Connected And Not exitThread
6: 'Time for regular refresh?
7: If (Environment.TickCount - lastShowOn > 5000) Then
8: Dim i As Byte
9: For i = 0 To 2
10: If (Not SendCommand(CreateShowImageCommand(i))) Then
11: Exit While
12: End If
13: Next
14: lastShowOn = Environment.TickCount
15: End If
16:
17: 'If we've commands ...
18: If (_CommandQueue.Count > 0) Then
19: While _CommandQueue.Count > 0
20: '... process them
21: Dim command() As Byte
22: SyncLock _CommandQueue
23: command = _CommandQueue.Dequeue()
24: End SyncLock
25: If (Not SendCommand(command)) Then
26: exitThread = True
27: Exit While
28: End If
29: End While
30: Else
31: 'No commands, time to relax
32: Thread.Sleep(10)
33: End If
34: End While
35:
36: End Sub
The last method is used to turn the device off. We stop the background thread and before closing the port a switch off command is send.
C#
1: public void Terminate()
2: {
3: if (!_Connected) { return; }
4:
5: // Stop processing commands
6: if (_ProcessCommandsThread.IsAlive)
7: {
8: _ProcessCommandsThread.Abort();
9: _ProcessCommandsThread.Join(1000);
10: }
11:
12: // Switch off
13: SendCommand(CreateSwitchOffCommand());
14:
15: // Close port
16: _Port.Close();
17: _Connected = false;
18: }
VB
1: Public Sub Terminate()
2: If (Not _Connected) Then Exit Sub
3:
4: 'Stop processing commands
5: If (_ProcessCommandsThread.IsAlive) Then
6: _ProcessCommandsThread.Abort()
7: _ProcessCommandsThread.Join(1000)
8: End If
9:
10: 'Switch off
11: SendCommand(CreateSwitchOffCommand())
12:
13: 'Close port
14: _Port.Close()
15: _Connected = False
16:
17: End Sub
Now comes the most interesting part of this exercise: seeing the code in action. For this we'll add some code to the main method of the console application to display the RGB colors on the key and to print the key down events. Ok, not that cool yet, but should give you an idea how to use it.
C#
1: static void Main(string[] args)
2: {
3: OptimusMiniDevice device = new OptimusMiniDevice();
4:
5: device.OnKeyDown += new OptimusMiniDevice.KeyDownEventHandler(device_OnKeyDown);
6:
7: device.Init();
8: device.SwitchOn();
9: device.SetBrightness(OptimusMiniDevice.Brightness.Low);
10:
11: device.SetImage(0, GetColor(System.Drawing.Brushes.Red));
12: device.SetImage(1, GetColor(System.Drawing.Brushes.Green));
13: device.SetImage(2, GetColor(System.Drawing.Brushes.Blue));
14:
15: device.ShowImage(0);
16: device.ShowImage(1);
17: device.ShowImage(2);
18:
19: Console.ReadKey();
20:
21: device.SwitchOff();
22: device.Terminate();
23: }
24:
25: static System.Drawing.Bitmap GetColor(System.Drawing.Brush brush)
26: {
27: System.Drawing.Bitmap bitmap = new Bitmap(OptimusMiniDevice.SCREEN_SIZE, OptimusMiniDevice.SCREEN_SIZE);
28: System.Drawing.Graphics graphic = Graphics.FromImage(bitmap);
29: graphic.FillRectangle(brush, new Rectangle(0, 0, OptimusMiniDevice.SCREEN_SIZE, OptimusMiniDevice.SCREEN_SIZE));
30: graphic.Flush();
31: return bitmap;
32: }
33:
34: static void device_OnKeyDown(byte keyIndex)
35: {
36: Console.WriteLine(string.Format("key down {0}", keyIndex));
37: }
VB
1: Sub Main()
2:
3: Dim device As OptimusMiniDevice = New OptimusMiniDevice()
4:
5: AddHandler device.OnKeyDown, AddressOf device_OnKeyDown
6:
7: device.Init()
8: device.SwitchOn()
9: device.SetBrightness(OptimusMiniDevice.Brightness.Low)
10:
11: device.SetImage(0, GetColor(Brushes.Red))
12: device.SetImage(1, GetColor(Brushes.Green))
13: device.SetImage(2, GetColor(Brushes.Blue))
14:
15: device.ShowImage(0)
16: device.ShowImage(1)
17: device.ShowImage(2)
18:
19: Console.ReadKey()
20:
21: device.SwitchOff()
22: device.Terminate()
23:
24: End Sub
25:
26: Function GetColor(ByVal brush As Brush)
27: Dim bitmap As Bitmap = New Bitmap(OptimusMiniDevice.SCREEN_SIZE, OptimusMiniDevice.SCREEN_SIZE)
28: Dim graphic As Graphics = Graphics.FromImage(bitmap)
29: graphic.FillRectangle(brush, New Rectangle(0, 0, OptimusMiniDevice.SCREEN_SIZE, OptimusMiniDevice.SCREEN_SIZE))
30: graphic.Flush()
31: Return bitmap
32: End Function
33:
34: Sub device_OnKeyDown(ByVal keyIndex As Byte)
35: Console.WriteLine(String.Format("key down {0}", keyIndex))
36: End Sub
That's it, a pretty straightforward class and easy to use - and for sure extendable. This is the prototype i've built to play around with the device and i'm working on an improved version, but it's not quite ready for prime time yet (you can take a look on the source though, comments welcome). I hope you found the article interesting, my first one.
Comments have been closed since this content was published more than 30 days ago, but if you'd like to continue the conversation,
please create a new thread in our Forums,
or
Contact Us and let us know.
Follow the Discussion
Oops, something didn't work.
What does this mean?
Following an item on Channel 9 allows you to watch for new content and comments that you are interested in. You need to be signed in to Channel 9 to use this feature.What does this mean?
Following an item on Channel 9 allows you to watch for new content and comments that you are interested in and view them all on your notifications page.sign up for email notifications?
If you're not sure what you need for this holiday season, look no further. After a year of coding and
Remove this comment
Remove this thread
close