| Hardware components | ||||||
|  | 
 | × | 1 | |||
| 
 | × | 1 | ||||
| 
 | × | 1 | ||||
| 
 | × | 1 | ||||
|  | 
 | × | 2 | |||
|  | 
 | × | 25 | |||
| 
 | × | 1 | ||||
| Software apps and online services | ||||||
|  | 
 | |||||
This project is an intelligent home thermostat that tries to improve on products like the nest learning thermostat. Those products require you to keep setting them manually and they eventually figure out your pattern. If your needs or schedules change, they have to gradually guess a new pattern as you manually fix them each day. Eventually this could grow into an entire smart home solution.
It's name is Halcyon, because the initial idea was to harness the overwhelming power of Cortana, who already knows all about my likes and schedule, to control things beyond my phone and laptop. Halcyon was the first class of ship she was the artificial intelligence for, so what better name for the system that can literally be her home too.
This thermostat is integrated into your digital calendar. You can configure a handful of simple settings (such as regular work schedule which may not appear in your calendar) and desired temperatures. Then the Halcyon system will intelligently read your calendar to determine when you are going to be home and adjust accordingly. You can have multiple people in the house all feeding calendar data to Halcyon and it will intelligently make choices based on what's going on in the real world.
Have a party scheduled? Halcyon will see in your calendar that 20 people are coming over and automatically drop a few degrees to keep things comfortable. Drinks after work this evening? Halcyon knows you aren't coming home right away and doesn't blast the AC until it needs to. Stay home from work today? Just tap the smartphone app to override Halcyon for the day and keep yourself just right. Doing this will not mess up Halcyon's decisions or start it trying to guess a new pattern. Sometimes, you just need to be in control.
The smartphone companion app is not yet made so all settings are currently hardcoded to their variables. Eventually this can be fully integrated with Cortana and your live calendar feed, allowing you to control it from your smartphone, website, or your Xbox One.
Also, being very novice (this is my first ever project like this), I don't know how to directly link to the calendar on my Microsoft Account, so I've shared the calendar XML for now via the link option. I'm still tweaking the code to parse that and it's not 100% ready for use yet. So look at this as a proof-of-concept and not something that's release ready.
I have tested reading temperature values and triggering the relays already, and the logic is pretty straight forward. However, in my latest full test tonight the relays weren't triggering correctly. I know they're getting triggered because the onboard LEDs light - but the relays are literally just not switching. So we're very close but I have to turn this in by tonight so must draw a line in the sand.
Fear not though, for I will keep trying to finish this in the coming weeks - I just won't win anything from Microsoft or Hacster.io for it =(
Pictures below are my final test before it's due. Still not working completely but VERY close.
Prototype Hardware Configuration

Relay Module

Prototype Hardware Configuration 2

HalcyonHardware.vb
VBScriptImports Windows.Devices.Gpio
Imports Windows.Devices.Spi
Imports Windows.Devices.Enumeration
'This module is for any code that directly interacts with hardware
Module HalcyonHardware
    Private Const RELAY_PIN_AC_NUM As Integer = 26 'Use GPIO number, not literal RPi pin number
    Private Const RELAY_PIN_HEAT_NUM As Integer = 13 'Use GPIO number, not literal RPi pin number
    Private RELAY_PIN_AC 'The pin object that we will use to send commands
    Private RELAY_PIN_HEAT 'The pin object that we will use to send commands
    Private SpiDisplay As SpiDevice
    Private readBuffer As Byte() = New Byte(2) {} 'this Is defined to hold the output data from MCP3008
    Private writeBuffer As Byte() = New Byte(2) {&H1, &H80, &H0} 'It Is SPI port serial input pin, And Is used To load channel configuration data into the device
    Public Sub InitializeHardware()
        InitializeRelayPins() 'This gets our GPIO pins ready to trigger the relays
        InitializeSpiDisplay() 'This gets our SPI ready to read data from the MCP3008
    End Sub
    Public Sub ToggleRelays()
        'This will turn devices plugged into the relays on or off
        'low = off; high = on
        If ac_ON Then RELAY_PIN_AC.write(GpioPinValue.High) Else RELAY_PIN_AC.write(GpioPinValue.Low)
        If heat_ON Then RELAY_PIN_HEAT.write(GpioPinValue.High) Else RELAY_PIN_HEAT.write(GpioPinValue.Low)
    End Sub
    Private Function ByteToInt(data As Byte()) As Integer
        'Comments assume readBuffer values of {00000000 , 00000010, 00110001} (0,2,49)
        'imagine all three separate bytes of readbuffer just need to be combined into one long "string" to equal the REAL binary value. Since we know the MCP3008 outputs 10 bits,
        '   we can safely ignore the first byte entirely as it should always be all zeros
        'So 10 bits = the entire 3rd byte (8 bits) and the least two positions of the second byte. First byte is unused.
        Dim result As Integer = data(1) And &H3 'this combines the second byte with hexadecimal 3 (3 in decimal, 0011 in binary) which will result in major six positions returning zero and minor two positions keeping value
        result <<= 8 'This shifts those bits to the left by 8 places leaving an empty byte of zeros (00000010 -> 00000010 00000000)
        result += data(2) 'This fills that empty byte of zeros with the values from the third byte in the readBuffer, generating the stitched back together binary number that has 10 bits (plus an additional 4 major zero bits).
        '(00000010 00000000 -> 00000010 00110001 = 561 in decimal)
        Return result
    End Function
    Public Sub GetTemp()
        Dim rawInt As Integer = 0
        SpiDisplay.TransferFullDuplex(writeBuffer, readBuffer) 'writeBuffer programs the MCP3008 to return channel 0. That 10 bit data gets encoded into the readBuffer in the second two bytes
        rawInt = ByteToInt(readBuffer) '10 bits from MCP3008 range 0 - 1023
        TempAmbient_Celsius = Math.Round((((rawInt / 1024) * power_mVolts) - 500) / 10, precision)
    End Sub
    Private Sub InitializeRelayPins()
        'This initializes the GPIO pins which will be used to activate the relays
        Dim gpio = GpioController.GetDefault()
        If gpio Is Nothing Then
            Throw New Exception("GPIO not found")
        End If
        RELAY_PIN_AC = gpio.OpenPin(RELAY_PIN_AC_NUM)
        If RELAY_PIN_AC Is Nothing Then
            Throw New Exception("AC Relay Pin Error. GPIO=" & RELAY_PIN_AC_NUM)
        End If
        RELAY_PIN_AC.Write(GpioPinValue.Low) 'default to low
        RELAY_PIN_AC.SetDriveMode(GpioPinDriveMode.Output)
        RELAY_PIN_HEAT = gpio.OpenPin(RELAY_PIN_HEAT_NUM)
        If RELAY_PIN_HEAT Is Nothing Then
            Throw New Exception("Heat Relay Pin Error. GPIO=" & RELAY_PIN_HEAT_NUM)
        End If
        RELAY_PIN_HEAT.Write(GpioPinValue.Low) 'default to low = off
        RELAY_PIN_HEAT.SetDriveMode(GpioPinDriveMode.Output)
    End Sub
    Private Async Sub InitializeSpiDisplay()
        'This initializes the SPI interface used to communicate with MCP3008
        Const SPI_CONTROLLER_NAME As String = "SPI0" 'For Raspberry Pi 2, use "SPI0"
        Const SPI_CHIP_SELECT_LINE As Int16 = 0 'Line 0 maps To physical pin number 24 On the Rpi2
        Try
            Dim settings = New SpiConnectionSettings(SPI_CHIP_SELECT_LINE)
            settings.ClockFrequency = 500000 'this should match your SPI Bus speed
            settings.Mode = SpiMode.Mode0
            Dim spiAqs As String = SpiDevice.GetDeviceSelector(SPI_CONTROLLER_NAME)
            Dim deviceInfo = Await DeviceInformation.FindAllAsync(spiAqs)
            SpiDisplay = Await SpiDevice.FromIdAsync(deviceInfo(0).Id, settings)
        Catch ex As Exception
            Throw New Exception("SPIO Error")
        End Try
    End Sub
End Module
HalcyonSettings.vb
VBScript'This module is for storing all the shared variables used in the program
'All settings are hardcoded and volatile for now. Later they will be configurable from your phone or web app
Module HalcyonSettings
    Public UpdateTemp_Seconds As Integer = 1 'frequency of ambient temp updates
    Public UpdateCal_Minutes As Integer = 60 'frequency of calendar sync updates
    Public HomeIndicator As String() = {"26H", "Piscassic", "Gary"} 'if event location CONTAINS any of these strings, it's home
    Public Const power_mVolts As Double = 5000 '3300 or 5000 depending on power supply to TMP36 and MCP3008
    Public ac_ON As Boolean = False
    Public heat_ON As Boolean = False
    Public AllCalendars As List(Of HalcyonCalendar) 'This is a list of all calendars that should be considered, for the prototype it is just my own
    Public CurrentEvent As HalcyonCalendarEvent 'This is the current HalcyonCalendarEvent that the temp setting is based on
    Public Const precision As Integer = 2 'number of decimal places to always round the temp to
    Private ActualTolerance_Celsius As Double = 1 'degrees Celsius of tolerance
    Public Property TempTolerance_Celsius As Double 'interfaces with ActualTolerance_Celsius to ensure nothing below 1 or above 10
        Get
            Return ActualTolerance_Celsius
        End Get
        Set(value As Double)
            If value < 1 Then value = 1
            If value > 10 Then value = 10
            ActualTolerance_Celsius = value
        End Set
    End Property
    Public TempAmbient_Celsius As Double = 20 'this variable will hold the latest ambient temperature reading
    Private ActualSet_Celsius As Double = 20 'degrees celsius of currently set temperature
    Public Property TempSet_Celsius As Double 'interfaces with ActualSet_Celsius to enforce safety min/max
        Get
            Return ActualSet_Celsius
        End Get
        Set(value As Double)
            If value < 1 Then value = 1 '1=33F
            If value > 37 Then value = 37 '37=100F
            ActualSet_Celsius = value
        End Set
    End Property
    Public CalendarLinks As String() = {
       "https://sharing.calendar.live.com/calendar/private/82856ffe-1447-4134-9164-2690d5cee38e/7661b193-8977-40ce-980f-1e20ff46d8ae/cid-60e973077a985b49/calendar.xml",
       "https://sharing.calendar.live.com/calendar/private/eca6cd71-ae7c-4146-b7c1-a8f9d03de0d2/0ff376e0-b10d-4067-acde-71cd450283db/cid-60e973077a985b49/calendar.xml"}
    Public TempEmpty_Min_Celsius As Double = 12 'min temp when house is empty in MinMax mode
    Public TempEmpty_Max_Celsius As Double = 10 'max temp when house is empty in MinMax mode
    Public TempOccupied_Day_Celsius As Double = 21 'Desired daytime temp
    Public TempOccupied_Night_Celsius As Double = 16 'Desired nighttime temp
    Public TempUnoccupied_Celsius As Double = 15 'Desired temp when house is empty in KeepTemp mode
    Public UnoccupiedMode As DecisionMode = DecisionMode.MinMax 'How to behave when house is empty
    Public PartyMode As PartyShift_Celsius = PartyShift_Celsius.Slight
    Public PartyShift_Custom_Celsius As Double = 200
    Public StartTime_Day As DateTime 'if current time is > StartTime_Day AND < StartTime_Night. It's Day
    Public StartTime_Night As DateTime 'if current time is > StartTime_Night OR < StartTime_Day, It's Night
    Public Enum DecisionMode
        KeepTemp 'This will keep temp within tolerance to current setting
        MinMax 'This will stop temp from falling outside of range, saves energy while unoccupied
        DoNothing 'Will never activate heating or cooling, best energy savings while unoccupied
    End Enum
    Public Enum PartyShift_Celsius
        'extra attendees at a home event will be cause the temp to be DECREASED by this/100 degrees
        None = 000 'no change
        Slight = 025 'about 0.5 F
        Moderate = 050 'about 1.0 F
        Aggressive = 075 'about 1.5 F
        Custom = 999 'user defined
    End Enum
    Public ReadOnly Property TempAmbient_Farenheit As Double
        'Returns the most recent ambient temperature reading converted to Farenheit
        'If you're using a temperature sensor that reads in farenheit instead of celsius,
        '   make this Property Not ReadOnly And uncomment the Set method so you can write to it
        Get
            Return TempConverter(TempAmbient_Celsius, True)
        End Get
        'Set(value As Double)
        '    TempAmbient_Celsius = TempConverter(value, False)
        'End Set
    End Property
    Public Property TempTolerance_Farenheit As Double
        'Allows use of TempTolerance_Celsius using Farenheit
        Get
            Return TempConverter(TempTolerance_Celsius, True)
        End Get
        Set(value As Double)
            TempTolerance_Celsius = TempConverter(value, False)
        End Set
    End Property
    Public Property TempSet_Farenheit As Double
        'Allows use of TempSet_Celsius using Farenheit
        Get
            Return TempConverter(TempSet_Celsius, True)
        End Get
        Set(value As Double)
            TempSet_Celsius = TempConverter(value, False)
        End Set
    End Property
End Module
HalcyonSoftware.vb
VBScript'This module is for any code that does NOT interact with hardware
Module HalcyonSoftware
    Private TimerTemp As DispatcherTimer 'Timer to update ambient temperature
    Private TimerCal As DispatcherTimer 'Timer to sync calendar data
    Public Sub InitializeSoftware()
        InitializeCalendars()
        UpdateCurrentEvent()
        InitializeTimers()
    End Sub
    Public Sub stopTimers()
        On Error Resume Next
        TimerTemp.Stop()
        TimerCal.Stop()
        On Error GoTo 0
    End Sub
    Private Sub InitializeCalendars()
        AllCalendars = New List(Of HalcyonCalendar)
        For Each CalLink As String In CalendarLinks
            Dim newCal As HalcyonCalendar = New HalcyonCalendar
            newCal.str_Link = CalLink.Trim
            newCal.Resync()
            AllCalendars.Add(newCal)
        Next
        UpdateCurrentEvent()
    End Sub
    Public Function TempConverter(degrees As Double, CtoF As Boolean) As Double
        If CtoF Then 'converting Celsius to Farenheit
            Return Math.Round((degrees * 9 / 5) + 32, precision)
        Else 'converting Farenheit to Celsius
            Return Math.Round((degrees - 32) * 5 / 9, precision)
        End If
    End Function
    Private Sub InitializeTimers()
        'This initiates the timers used to get temp updates and calendar syncs
        TimerTemp = New DispatcherTimer
        TimerTemp.Interval = New TimeSpan(0, 0, 0, UpdateTemp_Seconds)
        AddHandler TimerTemp.Tick, AddressOf TimerTemp_Tick
        TimerTemp.Start()
        TimerCal = New DispatcherTimer
        TimerCal.Interval = New TimeSpan(0, 0, UpdateCal_Minutes, 0)
        AddHandler TimerCal.Tick, AddressOf TimerCal_Tick
        TimerCal.Start()
    End Sub
    Private Sub TimerTemp_Tick()
        'If the current event has ended, then we need to update that. 
        If CurrentEvent IsNot Nothing AndAlso CurrentEvent.dt_EndTime < DateTime.Now Then
            TimerTemp.Stop() 'Calendar update could take a moment, don't want the timer to keep firing while updating
            UpdateCurrentEvent()
            TimerTemp.Start()
        End If
        GetTemp()
        MakeDecision()
    End Sub
    Private Sub MakeDecision()
        'Should be called after getting an updated temp
        'Will toggle heat and AC on or off based on current temp
        Try
            If CurrentEvent.AtHome Then
                If DateTime.Now.TimeOfDay > StartTime_Day.TimeOfDay And DateTime.Now.TimeOfDay < StartTime_Night.TimeOfDay Then
                    TempSet_Celsius = TempOccupied_Day_Celsius
                Else
                    TempSet_Celsius = TempOccupied_Night_Celsius
                End If
                'for party mode
                'For example, slight = 025
                '025 / 100 = 0.25
                'if 3 people are attending, subtract one for the owner
                '3 - 1 = 2 people extra, * .25 = 0.50 temp adjustment
                TempSet_Celsius -= (PartyMode / 100) * Math.Max((CurrentEvent.int_Attending - 1), 0)
            Else
                Select Case UnoccupiedMode
                    Case DecisionMode.DoNothing
                        TempSet_Celsius = TempAmbient_Celsius 'if setting == current temp then it will do nothing
                    Case DecisionMode.KeepTemp
                        TempSet_Celsius = TempUnoccupied_Celsius 'keep the unoccupied setting
                    Case DecisionMode.MinMax
                        If TempAmbient_Celsius < TempEmpty_Min_Celsius Then
                            TempSet_Celsius = TempEmpty_Min_Celsius 'we are below min so set to min
                        ElseIf TempAmbient_Celsius > TempEmpty_Max_Celsius Then
                            TempSet_Celsius = TempEmpty_Max_Celsius 'we are above max so set to max
                        Else
                            TempSet_Celsius = TempAmbient_Celsius 'do nothing
                        End If
                End Select
            End If
        Catch ex As Exception
            'in case of error, just call me occupied
            TempSet_Celsius = TempOccupied_Day_Celsius
        Finally
            'Simple if logic checks if we're more than tolerance away from setting
            If TempAmbient_Celsius < (TempSet_Celsius - TempTolerance_Celsius) Then heat_ON = True Else heat_ON = False
            If TempAmbient_Celsius > (TempSet_Celsius + TempTolerance_Celsius) Then ac_ON = True Else ac_ON = False
            ToggleRelays() 'Turn on or off as needed
        End Try
    End Sub
    Private Sub TimerCal_Tick()
        'resync all the calendars
        For Each oneCal As HalcyonCalendar In AllCalendars
            oneCal.Resync()
        Next
        'update the current event based on new calendar data
        UpdateCurrentEvent()
        'make a decision in case current event changed
        MakeDecision()
    End Sub
    Private Sub UpdateCurrentEvent()
        Dim mainEvent As HalcyonCalendarEvent = New HalcyonCalendarEvent
        mainEvent.dt_StartTime = DateTime.Now
        mainEvent.dt_EndTime = DateTime.Now.AddYears(1).AddHours(1) 'puts the date 1 year and 1 day into the future. to be replaced by found events
        mainEvent.AtHome = False
        For Each oneCal As HalcyonCalendar In AllCalendars
            For Each oneEvent As HalcyonCalendarEvent In oneCal.obj_Events
                If oneEvent.dt_EndTime < DateTime.Now Or oneEvent.dt_StartTime < DateTime.Now Then
                    'Event is either in the past or the future, ignore it
                    Continue For
                End If
                'This event is currently happening! YAY!
                If oneEvent.AtHome Then
                    If mainEvent.AtHome Then
                        'we are overwriting data because this is the first home event found
                        mainEvent.AtHome = True
                        mainEvent.int_Attending += oneEvent.int_Attending
                        'we keep the lowest end time so we can update the party temp when one overlapping event ends
                        If oneEvent.dt_EndTime < mainEvent.dt_EndTime Then mainEvent.dt_EndTime = oneEvent.dt_EndTime
                    End If
                Else
                    'if multiple events overlap (for example, from different calendars), then the HOME event has priority
                    If mainEvent.AtHome = True Then
                        'a home event already has priority so skip this away event
                        Continue For
                    Else
                        mainEvent.str_Location = "Away"
                        mainEvent.AtHome = False
                        If oneEvent.dt_EndTime < mainEvent.dt_EndTime Then mainEvent.dt_EndTime = oneEvent.dt_EndTime
                    End If
                End If
            Next
        Next
    End Sub
    Public Function IsAtHome(location_str As String) As Boolean
        For Each s As String In HomeIndicator
            If location_str.ToLower.Contains(s.ToLower) Then Return True
        Next
        Return False
    End Function
End Module
HalcyonCalendar.vb
VBScriptImports System.Net.Http
Public Class HalcyonCalendar
    Private xDoc As XDocument = Nothing
    Public Property obj_Events As List(Of HalcyonCalendarEvent)
    Public Property str_TimeZoneOffset As Integer
    Public Property str_Link As String
    Public Property dt_Updated As DateTime
    Public Property str_Title As String
    Public Property str_RawXML As String
    'timezone?
    Public Sub Resync()
        Dim client As New HttpClient
        Dim resp = client.GetAsync(str_Link)
        str_RawXML = resp.Result.Content.ReadAsStringAsync.Result
        Dim doc As New XDocument
        doc = XDocument.Parse(str_RawXML)
        Dim temporaryCal As HalcyonCalendar = Iterate_live(doc)
        obj_Events = New List(Of HalcyonCalendarEvent)
        obj_Events = temporaryCal.obj_Events
    End Sub
    Public Sub New()
        Me.obj_Events = New List(Of HalcyonCalendarEvent)
        Me.str_Title = "new calendar: title not set"
        Me.str_Link = "new calendar: link not set"
    End Sub
    Public Sub AddEvent(obj As HalcyonCalendarEvent)
        obj_Events.Add(obj)
    End Sub
End Class
Public Class HalcyonCalendarEvent
    Public Property dt_StartTime As DateTime
    Public Property dt_EndTime As DateTime
    Public Property str_Location As String
    Public Property int_Attending As Integer
    Public Property str_Title As String
    Public Property str_RawContent As String
    Public Property dt_Updated As DateTime
    Public Property AtHome As Boolean
    'timezone?
    Public Sub New(StartTime As DateTime, EndTime As DateTime, Location As String, Optional Attending As Integer = 1, Optional AllDay As Boolean = False)
        If AllDay Then
            Me.dt_StartTime = New DateTime(StartTime.Year, StartTime.Month, StartTime.Day, 0, 0, 1)
            Me.dt_EndTime = New DateTime(StartTime.Year, StartTime.Month, StartTime.Day, 23, 59, 59)
        Else
            Me.dt_StartTime = StartTime
            Me.dt_EndTime = EndTime
        End If
        Me.str_Location = Location
        Me.int_Attending = Attending
    End Sub
    Public Sub New()
    End Sub
End Class
IterateXML.vb
VBScript'This module is responsible for iterating the calendar XML. It's complicated enough that I want it isolated for easier reading.
'In a future version, XML parsing will be replaced with actual Microsoft Account linking
Public Module IterateXML
    Public Function Iterate_live(xDoc As XDocument) As HalcyonCalendar
        '<?xml version="1.0" encoding="utf-8"?>
        'feed
        '   title
        '   link
        '   updated
        '   entry
        '       title
        '       updated
        '       author
        '           email
        '       content
        '   timezone
        Dim cal As New HalcyonCalendar
        'pull raw info out of XML
        For Each oneElement As XElement In xDoc.Root.Descendants()
            Select Case oneElement.Name.LocalName
                Case "link"
                    'we already know this
                Case "title"
                    cal.str_Title = oneElement.Value
                Case "updated"
                    'cal.dt_Updated = 
                    DateTime.TryParse(oneElement.Value, cal.dt_Updated)
                Case "entry"
                    Dim newEntry As New HalcyonCalendarEvent
                    newEntry.int_Attending = 0
                    For Each entryElement As XElement In oneElement.Descendants
                        Select Case entryElement.Name.LocalName
                            Case "title"
                                newEntry.str_Title = entryElement.Value
                            Case "updated"
                                newEntry.dt_Updated = DateTime.Parse(entryElement.Value)
                            Case "content"
                                newEntry.str_RawContent = entryElement.Value
                            Case Else
                                'not used
                                'author
                        End Select
                    Next
                    cal.obj_Events.Add(newEntry)
                Case Else
                    'not used
            End Select
        Next
        'parse event content to fill in remaining calendarEvent data
        For Each oneEvent As HalcyonCalendarEvent In cal.obj_Events
            'Original
            '<table style="font-family:tahoma;font-size:11px"><tr><td style="vertical-align:top" align="right"><b>Start:</b></td><td style="width:10px" align="right" /><td>Thursday, August 16, 2012</td></tr><tr><td style="vertical-align:top" align="right"><b>End:</b></td><td style="width:10px" align="right" /><td>Thursday, August 16, 2012</td></tr><tr><td style="vertical-align:top" align="right"><b>Location:</b></td><td style="width:10px" align="right" /><td>St Louis</td></tr><tr><td style="vertical-align:top" align="right"><b>Who:</b></td><td style="width:10px" align="right" /><td>Aleisha Ritter;undertkr2002@yahoo.com</td></tr><tr><td style="vertical-align:top" align="right"><b>Description:</b></td><td style="width:10px" align="right" /><td /></tr></table>
            'HTML replacements IN THIS ORDER!
            '   "find!replace"
            '   "find!"            to leave blank
            '   "find!|"      for separator
            Dim replaceStrings() As String = {"<!<", ">!>", "<tr>!", "</tr>!", "<b>!", "</b>!", "<td style=""width:10px"" align=""right"" />!", "<td />!", "style=""vertical-align:top""!", "align=""right""|", "style=""font-family:tahoma;font-size:11px""!", "td>!", "<td!", "<table >!", "</table>!", ">!", "<!", "/td!|"}
            Dim tempStr As String()
            Dim tempContent As String
            tempContent = oneEvent.str_RawContent
            For Each oneString As String In replaceStrings
                Try
                    tempContent = tempContent.Replace(oneString.Split("|")(0), oneString.Split("|")(1))
                Catch ex As Exception
                End Try
            Next
            'After replacements we can just split on |, trim each string, and iterate through it
            'Start:|Thursday, August 16, 2012|End:|Thursday, August 16, 2012|Location:|St Louis|Who:|Aleisha Ritter|undertkr2002@yahoo.com|Description:|
            tempStr = tempContent.Split("|")
            Dim currentHeader As String = ""
            For Each oneString As String In tempStr
                'this will be a header followed by a value
                '   Start:
                '   blah blah
                '   End:
                '   blah blah
                '   etc...
                Select Case oneString.Trim.ToLower
                    Case "start:"
                        currentHeader = oneString.Trim.ToLower
                    Case "end:"
                        currentHeader = oneString.Trim.ToLower
                    Case "location:"
                        currentHeader = oneString.Trim.ToLower
                    Case "who:"
                        currentHeader = oneString.Trim.ToLower
                    Case "description:"
                        currentHeader = "skip me"
                    Case Else
                        'This is a value instead of a header then
                        Select Case currentHeader
                            Case "start:"
                                oneEvent.dt_StartTime = DateTime.Parse(oneString.Trim)
                            Case "end:"
                                oneEvent.dt_EndTime = DateTime.Parse(oneString.Trim)
                            Case "location:"
                                oneEvent.str_Location = oneString.Trim
                                If IsAtHome(oneString) Then oneEvent.AtHome = True
                            Case "who:"
                                oneEvent.int_Attending += 1
                            Case "skip me"
                                'doesn't matter to Halcyon
                            Case Else
                                'doesn't matter to Halcyon
                        End Select
                End Select
            Next
        Next
        Return cal
    End Function
    Public Function Iterate_GMAIL()
        'Because i'm iterating a shared calendar XML, we could also do the same for other providers.
        'Just look within the link string of the calendar to determine provider. If it contains "microsoft.com" or "google.com" etc
        Return Nothing
    End Function
End Module
MainPage.xaml
XML<Page
    x:Class="Halcyon_Alpha.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:Halcyon_Alpha"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">
    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <StackPanel>
            <TextBlock TextWrapping="Wrap" Text="Halcyon" VerticalAlignment="Top" Margin="0,25,0,0" TextAlignment="Center" Foreground="#FF035CD0" FontWeight="Bold" FontSize="29.333"/>
            <StackPanel Orientation="Horizontal" HorizontalAlignment="Center"  >
                <RadioButton x:Name="radioC" Content="Celsius" GroupName="displaySetting" IsChecked="True" />
                <RadioButton x:Name="radioF" Content="Farenheit" GroupName="displaySetting" FlowDirection="RightToLeft" />
            </StackPanel>
            <StackPanel Orientation="Horizontal" HorizontalAlignment="Center"  Margin="0,25,0,0">
                <TextBlock TextWrapping="Wrap" Text="Ambient Temperature:" VerticalAlignment="Top" Margin="0,0,0,0" TextAlignment="Center"/>
                <TextBlock x:Name="tbTempAmbient" TextWrapping="Wrap" Text="loading..." VerticalAlignment="Top" Margin="25,0,0,0" TextAlignment="Center"/>
            </StackPanel>
            <StackPanel Orientation="Horizontal" HorizontalAlignment="Center"  Margin="0,25,0,0">
                <TextBlock TextWrapping="Wrap" Text="Desired Temperature:" VerticalAlignment="Top" Margin="0,0,0,0" TextAlignment="Center"/>
                <TextBlock x:Name="tbTempSetting" TextWrapping="Wrap" Text="loading..." VerticalAlignment="Top" Margin="25,0,0,0" TextAlignment="Center"/>
            </StackPanel>
            <StackPanel Orientation="Horizontal" HorizontalAlignment="Center"  Margin="0,25,0,0">
                <TextBlock TextWrapping="Wrap" Text="Status:" VerticalAlignment="Top" Margin="0,0,0,0" TextAlignment="Center"/>
                <TextBlock TextWrapping="Wrap" Text="loading..." x:Name="tbStatus" VerticalAlignment="Top" Margin="25,0,0,0" TextAlignment="Center"/>
            </StackPanel>
            <StackPanel Orientation="Horizontal" HorizontalAlignment="Center"  Margin="0,25,0,0">
                <TextBlock TextWrapping="Wrap" Text="Current Time:" VerticalAlignment="Top" Margin="0,0,0,0" TextAlignment="Center"/>
                <TextBlock x:Name="tbCurrentTime"  TextWrapping="Wrap" Text="loading..." VerticalAlignment="Top" Margin="25,0,0,0" TextAlignment="Center"/>
            </StackPanel>
            <StackPanel Orientation="Horizontal" HorizontalAlignment="Center"  Margin="0,25,0,0">
                <TextBlock TextWrapping="Wrap" Text="Force Temp:" VerticalAlignment="Top" Margin="0,5,0,0" TextAlignment="Center"/>
                <TextBox x:Name="txtForce" TextWrapping="Wrap" Text="20" VerticalAlignment="Top" Margin="25,0,0,0" TextAlignment="Center"/>
           <Button x:Name="btnForceTemp" Content="Apply" Margin="25,0,0,0"/>
            </StackPanel>
            <TextBlock x:Name="tbError" TextWrapping="Wrap" Text="." VerticalAlignment="Top" Margin="0,25,0,0" TextAlignment="Center" Foreground="Red"/>
        </StackPanel>
    </Grid>
</Page>
MainPage.xaml.vb
VBScript'The MainPage code should initiate code on the other modules, and will also be responsible for handling GUI interactions and updates
''' <summary>
''' -> at the start of a comment denotes PSUEDO CODE, not an actual comments
''' </summary>
Public NotInheritable Class MainPage
    Inherits Page
    Private Sub MainPage_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
        Try
            InitializeHardware() 'make sure our hardware connections work and are started up
            InitializeSoftware() 'do initial sync and start our timers to make the magic happen
        Catch ex As Exception
            stopTimers()
            tbError.Text = "ERROR: " & ex.Message
        End Try
    End Sub
    Public Sub UpdateInterface()
        Select Case radioC.IsChecked
            Case True
                'display Celsius
                tbTempAmbient.Text = TempAmbient_Celsius.ToString
                tbTempSetting.Text = TempSet_Celsius.ToString
            Case False
                'display Farenheit
                tbTempAmbient.Text = TempAmbient_Farenheit.ToString
                tbTempSetting.Text = TempSet_Farenheit.ToString
        End Select
        tbCurrentTime.Text = DateTime.Now.ToString
        If ac_ON Then
            tbStatus.Text = "Cooling"
        ElseIf heat_ON Then
            tbStatus.Text = "Heating"
        Else
            tbStatus.Text = "Everything Off"
        End If
    End Sub
    Private Sub btnForceTemp_Click(sender As Object, e As RoutedEventArgs) Handles btnForceTemp.Click
        Try
            If radioC.IsChecked Then
                TempSet_Celsius = Math.Round(CInt(txtForce.Text.Trim), precision)
            Else
                TempSet_Farenheit = Math.Round(CInt(txtForce.Text.Trim), precision)
            End If
        Catch ex As Exception
            tbError.Text = ex.Message
            txtForce.Text = "20"
        End Try
    End Sub
End Class
Away Events.xml
XML<?xml version="1.0" encoding="utf-8"?>
<feed xmlns:sx="http://www.microsoft.com/schemas/sse" xmlns="http://www.w3.org/2005/Atom">
    <title>Away Events</title>
    <subtitle>Halcyon Away Events Testing</subtitle>
    <link rel="self" href="https://sharing.calendar.live.com/calendar/private/eca6cd71-ae7c-4146-b7c1-a8f9d03de0d2/0ff376e0-b10d-4067-acde-71cd450283db/cid-60e973077a985b49/calendar.xml" type="application/atom+xml" />
    <updated>2015-09-16T20:49:49Z</updated>
    <entry>
        <title>Away - Friday, September 25, 2015 8:00AM(Eastern Standard Time)</title>
        <updated>2015-09-16T20:49:19Z</updated>
        <author>
            <email>gd.ritter@live.com</email>
        </author>
        <content type="html"><table style="font-family:tahoma;font-size:11px"><tr><td style="vertical-align:top" align="right"><b>Start:</b></td><td style="width:10px" align="right" /><td>Friday, September 25, 2015 8:00AM (Eastern Standard Time)</td></tr><tr><td style="vertical-align:top" align="right"><b>End:</b></td><td style="width:10px" align="right" /><td>Friday, September 25, 2015 1:30PM (Eastern Standard Time)</td></tr><tr><td style="vertical-align:top" align="right"><b>Repeat:</b></td><td style="width:10px" align="right" /><td>Occurs every week on Friday.</td></tr><tr><td style="vertical-align:top" align="right"><b>Location:</b></td><td style="width:10px" align="right" /><td>Away</td></tr><tr><td style="vertical-align:top" align="right"><b>Who:</b></td><td style="width:10px" align="right" /><td /></tr><tr><td style="vertical-align:top" align="right"><b>Description:</b></td><td style="width:10px" align="right" /><td>Away</td></tr></table></content>
    </entry>
    <entry>
        <title>Away - Monday, September 21, 2015 8:00AM(Eastern Standard Time)</title>
        <updated>2015-09-16T20:49:48Z</updated>
        <author>
            <email>gd.ritter@live.com</email>
        </author>
        <content type="html"><table style="font-family:tahoma;font-size:11px"><tr><td style="vertical-align:top" align="right"><b>Start:</b></td><td style="width:10px" align="right" /><td>Monday, September 21, 2015 8:00AM (Eastern Standard Time)</td></tr><tr><td style="vertical-align:top" align="right"><b>End:</b></td><td style="width:10px" align="right" /><td>Monday, September 21, 2015 6:30PM (Eastern Standard Time)</td></tr><tr><td style="vertical-align:top" align="right"><b>Repeat:</b></td><td style="width:10px" align="right" /><td>Occurs every week on Monday, Tuesday, Wednesday and Thursday.</td></tr><tr><td style="vertical-align:top" align="right"><b>Location:</b></td><td style="width:10px" align="right" /><td>Away</td></tr><tr><td style="vertical-align:top" align="right"><b>Who:</b></td><td style="width:10px" align="right" /><td /></tr><tr><td style="vertical-align:top" align="right"><b>Description:</b></td><td style="width:10px" align="right" /><td>Away</td></tr></table></content>
    </entry>
    <timezone xmlns="http://calendar.live.com/schemas/sync/calendar"><![CDATA[BEGIN:VTIMEZONE
TZID:Eastern Standard Time
BEGIN:STANDARD
DTSTART:20061029T020000
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
TZOFFSETTO:-0500
TZOFFSETFROM:-0400
END:STANDARD
BEGIN:STANDARD
DTSTART:20071104T020000
RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=11
TZOFFSETTO:-0500
TZOFFSETFROM:-0400
END:STANDARD
BEGIN:DAYLIGHT
DTSTART:20060402T020000
RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4
TZOFFSETTO:-0400
TZOFFSETFROM:-0500
END:DAYLIGHT
BEGIN:DAYLIGHT
DTSTART:20070311T020000
RRULE:FREQ=YEARLY;BYDAY=2SU;BYMONTH=3
TZOFFSETTO:-0400
TZOFFSETFROM:-0500
END:DAYLIGHT
END:VTIMEZONE
]]></timezone>
</feed>











Comments