The Eject-A-Bed: Part 2

Now that we have a way of controlling the bed via a Netduino, we need a way of controlling the Netduino.  We thought about different scenarios – hacking into an alarm clock, a phone app, some kind of light sensor, etc…  In all of these scenarios, it made sense to make the Netduino Ethernet aware so I went and bought a new Netduino plus.  The two day wait for Amazon prime reminded me how we have come full-circle with getting our goods.  I am going to re-write the lyrics from the Music Man’s Well’s Fargo Wagon to the Amazon Wagon

O-ho the Am Azon Wagon is a-comin‘ down the street,

Oh please let it be for me!

O-ho the Am Azon Wagon is a-comin’ down the street,

I wish, I wish I knew what it could be!

In any event event, with the Netduino plus, I could send signals to the Netduino to move the bed up and down.  I checked Dan Theyer’s post about how build a solid class to cover Ethernet communications and I checked out this article to get a “Hello World’ Ethernet project up and going.

I decided to start with the ground up happy path using the ‘Hello World’ project.  To that end, I added a socket instance to the project

  1. private static Socket _socket = null;

 

I then add the SetUpWebServer method like so:

  1. private static void SetUpWebServer()
  2. {
  3.     _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
  4.     IPEndPoint ipEndpoint = new IPEndPoint(IPAddress.Any, 80);
  5.     _socket.Bind(ipEndpoint);
  6.     _socket.Listen(10);
  7.     ListenForRequest();
  8. }

 

And the ListenForRequest like so:

  1. public static void ListenForRequest()
  2. {
  3.     while(true)
  4.     {
  5.         using (Socket clientSocket = _socket.Accept())
  6.         {
  7.             IPEndPoint clientIP = clientSocket.RemoteEndPoint as IPEndPoint;
  8.             EndPoint clientEndPoint = clientSocket.RemoteEndPoint;
  9.  
  10.             int bytesReceived = clientSocket.Available;
  11.             if (bytesReceived > 0)
  12.             {
  13.                 byte[] buffer = new Byte[bytesReceived];
  14.                 int byteCount = clientSocket.Receive(buffer, bytesReceived, SocketFlags.None);
  15.                 String request = new String(Encoding.UTF8.GetChars(buffer));
  16.                 HandleRequest(clientSocket, request);
  17.                 SendResponse(clientSocket, request);
  18.  
  19.             }
  20.         }
  21.     }
  22. }

You will notice a lack of  exception handling and threading.  Typically, the _socket.Accept() method should be on the main thread and then handling the message should be done on a separate thread so the _socket can get to listening the next message as fast as possible.  However, since the servo is directly tied to individual messages, I thought it was better to make the entire execution serial.  Plus, I am lazy.

In any event, once the Netduino gets a request, it then needs to adjust the servo:

  1. private static void HandleRequest(Socket clientSocket, String request)
  2. {
  3.     String[] chunkedRequest = request.Split('/');
  4.     String verb = chunkedRequest[0];
  5.     String direction = chunkedRequest[1];
  6.     String amount = chunkedRequest[2];
  7.     Int32 duration = Int32.Parse(amount);
  8.  
  9.     ActivateServoForBellows(direction, duration);
  10. }

 

And the actual controlling of the servo we have seen before:

  1. private static void ActivateServoForBellows(String direction, Int32 duration)
  2. {
  3.  
  4.     if (direction == "UP")
  5.     {
  6.         _servo.Duration = 1250;
  7.     }
  8.     else if (direction == "DOWN")
  9.     {
  10.         _servo.Duration = 1750;
  11.     }
  12.  
  13.     Thread.Sleep(duration);
  14.     _servo.Duration = 1500;
  15. }

There can be some confusion about the word “duration”.  duration with a little ‘d’ means how long the servo stays in the non-straight position – effectivly how long the bed is moving.  Duration with the big ‘D’ means the location of the servo as it rotates around the center – how far the servo moves.  When I make this ready for prime time, my covering class will fix this ambiguity because I am a big believer in domain-unique language.  I will also be copying much of Dan’s code.

In any event, I also created a response method so the requestor can see something:

  1. private static void SendResponse(Socket clientSocket, String request)
  2. {
  3.  
  4.     String[] chunkedRequest = request.Split('/');
  5.     String verb = chunkedRequest[0];
  6.     String direction = chunkedRequest[1];
  7.     String amount = chunkedRequest[2];
  8.     Int32 duration = Int32.Parse(amount);
  9.  
  10.     String response = direction + " : " + amount + " was sent.";
  11.     String header = "HTTP/1.0 200 OK\r\nContent-Type: text;charset=utf-8\r\nContent-Length: " +
  12.         response.Length.ToString() + "\r\nConnection: close\r\n\r\n";
  13.  
  14.     clientSocket.Send(Encoding.UTF8.GetBytes(header), header.Length, SocketFlags.None);
  15.     clientSocket.Send(Encoding.UTF8.GetBytes(response), response.Length, SocketFlags.None);
  16. }

 

So now when I send a browser request on my local Ethernet:

image

And sure enough, we can control the servo with my web browser

 

And then put together and using the browser in my phone:

Now if there was only a way to speed up the motor so I can launch my kid out of bed in the morning with more force….

The Eject-A-Bed: Part 1

Some people are born to greatness.  Some people have greatness thrust upon them.  Some people find greatness on Craig’s List.  A couple of months ago, I was killing some time searching for mechanical devices on Craig’s List when I ran into this:

image

So of course I picked it up and put it into the garage.  An interesting side note is that the seller got it at the UNC surplus warehouse.  Apparently, you can get really good deals on used medical and university equipment there.  I need to add it to my Christmas shopping rotation.

My daughter, Sonoma, and I first wanted to understand how the controller works.  We first thought that the controller used PWM so we hooked up our Oscilloscope to the two metal wires that travel from the controller to the bed’s motor.  We moved the controller up and down but no signal was being recorded.  Sonoma then noticed that the “wire” was a hollow tube.  Taking a wild guess, we blew down the pipe.  Sure enough, that made the bed move

image

So now we had to figure out how the controller moved air up and down the pipe.  We opened the controller and there is a small bellow that attaches to the pipe.  Press the controller on 1 side and air is forced down the pipe, press the controller on the other side and air is sucked up the pipe.

image

So we batted around ideas about how to push air up and down the pipe – ideas included using a small electric air compressor, some kind of mechanical pressure plate, etc…  We then decided to try and gluing a servo to the switch and controlling the movement that way.  However, we couldn’t figure how to attach the servo to the existing plastic switch.  So we decided to build our own switch – we used  my son’s erector set to create the harness for the bellows

WP_20130616_003

and we are now into the coding piece of it. 

Step one was to confirm that I hooked up my scope to the Netduino correctly.  I modified the Blinky code (Netduino equiv to ‘Hello World’ to add a pulse on one of the output ports:

  1. OutputPort port = new OutputPort(Pins.GPIO_PIN_D5,false);
  2. OutputPort led = new OutputPort(Pins.ONBOARD_LED,false);
  3. while (true)
  4. {
  5.     port.Write(true);
  6.     led.Write(true);
  7.     Thread.Sleep(250);
  8.     port.Write(false);
  9.     led.Write(false);
  10.     Thread.Sleep(250);
  11. }

 

Sure enough, I am seeing the pulse.

WP_20130619_001

I then wired PWM class to see if the output was what I expected:

  1. private static void ActivateServo()
  2. {
  3.     uint period = 20;
  4.     uint duration = 1;
  5.  
  6.     PWM servo = new PWM(PWMChannels.PWM_PIN_D5, period, duration, PWM.ScaleFactor.Milliseconds, false);
  7.     servo.Start();
  8.  
  9.     Thread.Sleep(Timeout.Infinite);
  10.  
  11. }

 

and sure enough I am getting a pulse every 20 milliseconds with a length of 1 millisecond.

WP_20130619_003

A word to the wise, do NOT use the Cpu.PWMChannel enum as it does not fire – use PWMChannels enum

So now it is a question of hooking this up to my servo.  It is a standard RC plan servo.  I have no idea about the period, pulse and/or duty cycle of this specific servo but it seems that there is a standard in the RC community that periods are 20 MS (as confirmed when I worked on the RC lawnmower) and the pulses are:

  • 1 MS = 180 to one direction
  • 1.5 MS = Top Dead Center
  • 2.0 MS 180 to the other direction

The problem is that the period is an uint – so I can’t set it to 1.5 MS.  I then learned about Duty Cycles where I can pass in a double and have the period adjust.  Basically:

  • 1 MS = Duty Cycle of 5%
  • 1.5 MS = Duty Cycle of 7.5%
  • 2.0 MS = Duty Cycle of 10%
    So to test this, I set up the PWM to have a 20 MS period, a 1 MS duration, and a Duty Cycle of 1.0. 
    1. uint period = 20;
    2. uint duration = 1;
    3.  
    4. PWM servo = new PWM(PWMChannels.PWM_PIN_D5, period, duration, PWM.ScaleFactor.Milliseconds, false);
    5. servo.Start();
    6.  
    7. while (true)
    8. {
    9.     servo.DutyCycle = 1;
    10. }

The problem is that this does not work.  If you use the period/duration constructor for the PWM class, you cannot set the DutyCycle property and get the desired effect.  What you have to do with that constructor is to use the duration property.  So to get the non-integral units, I needed to change the PWM.ScaleFactor to Microseconds and start thinking of things in terms of thousands.  Once I did that, I spent some time figuring out the TDC and the max left and right:

  1.  
  2. private static void TestServoFullRange()
  3. {
  4.     uint period = 20000;
  5.     uint duration = 1500;
  6.  
  7.     PWM servo = new PWM(PWMChannels.PWM_PIN_D5, period, duration, PWM.ScaleFactor.Microseconds, false);
  8.     servo.Start();
  9.     
  10.     Thread.Sleep(2000);
  11.     servo.Duration = 2500;
  12.     Thread.Sleep(3000);
  13.     servo.Duration = 500;
  14.     Thread.Sleep(3000);
  15.     servo.Duration = 1500;
  16. }

 

I then hooked up the servo to the bellow controller:

 

WP_20130619_004

 

and set up a program to activate the bellows for 2 seconds:

  1. private static void ActivateServoForBellows()
  2. {
  3.     uint period = 20000;
  4.     uint duration = 1500;
  5.  
  6.     PWM servo = new PWM(PWMChannels.PWM_PIN_D5, period, duration, PWM.ScaleFactor.Microseconds, false);
  7.     servo.Start();
  8.     Thread.Sleep(2000);
  9.     
  10.     InputPort button = new InputPort(Pins.ONBOARD_BTN, false, Port.ResistorMode.Disabled);
  11.  
  12.     Int32 numberOfPresses = 0;
  13.  
  14.     while (true)
  15.     {
  16.         if (button.Read())
  17.         {
  18.             if (numberOfPresses < 10)
  19.             {
  20.                 servo.Duration = 1250;
  21.                 Thread.Sleep(2000);
  22.                 servo.Duration = 1500;
  23.                 numberOfPresses++;
  24.             }
  25.         }
  26.     }
  27. }

and success.

F#: Different Syntaxes To Same End

So have 5 books on F# and that I working through.  Ironically, the best place to learn F# is not a book but to work through the tutorials in TryFSharp.  The best book to use after going through TryFSharp is Jon Skeets Real-World Functional Programming

image

I am in the middle of Chapter 2 when Jon uses the following example to show high-order functions:

  1. let numbers = [1..20]
  2. let IsOdd x = x % 2 = 1
  3. let Square x = x * x
  4.  
  5. List.filter IsOdd numbers
  6. List.map Square numbers

 

I thought, how many ways do I know how accomplish the same thing?  In FSharp, I know 3 ways.  Way #1 is what the code sample above does.  Another way is to pipe-forward the function calls:

  1. let numbers = [1..20]
  2. let IsOdd x = x % 2 = 1
  3. let Square x = x * x
  4.  
  5. numbers
  6.     |> List.filter IsOdd
  7.     |> List.map Square

 

And finally, I can can use anonymous functions:

  1. let numbers = [1..20]
  2. numbers
  3.     |>List.filter(fun x -> x % 2 = 1)
  4.     |>List.map(fun x -> x * x)

 

Being that I am coming to F# from C#, I prefer option #2.

WCF and Interfaces

I went back to my Road Alert project and I wanted to add modifiers to the geolocations to account for the seasonality of the alter types.  For example, there are 36% more crashes in May and 27% more crashes in November.

 image

To that end, I added some more tables to my database – a table for each of the seasonal factors.  I kept the database in third normal form:

image

and the updated the Entity Model on my service layer:

:image

I then need to update my service interface to account for the new data.  I noticed that all of the tables all had the same format: PK (Int32), FKs(Int32), TableValueId(Int32), and ModifierValue(Float).  The only variation is the TableValue – in the Month table it is 1-12, in the DayOfWeek it is 0-6, DayOfMonth is 1-31 and Hour is 0-23)

Channeling my Inner Uncle Bob, I created an interface for these data structures like so:

  1. public interface IAlertTypeModifier
  2. {
  3.     Int32 AlertTypeModifierId { get; set; }
  4.     Int32 AlertTypeId { get; set; }
  5.     Int32 ModifierId { get; set; }
  6.     Int32 ValueId { get; set; }
  7.     double Modifier { get; set; }
  8. }

 

I then updated the service interface like this:

  1. [ServiceContract(Namespace = "http://schemas.tff.com/2013/07/RoadAlert.Services&quot;)]
  2. public interface IRoadAlert
  3. {
  4.     [OperationContract]
  5.     Location GetLocation(Int32 locationId);
  6.  
  7.     [OperationContract]
  8.     List<Location> GetLocations();
  9.  
  10.     [OperationContract]
  11.     List<Location> GetLocationsForMultipleAlertTypes(List<Int32> alertTypeIds);
  12.  
  13.     [OperationContract]
  14.     AlertType GetAlertType(Int32 alertTypeId);
  15.  
  16.     [OperationContract]
  17.     List<AlertType> GetAlertTypes();
  18.  
  19.     [OperationContract]
  20.     IAlertTypeModifier GetAlertTypeModifier(Int32 alertTypeId, Int32 modifierId, Int32 valueId);
  21.     
  22.     [OperationContract]
  23.     List<IAlertTypeModifier> GetAlertTypeModifiers();
  24.  
  25.     [OperationContract]
  26.     List<IAlertTypeModifier> GetAlertTypeModifiersForAnAlertType(Int32 alertTypeId);
  27.  
  28.     [OperationContract]
  29.     List<IAlertTypeModifier> GetAlertTypeModifiersForAModifierId(Int32 modifierId);
  30.  
  31.     [OperationContract]
  32.     List<IAlertTypeModifier> GetAlertTypeModifiersForAnAlertTypeAndModifierId(Int32 alertTypeId, Int32 modifierId);
  33.  
  34. }

 

I then created the supporting internal methods to these public methods and ran them though my unit tests and the public methods ran green.  For example:

  1. [TestMethod]
  2. public void GetModifiers_ReturnExpectedValue()
  3. {
  4.     RoadAlert roadAlert = new RoadAlert();
  5.     List<IAlertTypeModifier> modifiers = roadAlert.GetAlertTypeModifiers();
  6.  
  7.     Int32 notExpected = 0;
  8.     Int32 actual = modifiers.Count;
  9.  
  10.     Assert.AreNotEqual(notExpected, actual);
  11. }

image

I then deployed the service to my web hosting provider and updated the service interface on my other tests.  Whn I did that, I ran into trouble:

 

  1. [TestMethod]
  2. public void GetModifiers_ReturnExpectedValue()
  3. {
  4.     RoadAlertClient client = new RoadAlertClient();
  5.     List<IAlertTypeModifier> modifiers = client.GetAlertTypeModifiers();
  6.  
  7.     Int32 notExpected = 0;
  8.     Int32 actual = modifiers.Count;
  9.  
  10.     Assert.AreNotEqual(notExpected, actual);
  11. }

 

With the exception:

Cannot implicitly convert type ‘System.Collections.Generic.List

And When I Googled on Bing, I found out that you can’t return interfaces because you can’t serialize interfaces.  Crap!  Looks like I need to concrete classes if I want to use WCF.

So now I have a choice:

1) Return each concrete type so I add 8 methods to the service interface (GetMonth, GetMonths, etc…)  Put these together on the client side

2) Return a more generic concrete class (equiv to an abstract class) and parse the results on the client

3) Use REST

Since I am already down the SOAP path on this project, I do not want to pivot to REST right now.  In order to offload processing from the client, I decided to clutter up my Service interface with more methods.  So I changed my interface like this:

  1. [ServiceContract(Namespace = "http://schemas.tff.com/2013/07/RoadAlert.Services&quot;)]
  2. public interface IRoadAlert
  3. {
  4.     [OperationContract]
  5.     Location GetLocation(Int32 locationId);
  6.  
  7.     [OperationContract]
  8.     List<Location> GetLocations();
  9.  
  10.     [OperationContract]
  11.     List<Location> GetLocationsForMultipleAlertTypes(List<Int32> alertTypeIds);
  12.  
  13.     [OperationContract]
  14.     AlertType GetAlertType(Int32 alertTypeId);
  15.  
  16.     [OperationContract]
  17.     List<AlertType> GetAlertTypes();
  18.  
  19.     [OperationContract]
  20.     AlertTypeDayOfMonthModifier GetAlertTypeDayOfMonthModifier(Int32 AlertTypeDayOfMonthModifierId);
  21.  
  22.     [OperationContract]
  23.     List<AlertTypeDayOfMonthModifier> GetAlertTypeDayOfMonthModifiers();
  24.  
  25.     [OperationContract]
  26.     AlertTypeDayOfWeekModifier GetAlertTypeDayOfWeekModifier(Int32 AlertTypeDayOfWeekModifierId);
  27.  
  28.     [OperationContract]
  29.     List<AlertTypeDayOfWeekModifier> GetAlertTypeDayOfWeekModifiers();
  30.  
  31.     [OperationContract]
  32.     AlertTypeHourModifier GetAlertTypeHourModifier(Int32 AlertTypeHourModifierId);
  33.  
  34.     [OperationContract]
  35.     List<AlertTypeHourModifier> GetAlertTypeHourModifiers();
  36.  
  37.     [OperationContract]
  38.     AlertTypeMonthModifier GetAlertTypeMonthModifier(Int32 AlertTypeMonthModifierId);
  39.  
  40.     [OperationContract]
  41.     List<AlertTypeMonthModifier> GetAlertTypeMonthModifiers();
  42. }

 

And because I don’t need to abstract the valueId, I renamed it to be more reflective of its intent.  For example:

AlertTypeDayOfWeekModifier.ValueId is now AlertTypeDayOfWeekModifier.DayOfWeekId

Also, I changed the PK to more intention revealing also:

AlertTypeDayOfMonthModifier.AlertTypeModifierId is now AlertTypeDayOfMonthModifier.AlertTypeDayOfMonthModifierId

I then ditched the interface and put the common fields into a base class:

  1. public abstract class AlertTypeModifier
  2. {
  3.     public Int32 AlertTypeId { get; set; }
  4.     public Int32 ModifierId { get; set; }
  5.     public double Modifier { get; set; }
  6.  
  7. }

 

And had each of the implementations like this:

  1. public class AlertTypeDayOfMonthModifier : AlertTypeModifier
  2. {
  3.     public Int32 AlertTypeDayOfMonthModifierId { get; set; }
  4.     public Int32 DayOfMonthId { get; set; }
  5. }

 

Which severs well enough.  Then it was just a question of updating the service.cs file with the implementation (an incredibly boring 15 minutes) and adding some tests.  I got green locally and then green via the web service.

image

So I guess the lesson learned is that you really can’t apply the Open/Closed principle using WCF Web Services.

Machine Learning For Hackers: Chapter 1, Part 2

I then wanted to show the data graphically.   I Added a ASP.NET Web Form project to my solution and added a Chart to the page.  That Chart points to an objectDataSource that consumes the UFO Library method:

  1. <asp:Chart ID="Chart1" runat="server" DataSourceID="ObjectDataSource1">
  2.     <series>
  3.         <asp:Series Name="Series1" XValueMember="Item4" YValueMembers="Item1">
  4.         </asp:Series>
  5.     </series>
  6.     <chartareas>
  7.         <asp:ChartArea Name="ChartArea1">
  8.         </asp:ChartArea>
  9.     </chartareas>
  10. </asp:Chart>
  11. <asp:ObjectDataSource ID="ObjectDataSource1" runat="server"
  12.     SelectMethod="GetDetailData"
  13.     TypeName="Tff.MachineLearningWithFSharp.Chapter01.UFOLibrary">
  14. </asp:ObjectDataSource>

 

When I ran it, I got this:

image

 

So that is cool that the Chart control can access the data and show ‘something’.  I then read this tutorial about how to show every state on the X Axis

image

Unfortunately, I was diving down too deep into the weeds of charting controls, which is really not where I want to be.  I then decided to build a function that aggregates the data

  1. member this.GetSummaryData() =
  2.     let subset =
  3.         this.GetDetailData()
  4.         |> Seq.map(fun (a,b,c,d,e,f,g) -> a,d)
  5.         |> Seq.map(fun (a,b) ->
  6.             a.Year,
  7.             b)
  8.  
  9.     let summary =
  10.         subset
  11.         |> Seq.groupBy fst
  12.         |> Seq.map (fun (a, b) -> (a, b
  13.             |> Seq.countBy snd))

Sure enough, when I look on my console app

 

image

I then decided to switch it so that the state would come up first and each year of UFO sightings would be shown (basically switching the Seq.Map)

  1. |> Seq.map(fun (a,b) ->
  2.     b,
  3.     a.Year)

 

And now:

image

So then I added another method that only returns a state’s aggregate data:

  1. member this.GetSummaryData(stateCode: string) =
  2.     let stateOnly =
  3.         this.GetSummaryData()
  4.         |> Seq.filter(fun (a,_) -> a = stateCode)
  5.     stateOnly

 

And I changed the ASP.NET UI to show that state:

  1. <div class="content-wrapper">
  2.     <asp:DropDownList ID="StatesDropList" runat="server" Width="169px" Height="55px">
  3.         <asp:ListItem Value="AZ"></asp:ListItem>
  4.         <asp:ListItem>MD</asp:ListItem>
  5.         <asp:ListItem>CA</asp:ListItem>
  6.         <asp:ListItem>NC</asp:ListItem>
  7.  
  8.     </asp:DropDownList>
  9.     <br />
  10.     <br />
  11.     <asp:Chart ID="Chart1" runat="server" DataSourceID="ObjectDataSource1" Width="586px">
  12.         <series>
  13.             <asp:Series Name="Series1" XValueMember="item1" YValueMembers="item2">
  14.             </asp:Series>
  15.         </series>
  16.         <chartareas>
  17.             <asp:ChartArea Name="ChartArea1">
  18.                 <AxisX IsLabelAutoFit="False">
  19.                     <LabelStyle Interval="Auto" IsStaggered="True" />
  20.                 </AxisX>
  21.             </asp:ChartArea>
  22.         </chartareas>
  23.     </asp:Chart>
  24.     <br />
  25.     <asp:ObjectDataSource ID="ObjectDataSource1" runat="server"
  26.         SelectMethod="GetSummaryData"
  27.         TypeName="Tff.MachineLearningWithFSharp.Chapter01.UFOLibrary">
  28.         <SelectParameters>
  29.             <asp:ControlParameter ControlID="StatesDropList" DefaultValue="NC" Name="stateCode" PropertyName="SelectedValue" Type="String" />
  30.         </SelectParameters>
  31.     </asp:ObjectDataSource>
  32. </div>

 

The problem is this:

image

I need to flatten the subTuple so that only native types are sent to the ODS.

Future Of the .NET Stack from a Developers Point of View

So I have been to two presentations about the future of the .NET stack: Rocky’s Llocka’s presentation to the TRINUG’s main meeting and Billy Hollis’s sales pitch presentation at Tech Ed in NOLA last week.  I have also read some of the articles about the demise of the PC in Barron’s and in Wired.

A couple of themes have been to be emerging that I have framed my decisions about technology:

1) Moore’s law is dead – PC sales are slowing not because of other device sales but because the hardware capabilities of the average PC does not need to change.

2) The PC doesn’t need to change because the apps that once drove hardware upgrades (games, etc…) are not being written.  Instead the software development is focusing on writing lighter-weight clients.  I wonder how many copies of Windows Doom sold?  I still am waiting for the game that takes advantage of my 3 monitor (Front, Side, Side) setup – could you imagine Starcraft across 3 27” monitors?

3) As a company, I own my device.  I own the operating system on the device.  I write my own software to run on that device.  I can’t side-load it?  WTF? MSFT is about enterprise computing and needs to stop this nonsense ASAP.  Stop trying to be the third horse in a two-horse race.  And in a related idea:

4) My 2 favorite quotes from Rocky and Billy about the iPAD:

“Window’s biggest competitor is not the iPAD.  It is the browser” (RL)

“My grandmother loves her iPad.  I am sure she does.  Your 2 year old likes her speak and spell too.  Neither of them have any work to do” (BH)

4B) The tablet is not made for the business – it is made for consumers.  Business leaders that use iPads probably manage people – it is very hard to do any other kind of work on it.  Apple could pivot and drive towards business (they have the $$ and smarts to do it) but it would be a very long and hard road to follow.  And they probably wouldn’t get 50% margin selling to large companies.

5) Rocky thinks BYOD is nonsense and will not stick.  Billy and the rest of the popular press thinks it is a very real change.  I don’t know.  On one hand, I agree it is impossible to do any kind of analytical work or manage multiple systems and process with that kind of screen real estate, keyboard, and processing power.  However, if you only manage people so all you need is email, Lync and don’t need to print anything, then you can get away with using a tablet as your primary machine.  I wonder if managers/executives that only use a tablet are less recession-proof than managers/executives that have a more analytical/process skill set?  Perhaps in the next recession, the BYOD will mean “Bullseye For Downsizing.”   In any event, I am skeptical that BYOD will stick – but it still has some time to become more popular before the coolness wears off.

6)  Business software developers can still get away with writing crappy UIs for the business.  They don’t need to out form factor consumer-facing apps.  However, they need to have UIs that are device-intelligent to accomplish the business task at hand.

7) Every speaker/article is making a big deal about how our industry is going a seismic, once every generation change.  If you have been doing this for a bit, it is just par for the course.

8) My own non-empirical observation is that we are seeing the high-water mark of BSCS students.  Kids that I have watched grow up in my neighborhood are going to college to get a BSCS.  Kids of business associates are getting their BSCS. Kids that don’t like writing computer code are getting a BSCS.

I guess every article that shows the salary of a BA major versus a BS major pushes more kids into our field.  When I ask them why they want the BSCS, it is pretty much the same “I want the money.”  I feel bad because on a per hour, the average software dev starting out is lower than that clerk at Starbucks that has a BA in History (that is the typical story I hear).  Not only that, that clerk might have other, larger dreams and enjoys what s/he does.  If the BSCS kid is in our profession for the money, they are going to be disappointed – very quickly. 

On the flip side, more junior developers means more code the be refactored later, which is a boon to consultants and people who have been in the business for awhile.