// Home Automation Project // S. Clarkson // 03-Feb-2009 // // REQUIRES: // #URL-lib "http://pin1.org/forthlib/flb/General/soft1.flb" sid=99 // #URL-lib "http://pin1.org/forthlib/flb/General/pinsel.flb" sid=100 // VARIABLES: // INPUTS variable I_kWh-p variable I_alm-set variable I_intrude // OUTPUTS variable O_led // Initialise the ports for use by the HA system // Port configuration will be as follows: // // INPUTS // P1.16 - KWh Pulse - 200 pulses = 1 KWh // P1.17 - Alarm armed // P1.18 - Alarm activated (intruder) // P1.27 - Escape run loop (for debug) // // OUTPUTS // P0.31 - Flash with kWh pulse trailing edge // // Word to initialise the I/O ports : init-io 16 io1-in 17 io1-in 18 io1-in 27 io1-in 28 io1-in 31 io0-out ; // Words to implement the io scans : scan-in // Scan inputs 16 p1@ I_kwh-p ! 17 p1@ I_alm-set ! 18 p1@ I_intrude ! ; : wr-out // Write outputs O_led @ 31 p0! ; // --------------------------------------------------------- // Below are the user logic sections variable kW variable kwh variable kwh-1000th variable osf variable a1 variable pr variable bpos variable prev-c variable inpkt // True when an STX has been received but no ETX yet variable Address // our bus address - configured in sys-init // Comms constants and variables 2 constant STX 6 constant ACK 21 constant NAK 3 constant ETX 16 constant DLE 1 constant SOH 27 constant ESC // Packet constants 20 constant SYNC-CLK 21 constant RQ-KWH 22 constant SD-KWH 23 constant RQ-W 24 constant SD-W 20 constant BLEN BLEN vspace$ d-buff // data goes here BLEN vspace$ s-buff // send data goes here : inpkt? inpkt @ 0 <> ; : c-buf // Empty internal buffer 0 bpos ! BLEN for 0 i d-buff + c! next 0 inpkt ! ; : dump-buf INT: k // Clear the h/w input buffer, if we get an ETX with no preceding ESC, stop 0 => k begin key1? k ETX <> and while key1 => k prev-c @ ESC = if 0 => k then // If previous key was esc, throw away this key k prev-c ! repeat // Empty local buffer too c-buf 0 prev-c ! ; // Wait for an STX character that was not preceded with ESC : stx? -1 // if we are already inside packet processing, send true inpkt? 0 = if drop 0 key1? if // Key on the serial input buffer drop prev-c @ ESC <> // Get previous key and store true if not escape key1 dup prev-c ! // Store as previous key STX = and // True if received STX and previous was not escape char dup if -1 inpkt ! then // Set flag to indicate that we are processing a packet then then ; : get-buf // Get characters from serial buffer, return -1 after an ETX has been received // Characters are added to the buffer until either it is full or we get an ETX -2 // Will return -2 if the buffer is full bpos @ BLEN < if drop -3 // Will return -3 if no keys on serial port key1? if drop prev-c @ ESC <> // Do check for preceding esc key1 dup prev-c ! // Get character and store as previous char dup ESC <> if // Don't store escape chars dup d-buff bpos @ + c! // Store character bpos @ 1 + bpos ! // Increment buffer pointer then ETX = and // Return true if ETX without ESC preceding then then ; : getall // Get as many characters as are available on the port begin get-buf dup 0 = while drop repeat ; : send-data kwh @ iu. 46 emit1 kwh-1000th @ iu. 32 emit1 kw @ iu. 87 emit1 13 emit1 10 emit1 10 emit1 ; : send-nak NAK emit1 ; : send-ack ACK emit1 ; : chk-len? // Check packet length is at least minimum int: len -1 bpos @ 6 < if drop send-nak 0 then // Now check that packet length matches declared length d-buff 3 + c@ => len // declared data length (bytes) bpos @ 6 len + <> if // Packet different than declared drop send-nak 0 then // length is OK ; : !sync-clk { src } send-ack ; : !send-kwh { src } send-ack STX emit1 Address @ emit1 src emit1 SD-KWH emit1 9 emit1 kwh @ <# # # # # # #> stype1 46 emit1 kwh-1000th @ <# # # # #> stype1 0 emit1 // CRC omitted for now ETX emit1 ; : !send-w { src } send-ack STX emit1 Address @ emit1 src emit1 SD-W emit1 5 emit1 kw @ <# # # # # # #> stype1 0 emit1 // CRC omitted for now ETX emit1 ; : do-pkt // Decode the received packet and respond accordingly int: src cmd len crc // Minimum packet length is 6 characters. If this is less than that, send a NAK chk-len? if // Packet basic length check OK // Break up packet d-buff c@ => src d-buff 2 + c@ => cmd d-buff 3 + c@ => len d-buff 4 len + + c@ => crc // Add CRC check later! // Process command cmd SYNC-CLK = if // Synchronise RTC to source src !sync-clk then cmd RQ-KWH = if // Send KWH src !send-kwh 0 kwh ! 0 kwh-1000th ! then cmd RQ-W = if // Send W src !send-w then then ; : parse-pkt { p-status --- Status from get-buff} // Determine if this packet is for us and if so is it valid, if it is process it int#: ourmsg 0 // true if message addressed to us p-status -3 <> if // Packet is complete or buffer is full, we need to check // Check if addressed to us bpos 1 > if // See if we are addressed (buffer + 1 since we don't store the STX) d-buff 1 + c@ Address @ = => ourmsg then p-status -2 = if // Buffer is full, we need to clear our receive // buffer and send a NAK if addressed to us dump-buf ourmsg if send-nak then then p-status -1 = if // We have a complete packet - // or at least one that ended with ETX // Is this for us? ourmsg if do-pkt then // dump our buffer c-buf then then ; : do-comms // Handle bus comms // Don't do anything before we get a non escaped STX stx? if // While we have an stx, get all the data // from the packet and parse it when complete getall parse-pkt then ; : 1st-scan // This word will be executed on the first program // scan only 1000 set-t1 1500 set-t2 700 set-t3 180000000 set-t4 // for calculating Kw load 1 O_led ! 0 kW ! 0 kwh ! 0 kwh-1000th ! // reset kwh pulse counter 0 osf ! ; // Logic loop : ob1 // This word is called on every program scan // Reset t4 when done (this would be <= 1W usage) t4-dn 0 = if en-t4 then // Debounce falling edge osf @ 1 = I_kwh-p @ 0 <> and if en-t2 then osf @ 1 = t2-dn and if // Ensure one shot 0 osf ! // i.e. pulse off (logic true) and timer done 5 kwh-1000th @ + kwh-1000th ! // Check for roll into kwh kwh-1000th @ 999 > if 0 kwh-1000th ! 1 kwh @ + kwh ! then // If kw, kwh-1000th and kwh zero ignore kwh @ 0 <> kwh-1000th @ 0 <> or if // Calculate instant Kw usage (actually Watts) t4pre @ dup t4acc @ - / kw ! // Reset kW timer t4pre @ t4acc ! then 1 pr ! then I_kwh-p @ 0 = if en-t1 then // Run debounce timer while ip on // Store flag - debounce on done t1-dn if 1 osf ! then // Flash led for n ms (t3) // while timer done, run the off debounce t2-dn t3-tt or if en-t3 then 1 O_led ! t3-tt if 0 O_led ! then ; // Test mode logic : ob1-test // Reflect sensor input to on board led for tuning I_kwh-p @ O_led ! ; // ---------------------------------------------------------- // END OF USER LOGIC // ---------------------------------------------------------- : sys-init init-io 65 Address ! c-buf // Initialise the comms by clearing the internal buffer 1st-scan t-init ; // --------------------------------------------------------- // Main program loop : sgl-step // Main program loop here scan-in ob1 wr-out do-comms // Timer status handling TSTATS ; : test-m // Test program loop here scan-in ob1-test wr-out do-comms // Timer status handling TSTATS ; // Initialise system, then loop through the : run // Initialise io & timers sys-init // 1000 ms begin 27 p1@ -1 = while // Normal running 28 p1@ -1 = if sgl-step then // Setup mode 28 p1@ 0 = if test-m then repeat drop drop // Clear all outputs 1 O_led ! // Final output cycle wr-out ;