diff options
author | Jakob Odersky <jakob@odersky.com> | 2016-04-18 03:15:43 -0700 |
---|---|---|
committer | Jakob Odersky <jakob@odersky.com> | 2016-04-18 03:15:43 -0700 |
commit | 05f2776ccb9989dbc359276c5c36c46d8282fc8f (patch) | |
tree | 931bd6728602694ebefbc52351287d0b08657aba | |
parent | ea468fe2af5dbc674c08e2f2c1d6766e6596f9a1 (diff) | |
download | mavigator-05f2776ccb9989dbc359276c5c36c46d8282fc8f.tar.gz mavigator-05f2776ccb9989dbc359276c5c36c46d8282fc8f.tar.bz2 mavigator-05f2776ccb9989dbc359276c5c36c46d8282fc8f.zip |
Implement serial backend
18 files changed, 1298 insertions, 242 deletions
@@ -1,2 +1,6 @@ +import mavigator.MavigatorBuild + +MavigatorBuild.defaultSettings + //goto main project on load -//onLoad in Global := (Command.process("project mavigator-main", _: State)) compose (onLoad in Global).value +//onLoad in Global := (Command.process("project mavigator-server", _: State)) compose (onLoad in Global).value diff --git a/mavigator-server/build.sbt b/mavigator-server/build.sbt index 89bb0cd..10b59d0 100644 --- a/mavigator-server/build.sbt +++ b/mavigator-server/build.sbt @@ -7,7 +7,50 @@ MavigatorBuild.defaultSettings libraryDependencies ++= Seq( Dependencies.akkaHttp, Dependencies.akkaHttpCore, - Dependencies.akkaStream + Dependencies.akkaStream, + Dependencies.flowNative //FIXME runtime dependencies from uav are not included, is this an sbt bug? ) Js.dependsOnJs(MavigatorBuild.cockpit) + +fork in run := true +connectInput in run := true +cancelable in Global := true + + +/* + * Deployment configuration + */ +enablePlugins(UniversalPlugin, DebianPlugin, DockerPlugin) +enablePlugins(JavaServerAppPackaging) + + +name in Universal := "mavigator" +packageName in Universal := "mavigator" +executableScriptName in Universal := "mavigator" + +name in Linux := (name in Universal).value +packageName in Linux := (packageName in Universal).value +executableScriptName in Linux := (executableScriptName in Universal).value + +maintainer in Linux := "Jakob Odersky <jakob@odersky.com>" +packageSummary in Linux := "Virtual cockpit for drones." +packageDescription in Linux := "Compatible with devices using the MAVLink protocol." + +version in Debian := version.value +debianPackageDependencies in Debian ++= Seq( + "java8-runtime-headless", + "bash (>= 2.05a-11)" +) + +import com.typesafe.sbt.packager.archetypes.ServerLoader +serverLoading in Debian := ServerLoader.Systemd + + +name in Docker := "mavigator" +packageName in Docker := "mavigator" +executableScriptName := "mavigator" +maintainer in Docker := "Jakob Odersky <jakob@odersky.com>" + +dockerBaseImage := "java:8" +dockerExposedPorts += 8080 diff --git a/mavigator-server/src/main/resources/assets/images/hud/attitude.svg b/mavigator-server/src/main/resources/assets/images/hud/attitude.svg index eb7dd70..04d5c71 100644 --- a/mavigator-server/src/main/resources/assets/images/hud/attitude.svg +++ b/mavigator-server/src/main/resources/assets/images/hud/attitude.svg @@ -20,12 +20,13 @@ id="defs4"> <clipPath clipPathUnits="userSpaceOnUse" - id="clipPath4472"> + id="clipPath4744"> <circle - id="circle4474" - cx="0" + r="380" cy="0" - r="380" /> + cx="0" + id="circle4746" + style="fill:#000080" /> </clipPath> </defs> <sodipodi:namedview @@ -35,9 +36,9 @@ borderopacity="1.0" inkscape:pageopacity="0.0" inkscape:pageshadow="2" - inkscape:zoom="1" - inkscape:cx="415.60712" - inkscape:cy="547.77206" + inkscape:zoom="0.70710678" + inkscape:cx="229.45655" + inkscape:cy="461.66458" inkscape:document-units="px" inkscape:current-layer="svg2" showgrid="true" @@ -61,7 +62,7 @@ inkscape:window-x="0" inkscape:window-y="27" inkscape:window-maximized="1" - showguides="false"> + showguides="true"> <inkscape:grid type="xygrid" id="grid4686" /> @@ -84,7 +85,7 @@ clip-path="none"> <g id="g4476" - clip-path="url(#clipPath4472)"> + clip-path="url(#clipPath4744)"> <g id="pitch"> <path @@ -109,11 +110,6 @@ inkscape:connector-curvature="0" /> <path inkscape:connector-curvature="0" - id="path5032" - d="m -30,-350 60,0" - style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> - <path - inkscape:connector-curvature="0" id="path5034" d="m -50,-200 100,0" style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> @@ -138,11 +134,6 @@ d="m -30,250 60,0" style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> <path - style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" - d="m -30,350 60,0" - id="path5044" - inkscape:connector-curvature="0" /> - <path inkscape:connector-curvature="0" id="path5046" d="m -50,100 100,0" @@ -152,11 +143,6 @@ d="m -50,200 100,0" id="path5048" inkscape:connector-curvature="0" /> - <path - inkscape:connector-curvature="0" - id="path5050" - d="m -50,300 100,0" - style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> <text xml:space="preserve" style="font-style:normal;font-weight:normal;font-size:12.5px;line-height:125%;font-family:sans-serif;text-align:start;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" @@ -214,17 +200,6 @@ y="204.55017">20</tspan></text> <text xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:12.5px;line-height:125%;font-family:sans-serif;text-align:start;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" - x="50" - y="304.55017" - id="text5078" - sodipodi:linespacing="125%"><tspan - sodipodi:role="line" - id="tspan5080" - x="50" - y="304.55017">30</tspan></text> - <text - xml:space="preserve" style="font-style:normal;font-weight:normal;font-size:12.5px;line-height:125%;font-family:sans-serif;text-align:end;letter-spacing:0px;word-spacing:0px;text-anchor:end;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" x="-50" y="-295.44983" @@ -278,17 +253,6 @@ id="tspan5100" x="-50" y="204.55017">20</tspan></text> - <text - xml:space="preserve" - style="font-style:normal;font-weight:normal;font-size:12.5px;line-height:125%;font-family:sans-serif;text-align:end;letter-spacing:0px;word-spacing:0px;text-anchor:end;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" - x="-50" - y="304.55017" - id="text5102" - sodipodi:linespacing="125%"><tspan - sodipodi:role="line" - x="-50" - y="304.55017" - id="tspan5106">30</tspan></text> <path inkscape:connector-curvature="0" id="path5110" @@ -397,26 +361,6 @@ d="m -10,-290 20,0" style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> <path - style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" - d="m -10,-340 20,0" - id="path4263" - inkscape:connector-curvature="0" /> - <path - inkscape:connector-curvature="0" - id="path4265" - d="m -10,-330 20,0" - style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> - <path - style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" - d="m -10,-320 20,0" - id="path4267" - inkscape:connector-curvature="0" /> - <path - inkscape:connector-curvature="0" - id="path4269" - d="m -10,-310 20,0" - style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> - <path inkscape:connector-curvature="0" id="path4273" d="m -10,-170 20,0" @@ -578,24 +522,895 @@ inkscape:connector-curvature="0" /> <path inkscape:connector-curvature="0" - id="path4337" + id="path4369" + d="m -30,-350 60,0" + style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + inkscape:connector-curvature="0" + id="path4371" + d="m -50,-400 100,0" + style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <text + sodipodi:linespacing="125%" + id="text4373" + y="-395.44983" + x="50" + style="font-style:normal;font-weight:normal;font-size:12.5px;line-height:125%;font-family:sans-serif;text-align:start;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + xml:space="preserve"><tspan + y="-395.44983" + x="50" + id="tspan4375" + sodipodi:role="line">40</tspan></text> + <text + sodipodi:linespacing="125%" + id="text4377" + y="-395.44983" + x="-50" + style="font-style:normal;font-weight:normal;font-size:12.5px;line-height:125%;font-family:sans-serif;text-align:end;letter-spacing:0px;word-spacing:0px;text-anchor:end;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + xml:space="preserve"><tspan + y="-395.44983" + x="-50" + id="tspan4379" + sodipodi:role="line">40</tspan></text> + <path + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -10,-370 20,0" + id="path4381" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path4383" + d="m -10,-380 20,0" + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + inkscape:connector-curvature="0" + id="path4385" + d="m -10,-360 20,0" + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -10,-340 20,0" + id="path4387" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path4389" + d="m -10,-330 20,0" + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -10,-320 20,0" + id="path4391" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path4393" + d="m -10,-310 20,0" + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -10,-390 20,0" + id="path4395" + inkscape:connector-curvature="0" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -30,-450 60,0" + id="path4397" + inkscape:connector-curvature="0" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -50,-500 100,0" + id="path4399" + inkscape:connector-curvature="0" /> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:12.5px;line-height:125%;font-family:sans-serif;text-align:start;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + x="50" + y="-495.44983" + id="text4401" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan4403" + x="50" + y="-495.44983">50</tspan></text> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:12.5px;line-height:125%;font-family:sans-serif;text-align:end;letter-spacing:0px;word-spacing:0px;text-anchor:end;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + x="-50" + y="-495.44983" + id="text4405" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan4407" + x="-50" + y="-495.44983">50</tspan></text> + <path + inkscape:connector-curvature="0" + id="path4409" + d="m -10,-470 20,0" + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -10,-480 20,0" + id="path4411" + inkscape:connector-curvature="0" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -10,-460 20,0" + id="path4413" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path4415" + d="m -10,-440 20,0" + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -10,-430 20,0" + id="path4417" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path4419" + d="m -10,-420 20,0" + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -10,-410 20,0" + id="path4421" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path4423" + d="m -10,-490 20,0" + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + inkscape:connector-curvature="0" + id="path4425" + d="m -30,-550 60,0" + style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + inkscape:connector-curvature="0" + id="path4427" + d="m -50,-600 100,0" + style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <text + sodipodi:linespacing="125%" + id="text4429" + y="-595.44983" + x="50" + style="font-style:normal;font-weight:normal;font-size:12.5px;line-height:125%;font-family:sans-serif;text-align:start;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + xml:space="preserve"><tspan + y="-595.44983" + x="50" + id="tspan4431" + sodipodi:role="line">60</tspan></text> + <text + sodipodi:linespacing="125%" + id="text4433" + y="-595.44983" + x="-50" + style="font-style:normal;font-weight:normal;font-size:12.5px;line-height:125%;font-family:sans-serif;text-align:end;letter-spacing:0px;word-spacing:0px;text-anchor:end;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + xml:space="preserve"><tspan + y="-595.44983" + x="-50" + id="tspan4435" + sodipodi:role="line">60</tspan></text> + <path + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -10,-570 20,0" + id="path4437" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path4439" + d="m -10,-580 20,0" + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + inkscape:connector-curvature="0" + id="path4441" + d="m -10,-560 20,0" + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -10,-540 20,0" + id="path4443" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path4445" + d="m -10,-530 20,0" + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -10,-520 20,0" + id="path4447" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path4449" + d="m -10,-510 20,0" + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -10,-590 20,0" + id="path4451" + inkscape:connector-curvature="0" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -30,-650 60,0" + id="path4453" + inkscape:connector-curvature="0" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -50,-700 100,0" + id="path4455" + inkscape:connector-curvature="0" /> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:12.5px;line-height:125%;font-family:sans-serif;text-align:start;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + x="50" + y="-695.44983" + id="text4457" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan4459" + x="50" + y="-695.44983">70</tspan></text> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:12.5px;line-height:125%;font-family:sans-serif;text-align:end;letter-spacing:0px;word-spacing:0px;text-anchor:end;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + x="-50" + y="-695.44983" + id="text4461" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan4463" + x="-50" + y="-695.44983">70</tspan></text> + <path + inkscape:connector-curvature="0" + id="path4465" + d="m -10,-670 20,0" + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -10,-680 20,0" + id="path4467" + inkscape:connector-curvature="0" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -10,-660 20,0" + id="path4469" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path4471" + d="m -10,-640 20,0" + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -10,-630 20,0" + id="path4473" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path4475" + d="m -10,-620 20,0" + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -10,-610 20,0" + id="path4477" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path4479" + d="m -10,-690 20,0" + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + inkscape:connector-curvature="0" + id="path4481" + d="m -30,-750 60,0" + style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + inkscape:connector-curvature="0" + id="path4483" + d="m -50,-800 100,0" + style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <text + sodipodi:linespacing="125%" + id="text4485" + y="-795.44983" + x="50" + style="font-style:normal;font-weight:normal;font-size:12.5px;line-height:125%;font-family:sans-serif;text-align:start;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + xml:space="preserve"><tspan + y="-795.44983" + x="50" + id="tspan4487" + sodipodi:role="line">80</tspan></text> + <text + sodipodi:linespacing="125%" + id="text4489" + y="-795.44983" + x="-50" + style="font-style:normal;font-weight:normal;font-size:12.5px;line-height:125%;font-family:sans-serif;text-align:end;letter-spacing:0px;word-spacing:0px;text-anchor:end;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + xml:space="preserve"><tspan + y="-795.44983" + x="-50" + id="tspan4491" + sodipodi:role="line">80</tspan></text> + <path + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -10,-770 20,0" + id="path4493" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path4495" + d="m -10,-780 20,0" + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + inkscape:connector-curvature="0" + id="path4497" + d="m -10,-760 20,0" + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -10,-740 20,0" + id="path4499" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path4501" + d="m -10,-730 20,0" + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -10,-720 20,0" + id="path4503" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path4505" + d="m -10,-710 20,0" + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -10,-790 20,0" + id="path4507" + inkscape:connector-curvature="0" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -30,-850 60,0" + id="path4509" + inkscape:connector-curvature="0" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -50,-900 100,0" + id="path4511" + inkscape:connector-curvature="0" /> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:12.5px;line-height:125%;font-family:sans-serif;text-align:start;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + x="50" + y="-895.44983" + id="text4513" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan4515" + x="50" + y="-895.44983">90</tspan></text> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:12.5px;line-height:125%;font-family:sans-serif;text-align:end;letter-spacing:0px;word-spacing:0px;text-anchor:end;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + x="-50" + y="-895.44983" + id="text4517" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan4519" + x="-50" + y="-895.44983">90</tspan></text> + <path + inkscape:connector-curvature="0" + id="path4521" + d="m -10,-870 20,0" + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -10,-880 20,0" + id="path4523" + inkscape:connector-curvature="0" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -10,-860 20,0" + id="path4525" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path4527" + d="m -10,-840 20,0" + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -10,-830 20,0" + id="path4529" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path4531" + d="m -10,-820 20,0" + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -10,-810 20,0" + id="path4533" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path4535" + d="m -10,-890 20,0" + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -30,350 60,0" + id="path4565" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path4567" + d="m -50,300 100,0" + style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <text + sodipodi:linespacing="125%" + id="text4569" + y="304.55017" + x="50" + style="font-style:normal;font-weight:normal;font-size:12.5px;line-height:125%;font-family:sans-serif;text-align:start;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + xml:space="preserve"><tspan + y="304.55017" + x="50" + id="tspan4571" + sodipodi:role="line">30</tspan></text> + <text + sodipodi:linespacing="125%" + id="text4573" + y="304.55017" + x="-50" + style="font-style:normal;font-weight:normal;font-size:12.5px;line-height:125%;font-family:sans-serif;text-align:end;letter-spacing:0px;word-spacing:0px;text-anchor:end;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + xml:space="preserve"><tspan + y="304.55017" + x="-50" + id="tspan4575" + sodipodi:role="line">30</tspan></text> + <path + inkscape:connector-curvature="0" + id="path4577" d="m -10,330 20,0" style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> <path style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" d="m -10,320 20,0" - id="path4339" + id="path4579" inkscape:connector-curvature="0" /> <path style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" d="m -10,340 20,0" - id="path4341" + id="path4581" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path4583" + d="m -10,360 20,0" + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -10,370 20,0" + id="path4585" inkscape:connector-curvature="0" /> <path inkscape:connector-curvature="0" - id="path4351" + id="path4587" + d="m -10,380 20,0" + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -10,390 20,0" + id="path4589" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path4591" d="m -10,310 20,0" style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + inkscape:connector-curvature="0" + id="path4593" + d="m -30,450 60,0" + style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -50,400 100,0" + id="path4595" + inkscape:connector-curvature="0" /> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:12.5px;line-height:125%;font-family:sans-serif;text-align:start;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + x="50" + y="404.55017" + id="text4597" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan4599" + x="50" + y="404.55017">40</tspan></text> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:12.5px;line-height:125%;font-family:sans-serif;text-align:end;letter-spacing:0px;word-spacing:0px;text-anchor:end;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + x="-50" + y="404.55017" + id="text4601" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan4603" + x="-50" + y="404.55017">40</tspan></text> + <path + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -10,430 20,0" + id="path4605" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path4607" + d="m -10,420 20,0" + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + inkscape:connector-curvature="0" + id="path4609" + d="m -10,440 20,0" + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -10,460 20,0" + id="path4611" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path4613" + d="m -10,470 20,0" + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -10,480 20,0" + id="path4615" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path4617" + d="m -10,490 20,0" + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -10,410 20,0" + id="path4619" + inkscape:connector-curvature="0" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -30,550 60,0" + id="path4621" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path4623" + d="m -50,500 100,0" + style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <text + sodipodi:linespacing="125%" + id="text4625" + y="504.55017" + x="50" + style="font-style:normal;font-weight:normal;font-size:12.5px;line-height:125%;font-family:sans-serif;text-align:start;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + xml:space="preserve"><tspan + y="504.55017" + x="50" + id="tspan4627" + sodipodi:role="line">50</tspan></text> + <text + sodipodi:linespacing="125%" + id="text4629" + y="504.55017" + x="-50" + style="font-style:normal;font-weight:normal;font-size:12.5px;line-height:125%;font-family:sans-serif;text-align:end;letter-spacing:0px;word-spacing:0px;text-anchor:end;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + xml:space="preserve"><tspan + y="504.55017" + x="-50" + id="tspan4631" + sodipodi:role="line">50</tspan></text> + <path + inkscape:connector-curvature="0" + id="path4633" + d="m -10,530 20,0" + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -10,520 20,0" + id="path4635" + inkscape:connector-curvature="0" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -10,540 20,0" + id="path4637" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path4639" + d="m -10,560 20,0" + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -10,570 20,0" + id="path4641" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path4643" + d="m -10,580 20,0" + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -10,590 20,0" + id="path4645" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path4647" + d="m -10,510 20,0" + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + inkscape:connector-curvature="0" + id="path4649" + d="m -30,650 60,0" + style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -50,600 100,0" + id="path4651" + inkscape:connector-curvature="0" /> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:12.5px;line-height:125%;font-family:sans-serif;text-align:start;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + x="50" + y="604.55017" + id="text4653" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan4655" + x="50" + y="604.55017">60</tspan></text> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:12.5px;line-height:125%;font-family:sans-serif;text-align:end;letter-spacing:0px;word-spacing:0px;text-anchor:end;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + x="-50" + y="604.55017" + id="text4657" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan4659" + x="-50" + y="604.55017">60</tspan></text> + <path + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -10,630 20,0" + id="path4661" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path4663" + d="m -10,620 20,0" + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + inkscape:connector-curvature="0" + id="path4665" + d="m -10,640 20,0" + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -10,660 20,0" + id="path4667" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path4669" + d="m -10,670 20,0" + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -10,680 20,0" + id="path4671" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path4673" + d="m -10,690 20,0" + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -10,610 20,0" + id="path4675" + inkscape:connector-curvature="0" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -30,750 60,0" + id="path4677" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path4679" + d="m -50,700 100,0" + style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <text + sodipodi:linespacing="125%" + id="text4681" + y="704.55017" + x="50" + style="font-style:normal;font-weight:normal;font-size:12.5px;line-height:125%;font-family:sans-serif;text-align:start;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + xml:space="preserve"><tspan + y="704.55017" + x="50" + id="tspan4683" + sodipodi:role="line">70</tspan></text> + <text + sodipodi:linespacing="125%" + id="text4685" + y="704.55017" + x="-50" + style="font-style:normal;font-weight:normal;font-size:12.5px;line-height:125%;font-family:sans-serif;text-align:end;letter-spacing:0px;word-spacing:0px;text-anchor:end;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + xml:space="preserve"><tspan + y="704.55017" + x="-50" + id="tspan4687" + sodipodi:role="line">70</tspan></text> + <path + inkscape:connector-curvature="0" + id="path4689" + d="m -10,730 20,0" + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -10,720 20,0" + id="path4691" + inkscape:connector-curvature="0" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -10,740 20,0" + id="path4693" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path4695" + d="m -10,760 20,0" + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -10,770 20,0" + id="path4697" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path4699" + d="m -10,780 20,0" + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -10,790 20,0" + id="path4701" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path4703" + d="m -10,710 20,0" + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + inkscape:connector-curvature="0" + id="path4705" + d="m -30,850 60,0" + style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -50,800 100,0" + id="path4707" + inkscape:connector-curvature="0" /> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:12.5px;line-height:125%;font-family:sans-serif;text-align:start;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + x="50" + y="804.55017" + id="text4709" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan4711" + x="50" + y="804.55017">80</tspan></text> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:12.5px;line-height:125%;font-family:sans-serif;text-align:end;letter-spacing:0px;word-spacing:0px;text-anchor:end;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + x="-50" + y="804.55017" + id="text4713" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan4715" + x="-50" + y="804.55017">80</tspan></text> + <path + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -10,830 20,0" + id="path4717" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path4719" + d="m -10,820 20,0" + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + inkscape:connector-curvature="0" + id="path4721" + d="m -10,840 20,0" + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -10,860 20,0" + id="path4723" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path4726" + d="m -10,870 20,0" + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -10,880 20,0" + id="path4728" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path4730" + d="m -10,890 20,0" + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -10,810 20,0" + id="path4732" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path4734" + d="m -50,900 100,0" + style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <text + sodipodi:linespacing="125%" + id="text4736" + y="904.55017" + x="-50" + style="font-style:normal;font-weight:normal;font-size:12.5px;line-height:125%;font-family:sans-serif;text-align:end;letter-spacing:0px;word-spacing:0px;text-anchor:end;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + xml:space="preserve"><tspan + y="904.55017" + x="-50" + id="tspan4738" + sodipodi:role="line">90</tspan></text> + <text + sodipodi:linespacing="125%" + id="text4740" + y="904.55017" + x="50" + style="font-style:normal;font-weight:normal;font-size:12.5px;line-height:125%;font-family:sans-serif;text-align:start;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + xml:space="preserve"><tspan + y="904.55017" + x="50" + id="tspan4742" + sodipodi:role="line">90</tspan></text> </g> </g> <text @@ -783,6 +1598,12 @@ <g id="static"> <path + sodipodi:nodetypes="cc" + inkscape:connector-curvature="0" + id="path4757" + style="fill:none;fill-rule:evenodd;stroke:#ffdd55;stroke-width:3;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="m 385,0 70,0" /> + <path inkscape:transform-center-y="-390" inkscape:transform-center-x="-0.0686585" sodipodi:nodetypes="cccc" @@ -807,9 +1628,16 @@ d="m -103.52762,-386.37033 -6.28209,22.3888 23.049582,-6.17612 z" style="fill:#fbff00;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> <path - d="M 30,0 150,0 M -30,0 -150,0 M 0,0 15,25 30,0 15,25 0,0 -15,25 -15,25 -30,0" + d="M 30,0 60,0 M -30,0 -60,0 M 0,0 15,15 30,0 15,15 0,0 -15,15 -15,15 -30,0" style="fill:none;fill-rule:evenodd;stroke:#ffdd55;stroke-width:3;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" id="path4991" - inkscape:connector-curvature="0" /> + inkscape:connector-curvature="0" + sodipodi:nodetypes="cccccccccccc" /> + <path + d="m -455,0 70,0" + style="fill:none;fill-rule:evenodd;stroke:#ffdd55;stroke-width:3;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path4759" + inkscape:connector-curvature="0" + sodipodi:nodetypes="cc" /> </g> </svg> diff --git a/mavigator-server/src/main/resources/assets/images/hud/horizon.svg b/mavigator-server/src/main/resources/assets/images/hud/horizon.svg index 8cf0150..36d0300 100644 --- a/mavigator-server/src/main/resources/assets/images/hud/horizon.svg +++ b/mavigator-server/src/main/resources/assets/images/hud/horizon.svg @@ -9,13 +9,13 @@ xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" - width="1200" - height="1200" + width="1000" + height="1000" id="svg2" version="1.1" - inkscape:version="0.48.5 r10040" + inkscape:version="0.91 r13725" sodipodi:docname="horizon.svg" - viewBox="-600 -600 1200 1200" + viewBox="-500 -500 1000 1000" preserveAspectRatio="none"> <defs id="defs4" /> @@ -26,15 +26,15 @@ borderopacity="1.0" inkscape:pageopacity="0.0" inkscape:pageshadow="2" - inkscape:zoom="0.35374999" - inkscape:cx="356.86783" - inkscape:cy="585.56122" + inkscape:zoom="0.176875" + inkscape:cx="417.16915" + inkscape:cy="602.15446" inkscape:document-units="px" - inkscape:current-layer="horizon" + inkscape:current-layer="svg2" showgrid="true" showborder="true" inkscape:window-width="1920" - inkscape:window-height="1033" + inkscape:window-height="1034" inkscape:window-x="0" inkscape:window-y="27" inkscape:window-maximized="1"> @@ -58,20 +58,23 @@ </rdf:RDF> </metadata> <g - id="horizon"> - <rect - style="fill:#c88f00;fill-opacity:1;stroke:none" - id="rect3000" - width="1800" - height="1200" - x="-900" - y="0" /> - <rect - y="-1200" - x="-900" - height="1200" - width="1800" - id="rect3770" - style="fill:#aaeeff;fill-opacity:1;stroke:none" /> + id="roll"> + <g + id="pitch"> + <rect + y="0" + x="-1000" + height="2000" + width="2000" + id="ground" + style="fill:#c88f00;fill-opacity:1;stroke:none" /> + <rect + style="fill:#aaeeff;fill-opacity:1;stroke:none" + id="sky" + width="2000" + height="2000" + x="-1000" + y="-2000" /> + </g> </g> </svg> diff --git a/mavigator-server/src/main/scala/mavigator/Main.scala b/mavigator-server/src/main/scala/mavigator/Main.scala index 6ea894e..3ad1460 100644 --- a/mavigator-server/src/main/scala/mavigator/Main.scala +++ b/mavigator-server/src/main/scala/mavigator/Main.scala @@ -1,11 +1,12 @@ package mavigator +import scala.concurrent.{Await, TimeoutException} +import scala.concurrent.duration._ + import akka.actor._ import akka.http.scaladsl._ -import akka.http.scaladsl.server._ import akka.stream._ -import scala.concurrent.Await -import scala.concurrent.duration.Duration +import mavigator.uav.Uav object Main { @@ -15,30 +16,33 @@ object Main { def main(args: Array[String]): Unit = { import system.dispatcher - system.log.info("System started.") + val route = Router.route - val router = Router.route + system.log.info(s"Initializing UAV connection backend...") + Uav().init() - system.log.info(s"Starting server") - val binding = Http(system).bindAndHandle(router, "0.0.0.0", 8080) + system.log.info(s"Starting server...") + val binding = Http(system).bindAndHandle(route, "::", 8080) for (b <- binding) { val addr = b.localAddress.getHostString() val port = b.localAddress.getPort() system.log.info(s"Server is listening on $addr:$port") } - - scala.io.StdIn.readLine() - - binding.flatMap{b => - system.log.info("Shutting down server...") - b.unbind() - }.onComplete{ _ => - system.log.info("Server shut down") - system.terminate() - } - Await.result(system.whenTerminated, Duration.Inf) + sys.addShutdownHook { stop() } + } + def stop() = { + system.log.info("Stopping server...") + system.terminate() + + try { + Await.result(system.whenTerminated, 2.seconds) + System.err.println("Bye.") + } catch { + case ex: TimeoutException => + System.err.println("Shutdown is taking too long, exiting now") + } } } diff --git a/mavigator-server/src/main/scala/mavigator/Router.scala b/mavigator-server/src/main/scala/mavigator/Router.scala index 389cb4d..226d6c1 100644 --- a/mavigator-server/src/main/scala/mavigator/Router.scala +++ b/mavigator-server/src/main/scala/mavigator/Router.scala @@ -1,80 +1,92 @@ package mavigator import akka.actor._ -import akka.stream._ -import akka.stream.scaladsl._ -import akka.http.scaladsl._ +import akka.http.scaladsl.marshalling.{Marshaller, ToEntityMarshaller} +import akka.http.scaladsl.model.MediaTypes._ import akka.http.scaladsl.model._ +import akka.http.scaladsl.model.Uri.Path import akka.http.scaladsl.model.ws._ import akka.http.scaladsl.server._ -import uav.Uav import akka.util._ - -import akka.http.scaladsl.marshalling.{Marshaller, ToEntityMarshaller} -import akka.http.scaladsl.model.MediaTypes._ -import akka.http.scaladsl.model.MediaType -import play.twirl.api.{ Xml, Txt, Html } +import akka.stream.scaladsl._ +import play.twirl.api.Html +import uav.Uav object Router { import Directives._ - val socketUrl = "ws://localhost:8080/mavlink" + final val SocketEndpoint = "mavlink" + + def withSocketUri: Directive1[Uri] = extractUri.map { uri => + uri.withScheme("ws").withPath(Path.Empty / SocketEndpoint) + } def route(implicit system: ActorSystem): Route = ( - path("cockpit" / IntNumber) { id => + path("whoami") { get { - val html = mavigator.views.html.app( - "Mavigator", - "mavigator_cockpit_Main", - Map( - "socketUrl" -> socketUrl, - "remoteSystemId" -> "0", - "systemId" -> "0", - "componentId" -> "0" - ) - ) - complete(html) + withSocketUri { sock => + complete(sock.toString) + } } } ~ - path("mavlink") { - get { - val fromWebSocket = Flow[Message].collect{ - case BinaryMessage.Strict(data) => data + path("cockpit" / IntNumber) { id => + get { + withSocketUri { socket => + val html = mavigator.views.html.app( + "Mavigator", + "mavigator_cockpit_Main", + Map( + "socketUrl" -> socket.toString, + "remoteSystemId" -> id.toString + ) + ) + complete(html) + } } + } ~ + path(SocketEndpoint) { + get { + val fromWebSocket = Flow[Message].collect{ + case BinaryMessage.Strict(data) => data + } - val toWebSocket = Flow[ByteString].map{bytes => - BinaryMessage(bytes) - } + val toWebSocket = Flow[ByteString].map{bytes => + BinaryMessage(bytes) + } - val backend = Uav().connect() + val backend = Uav().connect() - handleWebSocketMessages(fromWebSocket via backend via toWebSocket) - } - } ~ - pathPrefix("assets") { - get { - encodeResponse { - getFromResourceDirectory("assets") + handleWebSocketMessages(fromWebSocket via backend via toWebSocket) + } + } ~ + pathEndOrSingleSlash { + get { + withSocketUri { socket => + val html = mavigator.views.html.app( + "Index", + "mavigator_index_Main", + Map( + "socketUrl" -> socket.toString + ) + ) + complete(html) + } + } + } ~ + pathPrefix("assets") { + get { + encodeResponse { + getFromResourceDirectory("assets") + } } } - } ~ - pathEndOrSingleSlash { - get { - val html = mavigator.views.html.app( - "Index", - "mavigator_index_Main", - Map( - "socketUrl" -> socketUrl - ) - ) - complete(html) - } - } ) - implicit val twirlHtml : ToEntityMarshaller[Html] = Marshaller.StringMarshaller.wrap(`text/html`){(h: Html) => - h.toString - } - + /** Enables completing requests with html. */ + implicit val twirlHtml : ToEntityMarshaller[Html] = + Marshaller.StringMarshaller.wrap(`text/html`){ h: Html => + h.toString + } + } diff --git a/mavigator-uav/build.sbt b/mavigator-uav/build.sbt index abe935b..c2b97c6 100644 --- a/mavigator-uav/build.sbt +++ b/mavigator-uav/build.sbt @@ -7,5 +7,6 @@ libraryDependencies ++= Seq( Dependencies.akkaStream, Dependencies.flow, Dependencies.flowNative, + Dependencies.flowStream, Dependencies.reactiveStreams ) diff --git a/mavigator-uav/src/main/resources/reference.conf b/mavigator-uav/src/main/resources/reference.conf index 3b43d37..5e66de3 100644 --- a/mavigator-uav/src/main/resources/reference.conf +++ b/mavigator-uav/src/main/resources/reference.conf @@ -1,30 +1,25 @@ # Settings related to the connection with a UAV mavigator.uav { + # The type of connection to use # 'mock' or 'serial' - type = mock - - # Mavlink component ID used by this connection, - # in case it needs to inject messages. I.e. heartbeats - # will originate from this ID. - component_id = 1 - - # Interval in milliseconds between heartbeat messages injected by - # the connection - # 0 = no heartbeats injected - heartbeat = 2000 + type = serial # Settings related to serial connections serial { # Serial port - port = "/dev/ttyUSB0" + port = "/dev/ttyACM0" # Baud rate (b/s) - baud = 115200 + baud = 57600 # Use two stop bits two_stop_bits = false # Parity check # 0 = None, 1 = Odd, 2 = Even parity = 0 + + # Delay between detection of serial port and attempt to open it. + # Set this to provide time for the device to initialise. + connection_delay = 1000 } # Settings related to mock connections @@ -32,7 +27,7 @@ mavigator.uav { # Mavlink system ID of the simulated UAV remote_system_id = 42 # Mavlink component ID of the simulated UAV - remote_system_id = 0 + remote_component_id = 0 # Divide simulated message frequency prescaler = 1 } diff --git a/mavigator-uav/src/main/scala/mavigator/uav/Backend.scala b/mavigator-uav/src/main/scala/mavigator/uav/Backend.scala new file mode 100644 index 0000000..84b9673 --- /dev/null +++ b/mavigator-uav/src/main/scala/mavigator/uav/Backend.scala @@ -0,0 +1,8 @@ +package mavigator +package uav + +trait Backend { + + def init(core: Core): Unit + +} diff --git a/mavigator-uav/src/main/scala/mavigator/uav/Core.scala b/mavigator-uav/src/main/scala/mavigator/uav/Core.scala new file mode 100644 index 0000000..4418dae --- /dev/null +++ b/mavigator-uav/src/main/scala/mavigator/uav/Core.scala @@ -0,0 +1,100 @@ +package mavigator +package uav + +import akka.NotUsed +import akka.actor.{ActorLogging, ActorRef, ActorSystem, Props} +import akka.stream.Materializer +import akka.stream.actor.{ActorPublisher, ActorPublisherMessage} +import akka.stream.scaladsl.{Flow, Sink, Source} +import akka.util.ByteString +import org.reactivestreams.{Publisher, Subscriber} + +/** A core enables dynamic creation and removal of clients and backend connections. + * It is essentially a dynamic stream multiplexer. */ +class Core(implicit val system: ActorSystem, val materializer: Materializer) { + import Core._ + + /** The actor responsible for forwarding messages from the backend + * to this core. */ + private lazy val backendPublisherActor: ActorRef = system.actorOf(BackendPublisher()) + + /** Publisher that forwards messages received from the backend. */ + private lazy val backendPublisher: Publisher[ByteString] = ActorPublisher(backendPublisherActor) + + private lazy val backend = Flow.fromSinkAndSourceMat( + Sink.ignore, //FIXME: this will need to be changed for controlling the uav from the browser + Source.fromPublisher(backendPublisher) + )((toBackend, fromBackend) => (toBackend, fromBackend)) + + private lazy val clients = Flow.fromSinkAndSourceMat( + Sink.asPublisher[ByteString](true), + Source.asSubscriber[ByteString] + )((toClient, fromClient) => (toClient, fromClient)) + + private lazy val runnable = clients.joinMat(backend){ case ((toClient, fromClient), (toBackend, fromBackend)) => + toClient + } + + private lazy val clientPublisher: Publisher[ByteString] = { + system.log.info("Core started.") + runnable.run() + } + + def setBackend(): Flow[ByteString, ByteString, NotUsed] = { + val sink = Sink.actorRef(backendPublisherActor, BackendPublisher.BackendComplete) + val source = Source.empty[ByteString] //FIXME: this will need to be changed for controlling the uav from the browser + Flow.fromSinkAndSource(sink, source) + } + + def connect(): Flow[ByteString, ByteString, NotUsed] = { + Flow.fromSinkAndSource( + Sink.ignore, + Source.fromPublisher(clientPublisher) + ) + } + +} + +object Core { + + private class BackendPublisher extends ActorPublisher[ByteString] with ActorLogging { + import akka.stream.actor.ActorPublisherMessage._ + + override def preStart() = { + log.info("Starting backend publisher actor...") + } + + private var fromBackend: ByteString = null + + def receive = { + + case msg: ByteString => + fromBackend = msg + deliver() + + case BackendPublisher.BackendComplete => + sys.error("Backend completed normally, this should not happen.") + + // subscriber requests more + case ActorPublisherMessage.Request(_) => + deliver() + + //subscriber cancels + case ActorPublisherMessage.Cancel => + sys.error("Subscriber cancelled backend stream, this should not happen.") + + } + + def deliver() = if (fromBackend != null && totalDemand > 0 && isActive) { + onNext(fromBackend) + fromBackend = null + } + + } + + private object BackendPublisher { + case object BackendComplete + def apply(): Props = Props(classOf[BackendPublisher]) + } + +} diff --git a/mavigator-uav/src/main/scala/mavigator/uav/Multiplexer.scala.disabled b/mavigator-uav/src/main/scala/mavigator/uav/Multiplexer.scala.disabled deleted file mode 100644 index 5e48ea1..0000000 --- a/mavigator-uav/src/main/scala/mavigator/uav/Multiplexer.scala.disabled +++ /dev/null @@ -1,20 +0,0 @@ -package mavigator -package uav - -import akka.stream.scaladsl._ -import akka.stream._ -import org.reactivestreams._ - -private[uav] class Multiplexer[In, Out](service: Flow[In, Out, _])(implicit materializer: Materializer) { - - private val endpoint: Flow[Out, In, (Publisher[Out], Subscriber[In])] = Flow.fromSinkAndSourceMat( - Sink.asPublisher[Out](fanout = true), - Source.asSubscriber[In])((pub, sub) => (pub, sub)) - - private lazy val (publisher, subscriber) = (service.joinMat(endpoint)(Keep.right)).run() - - def connect(client: Flow[Out, In, _]) = { - Source.fromPublisher(publisher).via(client).to(Sink.ignore).run() - } - -} diff --git a/mavigator-uav/src/main/scala/mavigator/uav/Uav.scala b/mavigator-uav/src/main/scala/mavigator/uav/Uav.scala index 8f8c083..1387983 100644 --- a/mavigator-uav/src/main/scala/mavigator/uav/Uav.scala +++ b/mavigator-uav/src/main/scala/mavigator/uav/Uav.scala @@ -2,40 +2,38 @@ package mavigator package uav import java.lang.IllegalArgumentException -import mock._ + import akka._ import akka.actor._ -import akka.util._ +import akka.stream.ActorMaterializer import akka.stream.scaladsl._ +import akka.util._ +import mock._ +import serial._ + +//TODO: the whole backend system feels hacky, it probably needs a major redesign class Uav(system: ExtendedActorSystem) extends Extension { + private val materializer = ActorMaterializer()(system) + private lazy val config = system.settings.config.getConfig("mavigator.uav") private lazy val tpe = config.getString("type") - private lazy val componentId = config.getInt("componentId").toByte - private lazy val heartbeat = config.getInt("heartbeat") - private lazy val connection = config.getConfig(tpe) - lazy val source = tpe match { - case "mock" => - new MockConnection( - connection.getInt("remote_system_id").toByte, - componentId, - connection.getDouble("prescaler") - ) - - case "serial" => ??? + private lazy val core = new Core()(system, materializer) + lazy val backend: Backend = tpe match { + case "mock" => MockBackend + case "serial" => SerialBackend case _ => throw new IllegalArgumentException(s"Unsupported connection type: $tpe") } - def connect(): Flow[ByteString, ByteString, NotUsed] = { - Flow.fromSinkAndSource( - Sink.ignore, - (new MockConnection(0,0,1)).data //TODO: use source instead of hardcoded value - ) + def init(): Unit = { + backend.init(core) } + def connect(): Flow[ByteString, ByteString, NotUsed] = core.connect() + } object Uav extends ExtensionId[Uav] with ExtensionIdProvider { diff --git a/mavigator-uav/src/main/scala/mavigator/uav/mock/MockConnection.scala b/mavigator-uav/src/main/scala/mavigator/uav/mock/MockBackend.scala index 58b4977..0b89fbd 100644 --- a/mavigator-uav/src/main/scala/mavigator/uav/mock/MockConnection.scala +++ b/mavigator-uav/src/main/scala/mavigator/uav/mock/MockBackend.scala @@ -10,15 +10,16 @@ import akka.stream.Attributes._ import akka.stream.scaladsl._ import akka.util._ import org.mavlink._ -import org.mavlink.messages.{Heartbeat, Message} +import org.mavlink.messages.Message -class MockConnection( +/** A test connection that produces random MAVLink messages. */ +class MockBackend( remoteSystemId: Byte, remoteComponentId: Byte, prescaler: Double ) { - import MockConnection._ + import MockBackend._ private lazy val assembler = new Assembler(remoteSystemId, remoteComponentId) @@ -35,7 +36,7 @@ class MockConnection( delayed(0.1)(_.distance) ) - val data: Source[ByteString, NotUsed] = messages.map{ message => + private val data: Source[ByteString, NotUsed] = messages.map{ message => val (messageId, payload) = Message.pack(message) val packet = assembler.assemble(messageId, payload) ByteString(packet.toArray) @@ -43,7 +44,7 @@ class MockConnection( } -object MockConnection { +object MockBackend extends Backend { final val ClockTick: FiniteDuration = 0.02.seconds @@ -52,7 +53,7 @@ object MockConnection { Source.fromGraph(GraphDSL.create() { implicit b => val clock = Source.tick(ClockTick, ClockTick, plan) map { plan => - plan.tick(0.01) + plan.tick(ClockTick.toMillis / 1000.0) plan } val bcast = b.add(Broadcast[RandomFlightPlan](messages.length)) @@ -67,4 +68,28 @@ object MockConnection { }) } + + override def init(core: Core) = { + import core.materializer + import core.system + + system.log.info("Initializing mock backend...") + + val config = system.settings.config.getConfig("mavigator.uav.mock") + + val mock = new MockBackend( + config.getInt("remote_system_id").toByte, + config.getInt("remote_component_id").toByte, + config.getDouble("prescaler") + ) + + val mockFlow = Flow.fromSinkAndSource( + Sink.ignore, + mock.data + ) + + (mockFlow join core.setBackend()).run() + + } + } diff --git a/mavigator-uav/src/main/scala/mavigator/uav/serial/SerialBackend.scala b/mavigator-uav/src/main/scala/mavigator/uav/serial/SerialBackend.scala new file mode 100644 index 0000000..a52d238 --- /dev/null +++ b/mavigator-uav/src/main/scala/mavigator/uav/serial/SerialBackend.scala @@ -0,0 +1,58 @@ +package mavigator +package uav +package serial + +import scala.concurrent.Future +import scala.concurrent.duration._ +import scala.util.{Failure, Success} + +import akka.NotUsed +import akka.stream.scaladsl.{Flow, Keep} +import akka.util.ByteString +import com.github.jodersky.flow.{Parity, SerialSettings} +import com.github.jodersky.flow.stream.Serial +import com.github.jodersky.flow.stream.Serial.Connection + +object SerialBackend extends Backend { + + override def init(core: Core): Unit = { + import core.materializer + import core.system + import core.system.dispatcher + + system.log.info("Initializing serial backend...") + + val conf = system.settings.config.getConfig("mavigator.uav.serial") + val port = conf.getString("port") + val serialSettings = SerialSettings( + baud = conf.getInt("baud"), + twoStopBits = conf.getBoolean("two_stop_bits"), + parity = Parity(conf.getInt("parity")) + ) + + val connectionDelay = conf.getInt("connection_delay").millis + + system.log.info("Waiting for serial device on " + port + "...") + Serial().watch(Set(port)).map{ port => + system.log.info("Serial device connected on port " + port) + port + }.delay(connectionDelay).runForeach{ port => + system.log.info("Opening serial port " + port) + + val backend: Flow[ByteString, ByteString, NotUsed] = core.setBackend() + + val uav: Flow[ByteString, ByteString, Future[Connection]] = Serial().open(port, serialSettings) + + val connection = uav.joinMat(backend)(Keep.left).run().onComplete{ + case Success(connection) => + system.log.info("Successfully opened serial port " + connection.port) + case Failure(ex) => + system.log.error(ex, "Error occurred while trying to open " + port) + } + + } + + + } + +} diff --git a/project/Dependencies.scala b/project/Dependencies.scala index ba39c94..60e7410 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -6,7 +6,7 @@ import org.scalajs.sbtplugin.ScalaJSPlugin.autoImport._ object Dependencies { - final val AkkaVersion = "2.4.2-RC1" + final val AkkaVersion = "2.4.4" val akkaActor = "com.typesafe.akka" %% "akka-actor" % AkkaVersion val akkaHttp = "com.typesafe.akka" %% "akka-http-experimental" % AkkaVersion val akkaHttpCore = "com.typesafe.akka" %% "akka-http-core" % AkkaVersion @@ -14,10 +14,10 @@ object Dependencies { val reactiveStreams = "org.reactivestreams" % "reactive-streams" % "1.0.0" - final val FlowVersion = "2.5.0-M2" + final val FlowVersion = "2.6.0" val flow = "com.github.jodersky" %% "flow-core" % FlowVersion val flowNative = "com.github.jodersky" % "flow-native" % FlowVersion % Runtime - val flowStream = "com.github.jodersky" % "flow-stream" % FlowVersion + val flowStream = "com.github.jodersky" %% "flow-stream" % FlowVersion val jsDom = Def.setting{"org.scala-js" %%% "scalajs-dom" % "0.9.0"} val scalatags = Def.setting{"com.lihaoyi" %%% "scalatags" % "0.5.4"} diff --git a/project/MavigatorBuild.scala b/project/MavigatorBuild.scala index e46d5f2..f6c0884 100644 --- a/project/MavigatorBuild.scala +++ b/project/MavigatorBuild.scala @@ -8,7 +8,8 @@ object MavigatorBuild extends Build { // settings common to all projects val defaultSettings = Seq( - scalaVersion := "2.11.7", + resolvers += Resolver.jcenterRepo, + scalaVersion := "2.11.8", scalacOptions ++= Seq("-feature", "-deprecation") ) diff --git a/project/build.properties b/project/build.properties index 817bc38..43b8278 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=0.13.9 +sbt.version=0.13.11 diff --git a/project/plugins.sbt b/project/plugins.sbt index 301572f..611b313 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -2,14 +2,10 @@ * Additional resolvers */ -//resolvers += "Typesafe repository" at "https://repo.typesafe.com/typesafe/releases/" - -//resolvers += "jgit-repo" at "http://download.eclipse.org/jgit/maven" - resolvers += Resolver.jcenterRepo /* - * Main plugins + * Main plugins (required for building) */ // add support for scalajs @@ -19,11 +15,11 @@ addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.8") addSbtPlugin("com.typesafe.sbt" % "sbt-twirl" % "1.1.1") // generate MAVLink protocol bindings -addSbtPlugin("com.github.jodersky" % "sbt-mavlink" % "0.5.2") +addSbtPlugin("com.github.jodersky" % "sbt-mavlink" % "0.7.0") /* - * Utility or meta plugins + * Utility or meta plugins (non-essential, can be disabled) */ // automate publishing documentation @@ -35,4 +31,4 @@ addSbtPlugin("com.github.jodersky" % "sbt-mavlink" % "0.5.2") // generate documentation for all projects //addSbtPlugin("com.eed3si9n" % "sbt-unidoc" % "0.3.3") -addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.0.0") +addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.1.0-RC3") |