Protocol over new hostside stdin and to multiplexer:
- U< ?... means realtime prints that message without any ?
- when reflected by multiplexer, one of - + &
- is prepended, as follows
- - result of command by another client
- + result of command by us
- & asynchronous
- other prefixes
- = related to this actual connection
+ U< possibly-asynchronous messages from realtime
+ R< ditto, but saved for replay too
+ S messages to simulation log
+
+ C> commands (reflected to other clients)
+ L> commands implemented by multiplexer and not reflected to other clients
+ O< framming output from commands
+
+Messages from the multiplexer to its clients are always prefixed
+with a relevance character, shown as ? below. In messages from
+realtime, the ? is absent. ? may be
+ - result of command by another client
+ + result of command by us
+ & asynchronous, not related to a specific command
+ = related to this actual multiplexer client connection
======================================================================
U< ?picioh in toolong <byte> [<byte>...]
U< ?picioh in msg <byte> [<byte>...]
- U< ?picio out polarity <[<segment>[,...]]> literal < and > bracket segs
+ R< ?picio out polarity <[<segment>[,...]]> literal < and > bracket segs
U< ?picio out unknown data printed in assoc'd picioh
U< ?picio in <messagename> [<objectnum>]
U< ?picio out <messagename> [<objectnum>]
- U< ?picio in-info detect 0|1 <segment-name> decoded `picio in detect[01]'
+ U< ?detect <segment-name> 0|1 decoded `picio in detect[01]'
Suppression (see parse-proto-spec and realtime.c:serial_transmit)
nmra data ping/pong special in other out
U< ?resolving <some message about resolution algorithm>
- S< ?resolution inexplicable <segment>
- S< ?resolution mispositioned head|tail <train> <crush-ending-seg> <distance>
- S< ?resolution movpos-change-failed <segment>/<poscombname>
- S< ?resolution problems <number-of-problems>
+ R< ?resolution inexplicable <segment>
+ R< ?resolution mispositioned head|tail <train> <crush-ending-seg> <distance>
+ R< ?resolution movpos-change-failed <segment>/<poscombname>
+ R< ?resolution problems <number-of-problems>
- S< ?stastate <state>
+ R< ?stastate <state>
U< ?warning <type>[ <arguments>] : <warning message>
U< ?warning watchdog : PIC watchdog timer triggered
U< ?warning spurious <count> : spurious short circuit (fault) etc.
+ U< ?warning realtime-stopped SIG*|exit*|E*|... : <explanation>
+ U< ?warning realtime-stderr : <emsg>
+ U< ?warning realtime-stderr-unavailable : <emsg>
U< ?train <train> signalling-problem ....
U< ?train <train> signalling-problem <problematic-segment> : <message>
- S< ?train <train> forwards|backwards at [-]<segment>:<maxinto>+-<uncertainty>
- S< ?train <train> has <markchar>[-]<segment>[/<movposcomb>]....
+ R< ?train <train> at [-]<segment>:<maxinto>+-<uncertainty> forwards|backwards
+ R< ?train <train> has <markchar>[-]<segment>[/<movposcomb>]....
<markchar> is * for det_expected
! for foredetect
@ for det_ignore
- S< ?movpos <segment> feat <feat><posn> point|relay
- S< ?movpos <segment> position <overallposn> moving
- S< ?movpos <segment> position <overallposn> stable
+ R< ?movpos <segment> feat <feat><posn> point|relay
+ R< ?movpos <segment> position <overallposn> moving
+ R< ?movpos <segment> position <overallposn> stable
<overallposn> may be ? for unknown
U< ?debug <context> : <debug message>
MESSAGES TO SIMULATION LOG
- L picioh in suppressed <byte> [<byte>...]
- L picioh in suppressed-detect <byte> [<byte>...]
- L command-in <command> <args>....
- L timestamp <seconds>.<microseconds>
- L timer-event <class>.<instance>
- L 0|1 detection, same segment as last suppressed-detect
+ S picioh in suppressed <byte> [<byte>...]
+ S picioh in suppressed-detect <byte> [<byte>...]
+ S command-in <command> <args>....
+ S timestamp <seconds>.<microseconds>
+ S timer-event <class>.<instance>
+ S 0|1 detection, same segment as last suppressed-detect
======================================================================
COMMANDS AND RESPONSES
- P> <command> [<arguments>...]
+ C> <command> [<arguments>...]
results in:
- R< ?nak unknown-command|permission-denied|invalid-syntax [: <message...>]
- R< ?executing <command>
+ O< ?nak <errorcode> [<remaining info>] [: <message...>]
+ O< ?executing <command> [<arguments>...]
consequential messages including picio, signalling problems etc.
- then one of these
- R< ?ack <command> ok
- R< ?ack <command> <errorcode>
- R< ?ack <command> <errorcode> [<remaining info>] : <remaining error msg>...
- R< ?ack <command> <errorcode> SignallingPredictedProblem \
+ which may be U< R< then one of these
+ O< ?ack <command> ok
+ O< ?ack <command> <errorcode>
+ O< ?ack <command> <errorcode> [<remaining info>] : <remaining error msg>...
+ O< ?ack <command> <errorcode> SignallingPredictedProblem \
<problematic-train> <problematic-segment>|? : <error message>...
when these come through the multiplexer, everything which is a
MULTIPLEXER FACILITIES
- M< = indicates messages from the multiplexer about this
- M> = specific client connection
-
- M< =connected
- M< =permission normal|super
- or
- M< =denied
+ U< =connected } on
+ U< =permission normal|super } initial
+ or } connection
+ U< =denied }
and later perhaps
- A< =failed : <message> regardless of selected message patterns
- otherwise there is no need to quit - just send eof
+ U< =failed : <message> } regardless of
+ U< =failed client-io : <message> } selected message patterns
+ } and then connection closed
+ There is no need to ask to quit - just send eof
- M> select [~]<pattern>...
+ L> select [~]<pattern>...
first match wins; ~ discards the message.
in pattern
-+./:0-9a-zA-Z match themselves
if no match, it is as if the following patterns were appended
?info ?warning ~-* ~&* ~+debug *
- M> replay [~]<glob-pattern>...
- M< +executing replay
- M< <all saved messages, ie those marked with S< above>
- M< +ack replay ok
+ L> replay [[~]<glob-pattern>...] if none, uses result from select
+ O< +executing replay
+ R< +<all messages saved for replay, ie those marked with R< above,
+ matching the patterns, all of which will be prefixed with + this time>
+ O< +ack replay ok
MULTIPLEXER-IMPLEMENTED FUNCTIONALITY AFFECTING WHOLE SYSTEM
- P> [!]<command> <args>...
- R< ?nak|executing...ack... as above
+ C> [!]<command> <args>...
+ O< ?nak|executing...ack... as above
- C> !realtime kill|finish|start|start-manual
- C> !save
+ C> !realtime kill|start|start-manual
+ C> !dump
======================================================================
DIRECT NMRA AND PIC INSTRUCTIONS
Example (always the same msg):
- P> !nmra [<slot>] [<nmra-command [<nmra-args>...]] nmra speed28 3 13 1
- P> !nmra [<slot>] [=<nmra-bytes>] nmra =0348
- P> !nmra [<slot>] [:<nmra-bytes-with-csum>] nmra :03484b
- P> !nmra [<slot>] [_<pic-literal-bytes>] nmra _7f7f00644197
+ C> !nmra [<slot>] [<nmra-command [<nmra-args>...]] nmra speed28 3 13 1
+ C> !nmra [<slot>] [=<nmra-bytes>] nmra =0348
+ C> !nmra [<slot>] [:<nmra-bytes-with-csum>] nmra :03484b
+ C> !nmra [<slot>] [_<pic-literal-bytes>] nmra _7f7f00644197
in each case <slot> (if present) is *<slotname> or %<slotname> and
indicates that the message should be put in the retransmission
cycle, and cancels any previous message with the same <slotname>.
<slotname> may be empty. * indicates urgent
- P> !nmra <slot>
+ C> !nmra <slot>
cancels the relevant retransmission.
Example (always the same msg):
- P> !pic =<pic-bytes> pic =a003
- P> !pic <pic-command> [<pic-args...>] pic point 3
+ C> !pic =<pic-bytes> pic =a003
+ C> !pic <pic-command> [<pic-args...>] pic point 3
the latter for commands with no `...' in README.protocol only
Keen readers will observe that
-#!/usr/bin/tclsh8.4
+#!/usr/bin/tcl
# per connection:
# c/$conn(super) 0 or 1
# c/$conn(ipaddr)
# [msel/$conn "$msg "] 0 or 1
+# [mreplay/$conn "$msg "] 0 or 1 during replay only
+# c/$conn(q) [list $inputline ...]
#
# globals:
-# $cmdqueue [list $conn $cmd ...]
-# $master socket
-# $permissions [list allow|super|deny $ipaddrhex $maskhex ...]
-
-#---------- i/o to realtime ----------
+# $queueing [list $conn ...]
+# $master socket
+# $permissions [list allow|super|deny $ipaddrhex $maskhex ...]
+# $realtime pipes
+# $replay("$pri $key") $rhs $pri is \d\d; causes replay of "$key $rhs"
+# $detect0($seg) unset -> 1 or irrelevant; [after ...]
+# $conns($conn) 1
+#
+# configs set directly in multiplex-config:
+# $records [list filename.record ...]
+# $detectlag <ms>
+#
+# $conn is in $queueing iff c/$conn(q) is nonempty
+#
+# globals relating to realtime's command execution state:
+# awaiting awaiting executing global
+# idle executing/nak ack internal cmd dead
+#
+# $realtime set set set ? unset
+# $currentcmd unset $cmd $cmd $cmd unset
+# $currentconn unset $conn or {} $conn or {} $conn unset
+# $executing 0 0 1 1 0
+#
+# replay priorities and messages:
+# 10 stastate
+# 50 resolution ....
+# 60 movpos ...
+# train ...
+# 70 detect <seg> 0|1
-proc cmdqueue-check {args} { }
+#---------- replay, general utilities, etc. ----------
proc compile-glob-patterns {pats procname} {
if {[llength $pats] > 20 || [string length $pats] > 200} {
append def { {^[-&]\S|^\+debug\s} { return 0 }} "\n"
append def " }\n"
append def " return 1\n"
- puts $def
proc $procname {m} $def
}
-proc cmd/select {conn args} {
+proc nargs {l {n 0}} {
+ if {[llength $l]!=$n} { cmderr BadCmd "wrong number of arguments" }
+}
+
+proc xmit-relevantly {m} {
+ global executing currentconn conns
+ if {$executing} {
+ puts "<* $m"
+ set myconn $currentconn
+ trapping xmit-only-noreport $currentconn +$m
+ set othersm -$m
+ } else {
+ puts "<& $m"
+ set myconn {}
+ set othersm &$m
+ }
+ foreach conn [array names conns] {
+ if {[string compare $myconn $conn]} {
+ trapping xmit-only-noreport $conn $othersm
+ }
+ }
+}
+
+#---------- multiplexer-implemented command ----------
+
+proc local/select {conn args} {
upvar #0 c/$conn c
compile-glob-patterns $args msel/$conn
}
+proc global/!dump {conn args} {
+ nargs $args
+ if {[catch { save-dump } emsg]} {
+ cmderr HostSupportSystemsProblem "failed to save dump: $emsg"
+ }
+}
+
+proc local/replay {conn args} {
+ global replay
+ if {[llength $args]} {
+ rename msel/$conn mreplay/$conn ;# park it here for a moment
+ compile-glob-patterns $args msel/$conn
+ }
+ foreach pk [lsort [array names replay]] {
+ set m "+[string range $pk 3 end] $replay($pk)"
+ xmit-only $conn $m
+ }
+ if {[llength $args]} {
+ rename mreplay/$conn msel/$conn
+ }
+}
+
+proc global/!realtime {conn args} {
+ nargs $args 1
+ switch -exact [lindex $args 0] {
+ kill { realtime-failed killed "termination requested by command" }
+ start { realtime-start {} }
+ start-manual { realtime-start -m }
+ default { cmderr BadCmd "unknown !realtime subcommand" }
+ }
+}
+
#---------- connection and commands ----------
proc client-inputline {conn l} {
+ global queueing
+ upvar #0 c/$conn c
+ puts "$conn> $l"
+ client-disable-readable $conn
+ if {![llength $c(q)]} { lappend queueing $conn }
+ lappend c(q) $l
+ after idle process-queues
+}
+
+proc process-queues {} {
+ global queueing currentcmd
+ while {![info exists currentcmd] && [llength $queueing]} {
+ set conn [lindex $queueing 0]
+ set queueing [lrange $queueing 1 end]
+
+ upvar #0 c/$conn c
+ if {![llength $c(q)]} continue
+
+ set l [lindex $c(q) 0]
+ set c(q) [lrange $c(q) 1 end]
+ if {[llength $c(q)]} {
+ lappend queueing $conn
+ } else {
+ client-enable-readable $conn
+ }
+
+ trapping process-command $conn $l
+ }
+}
+
+proc process-command {conn l} {
+ global currentcmd currentconn realtime executing
global errorInfo errorCode
- global cmdqueue
upvar #0 c/$conn c
set cmd ?
set r [catch {
set l [string trim $l]
if {![string length $l]} return
if {![regexp {^((!?)[-a-z0-9]+)(?:\s.*)?$} $l dummy cmd priv]} {
- error "improper command name" {} {TRAIN CMDNAK invalid-syntax}
+ error "improper command name" {} {TRAIN CMDNAK BadCmd}
}
if {[regexp {[^ \t!-~]} $l]} {
- error "improper character" {} {TRAIN CMDNAK invalid-syntax}
+ error "improper character" {} {TRAIN CMDNAK BadCmd}
}
if {[string length $priv] && !$c(super)} {
- error "" {} {TRAIN CMDNAK permission-denied}
+ error "" {} {TRAIN CMDNAK PermissionDenied}
}
- if {![catch { info args cmd/$cmd }]} {
- xmit $conn "+executing $cmd"
- eval [list cmd/$cmd $conn] [lrange [split $l] 1 end]
+
+ if {![catch { info args global/$cmd }]} {
+ set currentcmd $cmd
+ set currentconn $conn
+ set executing 1
+ xmit-relevantly "executing $cmd"
+ eval [list global/$cmd $conn] [lrange [split $l] 1 end]
+ } elseif {![catch { info args local/$cmd }]} {
+ xmit-only $conn "+executing $cmd"
+ eval [list local/$cmd $conn] [lrange [split $l] 1 end]
+ } elseif {![info exists realtime]} {
+ error "" {} {TRAIN CMDNAK realtime-not-running}
} else {
- lappend cmdqueue $conn $l
- after idle cmdqueue-check
+ set currentcmd $cmd
+ set currentconn $conn
+ if {[catch {
+ puts ">> $l"
+ puts $realtime $l
+ } emsg]} {
+ if {[string match {POSIX EPIPE *} $errorCode]} {
+ realtime-failed EPIPE ""
+ } elseif {[string match {POSIX *} $errorCode]} {
+ realtime-failed [lindex $errorCode 1] \
+ "write failed: [lindex $errorCode 2]"
+ } else {
+ realtime-failed ?write-[lindex $errorCode 0] \
+ "puts failed ($errorCode): $emsg"
+ }
+ }
+ return
}
} emsg]
if {$r==2} return
- if {$r==0} { xmit $conn "+ack $cmd ok"; return }
+ if {$r==0} {
+ set m "ack $cmd ok"
+ if {$executing} {
+ xmit-relevantly $m
+ set executing 0
+ unset currentconn
+ unset currentcmd
+ } else {
+ xmit-only $conn +$m
+ }
+ return
+ }
switch -glob $errorCode {
{TRAIN REPORTED*} {
}
{TRAIN CMDNAK*} {
set el [concat [list +nack] [lrange $errorCode 2 end]]
if {[string length $emsg]} { append el ": " $emsg }
- xmit $conn "$el"
+ xmit-only $conn "$el"
}
{TRAIN CMDERR*} {
set el [concat [list +ack $cmd] [lrange $errorCode 2 end]]
- xmit $conn "$el : $emsg"
+ xmit-only $conn "$el : $emsg"
}
* {
set ei $errorInfo; set ec $errorCode
}
}
-proc client-eof {conn} { kill-conn $conn "" }
+proc client-eof {conn} {
+ puts "$conn>\$"
+ kill-conn $conn ""
+}
+
+proc client-enable-readable {conn} {
+ fileevent $conn readable [list trapping readable client $conn]
+}
+proc client-disable-readable {conn} {
+ fileevent $conn readable {}
+}
#---------- general IO ----------
if {[eof $conn]} { $whatfor-eof $conn }
}
-proc xmit-always {conn msg} { puts $conn $msg }
+proc xmit-puts {conn msg} {
+ global conns errorInfo
+ if {![info exists conns($conn)]} return
+ if {[catch { puts -nonewline $conn $msg } emsg]} {
+ kill-conn $conn "=failed client-io : $emsg"
+ error $emsg $errorInfo {TRAIN REPORTED}
+ }
+}
-proc xmit {conn msg} {
+proc xmit-only-always {conn msg} {
+ puts "<$conn $msg"
+ xmit-puts $conn "$msg\n"
+}
+proc xmit-only-noreport {conn msg} {
append msg "\n"
- if {[msel/$conn $msg]} { puts -nonewline $conn $msg }
+ if {[msel/$conn $msg]} { xmit-puts $conn $msg }
+}
+proc xmit-only {conn msg} {
+ puts "<$conn $msg"
+ xmit-only-noreport $conn $msg
}
#---------- error handling ----------
proc kill-conn {conn msg} {
+ global conns queueing
upvar #0 c/$conn c
- global cmdqueue
- if {[string length $msg]} { catch { xmit-always $conn $msg } }
+ catch { unset conns($conn) } ;# do this first to stop any recursion
+ puts "<$conn\$ closing : $msg"
+ if {[string length $msg]} { catch { xmit-only-always $conn "$msg" } }
catch { close $conn }
+ if {[set ix [lsearch -exact $queueing $conn]] >= 0} {
+ set queueing [lreplace $queueing $ix $ix]
+ }
catch { unset c }
catch { rename msel/$conn {} }
+ catch { rename mreplay/$conn {} }
set qn {}
- foreach {tconn tl} $cmdqueue {
- if {[string compare $tconn $tl]} { lappend qn $tconn $tl }
- }
set cmdqueue $qn
}
if {![catch { uplevel #0 [list $proc $conn] $args } r]} { return $r }
switch -glob $errorCode {
{TRAIN EXPECTED*} { kill-conn $conn "=failed : $r" }
- {TRAIN SILENT*} { kill-conn $conn }
+ {TRAIN REPORTED*} { kill-conn $conn "" }
* {
report-unexpected {UNEXPECTED ERROR} $r
kill-conn-ierr $conn
}
}
+#---------- realtime subprocess ----------
+
+proc realtime-failed {k m} {
+ global realtime currentcmd currentconn executing
+ global errorInfo errorCode
+ # if $m is "", use wait status
+ if {![string length $m]} {
+ set r [catch {
+ fconfigure $realtime -blocking 1
+ close $realtime
+ } emsg]
+ if {!$r} {
+ set m "unexpectedly closed pipe ?!"
+ set k unexpected
+ } elseif {[string match {CHILDSTATUS*} $errorCode]} {
+ set m "exited with status [lindex $errorCode 2]"
+ set k "exit[lindex $errorCode 2]"
+ } elseif {[string match {CHILDKILLED*} $errorCode]} {
+ set m "killed by signal [lindex $errorCode 3]"
+ set k [lindex $errorCode 2]
+ } else {
+ set m "failed confusingly ($errorCode): $emsg"
+ set k ?wait-[lindex $errorCode 0]
+ }
+ } else {
+ catch { close $realtime }
+ }
+ puts "<<\$ $k : $m"
+ catch { unset realtime }
+ if {[catch {
+ set sef [open +realtime.stderr r]
+ while {[gets $sef l] >= 0} {
+ xmit-relevantly "warning realtime-stderr : $l"
+ }
+ close $sef
+ unset sef
+ } emsg]} {
+ if {![string match {POSIX ENOENT *} $errorCode]} {
+ xmit-relevantly "warning realtime-stderr-unreadable : $emsg"
+ }
+ catch { close $sef }
+ }
+ xmit-relevantly "warning realtime-stopped $k : $m"
+ if {[catch {
+ save-dump
+ } emsg]} {
+ xmit-relevantly "warning dump-failed : $emsg"
+ }
+ if {$executing} {
+ xmit-relevantly "ack $currentcmd SystemFailed realtime : $m"
+ set executing 0
+ } elseif {[info exists currentcmd]} {
+ xmit-relevantly "nak SystemFailed realtime : $m"
+ unset currentcmd
+ unset currentconn
+ }
+ realtime-notrunning-init
+}
+
+proc realtime-notrunning-init {} {
+ realtime-inputline dummy "stastate -" ;# fake this up
+}
+
+proc realtime-start {xopts} {
+ global realtime records
+ if {[info exists realtime]} {
+ cmderr InvalidState "realtime already running"
+ }
+ set cl [concat \
+ [list 2> +realtime.stderr ./realtime -v2] \
+ $xopts $records]
+ if {[catch {
+ set realtime [open |$cl r+]
+ fconfigure $realtime -translation binary -buffering line -blocking 0
+ fileevent $realtime readable [list readable realtime $realtime]
+ } emsg]} {
+ realtime-failed start $emsg
+ error "realtime failed : $emsg" "" {TRAIN REPORTED}
+ }
+}
+
+proc realtime-eof {dummy} { realtime-failed EOF "" }
+
+proc detect0timeout {seg} {
+ global replay
+ upvar #0 detect0($seg) d0
+ unset d0
+ set key "detect $seg"
+ set replay("70 $key") 0
+ xmit-relevantly "$key 0"
+}
+
+proc clear-replay-detects {} {
+ global replay
+ foreach k [array names replay] {
+ if {[regexp {^.. detect } $k]} { unset replay($k) }
+ }
+}
+
+proc realtime-inputline {dummy l} {
+ global detectlag replay
+
+ puts "<< $l"
+
+ if {[regexp {^(detect (\w+)) ([01])$} $l dummy key seg value]} {
+ upvar #0 detect0($seg) d0
+ catch { after cancel $d0 }
+ if {$value} {
+ if {[info exists d0]} { unset d0; return }
+ } else {
+ set d0 [after $detectlag detect0timeout $seg]
+ return
+ }
+ set pri 70
+ } elseif {[regexp {^(stastate) (.*)$} $l dummy key value]} {
+ switch -exact -- $value {
+ Settling { clear-replay-detects }
+ Resolving { catch { unset replay("50 resolution") } }
+ }
+ set pri 10
+ } elseif {[regexp {^(resolution) (.*)$} $l dummy key addvalue]} {
+ set pri 50
+ } elseif {[regexp -expanded {
+ ^( movpos \s \S+ \s (?: feat \s \S+ | position) |
+ train \s \S+ \s (?: has | at ) |
+ picio \s out \s polarity
+ ) \s (.*) $
+ } $l dummy key value]} {
+ set pri 60
+ }
+ if {[info exists key]} {
+ set pk "$pri $key"
+ upvar #0 replay($pk) rep
+ if {[info exists value]} {
+ set rep $value
+ } else {
+ append rep $value
+ }
+ }
+
+ switch -regexp -- $l {
+ {^executing\s|^nak\s} {
+ set executing 1
+ }
+ }
+
+ xmit-relevantly $l
+
+ switch -regexp -- $l {
+ {^ack\s|^nak\s} {
+ set executing 0
+ unset currentcmd
+ unset currentconn
+ after idle process-queues
+ }
+ }
+}
+
#---------- new connections ----------
proc find-permission {ipaddr} {
}
proc connected {conn} {
+ global conns
upvar #0 c/$conn c
fconfigure $conn -blocking 0 -buffering none -translation auto
+ set c(q) {}
+ set conns($conn) 1
set perm [find-permission $c(ipaddr)]
switch -exact $perm {
deny { finally $conn =denied; return }
}
compile-glob-patterns {} msel/$conn
- xmit-always $conn =connected
- xmit-always $conn "=permission [lindex {normal super} $c(super)]"
- fileevent $conn readable [list trapping readable client $conn]
+ xmit-only-always $conn =connected
+ xmit-only-always $conn "=permission [lindex {normal super} $c(super)]"
+ client-enable-readable $conn
}
proc newconn {conn ipaddr port} {
upvar #0 c/$conn c
catch { unset c }
+ puts "$conn new-client $conn $ipaddr $port"
set c(ipaddr) $ipaddr
trapping connected $conn
}
}
proc startup {} {
- global cmdqueue
+ global queueing executing
catch { close $master }; catch { unset master }
- source multiplex-config
- set cmdqueue {}
+ uplevel #0 source multiplex-config
+ set queueing {}
+ set executing 0
+ realtime-notrunning-init
+ commandloop -async -prompt1 { return "% " } -prompt2 { return "> " }
}
startup