I've gone a little strange, these days...

Snipehunter's picture
I was inspired recently, by something a friend was doing. It was an art project; one that really grabbed me because, well, in its way it was a piece of lore. I mean it seemed to tell a story and it seemed like a glimpse into at a larger, deeper world. I'm sure some art critic somewhere would tell me it's not really art, blah blah blah, but it inspired me to take on something I've never tried before and that's pretty rare. So what did it inspire me to do? It inspired me to learn how to build electronics projects -- to [i]tinker[/i] out here, in the physical world instead of in the game worlds I sculpt for a living. With a few comments from said friend, I was off and searching the internet for information about something called an Arduino. An "open-source prototyping platform". There's this whole world out there of people who tinker; a world populated by everyone from the stodgy and moribund 70s era engineer who tinkers to remember the glory days, down to the punk kid circuit-bending his sister's talking Barbie to make it sound satanic the next time she goes to play. So where am I on the spectrum? Probably somewhere closer to the kid; I don't know anything at all about what I'm doing. I mean, if you've been over to the [url=http://infinityproject.dopass.com]Infinity Project[/url] then you know I've been teaching myself C#, but this is something else. Beyond some basic science I learned a couple decades back, I don't know much about building circuits and I've certainly never coded anything that interacts with said circuitry so directly, before. That didn't matter I was going to give it a try anyway. As it turned out the Arduino was really easy to work with. The language it uses, Processing, is like C and very easy to pick up. However, I was quickly growing frustrated by some of its limitations. Since I don't much about circuit building, I'm doing everything sort of intuitively. So, when something doesn't work I usually don't even know where to start looking. Is it the circuit? Is it the hardware? Is it the software? How do you even begin to tell? In a C# app I'd just fire up the debugger and go through the code line by line. If the code was working as I expected, I'd know it was either the hardware or the circuit and then I could start to poke around there. You can't really do that with the Arduino, at least on the code side. Still, I poked and prodded my way to progress anyway and I was having a blast. Then my friend pointed me to the [url=http://www.netduino.com]Netduino[/url]. Here was an open source prototyping platform, like the Arduino, that you could program with C#. Not only that, but you could debug the code line by line, as it ran on the hardware. Before I even really realized I was doing it, I'd clicked "buy now" and the Netduino was on its way. It arrived a couple of days ago and today was the first day I could really devote a lot of time to it. Man, did that turn into a time-bending singularity of geek-fun! No, seriously. It's 3am and I'm still up. I finished working on my test project hours ago, but I wanted to share it here and I couldn't do that if I didn't do a much needed overhaul, update and patch pass on the server. So I did all of that, picking up some new SQL knowledge on the way, and I'm still up to write this, just because of how much fun I'm having. Let me show you what I did today: It's really not much, but consider that it was made out of a Netduino, breadboard, some LEDs, a few resistors, a pushbutton, some wires and some C# code. In other words, I made that! In a couple of hours of tinkering. I'm a game designer, not an engineer, but this whole open source prototyping movement, and the Netduino specifically have made it easy for someone like me to experiment with this stuff. Suddenly, when I see something that behaves as if it's stupid; like my sprinkler controller, I start to think, "Hmm, what if I made it smarter?" and I start to consider things like wiring my lawn into the weather bureau's reports via the Netduino and some sensors, so that it only ever gets water when it actually needs it. [i]I can [b]do[/b] that now![/i] That's not even a fantasy; it's a bullet point on my to-do list now, thanks to the Netduino. Pretty cool, right? - [color=green]Snipehunter[/color] P.S. I'm also learning all sorts of things about C# in general. I'm about to convert the code I wrote for this project into a multi-threaded version, but here's the source code so far, if you're interested. This deploys to the Netduino and works, it's just got stuff in it like delegates that don't really need to be there:
using System;
using System.Threading;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
using SecretLabs.NETMF.Hardware;
using SecretLabs.NETMF.Hardware.Netduino;

namespace TalkSessionTimer
{
    public class Program
    {
        // These are the timer related constants. They're calculated but they don't change after that.
        const double talkSessionMax = 20000;
        const double state2Time = (talkSessionMax * 40) / 100;
        const double state3Time = (talkSessionMax * 75) / 100;
        const double state4Time = talkSessionMax;
        const double state5Time = (talkSessionMax * 120) / 100;
        const double flashOnTime = (talkSessionMax * 140) / 100;

        static public TimeSpan pressDuration = new TimeSpan();
        static public TimeSpan xPressDuration = new TimeSpan();

        public const int buttonDeadTime = 1500;

        // OK on to the variables
        // TalkSession1State: 0 == idle, 1 == someone is talkSessionting (green on), 2 == yellow mode (yellow on), 3 == someone is camping (red on), 
        // TalkSession1State: 4 == someone is seriously camping (red flash, blue on), 5 == someone needs correction (cop lights)
        static private byte talkSession1State = 0;
        static public byte TalkSession1State
        {
            get { return talkSession1State; }
            set { talkSession1State = value; }
        }
        static private byte talkSession2State = 0;
        static public byte TalkSession2State
        {
            get { return talkSession2State; }
            set { talkSession2State = value; }
        }

        static byte lastTalkSession1State = 0;
        static byte lastTalkSession2State = 0;

        // these control whether the LED is on or off in the flashing states. (off == false)
        static private bool redFlash = false;
        static public bool RedLED
        {
            get { return redFlash; }
            set { redFlash = value; }
        }

        static private bool blueFlash = false;
        static public bool BlueLED
        {
            get { return blueFlash; }
            set { blueFlash = value; }
        }

        static private bool exBlueLED = false;
        static public bool XBlueLED
        {
            get { return exBlueLED; }
            set { exBlueLED = value; }
        }

        static private bool exRedLED = false;
        static public bool XRedLED
        {
            get { return exRedLED; }
            set { exRedLED = value; }
        }

        // These track whether each button is currently being pressed, or not.
        static private bool pressing = false;
        static public bool Pressing
        {
            get { return pressing; }
            set { pressing = value; }
        }

        static bool exPressing = false;
        static public bool XPressing
        {
            get { return exPressing; }
            set { exPressing = value; }
        }

        // These track the times (in ms) related to each Talk Session (start time and duration)
        static private DateTime talkSession1start = new DateTime();
        static public DateTime TalkSession1Start
        {
            get { return talkSession1start; }
            set { talkSession1start = value; }
        }

        static private DateTime talkSession2start = new DateTime();
        static public DateTime TalkSession2Start
        {
            get { return talkSession2start; }
            set { talkSession2start = value; }
        }

        static private TimeSpan talkSession1Dur = new TimeSpan();
        static public TimeSpan TalkSession1Duration
        {
            get { return talkSession1Dur; }
            set { talkSession1Dur = value; }
        }

        static private TimeSpan talkSession2Dur = new TimeSpan();
        static public TimeSpan TalkSession2Duration
        {
            get { return talkSession2Dur; }
            set { talkSession2Dur = value; }
        }

        // These track the button press duration
        static private DateTime buttonStart = new DateTime();
        static public DateTime ButtonPressStart
        {
            get { return buttonStart; }
            set { buttonStart = value; }
        }

        static private DateTime exButtonStart = new DateTime();
        static public DateTime XButtonPressStart
        {
            get { return exButtonStart; }
            set { exButtonStart = value; }
        }

        // Here we declare the output ports for the LED banks
        // Each bank is 4 LEDs and other than the switches, the Netduino doesn't need to do anything else.
        // So, for convenience's sake, we'll just allocate a pin to each LED
        // these are the bank 1 LEDs
        static private OutputPort bluePin = new OutputPort(Pins.GPIO_PIN_D13, false);
        static public OutputPort BluePin
        {
            get { return bluePin; }
            set { bluePin = value; }
        }

        static private OutputPort redPin = new OutputPort(Pins.GPIO_PIN_D12, false);
        static public OutputPort RedPin
        {
            get { return redPin; }
            set { redPin = value; }
        }

        static private OutputPort yellowPin = new OutputPort(Pins.GPIO_PIN_D11, false);
        static public OutputPort YellowPin
        {
            get { return yellowPin; }
            set { yellowPin = value; }
        }

        static private OutputPort greenPin = new OutputPort(Pins.GPIO_PIN_D10, false);
        static public OutputPort GreenPin
        {
            get { return greenPin; }
            set { greenPin = value; }
        }

        // This is the port for the switch that's not on the Netduino. It controls bank 1 (talkSession1)
        static private InterruptPort button = new InterruptPort(Pins.GPIO_PIN_D0, false, Port.ResistorMode.Disabled, Port.InterruptMode.InterruptEdgeBoth);
        static public InterruptPort TalkSessionButton
        {
            get { return button; }
            set { button = value; }
        }

        // Here we're declaring the pins for our second LED array (talkSession2)
        static OutputPort exGreenPin = new OutputPort(Pins.GPIO_PIN_D6, false);
        static OutputPort exYellowPin = new OutputPort(Pins.GPIO_PIN_D7, false);
        static OutputPort exRedPin = new OutputPort(Pins.GPIO_PIN_D8, false);
        static OutputPort exBluePin = new OutputPort(Pins.GPIO_PIN_D9, false);

        // This is the port for the switch that's on the Netduino. It controls bank 2 (talkSession2)
        static private InterruptPort eXButton = new InterruptPort(Pins.ONBOARD_SW1, false, Port.ResistorMode.Disabled, Port.InterruptMode.InterruptEdgeBoth);
                        
        public static void Main()
        {    
            // first things first, let's set up the interrupts for the buttons.
            TalkSessionButton.OnInterrupt += new NativeEventHandler(TalkSessionButton_OnInterrupt);
            eXButton.OnInterrupt += new NativeEventHandler(eXButton_OnInterrupt);
            
            while (true)
            {
                
                // This is the main loop.
                // It runs, roughly, 4 times a second
                // First we check: Is a talkSession currently ongoing?
                if (TalkSession1State != 0)
                {
                    // First we figure out how much time has passed
                    TalkSession1Duration = DateTime.Now - TalkSession1Start;

                    // Now we manage the State of talkSession1
                    manageState(TalkSession1State, TalkSession1Duration, 0);
                    // And now we manage its LEDs
                    manageLEDs(TalkSession1State, 0);
                }

                if (TalkSession2State != 0)
                {
                    TalkSession2Duration = (DateTime.Now - TalkSession2Start);

                    // Now we manage the State of talkSession2
                    manageState(TalkSession2State, TalkSession2Duration, 1);
                    // And now we manage its LEDs
                    manageLEDs(TalkSession2State, 1);
                }


                // Next catch the case of transiting into state 0, which the If's above will miss.
                if ((TalkSession1State == 0) && (TalkSession1State != lastTalkSession1State))
                {
                    manageLEDs(TalkSession1State, 0);
                }

                if ((TalkSession2State == 0) && (TalkSession2State != lastTalkSession2State))
                {
                    manageLEDs(TalkSession2State, 1);
                }

                // Now we wait for a quarter second
                // Once I've threaded the manage LEDs routine, this will go there, instead of here.
                // I'm delaying for 250ms(ish) so the blinks look nice.
                Thread.Sleep(250);
            }

        }

        /// <summary>
        /// detects when the button controlling talkSession2 is pressed or released
        /// </summary>
        /// <param name="data1">I have no idea what this is for</param>
        /// <param name="data2">The state of the button (0 is pressed, 1 is open)</param>
        /// <param name="time">The time the button was pressed or released</param>
        static void eXButton_OnInterrupt(uint data1, uint data2, DateTime time)
        {
            // This is the interrupt code for the button that controls the second bank of LEDs (talkSession2)
            eXButton.DisableInterrupt();
            // First let's see if the button is up, or down.
            if ((data2 == 0) && (!exPressing)) // 0 == switch is pressed
            {
                // TalkSession2State: 0 == idle, 1 == someone is talkSessionting (green on), 2 == yellow mode (yellow on), 3 == someone is camping (red on), 
                // TalkSession2State: 4 == someone is seriously camping (red flash, blue on), 5 == someone needs correction (cop lights)

                // OK, someone has pressed the button. Now we need to figure out what we're doing about it.
                // The default case is "start a talkSession"
                switch (TalkSession2State)
                {
                    // For now there is no extra behavior beyond "reset" so let's just set that up as the default case.
                    default:
                        // We were idle, so now we're going to TalkSession2State 1.
                        TalkSession2State = 1;
                        exPressing = true;
                        TalkSession2Start = DateTime.Now;
                        XButtonPressStart = time;
                        exPressing = true;
                        break;
                }
            }
            else
            {
                // The button was let go. (there are only two states, so if it's not one, it's another)
                // Now let's see if it was let go after the required delay
                xPressDuration = (DateTime.Now - XButtonPressStart);
                double xPressDurationMS = convertToMS(xPressDuration);
                if (xPressDurationMS >= buttonDeadTime)
                {
                    // OK, it's been long enough, time to turn off
                    TalkSession2State = 0;
                    TalkSession2Start = new DateTime();
                    TalkSession2Duration = new TimeSpan();
                }

                exPressing = false;
            }

            Thread.Sleep(25);
            eXButton.EnableInterrupt();
        }

        /// <summary>
        /// reacts to the button controlling talkSession1 being pressed or released.
        /// </summary>
        /// <param name="data1">dunno what this does</param>
        /// <param name="data2">The button's State</param>
        /// <param name="time">time of the press</param>
        static void TalkSessionButton_OnInterrupt(uint data1, uint data2, DateTime time)
        {
            TalkSessionButton.DisableInterrupt();
            // First let's see if the button is up, or down.
            // if ((data2 == 0) && (!pressing)) // 0 == switch is pressed
            if ((data2 == 1) && (!pressing)) // 1 == switch is pressed
            {
                // TalkSession1State: 0 == idle, 1 == someone is talkSessionting (green on), 2 == yellow mode (yellow on), 3 == someone is camping (red on), 
                // TalkSession1State: 4 == someone is seriously camping (red flash, blue on), 5 == someone needs correction (cop lights)

                // OK, someone has pressed the button. Now we need to figure out what we're doing about it.
                // The default case is "start a talkSession"
                switch (TalkSession1State)
                {
                    // For now there is no extra behavior beyond "reset" so let's just set that up as the default case.
                    default:
                        // We were idle, so now we're going to talkSession1State 1.
                        TalkSession1State = 1;
                        pressing = true;
                        TalkSession1Start = DateTime.Now;
                        ButtonPressStart = time;
                        pressing = true;
                        break;
                }
            }
            else
            {
                // The button was let go. (there are only two states, so if it's not one, it's another)
                // Now let's see if it was let go after the required delay
                pressDuration = (DateTime.Now - ButtonPressStart);
                double pressDurationMS = convertToMS(pressDuration);
                if (pressDurationMS >= buttonDeadTime)
                {
                    // OK, it's been long enough, time to turn off
                    talkSession1State = 0;
                    TalkSession1Start = new DateTime();
                    TalkSession1Duration = new TimeSpan();
                }

                pressing = false;
            }

            Thread.Sleep(25);
            TalkSessionButton.EnableInterrupt();
        }

        /// <summary>
        /// Manipulates indicated LED bank based on the supplied state
        /// </summary>
        /// <param name="state">The current state</param>
        /// <param name="talkSession">which "talkSession" (LED bank) are we controlling, 0 or 1?</param>
        static void manageLEDs(int state, byte talkSession)
        {
            // Originally I wrote this specific to a single bank of 4 LEDs.
            // Later I added a second bank of LEDs, so I tried a more abstracted approach.
            // To do that I set up proxies for the LEDs and the mechanism that controls them.
            // This whole thing is probably wasteful, but it works. :)
            OutputPort tempGreen = GreenPin;
            OutputPort tempYellow = YellowPin;
            OutputPort tempRed = RedPin;
            OutputPort tempBlue = BluePin;
            bool redBlink = false;
            bool blueBlink = false;

            // Now that the proxies are made, here I assign them to the appropriate LEDs and values
            // based on which bank is being used.
            if (talkSession == 0)
            {
                redBlink = RedLED;
                blueBlink = BlueLED;
            }
            else if (talkSession == 1) 
            {
                tempGreen = exGreenPin;
                tempYellow = exYellowPin;
                tempRed = exRedPin;
                tempBlue = exBluePin;
                redBlink = exRedLED;
                blueBlink = exBlueLED;
            }


            // Now that my proxies point to the right LEDs, it's time to manipulate the LEDs based on State.
            // State: 0 == idle, 1 == someone is talkSessionting (green on), 2 == yellow mode (yellow on), 3 == someone is camping (red on), 
            // State: 4 == someone is seriously camping (red flash, blue on), 5 == someone needs correction (cop lights)
            switch (state)
            {
                case 0:
                    // time to turn all the LEDs off.
                    tempGreen.Write(false);
                    tempYellow.Write(false);
                    tempRed.Write(false);
                    tempBlue.Write(false);
                    break;
                case 1:
                    // Starting all off but green
                    tempBlue.Write(false);
                    tempRed.Write(false);
                    tempYellow.Write(false);
                    tempGreen.Write(true);
                    break;
                case 2:
                    // All off but yellow
                    tempBlue.Write(false);
                    tempRed.Write(false);
                    tempYellow.Write(true);
                    tempGreen.Write(false);
                    break;
                case 3:
                    // All off but red
                    tempBlue.Write(false);
                    tempRed.Write(true);
                    tempYellow.Write(false);
                    tempGreen.Write(false);
                    break;
                case 4:
                    // time to blink the red led
                    // which means we need to manage flashing for the red light.
                    switch (redBlink)
                    {
                        case true:
                            tempRed.Write(false);
                            redBlink = false;
                            break;
                        default:
                            tempRed.Write(true);
                            redBlink = true;
                            break;
                    }
                    if (talkSession == 0)
                    {
                        RedLED = redBlink;
                    }
                    else if (talkSession == 1)
                    {
                        exRedLED = redBlink;
                    }
                    break;
                case 5:
                    // Oh no! Here come the po po!
                    // which means we're doing cop lights. We'll use
                    // the RED LED as the control LED.
                    switch (redBlink)
                    {
                        case true:
                            tempRed.Write(false);
                            redBlink = false;
                            tempBlue.Write(true);
                            blueBlink = true;
                            break;
                        default:
                            tempRed.Write(true);
                            redBlink = true;
                            tempBlue.Write(false);
                            blueBlink = false;
                            break;
                    }
                    if (talkSession == 0)
                    {
                        RedLED = redBlink;
                        BlueLED = blueBlink;
                    }
                    else if (talkSession == 1)
                    {
                        exRedLED = redBlink;
                        exBlueLED = blueBlink;
                    }
                    break;
                default:
                    break;
            }
        }

        /// <summary>
        /// Takes in how much time has passed since the Talk Session started and adjusts the session's state accordingly
        /// </summary>
        /// <param name="currentState">The current state of the Talk Session to manage</param>
        /// <param name="talkSessionTime">The duration of the Talk Session, so far</param>
        /// <param name="talkSession">Which Talk Session we're modifying state for</param>
        static void manageState(byte currentState, TimeSpan talkSessionTimeSpan, byte talkSession)
        {

            // currentState: 0 == idle, 1 == someone is talkSessionting (green on), 2 == yellow mode (yellow on), 3 == someone is camping (red on), 
            // currentState: 4 == someone is seriously camping (red flash, blue on), 5 == someone needs correction (cop lights)

            if (currentState != 0) // If currentState is 0 we're not caring about talkSession time.
            {
                double talkSessionTime = convertToMS(talkSessionTimeSpan);

                if (talkSessionTime < state2Time)
                {
                    currentState = 1;
                }
                else if ((talkSessionTime >= state2Time) && (talkSessionTime < state3Time))
                {
                    currentState = 2;
                }
                else if ((talkSessionTime >= state3Time) && (talkSessionTime < state4Time))
                {
                    currentState = 3;
                }
                else if ((talkSessionTime >= state4Time) && (talkSessionTime < state5Time))
                {
                    currentState = 4;
                }
                else if (talkSessionTime >= state5Time)
                {
                    // There's really nothing left, so they must be moving into currentState 5.
                    currentState = 5;
                }

                switch (talkSession)
                {
                    case 0:
                        TalkSession1State = currentState;
                        break;
                    case 1:
                        TalkSession2State = currentState;
                        break;
                    default:
                        break;
                }
            }

            // here we set the last state, so we can efficiently manage LEDs later
            if (talkSession == 0)
            {
                lastTalkSession1State = currentState;
            }
            else if (talkSession == 1)
            {
                lastTalkSession2State = currentState;
            }
        }

        /// <summary>
        /// Converts a timespan into its total milliseconds and returns that value as a double int
        /// </summary>
        /// <param name="sourceSpan">The timespan to be converted</param>
        /// <returns>timespan in milliseconds (as a double)</returns>
        static double convertToMS(TimeSpan sourceSpan)
        {
            double convertedMS = 0;
            if (!sourceSpan.Equals(null))
            {
                convertedMS = ((((((((sourceSpan.Days * 24) + sourceSpan.Hours) * 60) + sourceSpan.Minutes) * 60) + sourceSpan.Seconds) * 1000)
                    + sourceSpan.Milliseconds);
            }

            return convertedMS;
        }
    }
}
Technorati Tags:Technorati Tags: