1# nmea.tcl -- 2# 3# NMEA protocol implementation 4# 5# Copyright (c) 2006-2009 Aaron Faupell 6# 7# RCS: @(#) $Id: nmea.tcl,v 1.5 2009/01/09 06:49:25 afaupell Exp $ 8 9package require Tcl 8.4 10package provide nmea 1.0.0 11 12namespace eval ::nmea { 13 array set ::nmea::nmea [list checksum 1 log {} rate 0] 14 array set ::nmea::dispatch "" 15} 16 17proc ::nmea::open_port {port {speed 4800}} { 18 variable nmea 19 if {[info exists nmea(fh)]} { ::nmea::close } 20 set nmea(fh) [open $port] 21 fconfigure $nmea(fh) -mode $speed,n,8,1 -handshake xonxoff -buffering line -translation crlf 22 fileevent $nmea(fh) readable [list ::nmea::read_port $nmea(fh)] 23 return $port 24} 25 26proc ::nmea::open_file {file {rate {}}} { 27 variable nmea 28 if {[info exists nmea(fh)]} { ::nmea::close } 29 set nmea(fh) [open $file] 30 if {[string is integer -strict $rate]} { 31 if {$rate < 0} { set rate 0 } 32 set nmea(rate) $rate 33 } 34 fconfigure $nmea(fh) -buffering line -blocking 0 -translation auto 35 if {$nmea(rate) > 0} { 36 after $nmea(rate) [list ::nmea::read_file $nmea(fh)] 37 } 38 return $file 39} 40 41proc ::nmea::configure_port {settings} { 42 variable nmea 43 fconfigure $nmea(fh) -mode $settings 44} 45 46proc ::nmea::close {} { 47 variable nmea 48 catch {::close $nmea(fh)} 49 unset -nocomplain nmea(fh) 50 foreach x [after info] { 51 if {[lindex [after info $x] 0 0] == "::nmea::read_file"} { 52 after cancel $x 53 } 54 } 55} 56 57proc ::nmea::read_port {f} { 58 if {[catch {gets $f} line] || [eof $f]} { 59 if {[info exists ::nmea::dispatch(EOF)]} { 60 $::nmea::dispatch(EOF) 61 } 62 nmea::close 63 } 64 if {$::nmea::nmea(log) != ""} { 65 puts $::nmea::nmea(log) $line 66 } 67 ::nmea::parse_nmea $line 68} 69 70proc ::nmea::read_file {f {auto 1}} { 71 variable nmea 72 set line [gets $f] 73 if {[eof $f]} { 74 if {[info exists ::nmea::dispatch(EOF)]} { 75 $::nmea::dispatch(EOF) 76 } 77 nmea::close 78 return 0 79 } 80 if {[string match {$*} $line]} { 81 ::nmea::parse_nmea $line 82 } else { 83 ::nmea::parse_nmea \$$line 84 } 85 if {$auto} { 86 after $nmea(rate) [list ::nmea::read_file $f] 87 } 88 return 1 89} 90 91proc ::nmea::do_line {} { 92 variable nmea 93 if {![info exists nmea(fh)]} { return -code error "there is no currently open file" } 94 return [::nmea::read_file $nmea(fh) 0] 95} 96 97proc ::nmea::configure {opt {val {}}} { 98 variable nmea 99 switch -exact -- $opt { 100 rate { 101 if {$val == ""} { return $nmea(rate) } 102 if {![string is integer $val]} { return -code error "rate must be an integer value" } 103 if {$val <= 0} { 104 foreach x [after info] { 105 if {[lindex [after info $x] 0 0] == "::nmea::read_file"} { 106 after cancel $x 107 } 108 } 109 set val 0 110 } 111 if {$nmea(rate) == 0 && $val > 0} { 112 after $val [list ::nmea::read_file $nmea(fh)] 113 } 114 set nmea(rate) $val 115 return $val 116 } 117 checksum { 118 if {$val == ""} { return $nmea(checksum) } 119 if {![string is bool $val]} { return -code error "checksum must be a boolean value" } 120 set nmea(checksum) $val 121 return $val 122 } 123 default { 124 return -code error "unknown option $opt" 125 } 126 } 127} 128 129proc ::nmea::input {sentence} { 130 if {![string match "*,*" $sentence]} { set sentence [join $sentence ,] } 131 if {[string match {$*} $sentence]} { 132 ::nmea::parse_nmea $sentence 133 } else { 134 ::nmea::parse_nmea \$$sentence 135 } 136} 137 138proc ::nmea::log {{file _X}} { 139 variable nmea 140 if {$file == "_X"} { return [expr {$nmea(log) != ""}] } 141 if {$file != ""} { 142 if {$nmea(log) != ""} { ::nmea::log {} } 143 set nmea(log) [open $file a] 144 } else { 145 catch {::close $nmea(log)} 146 set nmea(log) "" 147 } 148 return $file 149} 150 151proc ::nmea::parse_nmea {line} { 152 set line [split $line \$*] 153 set cksum [lindex $line 2] 154 set line [lindex $line 1] 155 if {$cksum == "" || !$::nmea::nmea(checksum) || [checksum $line] == $cksum} { 156 set line [split $line ,] 157 set sentence [lindex $line 0] 158 set line [lrange $line 1 end] 159 if {[info exists ::nmea::dispatch($sentence)]} { 160 $::nmea::dispatch($sentence) $line 161 } elseif {[info exists ::nmea::dispatch(DEFAULT)]} { 162 $::nmea::dispatch(DEFAULT) $sentence $line 163 } 164 } 165} 166 167proc ::nmea::checksum {line} { 168 set sum 0 169 binary scan $line c* line 170 foreach char $line { 171 set sum [expr {$sum ^ ($char % 128)}] 172 } 173 return [format %02X [expr {$sum % 256}]] 174} 175 176proc ::nmea::write {type args} { 177 variable nmea 178 set data $type,[join $args ,] 179 puts $nmea(fh) \$$data*[checksum $data] 180} 181 182proc ::nmea::event {sentence {command _X}} { 183 variable dispatch 184 set sentence [string toupper $sentence] 185 if {$command == "_X"} { 186 if {[info exists dispatch($sentence)]} { 187 return $dispatch($sentence) 188 } 189 return {} 190 } 191 if {$command == ""} { 192 unset -nocomplain dispatch($sentence) 193 return {} 194 } 195 set dispatch($sentence) $command 196 return $command 197} 198