x10-cm17a

Michael Jacobson: This is a console program to interface with the X10.com CM17A firecracker [L1 ]. This was one of my first Tcl programs and it needs to cleaned up (a lot!!!) to use namespaces and be a library. But I have had a couple people ask for the code so instead of reinventing the wheel just grap if from here (or update it and post below).

Link to the FireCracker (CM17A) Communications Specification [L2 ].

http://graphics.x10.com/images2/ck17a_detail.jpg


 ## By Mike Jacobson
 ## need WinCom or Tcl8.4a with TIP35 patches
 ## to control the DTR and DSR lines
 ## tested on Windows 95/98/Me/NT only
 package require WinCom
 catch { console show }

 set rts 0
 set dtr 0
 set chan {}

 #added not in spec ???
 # B0 90 for "H All Lights On" 
 # B0 80 for "H All Units Off".
 #        $operation        FUNCTION
 #            L                Brighten Last Light Programmed 14%
 #            M                Dim Last Light Programmed 14%
 #            N                All Lights Off
 #            O                All Lights On
 #            P                All Units Off

 # L  000 1000 1000     0 88
 # M  000 1001 1000     0 98
 # N  000 1010 0000     0 A0
 # O  000 1001 0000     0 90
 # P  000 1000 0000     0 80
 proc X10_Cmd {house station command} {
        #|    Byte 1     |    Byte 2     |
        #|X|X|X|X|0|X|0|0|X|X|X|X|X|0|0|0|
        #|1 2 3 4 5 6 7 8|1 2 3 4 5 6 7 8|
        #Byte 1
        set outstr 0
        if {[lsearch {BRIGHT DIM AUO ALF ALO} $command] != -1} {
                set station 0
        }
        #1: One for house codes E-L
        if {[regexp {[E-L]} $house]} {
                incr outstr 0x8000
        }
        #2: One for house codes A-D & I-L
        if {[regexp {[A-D,I-L]} $house]} {
                incr outstr 0x4000
        }
        #3: One for house codes A,B,G,H,I,J,O,P
        if {[regexp {[A,B,G,H,I,J,O,P]} $house]} {
                incr outstr 0x2000
        }
        #4: One for house codes B,D,F,H,J,L,N,P
        if {[regexp {[B,D,F,H,J,L,N,P]} $house]} {
                incr outstr 0x1000
        }
        #5: Always zero
        #6: One for units 9-16
        if {(9 <= $station) && ( $station <= 16) } {
                incr outstr 0x0400
        }
        #7: Always zero
        #8: Always zero
        #Byte 2


        #1: One for BRIGHT or DIM or AUO or ALF or ALO
        if {[lsearch {BRIGHT DIM AUO ALF ALO} $command] != -1} {
                incr outstr 0x0080
        }
        #2: One for unit codes 5-8 & 13-16
        if {((5 <= $station) && ( $station <= 8)) || ((13 <= $station) && ( $station <= 16)) } {
                incr outstr 0x0040
        }
        #3: One for OFF or ALF (Zero for ON or BRIGHT/DIM)
        if {[lsearch {OFF ALF} $command] != -1} {
                incr outstr 0x0020
        }
        #4: One for unit codes 2,4,6,8,10,12,14,16 & DIM & ALO command
        if {[lsearch {2 4 6 8 10 12 14 16} $station] != -1 } {
                incr outstr 0x0010
        } elseif {[lsearch {DIM ALO} $command] != -1 } {
                incr outstr 0x0010
        }
        #5: One for unit codes 3,4,7,8,11,12,15,16 & BRIGHT & DIM commands
        if {[lsearch {3 4 7 8 11 12 15 16} $station] != -1 } {
                incr outstr 0x0008
        } elseif {[lsearch {DIM BRIGHT} $command] != -1} {
                incr outstr 0x0008
        }
        #6: Always zero
        #7: Always zero
        #8: Always zero
        SendByte 0xD5  ;# header
        SendByte 0xAA
        SendByte [expr [expr $outstr & 0xFF00] >> 8]
        SendByte [expr $outstr & 0xFF]
        SendByte 0xAD  ;# footer
        return $outstr
 }

 proc Pause { {msec 1000} } {
          update
          after $msec {set Pause 1}
          vwait Pause
 }

 proc Send_Standby {} {
        #        puts "in standby"
        global chan dts rts
        set rts 1
        set dts 1
        wincom::config $chan -dtr enable
        wincom::config $chan -rts enable
        Pause 10
 }

 proc Send_1 {} {
        #        puts "in send 1"
        global chan dtr

# wincom::config $chan -rts enable

        set dts 0
        wincom::config $chan -dtr disable
        Pause 1
 }

 proc Send_0 {} {
        #        puts "in send 0"
        global chan rts

# wincom::config $chan -dtr enable

    set rts 0
        wincom::config $chan -rts disable
        Pause 1
 }

 proc Send_Reset {} {
        #        puts "in reset"
        global chan rts dtr
        set rts 0
        set dts 0
        wincom::config $chan -dtr disable
        wincom::config $chan -rts disable
        Pause 50
 }

 proc Send_Wait {} {
        #puts "in wait"
        global chan rts dtr
        if {$rts == 0} {
                set rts 1
                wincom::config $chan -rts enable
        }
        if {$dtr == 0} {
                set dtr 1
                wincom::config $chan -dtr disable
        }
        Pause 1
        #puts "out wait"
 }

 proc SendByte {iByte} {
    #puts "Byte to send [DispHex $iByte]"
        for {set i 0} {$i <=7} {incr i} {
                if {[expr $iByte & 0x80]} {
                        Send_1
                        #puts "1"
                } else {
                        Send_0
                        #puts "0"
                }
                Send_Wait
                set iByte [expr $iByte * 2]
        }

}

 proc DispHex {NumIn} {
     set StrOut [format "%X" $NumIn]
     return $StrOut
 }

 proc Bin2Int {binstr} {
        set binstr [string trim $binstr]
        puts $binstr
        set binlen [string length "$binstr"]
        puts $binlen
        set outstr 0
        for {set i 0} {$i < $binlen} {incr i} {
                set num [string index "$binstr" $i]
                puts $num
                if {$num == 0} {
                        set outstr [expr $outstr << 1]
                } elseif {$num == 1} {
                        set outstr [expr [expr $outstr << 1] + 1]
                }
        }
        return $outstr
 }

 proc X10 {cmd args} {
        global chan
        if {[string equal -nocase $cmd SEND]} {
                if {$chan == ""} {
                        puts "init COM port 1"
                        set chan [open com1 r+]
                        Pause 50
                        Send_Reset
                        Send_Standby
                        # ???? may need to restore this stuff
                        #fconfigure $chan -blocking 1 -buffering none
                        #wincom::config $chan -mode "19200,n,8,1" -timeout 10
                } 
                #puts "X10_Cmd $args"
                puts "X10 command sent [DispHex [eval X10_Cmd $args]]"
        } elseif {[string equal -nocase $cmd CLOSE]} {
                close $chan
                set chan {}
                puts "close COM port 1"
        }
 }

 ## example
 ##   X10 {type} {house} {station} {command}
 ##   X10 {SEND CLOSE} {A-P} {1-16} {OFF ON BRIGHT DIM AUO ALF ALO}
 ##
 ##   X10 SEND A 1 ON
 ##   X10 SEND P 12 DIM
 ##   X10 SEND H 2 AU0  ;# all units on (may not work)
 ##   X10 SEND H 2 ALF  ;# all units off (may not work)

MacSoileau - 2011-12-18 15:03:33

Modified the original script to work using fconfigure instead of the WinCom. Also had to modify the Pause timing to make it work for me. Thanks to Mike Jacobson for the original work!

## By Mike Jacobson
## Modified by Mac Soileau to work without additional packages
## Tested on Windows 7
 set rts 0
 set dtr 0
 set chan {}

 #added not in spec ???
 # B0 90 for "H All Lights On"
 # B0 80 for "H All Units Off".
 #        $operation        FUNCTION
 #            L                Brighten Last Light Programmed 14%
 #            M                Dim Last Light Programmed 14%
 #            N                All Lights Off
 #            O                All Lights On
 #            P                All Units Off

 # L  000 1000 1000     0 88
 # M  000 1001 1000     0 98
 # N  000 1010 0000     0 A0
 # O  000 1001 0000     0 90
 # P  000 1000 0000     0 80
 proc X10_Cmd {house station command} {
        #|    Byte 1     |    Byte 2     |
        #|X|X|X|X|0|X|0|0|X|X|X|X|X|0|0|0|
        #|1 2 3 4 5 6 7 8|1 2 3 4 5 6 7 8|
        #Byte 1
        set outstr 0
        if {[lsearch {BRIGHT DIM AUO ALF ALO} $command] != -1} {
                set station 0
        }
        #1: One for house codes E-L
        if {[regexp {[E-L]} $house]} {
                incr outstr 0x8000
        }
        #2: One for house codes A-D & I-L
        if {[regexp {[A-D,I-L]} $house]} {
                incr outstr 0x4000
        }
        #3: One for house codes A,B,G,H,I,J,O,P
        if {[regexp {[A,B,G,H,I,J,O,P]} $house]} {
                incr outstr 0x2000
        }
        #4: One for house codes B,D,F,H,J,L,N,P
        if {[regexp {[B,D,F,H,J,L,N,P]} $house]} {
                incr outstr 0x1000
        }
        #5: Always zero
        #6: One for units 9-16
        if {(9 <= $station) && ( $station <= 16) } {
                incr outstr 0x0400
        }
        #7: Always zero
        #8: Always zero
        #Byte 2

        #1: One for BRIGHT or DIM or AUO or ALF or ALO
        if {[lsearch {BRIGHT DIM AUO ALF ALO} $command] != -1} {
                incr outstr 0x0080
        }
        #2: One for unit codes 5-8 & 13-16
        if {((5 <= $station) && ( $station <= 8)) || ((13 <= $station) && ( $station <= 16)) } {
                incr outstr 0x0040
        }
        #3: One for OFF or ALF (Zero for ON or BRIGHT/DIM)
        if {[lsearch {OFF ALF} $command] != -1} {
                incr outstr 0x0020
        }
        #4: One for unit codes 2,4,6,8,10,12,14,16 & DIM & ALO command
        if {[lsearch {2 4 6 8 10 12 14 16} $station] != -1 } {
                incr outstr 0x0010
        } elseif {[lsearch {DIM ALO} $command] != -1 } {
                incr outstr 0x0010
        }
        #5: One for unit codes 3,4,7,8,11,12,15,16 & BRIGHT & DIM commands
        if {[lsearch {3 4 7 8 11 12 15 16} $station] != -1 } {
                incr outstr 0x0008
        } elseif {[lsearch {DIM BRIGHT} $command] != -1} {
                incr outstr 0x0008
        }
        #6: Always zero
        #7: Always zero
        #8: Always zero
        SendByte 0xD5  ;# header
        SendByte 0xAA
        SendByte [expr [expr $outstr & 0xFF00] >> 8]
        SendByte [expr $outstr & 0xFF]
        SendByte 0xAD  ;# footer
        return $outstr
 }

 proc Pause { {msec 1000} } {
          update
          after $msec {set Pause 1}
          vwait Pause
 }

 proc Send_Standby {} {
        global chan dts rts
        set rts 1
        set dts 1
        fconfigure $chan -ttycontrol {RTS 1 DTR 1}
        #puts "Send_Standby" 
        Pause 100
 }

 proc Send_1 {} {
        global chan dtr
        set dts 0
        fconfigure $chan -ttycontrol {DTR 0 RTS 1}
#        puts "Send 1"
        Pause 10
 }

 proc Send_0 {} {
        global chan rts
    set rts 0
        fconfigure $chan -ttycontrol {RTS 0 DTR 1}
#        puts "Send 0"
        Pause 10
 }

 proc Send_Reset {} {
        global chan rts dtr
        set rts 0
        set dts 0
        fconfigure $chan -ttycontrol {RTS 0 DTR 0}
#        puts "Send Reset"
        Pause 500
 }

 proc Send_Wait {} {
        global chan rts dtr
        fconfigure $chan -ttycontrol {DTR 1 RTS 1}
#        puts "Send Wait"
        Pause 10
 }

 proc SendByte {iByte} {
    #puts "Byte to send [DispHex $iByte]"
        for {set i 0} {$i <=7} {incr i} {
                if {[expr $iByte & 0x80]} {
                        Send_1
                } else {
                        Send_0
                }
                Send_Wait
                set iByte [expr $iByte * 2]
        }
}
 proc DispHex {NumIn} {
     set StrOut [format "%X" $NumIn]
     return $StrOut
 }

 proc Bin2Int {binstr} {
        set binstr [string trim $binstr]
        puts $binstr
        set binlen [string length "$binstr"]
        puts $binlen
        set outstr 0
        for {set i 0} {$i < $binlen} {incr i} {
                set num [string index "$binstr" $i]
                puts $num
                if {$num == 0} {
                        set outstr [expr $outstr << 1]
                } elseif {$num == 1} {
                        set outstr [expr [expr $outstr << 1] + 1]
                }
        }
        return $outstr
 }

 proc X10 {cmd args} {
        global chan
        if {[string equal -nocase $cmd SEND]} {
                if {$chan == ""} {
                        puts "init COM port 1"
                        set chan [open com1 r+]
                        fconfigure $chan -handshake none -blocking 1 -buffering none -mode 19200,n,8,1
                        Pause 50
                        Send_Reset
                        Send_Standby

                }
                puts "X10 command sent [DispHex [eval X10_Cmd $args]]"
        } elseif {[string equal -nocase $cmd CLOSE]} {
                close $chan
                set chan {}
                puts "close COM port 1"
        }
 }

 ## example
 ##   X10 {type} {house} {station} {command}
 ##   X10 {SEND CLOSE} {A-P} {1-16} {OFF ON BRIGHT DIM AUO ALF ALO}
 ##
 ##   X10 SEND A 1 ON
 ##   X10 SEND P 12 DIM
 ##   X10 SEND H 2 AU0  ;# all units on (may not work)
 ##   X10 SEND H 2 ALF  ;# all units off (may not work)