Compare commits
28 Commits
Author | SHA1 | Date | |
---|---|---|---|
d0aa68aeee | |||
51731fbbc9 | |||
751ea1494b | |||
dfe84471e8 | |||
0f3de6cf07 | |||
21195e4abf | |||
c992100a38 | |||
20a4673f98 | |||
966fd81870 | |||
cb9d2aeb39 | |||
3cee55f4c2 | |||
3815d2fd3b | |||
1453b7e0a3 | |||
6fb5c54280 | |||
9d5ca35e83 | |||
893dd0f9c4 | |||
c3419c0b74 | |||
a4164470d8 | |||
0dd5ab9bde | |||
e93e659e8a | |||
8357372fb5 | |||
54cb076002 | |||
dbd3eb464b | |||
10cd39a4ec | |||
aa8635c4e7 | |||
4e4f5495da | |||
16b9a5027d | |||
cbe68b3199 |
109
docs/static/avatar.svg
vendored
Normal file
109
docs/static/avatar.svg
vendored
Normal file
@ -0,0 +1,109 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1"
|
||||
id="svg2" inkscape:version="0.91 r13725" sodipodi:docname="avatar.svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 32 32"
|
||||
style="enable-background:new 0 0 32 32;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#303030;}
|
||||
</style>
|
||||
<sodipodi:namedview bordercolor="#666666" borderopacity="1" gridtolerance="10" guidetolerance="10" id="namedview15" inkscape:current-layer="svg2" inkscape:cx="16" inkscape:cy="16" inkscape:pageopacity="0" inkscape:pageshadow="2" inkscape:window-height="661" inkscape:window-maximized="0" inkscape:window-width="997" inkscape:window-x="0" inkscape:window-y="0" inkscape:zoom="5.2149125" objecttolerance="10" pagecolor="#ffffff" showgrid="false">
|
||||
</sodipodi:namedview>
|
||||
<g id="avatar_mf" transform="translate(-5304.979 8145.745)">
|
||||
<path id="Path_180" class="st0" d="M5317.1-8125.6c0.2,0,0.3-0.1,0.5-0.1l2.9-0.6c1.3-0.3,2.5-0.5,3.8-0.8c0.5-0.1,1-0.2,1.5-0.3
|
||||
c0.1,0,0.1,0,0.1-0.1c0-0.5,0-1-0.1-1.5c-0.1-0.6-0.2-1.1-0.3-1.7c-0.1-0.4-0.1-0.8-0.2-1.2c-0.1-0.7-0.2-1.4-0.3-2
|
||||
c-0.1-0.6-0.2-1.1-0.3-1.7c0-0.2-0.2-0.4-0.4-0.4c0,0,0,0-0.1,0c-0.2,0-0.5,0.1-0.7,0.1c-0.5,0.1-1,0.1-1.5,0.2
|
||||
c-0.5,0.1-1,0.1-1.6,0.2c-0.5,0.1-1,0.1-1.4,0.2c-0.2,0-0.3,0-0.5,0.1c-0.2,0-0.4,0.2-0.5,0.4c0,0.2-0.1,0.4-0.1,0.5
|
||||
c0,0.4-0.1,0.8-0.1,1.2c-0.1,0.5-0.1,1-0.2,1.5c0,0.4-0.1,0.9-0.1,1.3l-0.1,1.3l-0.1,1.3c-0.1,0.5-0.1,1-0.2,1.5
|
||||
C5317.1-8126,5317.1-8125.8,5317.1-8125.6z"/>
|
||||
<path id="Path_181" class="st0" d="M5316.1-8140c0,0.6,0.1,1.2,0.1,1.8c0,0.4,0.2,0.8,0.6,1c0.3,0.2,0.7,0.3,1.1,0.3
|
||||
c0.7,0.1,1.4,0.1,2.1,0c0.6,0,1.2-0.1,1.8-0.2c0.4,0,0.9-0.2,1.2-0.5c0.3-0.3,0.5-0.7,0.5-1.1c0-1.2,0-2.3-0.1-3.5
|
||||
c0-0.3-0.1-0.6-0.3-0.8c-0.3-0.4-0.8-0.6-1.4-0.6c0,0,0,0-0.1,0c-0.2,0.1-0.4,0.1-0.6,0.1c-0.9,0-1.8,0.1-2.7,0.1
|
||||
c-0.1,0-0.3,0-0.4,0c-0.4-0.1-0.8,0-1.2,0.2c-0.4,0.2-0.6,0.6-0.7,1c0,0.1,0,0.2,0,0.3C5316-8141.2,5316.1-8140.6,5316.1-8140
|
||||
L5316.1-8140z"/>
|
||||
<path id="Path_182" class="st0" d="M5312-8129.2c0,0.6,0,1.2,0,1.8c0,0.1,0,0.1,0.1,0.1c0.3,0,0.6,0.1,0.9,0.1c0.3,0,0.7,0,1,0
|
||||
c0.1,0,0.2,0,0.3,0c0.1,0,0.1,0,0.1-0.1c0.1-0.4,0.2-0.8,0.2-1.2c0.1-0.5,0.2-1,0.3-1.4c0-0.1,0.1-0.1,0.1-0.1
|
||||
c0.4-0.2,0.8-0.5,1.2-0.8c0.4-0.4,0.7-0.9,0.8-1.5c0.1-0.6,0-1.2-0.4-1.7c-0.2-0.3-0.6-0.4-0.9-0.3c-0.3,0.1-0.6,0.3-0.9,0.5
|
||||
c-0.7,0.6-1.5,1.2-2.2,1.8c-0.3,0.2-0.5,0.5-0.6,0.8c0,0.2-0.1,0.4-0.1,0.6C5311.9-8130.2,5311.9-8129.7,5312-8129.2
|
||||
C5311.9-8129.2,5312-8129.2,5312-8129.2z"/>
|
||||
<path id="Path_183" class="st0" d="M5315.5-8124.7c0,0.2-0.1,0.5-0.1,0.7c-0.1,0.5-0.4,0.9-0.8,1.1c0,0,0,0,0,0
|
||||
c-0.1,0-0.2,0.2-0.2,0.3c-0.4,1.4-0.9,2.7-1.3,4.1c0,0,0,0.1,0,0.1c0.8,1.1,1.6,2.2,2.4,3.3c0,0,0,0,0.1,0.1
|
||||
c0.1-0.2,0.1-0.3,0.2-0.5c0.1-0.3,0.2-0.5,0.3-0.8c0,0,0-0.1,0-0.1c-0.2-0.3-0.5-0.7-0.7-1c0,0,0-0.1,0-0.2
|
||||
c0.3-0.9,0.6-1.7,0.9-2.6c0,0,0-0.1,0.1-0.1c0.5-0.2,0.9-0.6,1.1-1.1c0.1-0.3,0.1-0.7,0.1-1.1c0-0.4-0.1-0.8-0.3-1.2
|
||||
c-0.2-0.4-0.5-0.8-1-1C5315.9-8124.7,5315.7-8124.7,5315.5-8124.7z"/>
|
||||
<path id="Path_184" class="st0" d="M5314.7-8134.2c0.1-0.1,0.2-0.1,0.3-0.2c0.3-0.2,0.7-0.3,1-0.3c0.4,0,0.7,0.2,0.8,0.6
|
||||
c0.6,1.1,0.5,2.4-0.4,3.3c-0.4,0.4-0.8,0.7-1.3,1c0,0-0.1,0.1-0.1,0.1c-0.2,0.8-0.3,1.6-0.5,2.5c0,0,0,0.1,0,0.1
|
||||
c0,0.2,0,0.2-0.2,0.2c-0.1,0-0.1,0-0.2,0c0,0.1,0,0.2,0,0.3c0,0,0,0,0.1,0.1l1.9,0.7c0.2,0.1,0.4,0.2,0.7,0.2c0-0.1,0-0.2,0-0.2
|
||||
c0-0.3,0-0.6,0-0.8l0.2-1.5l0.2-1.5c0.1-0.5,0.1-1,0.2-1.5c0.1-0.5,0.1-1.1,0.2-1.6c0.1-0.5,0.1-1,0.2-1.5c0-0.2,0-0.4,0-0.6
|
||||
c0-0.2-0.1-0.4-0.3-0.4c0,0,0,0,0,0c-0.1,0-0.1,0-0.2,0c-0.6-0.1-1.2-0.2-1.9-0.3c-0.1,0-0.2,0-0.3,0c-0.1,0-0.2,0.1-0.3,0.3
|
||||
C5314.8-8135,5314.7-8134.6,5314.7-8134.2z"/>
|
||||
<path id="Path_185" class="st0" d="M5324.5-8117.2c-0.2-0.7-0.5-1.3-0.7-2l-0.7,0.3c0,0-0.1,0-0.1,0c-0.1,0.1-0.2,0-0.2-0.1
|
||||
c-0.1-0.3-0.2-0.5-0.3-0.8c-0.2-0.7-0.5-1.4-0.7-2.1c0,0,0-0.1,0-0.1c0,0,0,0.1-0.1,0.1c-0.1,0.2-0.3,0.3-0.5,0.5c0,0-0.1,0-0.1,0
|
||||
c-0.3,0.1-0.6,0.1-0.8,0.2c0,0-0.1,0-0.1,0c0,0.2-0.1,0.3-0.1,0.5c0.6,1.5,1.1,3.1,1.7,4.6c0,0,0,0,0,0.1
|
||||
C5322.7-8116.3,5323.6-8116.7,5324.5-8117.2z"/>
|
||||
<path id="Path_186" class="st0" d="M5325.4-8123.1c-0.7,0.2-1.3,0.3-2,0.5c-0.5,0.1-0.9,0.2-1.4,0.3c-0.1,0-0.2,0.1-0.1,0.2
|
||||
c0,0,0,0,0,0.1c0.2,0.5,0.3,0.9,0.5,1.4c0.2,0.5,0.4,1.1,0.6,1.6c0,0,0,0,0,0c0.1-0.1,0.2-0.1,0.3-0.2c0.2-0.1,0.3-0.2,0.5-0.2
|
||||
c0.2-0.1,0.4-0.1,0.6-0.2c0.7-0.2,1.5-0.4,2.2-0.6c0,0,0,0,0.1,0C5326.3-8121.1,5325.9-8122.1,5325.4-8123.1z"/>
|
||||
<path id="Path_187" class="st0" d="M5320.7-8124.4c-0.4,0.1-0.8,0.2-1.2,0.3c-0.6,0.1-1.3,0.3-1.9,0.4c0,0-0.1,0-0.1,0
|
||||
c-0.1,0-0.2-0.1-0.3-0.1c0,0,0,0.1,0,0.1c0.3,0.7,0.4,1.5,0.3,2.2c-0.1,0.4-0.3,0.8-0.5,1.1c0,0,0,0-0.1,0.1
|
||||
c0.2-0.1,0.4-0.1,0.6-0.1l2.5-0.6c0.5-0.1,0.9-0.6,1-1.1c0.1-0.5,0-1-0.1-1.5C5320.8-8124,5320.8-8124.2,5320.7-8124.4z"/>
|
||||
<path id="Path_188" class="st0" d="M5325.5-8127c-2.7,0.5-5.3,1.1-7.9,1.6c0,0.5,0,0.9,0.1,1.4c0.1,0,0.2,0,0.3-0.1
|
||||
c1.2-0.3,2.3-0.5,3.5-0.8c1.2-0.3,2.4-0.5,3.6-0.8c0.1,0,0.3-0.1,0.4-0.1c0,0,0.1,0,0.1-0.1
|
||||
C5325.6-8126.2,5325.5-8126.6,5325.5-8127z"/>
|
||||
<path id="Path_189" class="st0" d="M5328.6-8118.4c-0.2-0.4-0.3-0.7-0.5-1.1c-0.1-0.2-0.2-0.5-0.3-0.7c0-0.1,0-0.1-0.1,0
|
||||
c-1.2,0.3-2.4,0.6-3.6,1c0,0,0,0-0.1,0c0.2,0.7,0.5,1.3,0.7,2C5326-8117.6,5327.3-8118,5328.6-8118.4z"/>
|
||||
<path id="Path_190" class="st0" d="M5319.9-8120.9l-0.6,0.1c-1,0.2-2,0.5-3,0.7c-0.1,0-0.1,0.1-0.1,0.1c-0.3,0.7-0.5,1.5-0.8,2.2
|
||||
c0,0,0,0,0,0.1c0.2,0,0.4-0.1,0.5-0.1c1-0.3,2-0.5,3.1-0.8c0.1,0,0.2-0.1,0.3-0.2C5319.5-8119.5,5319.7-8120.2,5319.9-8120.9z"/>
|
||||
<path id="Path_191" class="st0" d="M5325.1-8125.4c-1.1,0.2-2.2,0.5-3.3,0.7c0.3,0.7,0.4,1.4,0.3,2.2c0.2,0,0.4-0.1,0.5-0.1
|
||||
c0.9-0.2,1.8-0.4,2.7-0.6c0.1,0,0.1,0,0.1-0.1c0.1-0.4,0-0.9-0.1-1.3C5325.3-8124.9,5325.2-8125.1,5325.1-8125.4
|
||||
C5325.1-8125.3,5325.1-8125.4,5325.1-8125.4z"/>
|
||||
<path id="Path_192" class="st0" d="M5325.6-8132c0.2,0,0.4,0,0.5,0c0.2,0,0.4,0,0.6,0c0.3,0,0.6-0.2,0.8-0.4c0.3-0.4,0.7-0.8,1-1.2
|
||||
c-0.1-0.1-0.1-0.1-0.1-0.2c0,0,0,0-0.1,0c-0.7-0.1-1.3-0.5-1.4-1.2c-0.1-0.2-0.1-0.4-0.1-0.6c-0.2,0.1-0.3,0.3-0.5,0.4
|
||||
c-0.1,0.1-0.2,0.1-0.3,0.2c0,0-0.1,0.1-0.2,0c-0.2,0-0.5-0.1-0.7-0.1c0,0,0,0-0.1,0C5325.3-8134.1,5325.4-8133.1,5325.6-8132z"/>
|
||||
<path id="Path_193" class="st0" d="M5328.4-8134.2c0.2-0.3,0.5-0.5,0.8-0.7c0.4-0.4,0.6-0.8,0.7-1.4c0-0.1,0-0.2-0.1-0.3
|
||||
c-0.3-0.5-0.7-1.1-1-1.6c0-0.1-0.1-0.1-0.1,0c-0.4,0.2-0.8,0.5-1.1,0.8c-0.2,0.2-0.4,0.4-0.6,0.6c-0.1,0.1-0.1,0.3,0,0.4
|
||||
c0.5,0.7,1,1.5,1.4,2.2C5328.4-8134.2,5328.4-8134.2,5328.4-8134.2z"/>
|
||||
<path id="Path_194" class="st0" d="M5314.3-8126.1c-0.1-0.1-0.1-0.1-0.2-0.2c-0.1,0-0.1-0.1-0.1-0.2c0-0.1,0-0.3,0-0.4h-0.1
|
||||
c-0.6,0-1.2-0.1-1.7-0.2c0,0-0.1,0-0.1,0c-0.4,0.2-0.7,0.4-1,0.8c-0.4,0.6-0.4,1.4,0.1,1.9c0.2,0.2,0.4,0.4,0.6,0.6
|
||||
c0.1,0.1,0.2,0.2,0.3,0.2c-0.2-0.6,0-1.3,0.5-1.8C5313-8125.9,5313.6-8126.2,5314.3-8126.1z"/>
|
||||
<path id="Path_195" class="st0" d="M5315.7-8114.9c0.5-0.1,0.9-0.3,1.4-0.4c0.8-0.2,1.6-0.4,2.4-0.7c0.1,0,0.1,0,0.1-0.1
|
||||
c0.1-0.4,0.2-0.7,0.4-1.1c0,0,0,0,0-0.1c-0.1,0-0.2,0-0.3,0.1c-1.2,0.3-2.3,0.6-3.5,1c-0.1,0-0.1,0.1-0.1,0.1
|
||||
c-0.1,0.4-0.3,0.7-0.4,1.1C5315.7-8115,5315.7-8115,5315.7-8114.9z"/>
|
||||
<path id="Path_196" class="st0" d="M5315.4-8117.4c0.2,0.3,0.4,0.6,0.6,0.9c0,0,0.1,0,0.1,0c1-0.3,1.9-0.5,2.9-0.8
|
||||
c0.3-0.1,0.6-0.2,0.9-0.2c-0.1-0.1-0.1-0.2-0.2-0.2c-0.2-0.2-0.3-0.4-0.5-0.6c0,0-0.1-0.1-0.1,0c-0.9,0.3-1.9,0.5-2.8,0.8
|
||||
C5316-8117.5,5315.7-8117.5,5315.4-8117.4z"/>
|
||||
<path id="Path_197" class="st0" d="M5321.3-8143.6c0-0.3,0-0.7,0-1c0,0-0.1,0-0.1,0c-0.7,0-1.5,0-2.2,0.1c-0.4,0-0.7,0-1.1,0.1
|
||||
c-0.1,0-0.1,0-0.1,0.1c0,0.3,0,0.6,0,0.8c0,0,0,0.1,0.1,0.1c0.4,0,0.8,0,1.1,0c0.5,0,1.1,0,1.6-0.1
|
||||
C5320.9-8143.5,5321.1-8143.6,5321.3-8143.6z"/>
|
||||
<path id="Path_198" class="st0" d="M5329.2-8134.3c0.1,0,0.2,0,0.2-0.1c0.4-0.2,0.8-0.4,1.1-0.7c0.3-0.2,0.4-0.5,0.6-0.8
|
||||
c0.1-0.3,0.1-0.6-0.1-0.9c-0.4-0.5-0.7-1-1.1-1.6c0,0,0,0-0.1-0.1c-0.2,0.2-0.4,0.4-0.6,0.6c0,0,0,0,0,0c0,0,0,0.1,0,0.1
|
||||
c0.2,0.4,0.5,0.7,0.7,1c0.1,0.2,0.2,0.4,0.1,0.6c0,0.5-0.1,0.9-0.4,1.3c-0.1,0.1-0.2,0.2-0.3,0.3
|
||||
C5329.4-8134.5,5329.3-8134.4,5329.2-8134.3C5329.2-8134.3,5329.2-8134.3,5329.2-8134.3z"/>
|
||||
<path id="Path_199" class="st0" d="M5317.5-8136.7c0,0.2,0,0.3,0,0.5c0,0,0,0.1,0.1,0.1c0.2,0,0.3,0.1,0.5,0.1c0.7,0.1,1.4,0.1,2,0
|
||||
c0.4,0,0.8-0.1,1.2-0.1c0.2,0,0.5-0.1,0.7-0.1c0.1,0,0.1,0,0.1-0.1c0-0.2,0-0.3,0-0.5C5320.5-8136.6,5319-8136.5,5317.5-8136.7z"/>
|
||||
<path id="Path_200" class="st0" d="M5312.5-8123.2c0.1-0.4,0.1-0.8,0.2-1.2c0.1-0.6,0.6-1,1.2-1c0.3,0,0.5,0.1,0.7,0.3
|
||||
c0.2,0.2,0.2,0.4,0.2,0.7c-0.1,0.3-0.2,0.6-0.2,0.9c0,0.1-0.1,0.2-0.1,0.4c0,0,0,0,0,0c0.1-0.1,0.2-0.2,0.3-0.3
|
||||
c0.3-0.4,0.4-0.9,0.4-1.4c0-0.6-0.4-1-0.9-1.1c-0.1,0-0.1,0-0.2,0c-0.5,0-1,0.3-1.4,0.7c-0.3,0.3-0.4,0.6-0.5,1
|
||||
c-0.1,0.3,0,0.7,0.2,1C5312.5-8123.2,5312.5-8123.2,5312.5-8123.2z"/>
|
||||
<path id="Path_201" class="st0" d="M5314.3-8123.2c0.1-0.5,0.3-0.9,0.4-1.4c0.1-0.2,0-0.4-0.2-0.5c-0.4-0.2-0.9-0.1-1.2,0.2
|
||||
c0,0,0,0,0,0.1c0,0.2,0,0.4,0.1,0.6c0,0.1,0.1,0.2,0.2,0.3C5313.7-8123.6,5314-8123.4,5314.3-8123.2
|
||||
C5314.2-8123.2,5314.3-8123.2,5314.3-8123.2z"/>
|
||||
<path id="Path_202" class="st0" d="M5317.4-8124.1c0-0.4,0-0.9-0.1-1.3c-0.1,0-0.1,0-0.2,0c-0.1,0-0.2,0-0.3,0
|
||||
c-0.7-0.3-1.4-0.5-2.1-0.8c0,0,0,0,0,0c0.1,0.1,0.1,0.1,0.2,0.2c0.3,0.2,0.5,0.6,0.6,0.9c0,0,0,0,0,0c0.1,0,0.3,0,0.4,0.1
|
||||
c0.3,0.1,0.6,0.3,0.9,0.5c0,0,0.1,0.1,0.1,0.1C5317.1-8124.2,5317.2-8124.1,5317.4-8124.1z"/>
|
||||
<path id="Path_203" class="st0" d="M5321.6-8124.6c-0.2,0-0.4,0.1-0.6,0.1c0.1,0.2,0.1,0.4,0.2,0.6c0.1,0.4,0.2,0.8,0.1,1.2
|
||||
c0,0.5-0.1,0.9-0.4,1.3c0,0,0,0,0,0c0.3,0,0.6-0.2,0.7-0.5c0.2-0.3,0.3-0.6,0.3-0.9C5321.9-8123.4,5321.8-8124,5321.6-8124.6z"/>
|
||||
<path id="Path_204" class="st0" d="M5315.7-8135.8c0.2,0,0.3,0.1,0.5,0.1c0.5,0.1,1,0.2,1.5,0.2c0.2,0,0.5,0,0.7,0
|
||||
c0.7-0.1,1.4-0.2,2.1-0.2c0.6-0.1,1.2-0.2,1.8-0.2c0.5-0.1,1.1-0.1,1.6-0.2l-1.5-0.2c0,0,0,0,0,0c0,0.2-0.1,0.3-0.2,0.3
|
||||
c0,0,0,0,0,0c-0.3,0.1-0.5,0.1-0.8,0.2c-0.8,0.1-1.6,0.1-2.3,0.1c-0.4,0-0.9,0-1.3-0.1c-0.1,0-0.2-0.1-0.3-0.2c0,0-0.1,0-0.1,0
|
||||
c-0.3,0-0.6,0.1-0.9,0.1L5315.7-8135.8L5315.7-8135.8z"/>
|
||||
<path id="Path_205" class="st0" d="M5327.3-8117.7c-0.3,0.1-0.5,0.2-0.8,0.2c-0.6,0.2-1.1,0.3-1.7,0.5c0,0-0.1,0-0.1,0.1
|
||||
c-0.5,0.2-1,0.5-1.5,0.7c0,0,0,0,0,0c0.3-0.1,0.5-0.1,0.8-0.2c0.5-0.2,1.1-0.3,1.6-0.5c0.1,0,0.2-0.1,0.2-0.1
|
||||
C5326.2-8117.2,5326.8-8117.5,5327.3-8117.7C5327.3-8117.7,5327.3-8117.7,5327.3-8117.7C5327.3-8117.7,5327.3-8117.7,5327.3-8117.7
|
||||
z"/>
|
||||
<path id="Path_206" class="st0" d="M5327-8135.8c0,0.1,0,0.2,0,0.4c0.1,0.5,0.3,0.9,0.7,1.1c0.1,0.1,0.3,0.1,0.4,0.2l0,0
|
||||
C5327.8-8134.7,5327.4-8135.2,5327-8135.8L5327-8135.8z"/>
|
||||
<path id="Path_207" class="st0" d="M5329-8134.4c-0.1,0.1-0.2,0.2-0.3,0.4c0,0,0,0,0,0.1c0,0.1,0,0.1,0.1,0.1c0.1,0,0.2,0,0.3-0.1
|
||||
c0.1,0,0.2-0.1,0.3-0.1c-0.1,0-0.1-0.1-0.2-0.1C5329-8134.2,5329-8134.3,5329-8134.4z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 11 KiB |
@ -1,4 +1,5 @@
|
||||
{
|
||||
"BrickLight": "Patterns for lights under the buttons.",
|
||||
"ButtonEvent": "User interaction on buttons",
|
||||
"Draw": "Drawing modes",
|
||||
"Image.buffer": "Returns the underlaying Buffer object.",
|
||||
@ -6,14 +7,15 @@
|
||||
"Image.draw": "Draw an image on the screen.",
|
||||
"Image.height": "Returns the height of an image.",
|
||||
"Image.width": "Returns the width of an image.",
|
||||
"LightsPattern": "Patterns for lights under the buttons.",
|
||||
"MMap.getNumber": "Read a number in specified format from the buffer.",
|
||||
"MMap.ioctl": "Perform ioctl(2) on the underlaying file",
|
||||
"MMap.length": "Returns the length of a Buffer object.",
|
||||
"MMap.lseek": "Set pointer on the underlaying file.",
|
||||
"MMap.read": "Perform read(2) on the underlaying file",
|
||||
"MMap.setNumber": "Write a number in specified format in the buffer.",
|
||||
"MMap.slice": "Read a range of bytes into a buffer.",
|
||||
"MMap.write": "Perform write(2) on the underlaying file",
|
||||
"SeekWhence": "Mode for lseek()",
|
||||
"brick.Button": "Generic button class, for device buttons and sensors.",
|
||||
"brick.Button.isPressed": "Check if button is currently pressed or not.",
|
||||
"brick.Button.onEvent": "Do something when a button or sensor is clicked, up or down.",
|
||||
@ -28,11 +30,9 @@
|
||||
"brick.buttonRight": "Right button on the EV3 Brick.",
|
||||
"brick.buttonUp": "Up button on the EV3 Brick.",
|
||||
"brick.clearScreen": "Clears the screen",
|
||||
"brick.lightPattern": "Pattern block.",
|
||||
"brick.lightPattern|param|pattern": "the lights pattern to use. eg: LightsPattern.Green",
|
||||
"brick.printPorts": "Prints the port states on the screen",
|
||||
"brick.setLight": "Set lights.",
|
||||
"brick.setLight|param|pattern": "the lights pattern to use.",
|
||||
"brick.setLight|param|pattern": "the lights pattern to use. eg: BrickLight.Orange",
|
||||
"brick.showImage": "Shows an image on screen",
|
||||
"brick.showImage|param|image": "image to draw",
|
||||
"brick.showNumber": "Shows a number on the screen",
|
||||
@ -61,6 +61,8 @@
|
||||
"control.raiseEvent|param|value": "Component specific code indicating the cause of the event.",
|
||||
"motors.Motor.angle": "Gets motor angle.",
|
||||
"motors.Motor.clearCounts": "Clears the motor count",
|
||||
"motors.Motor.setRegulated": "Indicates if the motor speed should be regulated. Default is true.",
|
||||
"motors.Motor.setRegulated|param|value": "true for regulated motor",
|
||||
"motors.Motor.speed": "Gets motor actual speed.",
|
||||
"motors.Motor.tacho": "Gets motor tachometer count.",
|
||||
"motors.Motor.toString": "Returns the status of the motor",
|
||||
|
@ -1,17 +1,17 @@
|
||||
{
|
||||
"BrickLight.GreenFlash|block": "green flash",
|
||||
"BrickLight.GreenPulse|block": "green pulse",
|
||||
"BrickLight.Green|block": "green",
|
||||
"BrickLight.Off|block": "off",
|
||||
"BrickLight.OrangeFlash|block": "orange flash",
|
||||
"BrickLight.OrangePulse|block": "orange pulse",
|
||||
"BrickLight.Orange|block": "orange",
|
||||
"BrickLight.RedFlash|block": "red flash",
|
||||
"BrickLight.RedPulse|block": "red pulse",
|
||||
"BrickLight.Red|block": "red",
|
||||
"ButtonEvent.Click|block": "click",
|
||||
"ButtonEvent.Down|block": "down",
|
||||
"ButtonEvent.Up|block": "up",
|
||||
"LightsPattern.GreenFlash|block": "Flashing Green",
|
||||
"LightsPattern.GreenPulse|block": "Pulsing Green",
|
||||
"LightsPattern.Green|block": "Green",
|
||||
"LightsPattern.Off|block": "Off",
|
||||
"LightsPattern.OrangeFlash|block": "Flashing Orange",
|
||||
"LightsPattern.OrangePulse|block": "Pulsing Orange",
|
||||
"LightsPattern.Orange|block": "Orange",
|
||||
"LightsPattern.RedFlash|block": "Flashing Red",
|
||||
"LightsPattern.RedPulse|block": "Pulsing Red",
|
||||
"LightsPattern.Red|block": "Red",
|
||||
"MoveUnit.Degrees|block": "degrees",
|
||||
"MoveUnit.MilliSeconds|block": "milliseconds",
|
||||
"MoveUnit.Rotations|block": "rotations",
|
||||
@ -30,15 +30,14 @@
|
||||
"brick.Button.pauseUntil|block": "pause until %button|%event",
|
||||
"brick.Button.wasPressed|block": "%button|was pressed",
|
||||
"brick.batteryLevel|block": "battery level",
|
||||
"brick.buttonDown|block": "down",
|
||||
"brick.buttonEnter|block": "enter",
|
||||
"brick.buttonLeft|block": "left",
|
||||
"brick.buttonRight|block": "right",
|
||||
"brick.buttonUp|block": "up",
|
||||
"brick.buttonDown|block": "button down",
|
||||
"brick.buttonEnter|block": "button enter",
|
||||
"brick.buttonLeft|block": "button left",
|
||||
"brick.buttonRight|block": "button right",
|
||||
"brick.buttonUp|block": "button up",
|
||||
"brick.clearScreen|block": "clear screen",
|
||||
"brick.lightPattern|block": "%pattern",
|
||||
"brick.printPorts|block": "print ports",
|
||||
"brick.setLight|block": "set light to %pattern=led_pattern",
|
||||
"brick.setLight|block": "set light to %pattern",
|
||||
"brick.showImage|block": "show image %image=screen_image_picker",
|
||||
"brick.showNumber|block": "show number %name|at line %line",
|
||||
"brick.showString|block": "show string %text|at line %line",
|
||||
@ -52,9 +51,11 @@
|
||||
"control|block": "control",
|
||||
"motors.Motor.angle|block": "%motor|angle",
|
||||
"motors.Motor.clearCounts|block": "%motor|clear counts",
|
||||
"motors.Motor.setRegulated|block": "set %motor|regulated %value",
|
||||
"motors.Motor.speed|block": "%motor|speed",
|
||||
"motors.Motor.tacho|block": "%motor|tacho",
|
||||
"motors.MotorBase.pauseUntilReady|block": "%motor|pause until ready",
|
||||
"motors.MotorBase.reset|block": "%motors|reset",
|
||||
"motors.MotorBase.setBrake|block": "set %motor|brake %brake",
|
||||
"motors.MotorBase.setReversed|block": "set %motor|reversed %reversed",
|
||||
"motors.MotorBase.setSpeed|block": "set %motor|speed to %speed=motorSpeedPicker|%",
|
||||
@ -91,7 +92,6 @@
|
||||
"{id:category}Serial": "Serial",
|
||||
"{id:group}Buttons": "Buttons",
|
||||
"{id:group}Counters": "Counters",
|
||||
"{id:group}Light": "Light",
|
||||
"{id:group}More": "More",
|
||||
"{id:group}Move": "Move",
|
||||
"{id:group}Screen": "Screen",
|
||||
|
@ -2,35 +2,35 @@
|
||||
/**
|
||||
* Patterns for lights under the buttons.
|
||||
*/
|
||||
const enum LightsPattern {
|
||||
//% block=Off enumval=0
|
||||
const enum BrickLight {
|
||||
//% block=off enumval=0
|
||||
//% blockIdentity=brick.lightPattern
|
||||
Off = 0,
|
||||
//% block=Green enumval=1
|
||||
//% block=green enumval=1
|
||||
//% blockIdentity=brick.lightPattern
|
||||
Green = 1,
|
||||
//% block=Red enumval=2
|
||||
//% block=red enumval=2
|
||||
//% blockIdentity=brick.lightPattern
|
||||
Red = 2,
|
||||
//% block=Orange enumval=3
|
||||
//% block=orange enumval=3
|
||||
//% blockIdentity=brick.lightPattern
|
||||
Orange = 3,
|
||||
//% block="Flashing Green" enumval=4
|
||||
//% block="green flash" enumval=4
|
||||
//% blockIdentity=brick.lightPattern
|
||||
GreenFlash = 4,
|
||||
//% block="Flashing Red" enumval=5
|
||||
//% block="red flash" enumval=5
|
||||
//% blockIdentity=brick.lightPattern
|
||||
RedFlash = 5,
|
||||
//% block="Flashing Orange" enumval=6
|
||||
//% block="orange flash" enumval=6
|
||||
//% blockIdentity=brick.lightPattern
|
||||
OrangeFlash = 6,
|
||||
//% block="Pulsing Green" enumval=7
|
||||
//% block="green pulse" enumval=7
|
||||
//% blockIdentity=brick.lightPattern
|
||||
GreenPulse = 7,
|
||||
//% block="Pulsing Red" enumval=8
|
||||
//% block="red pulse" enumval=8
|
||||
//% blockIdentity=brick.lightPattern
|
||||
RedPulse = 8,
|
||||
//% block="Pulsing Orange" enumval=9
|
||||
//% block="orange pulse" enumval=9
|
||||
//% blockIdentity=brick.lightPattern
|
||||
OrangePulse = 9,
|
||||
}
|
||||
@ -170,6 +170,7 @@ namespace brick {
|
||||
// this needs to be done in query(), which is run without the main JS execution mutex
|
||||
// otherwise, while(true){} will lock the device
|
||||
if (ret & DAL.BUTTON_ID_ESCAPE) {
|
||||
motors.stopAllMotors(); // ensuring that all motors are off
|
||||
control.reset()
|
||||
}
|
||||
return ret
|
||||
@ -203,31 +204,31 @@ namespace brick {
|
||||
/**
|
||||
* Enter button on the EV3 Brick.
|
||||
*/
|
||||
//% whenUsed block="enter" weight=95 fixedInstance
|
||||
//% whenUsed block="button enter" weight=95 fixedInstance
|
||||
export const buttonEnter: Button = new DevButton(DAL.BUTTON_ID_ENTER)
|
||||
|
||||
/**
|
||||
* Left button on the EV3 Brick.
|
||||
*/
|
||||
//% whenUsed block="left" weight=95 fixedInstance
|
||||
//% whenUsed block="button left" weight=95 fixedInstance
|
||||
export const buttonLeft: Button = new DevButton(DAL.BUTTON_ID_LEFT)
|
||||
|
||||
/**
|
||||
* Right button on the EV3 Brick.
|
||||
*/
|
||||
//% whenUsed block="right" weight=94 fixedInstance
|
||||
//% whenUsed block="button right" weight=94 fixedInstance
|
||||
export const buttonRight: Button = new DevButton(DAL.BUTTON_ID_RIGHT)
|
||||
|
||||
/**
|
||||
* Up button on the EV3 Brick.
|
||||
*/
|
||||
//% whenUsed block="up" weight=95 fixedInstance
|
||||
//% whenUsed block="button up" weight=95 fixedInstance
|
||||
export const buttonUp: Button = new DevButton(DAL.BUTTON_ID_UP)
|
||||
|
||||
/**
|
||||
* Down button on the EV3 Brick.
|
||||
*/
|
||||
//% whenUsed block="down" weight=95 fixedInstance
|
||||
//% whenUsed block="button down" weight=95 fixedInstance
|
||||
export const buttonDown: Button = new DevButton(DAL.BUTTON_ID_DOWN)
|
||||
}
|
||||
|
||||
@ -251,32 +252,21 @@ namespace control {
|
||||
}
|
||||
|
||||
namespace brick {
|
||||
let currPattern: LightsPattern
|
||||
// the brick starts with the red color
|
||||
let currPattern: BrickLight = BrickLight.Red;
|
||||
|
||||
/**
|
||||
* Set lights.
|
||||
* @param pattern the lights pattern to use.
|
||||
* @param pattern the lights pattern to use. eg: BrickLight.Orange
|
||||
*/
|
||||
//% blockId=setLights block="set light to %pattern=led_pattern"
|
||||
//% blockId=setLights block="set light to %pattern"
|
||||
//% weight=100 group="Buttons"
|
||||
export function setLight(pattern: number): void {
|
||||
export function setLight(pattern: BrickLight): void {
|
||||
if (currPattern === pattern)
|
||||
return
|
||||
currPattern = pattern
|
||||
let cmd = output.createBuffer(2)
|
||||
currPattern = pattern;
|
||||
const cmd = output.createBuffer(2)
|
||||
cmd[0] = pattern + 48
|
||||
brick.internal.getBtnsMM().write(cmd)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Pattern block.
|
||||
* @param pattern the lights pattern to use. eg: LightsPattern.Green
|
||||
*/
|
||||
//% blockId=led_pattern block="%pattern"
|
||||
//% shim=TD_ID colorSecondary="#6e9a36" group="Light"
|
||||
//% blockHidden=true useEnumVal=1 pattern.fieldOptions.decompileLiterals=1
|
||||
export function lightPattern(pattern: LightsPattern): number {
|
||||
return pattern;
|
||||
}
|
||||
}
|
||||
|
11
libs/core/enums.d.ts
vendored
11
libs/core/enums.d.ts
vendored
@ -1,6 +1,17 @@
|
||||
// Auto-generated. Do not edit.
|
||||
|
||||
|
||||
/**
|
||||
* Mode for lseek()
|
||||
*/
|
||||
|
||||
declare const enum SeekWhence {
|
||||
Set = 0,
|
||||
Current = 1,
|
||||
End = 2,
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Drawing modes
|
||||
*/
|
||||
|
@ -93,7 +93,7 @@ namespace sensors.internal {
|
||||
init();
|
||||
return {
|
||||
temp: analogMM.getNumber(NumberFormat.Int16LE, AnalogOff.BatteryTemp),
|
||||
current: analogMM.getNumber(NumberFormat.Int16LE, AnalogOff.BatteryCurrent)
|
||||
current: Math.round(analogMM.getNumber(NumberFormat.Int16LE, AnalogOff.BatteryCurrent) / 10)
|
||||
}
|
||||
}
|
||||
|
||||
@ -310,7 +310,7 @@ namespace sensors.internal {
|
||||
return getUartNumber(fmt, off, this._port)
|
||||
}
|
||||
|
||||
protected reset() {
|
||||
reset() {
|
||||
if (this.isActive()) uartReset(this._port);
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,16 @@
|
||||
#include <fcntl.h>
|
||||
#include <sys/ioctl.h>
|
||||
|
||||
/**
|
||||
* Mode for lseek()
|
||||
*/
|
||||
enum class SeekWhence {
|
||||
Set = 0,
|
||||
Current = 1,
|
||||
End = 2,
|
||||
};
|
||||
|
||||
|
||||
namespace pxt {
|
||||
PXT_VTABLE_CTOR(MMap) {
|
||||
length = 0;
|
||||
@ -111,4 +121,10 @@ int read(MMap *mmap, Buffer data) {
|
||||
return ::read(mmap->fd, data->data, data->length);
|
||||
}
|
||||
|
||||
/** Set pointer on the underlaying file. */
|
||||
//%
|
||||
int lseek(MMap *mmap, int offset, SeekWhence whence) {
|
||||
return ::lseek(mmap->fd, offset, (int)whence);
|
||||
}
|
||||
|
||||
}
|
@ -111,7 +111,7 @@ namespace motors {
|
||||
* Stops all motors
|
||||
*/
|
||||
//% blockId=motorStopAll block="stop all motors"
|
||||
//% weight=5
|
||||
//% weight=1
|
||||
//% group="Move"
|
||||
export function stopAllMotors() {
|
||||
const b = mkCmd(Output.ALL, DAL.opOutputStop, 0)
|
||||
@ -175,7 +175,7 @@ namespace motors {
|
||||
*/
|
||||
//% blockId=motorSetReversed block="set %motor|reversed %reversed"
|
||||
//% reversed.fieldEditor=toggleonoff
|
||||
//% weight=59
|
||||
//% weight=59 blockGap=8
|
||||
//% group="Move"
|
||||
setReversed(reversed: boolean) {
|
||||
this.init();
|
||||
@ -198,7 +198,9 @@ namespace motors {
|
||||
/**
|
||||
* Resets the motor(s).
|
||||
*/
|
||||
//%
|
||||
//% weight=5
|
||||
//% group="Move"
|
||||
//% blockId=motorReset block="%motors|reset"
|
||||
reset() {
|
||||
this.init();
|
||||
reset(this._port);
|
||||
@ -280,10 +282,12 @@ namespace motors {
|
||||
//% fixedInstances
|
||||
export class Motor extends MotorBase {
|
||||
private _large: boolean;
|
||||
private _regulated: boolean;
|
||||
|
||||
constructor(port: Output, large: boolean) {
|
||||
super(port, () => this.__init(), (speed) => this.__setSpeed(speed), (steps, stepsOrTime, speed) => this.__move(steps, stepsOrTime, speed));
|
||||
this._large = large;
|
||||
this._regulated = true;
|
||||
this.markUsed();
|
||||
}
|
||||
|
||||
@ -299,7 +303,7 @@ namespace motors {
|
||||
}
|
||||
|
||||
private __setSpeed(speed: number) {
|
||||
const b = mkCmd(this._port, DAL.opOutputSpeed, 1)
|
||||
const b = mkCmd(this._port, this._regulated ? DAL.opOutputSpeed : DAL.opOutputPower, 1)
|
||||
b.setNumber(NumberFormat.Int8LE, 2, speed)
|
||||
writePWM(b)
|
||||
if (speed) {
|
||||
@ -313,11 +317,24 @@ namespace motors {
|
||||
step1: 0,
|
||||
step2: stepsOrTime,
|
||||
step3: 0,
|
||||
speed: speed,
|
||||
speed: this._regulated ? speed : undefined,
|
||||
power: this._regulated ? undefined : speed,
|
||||
useBrake: this._brake
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the motor speed should be regulated. Default is true.
|
||||
* @param value true for regulated motor
|
||||
*/
|
||||
//% blockId=outputMotorSetRegulated block="set %motor|regulated %value"
|
||||
//% value.fieldEditor=toggleonoff
|
||||
//% weight=58
|
||||
//% group="Move"
|
||||
setRegulated(value: boolean) {
|
||||
this._regulated = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets motor actual speed.
|
||||
* @param motor the port which connects to the motor
|
||||
|
4
libs/core/shims.d.ts
vendored
4
libs/core/shims.d.ts
vendored
@ -41,6 +41,10 @@ declare interface MMap {
|
||||
/** Perform read(2) on the underlaying file */
|
||||
//% shim=MMapMethods::read
|
||||
read(data: Buffer): int32;
|
||||
|
||||
/** Set pointer on the underlaying file. */
|
||||
//% shim=MMapMethods::lseek
|
||||
lseek(offset: int32, whence: SeekWhence): int32;
|
||||
}
|
||||
declare namespace control {
|
||||
|
||||
|
@ -2,7 +2,7 @@ screen.clear()
|
||||
brick.print("PXT!", 10, 30, Draw.Quad)
|
||||
|
||||
brick.drawRect(40, 40, 20, 10, Draw.Fill)
|
||||
brick.setLight(LightsPattern.Orange)
|
||||
brick.setLight(BrickLight.Orange)
|
||||
|
||||
brick.heart.doubled().draw(100, 50, Draw.Double | Draw.Transparent)
|
||||
|
||||
@ -12,7 +12,7 @@ brick.buttonEnter.onEvent(ButtonEvent.Click, () => {
|
||||
|
||||
brick.buttonLeft.onEvent(ButtonEvent.Click, () => {
|
||||
brick.drawRect(10, 70, 20, 10, Draw.Fill)
|
||||
brick.setLight(LightsPattern.Red)
|
||||
brick.setLight(BrickLight.Red)
|
||||
brick.setFont(brick.microbitFont())
|
||||
})
|
||||
|
||||
|
3
libs/datalog/README.md
Normal file
3
libs/datalog/README.md
Normal file
@ -0,0 +1,3 @@
|
||||
# Datalog
|
||||
|
||||
A tiny libraty to create CSV datalog files.
|
8
libs/datalog/_locales/datalog-jsdoc-strings.json
Normal file
8
libs/datalog/_locales/datalog-jsdoc-strings.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"datalog.addRow": "Starts a row of data",
|
||||
"datalog.addValue": "Adds a cell to the row of data",
|
||||
"datalog.addValue|param|name": "name of the cell, eg: \"x\"",
|
||||
"datalog.addValue|param|value": "value of the cell, eg: 0",
|
||||
"datalog.setFile": "Starts a new data logger for the given file",
|
||||
"datalog.setStorage": "* @param storage custom storage solution"
|
||||
}
|
6
libs/datalog/_locales/datalog-strings.json
Normal file
6
libs/datalog/_locales/datalog-strings.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"datalog.addRow|block": "datalog add row",
|
||||
"datalog.addValue|block": "datalog add %name|=%value",
|
||||
"datalog|block": "datalog",
|
||||
"{id:category}Datalog": "Datalog"
|
||||
}
|
90
libs/datalog/datalog.ts
Normal file
90
libs/datalog/datalog.ts
Normal file
@ -0,0 +1,90 @@
|
||||
//% weight=100 color=#0fbc11 icon=""
|
||||
namespace datalog {
|
||||
let _headers: string[] = undefined;
|
||||
let _headersLength: number;
|
||||
let _values: number[];
|
||||
let _start: number;
|
||||
let _filename = "data.csv";
|
||||
let _storage: storage.Storage = storage.temporary;
|
||||
|
||||
function clear() {
|
||||
_headers = undefined;
|
||||
_values = undefined;
|
||||
}
|
||||
|
||||
function init() {
|
||||
if (!_headers) {
|
||||
_headers = [];
|
||||
_headersLength = 0;
|
||||
_start = control.millis();
|
||||
_storage.remove(_filename);
|
||||
}
|
||||
_values = [];
|
||||
}
|
||||
|
||||
function commit() {
|
||||
// write row if any data
|
||||
if (_values && _values.length > 0) {
|
||||
// write headers for the first row
|
||||
if (!_headersLength) {
|
||||
_storage.appendCSVHeaders(_filename, _headers);
|
||||
_headersLength = _storage.size(_filename);
|
||||
}
|
||||
// commit row data
|
||||
_storage.appendCSV(_filename, _values);
|
||||
}
|
||||
|
||||
// clear values
|
||||
_values = undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a row of data
|
||||
*/
|
||||
//% weight=100
|
||||
//% blockId=datalogAddRow block="datalog add row"
|
||||
export function addRow(): void {
|
||||
commit();
|
||||
init();
|
||||
const s = (control.millis() - _start) / 1000;
|
||||
addValue("time (s)", s);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a cell to the row of data
|
||||
* @param name name of the cell, eg: "x"
|
||||
* @param value value of the cell, eg: 0
|
||||
*/
|
||||
//% weight=99
|
||||
//% blockId=datalogAddValue block="datalog add %name|=%value"
|
||||
export function addValue(name: string, value: number) {
|
||||
if (!_values) return;
|
||||
let i = _headers.indexOf(name);
|
||||
if (i < 0) {
|
||||
_headers.push(name);
|
||||
i = _headers.length - 1;
|
||||
}
|
||||
_values[i] = value;
|
||||
if (i > 0) // 0 is time
|
||||
console.logValue(name, value)
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a new data logger for the given file
|
||||
*/
|
||||
//%
|
||||
export function setFile(fn: string) {
|
||||
_filename = fn;
|
||||
clear();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param storage custom storage solution
|
||||
*/
|
||||
//%
|
||||
export function setStorage(storage: storage.Storage) {
|
||||
_storage = storage;
|
||||
clear();
|
||||
}
|
||||
}
|
16
libs/datalog/pxt.json
Normal file
16
libs/datalog/pxt.json
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "datalog",
|
||||
"description": "Tiny data logging framework",
|
||||
"files": [
|
||||
"README.md",
|
||||
"datalog.ts"
|
||||
],
|
||||
"testFiles": [
|
||||
"test.ts"
|
||||
],
|
||||
"public": true,
|
||||
"dependencies": {
|
||||
"core": "file:../core",
|
||||
"storage": "file:../storage"
|
||||
}
|
||||
}
|
6
libs/datalog/test.ts
Normal file
6
libs/datalog/test.ts
Normal file
@ -0,0 +1,6 @@
|
||||
|
||||
loops.forever(function () {
|
||||
datalog.addRow()
|
||||
datalog.addValue("x", Math.random())
|
||||
datalog.addValue("y", Math.random())
|
||||
})
|
@ -56,4 +56,9 @@ namespace loops {
|
||||
//% color="#1E5AA8"
|
||||
namespace light {
|
||||
|
||||
}
|
||||
|
||||
//% color="#b0b0b0" advanced=true weight=5
|
||||
namespace storage {
|
||||
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"sensors.GyroSensor.angle": "Get the current angle from the gyroscope.",
|
||||
"sensors.GyroSensor.calibrate": "Forces a calibration of the gyro. Must be called when the sensor is completely still.",
|
||||
"sensors.GyroSensor.rotationRate": "Get the current rotation rate from the gyroscope."
|
||||
"sensors.GyroSensor.rate": "Get the current rotation rate from the gyroscope.",
|
||||
"sensors.GyroSensor.reset": "Forces a calibration of the gyro. Must be called when the sensor is completely still."
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"sensors.GyroSensor.angle|block": "%sensor|angle",
|
||||
"sensors.GyroSensor.calibrate|block": "%sensor|calibrate",
|
||||
"sensors.GyroSensor.rotationRate|block": "%sensor|rotation rate",
|
||||
"sensors.GyroSensor.rate|block": "%sensor|rate",
|
||||
"sensors.GyroSensor.reset|block": "%sensor|reset",
|
||||
"sensors.gyro1|block": "gyro 1",
|
||||
"sensors.gyro2|block": "gyro 2",
|
||||
"sensors.gyro3|block": "gyro 3",
|
||||
|
@ -45,15 +45,15 @@ namespace sensors {
|
||||
* Get the current rotation rate from the gyroscope.
|
||||
* @param sensor the gyroscope to query the request
|
||||
*/
|
||||
//% help=input/gyro/rotation-rate
|
||||
//% block="%sensor|rotation rate"
|
||||
//% help=input/gyro/rate
|
||||
//% block="%sensor|rate"
|
||||
//% blockId=gyroGetRate
|
||||
//% parts="gyroscope"
|
||||
//% blockNamespace=sensors
|
||||
//% sensor.fieldEditor="ports"
|
||||
//% weight=65 blockGap=8
|
||||
//% group="Gyro Sensor"
|
||||
rotationRate(): number {
|
||||
rate(): number {
|
||||
if (this.calibrating)
|
||||
pauseUntil(() => !this.calibrating, 2000);
|
||||
|
||||
@ -65,21 +65,21 @@ namespace sensors {
|
||||
* Forces a calibration of the gyro. Must be called when the sensor is completely still.
|
||||
*/
|
||||
//% help=input/gyro/calibrate
|
||||
//% block="%sensor|calibrate"
|
||||
//% blockId=gyroCalibrate
|
||||
//% block="%sensor|reset"
|
||||
//% blockId=gyroReset
|
||||
//% parts="gyroscope"
|
||||
//% blockNamespace=sensors
|
||||
//% sensor.fieldEditor="ports"
|
||||
//% weight=50 blockGap=8
|
||||
//% group="Gyro Sensor"
|
||||
calibrate(): void {
|
||||
reset(): void {
|
||||
if (this.calibrating) return; // already in calibration mode
|
||||
|
||||
this.calibrating = true;
|
||||
// may be triggered by a button click, give time to settle
|
||||
loops.pause(500);
|
||||
// send a reset command
|
||||
this.reset();
|
||||
super.reset();
|
||||
// we need to switch mode twice to perform a calibration
|
||||
if (this.mode == GyroSensorMode.Rate)
|
||||
this.setMode(GyroSensorMode.Angle);
|
||||
@ -90,7 +90,9 @@ namespace sensors {
|
||||
this.setMode(GyroSensorMode.Angle);
|
||||
else
|
||||
this.setMode(GyroSensorMode.Rate);
|
||||
this.calibrating = false;
|
||||
// give it more time to settle
|
||||
loops.pause(500);
|
||||
this.calibrating = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
29
libs/storage/_locales/storage-jsdoc-strings.json
Normal file
29
libs/storage/_locales/storage-jsdoc-strings.json
Normal file
@ -0,0 +1,29 @@
|
||||
{
|
||||
"storage.Storage.append": "Append string data to a new or existing file.",
|
||||
"storage.Storage.appendBuffer": "Append a buffer to a new or existing file.",
|
||||
"storage.Storage.appendCSV": "Append a row of CSV data",
|
||||
"storage.Storage.appendCSVHeaders": "Append a row of CSV headers",
|
||||
"storage.Storage.appendCSVHeaders|param|filename": "the file name to append data, eg: \"data.csv\"",
|
||||
"storage.Storage.appendCSVHeaders|param|headers": "the data to append",
|
||||
"storage.Storage.appendCSV|param|data": "the data to append",
|
||||
"storage.Storage.appendCSV|param|filename": "the file name to append data, eg: \"data.csv\"",
|
||||
"storage.Storage.appendLine": "Appends a new line of data in the file",
|
||||
"storage.Storage.appendLine|param|data": "the data to append",
|
||||
"storage.Storage.appendLine|param|filename": "the file name to append data, eg: \"data.txt\"",
|
||||
"storage.Storage.append|param|data": "the data to append",
|
||||
"storage.Storage.append|param|filename": "the file name to append data, eg: \"data.txt\"",
|
||||
"storage.Storage.exists": "Tests if a file exists",
|
||||
"storage.Storage.exists|param|filename": "the file name to append data, eg: \"data.txt\"",
|
||||
"storage.Storage.limit": "Resizing the size of a file to stay under the limit",
|
||||
"storage.Storage.limit|param|filename": "name of the file to drop",
|
||||
"storage.Storage.limit|param|size": "maximum length",
|
||||
"storage.Storage.overwrite": "Overwrite file with string data.",
|
||||
"storage.Storage.overwriteWithBuffer": "Overwrite file with a buffer.",
|
||||
"storage.Storage.overwrite|param|data": "the data to append",
|
||||
"storage.Storage.overwrite|param|filename": "the file name to append data, eg: \"data.txt\"",
|
||||
"storage.Storage.read": "Read contents of file as a string.",
|
||||
"storage.Storage.readAsBuffer": "Read contents of file as a buffer.",
|
||||
"storage.Storage.remove": "Delete a file, or do nothing if it doesn't exist.",
|
||||
"storage.Storage.size": "Return the size of the file, or -1 if it doesn't exists.",
|
||||
"storage.temporary": "Temporary storage in memory, deleted when the device restarts."
|
||||
}
|
15
libs/storage/_locales/storage-strings.json
Normal file
15
libs/storage/_locales/storage-strings.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"storage.Storage.appendCSVHeaders|block": "storage %source|%filename|append CSV headers %headers",
|
||||
"storage.Storage.appendCSV|block": "storage %source|%filename|append CSV %data",
|
||||
"storage.Storage.appendLine|block": "storage %source|%filename|append line %data",
|
||||
"storage.Storage.append|block": "storage %source|%filename|append %data",
|
||||
"storage.Storage.exists|block": "storage %source|%filename|exists",
|
||||
"storage.Storage.limit|block": "storage %source|limit %filename|to %size|bytes",
|
||||
"storage.Storage.overwrite|block": "storage %source|%filename|overwrite with|%data",
|
||||
"storage.Storage.read|block": "storage %source|read %filename|as string",
|
||||
"storage.Storage.remove|block": "storage %source|remove %filename",
|
||||
"storage.Storage.size|block": "storage %source|%filename|size",
|
||||
"storage.temporary|block": "temporary",
|
||||
"storage|block": "storage",
|
||||
"{id:category}Storage": "Storage"
|
||||
}
|
14
libs/storage/pxt.json
Normal file
14
libs/storage/pxt.json
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "storage",
|
||||
"description": "USB Pen-drive support and flash storage",
|
||||
"files": [
|
||||
"storage.cpp",
|
||||
"storage-core.ts",
|
||||
"storage.ts",
|
||||
"shims.d.ts"
|
||||
],
|
||||
"public": true,
|
||||
"dependencies": {
|
||||
"core": "file:../core"
|
||||
}
|
||||
}
|
17
libs/storage/shims.d.ts
vendored
Normal file
17
libs/storage/shims.d.ts
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
// Auto-generated. Do not edit.
|
||||
declare namespace storage {
|
||||
|
||||
/** Will be moved. */
|
||||
//% shim=storage::__stringToBuffer
|
||||
function __stringToBuffer(s: string): Buffer;
|
||||
|
||||
/** Will be moved. */
|
||||
//% shim=storage::__bufferToString
|
||||
function __bufferToString(s: Buffer): string;
|
||||
|
||||
/** Create named directory. */
|
||||
//% shim=storage::__mkdir
|
||||
function __mkdir(filename: string): void;
|
||||
}
|
||||
|
||||
// Auto-generated. Do not edit. Really.
|
197
libs/storage/storage-core.ts
Normal file
197
libs/storage/storage-core.ts
Normal file
@ -0,0 +1,197 @@
|
||||
namespace storage {
|
||||
//% shim=storage::__unlink
|
||||
function __unlink(filename: string): void { }
|
||||
//% shim=storage::__truncate
|
||||
function __truncate(filename: string): void { }
|
||||
|
||||
//% fixedInstances
|
||||
export class Storage {
|
||||
csvSeparator: string;
|
||||
constructor() {
|
||||
this.csvSeparator = ",";
|
||||
}
|
||||
|
||||
protected mapFilename(filename: string) {
|
||||
return filename;
|
||||
}
|
||||
|
||||
private getFile(filename: string): MMap {
|
||||
filename = this.mapFilename(filename)
|
||||
let r = control.mmap(filename, 0, 0)
|
||||
if (!r) {
|
||||
__mkdir(this.dirname(filename))
|
||||
__truncate(filename)
|
||||
r = control.mmap(filename, 0, 0)
|
||||
}
|
||||
if (!r)
|
||||
control.panic(906)
|
||||
return r
|
||||
}
|
||||
|
||||
dirname(filename: string) {
|
||||
let last = 0
|
||||
for (let i = 0; i < filename.length; ++i)
|
||||
if (filename[i] == "/")
|
||||
last = i
|
||||
return filename.substr(0, last)
|
||||
}
|
||||
|
||||
/**
|
||||
* Append string data to a new or existing file.
|
||||
* @param filename the file name to append data, eg: "data.txt"
|
||||
* @param data the data to append
|
||||
*/
|
||||
//% blockId=storageAppend block="storage %source|%filename|append %data"
|
||||
append(filename: string, data: string): void {
|
||||
this.appendBuffer(filename, __stringToBuffer(data))
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends a new line of data in the file
|
||||
* @param filename the file name to append data, eg: "data.txt"
|
||||
* @param data the data to append
|
||||
*/
|
||||
//% blockId=storageAppendLine block="storage %source|%filename|append line %data"
|
||||
appendLine(filename: string, data: string): void {
|
||||
this.append(filename, data + "\r\n");
|
||||
}
|
||||
|
||||
/** Append a buffer to a new or existing file. */
|
||||
appendBuffer(filename: string, data: Buffer): void {
|
||||
let f = this.getFile(filename);
|
||||
f.lseek(0, SeekWhence.End)
|
||||
f.write(data)
|
||||
}
|
||||
|
||||
/**
|
||||
* Append a row of CSV headers
|
||||
* @param filename the file name to append data, eg: "data.csv"
|
||||
* @param headers the data to append
|
||||
*/
|
||||
//% blockId=storageAppendCSVHeaders block="storage %source|%filename|append CSV headers %headers"
|
||||
appendCSVHeaders(filename: string, headers: string[]) {
|
||||
let s = ""
|
||||
for (const d of headers) {
|
||||
if (s) s += this.csvSeparator;
|
||||
s = s + d;
|
||||
}
|
||||
s += "\r\n"
|
||||
this.append(filename, s)
|
||||
}
|
||||
|
||||
/**
|
||||
* Append a row of CSV data
|
||||
* @param filename the file name to append data, eg: "data.csv"
|
||||
* @param data the data to append
|
||||
*/
|
||||
//% blockId=storageAppendCSV block="storage %source|%filename|append CSV %data"
|
||||
appendCSV(filename: string, data: number[]) {
|
||||
let s = ""
|
||||
for (const d of data) {
|
||||
if (s) s += this.csvSeparator;
|
||||
s = s + d;
|
||||
}
|
||||
s += "\r\n"
|
||||
this.append(filename, s)
|
||||
}
|
||||
|
||||
/** Overwrite file with string data.
|
||||
* @param filename the file name to append data, eg: "data.txt"
|
||||
* @param data the data to append
|
||||
*/
|
||||
//% blockId=storageOverwrite block="storage %source|%filename|overwrite with|%data"
|
||||
overwrite(filename: string, data: string): void {
|
||||
this.overwriteWithBuffer(filename, __stringToBuffer(data))
|
||||
}
|
||||
|
||||
/** Overwrite file with a buffer. */
|
||||
overwriteWithBuffer(filename: string, data: Buffer): void {
|
||||
__truncate(this.mapFilename(filename))
|
||||
this.appendBuffer(filename, data)
|
||||
}
|
||||
|
||||
/** Tests if a file exists
|
||||
* @param filename the file name to append data, eg: "data.txt"
|
||||
*/
|
||||
//% blockId=storageExists block="storage %source|%filename|exists"
|
||||
exists(filename: string): boolean {
|
||||
return !!control.mmap(this.mapFilename(filename), 0, 0);
|
||||
}
|
||||
|
||||
/** Delete a file, or do nothing if it doesn't exist. */
|
||||
//% blockId=storageRemove block="storage %source|remove %filename"
|
||||
remove(filename: string): void {
|
||||
__unlink(this.mapFilename(filename))
|
||||
}
|
||||
|
||||
/** Return the size of the file, or -1 if it doesn't exists. */
|
||||
//% blockId=storageSize block="storage %source|%filename|size"
|
||||
size(filename: string): int32 {
|
||||
let f = control.mmap(this.mapFilename(filename), 0, 0)
|
||||
if (!f) return -1;
|
||||
return f.lseek(0, SeekWhence.End)
|
||||
}
|
||||
|
||||
/** Read contents of file as a string. */
|
||||
//% blockId=storageRead block="storage %source|read %filename|as string"
|
||||
read(filename: string): string {
|
||||
return __bufferToString(this.readAsBuffer(filename))
|
||||
}
|
||||
|
||||
/** Read contents of file as a buffer. */
|
||||
//%
|
||||
readAsBuffer(filename: string): Buffer {
|
||||
let f = this.getFile(filename)
|
||||
let sz = f.lseek(0, SeekWhence.End)
|
||||
let b = output.createBuffer(sz)
|
||||
f.lseek(0, SeekWhence.Set);
|
||||
f.read(b)
|
||||
return b
|
||||
}
|
||||
|
||||
/**
|
||||
* Resizing the size of a file to stay under the limit
|
||||
* @param filename name of the file to drop
|
||||
* @param size maximum length
|
||||
*/
|
||||
//% blockId=storageLimit block="storage %source|limit %filename|to %size|bytes"
|
||||
limit(filename: string, size: number) {
|
||||
if (!this.exists(filename) || size < 0) return;
|
||||
|
||||
const sz = storage.temporary.size(filename);
|
||||
if (sz > size) {
|
||||
let buf = storage.temporary.readAsBuffer(filename)
|
||||
buf = buf.slice(buf.length / 2);
|
||||
storage.temporary.overwriteWithBuffer(filename, buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class TemporaryStorage extends Storage {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
protected mapFilename(filename: string) {
|
||||
if (filename[0] == '/') filename = filename.substr(1);
|
||||
return '/tmp/logs/' + filename;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Temporary storage in memory, deleted when the device restarts.
|
||||
*/
|
||||
//% whenUsed fixedInstance block="temporary"
|
||||
export const temporary: Storage = new TemporaryStorage();
|
||||
|
||||
class PermanentStorage extends Storage {
|
||||
constructor() {
|
||||
super()
|
||||
}
|
||||
|
||||
protected mapFilename(filename: string) {
|
||||
if (filename[0] == '/') return filename;
|
||||
return '/' + filename;
|
||||
}
|
||||
}
|
||||
}
|
44
libs/storage/storage.cpp
Normal file
44
libs/storage/storage.cpp
Normal file
@ -0,0 +1,44 @@
|
||||
#include "pxt.h"
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
namespace storage {
|
||||
|
||||
/** Will be moved. */
|
||||
//%
|
||||
Buffer __stringToBuffer(String s) {
|
||||
return mkBuffer((uint8_t *)s->data, s->length);
|
||||
}
|
||||
|
||||
/** Will be moved. */
|
||||
//%
|
||||
String __bufferToString(Buffer s) {
|
||||
return mkString((char*)s->data, s->length);
|
||||
}
|
||||
|
||||
//%
|
||||
void __init() {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
//%
|
||||
void __unlink(String filename) {
|
||||
::unlink(filename->data);
|
||||
}
|
||||
|
||||
//%
|
||||
void __truncate(String filename) {
|
||||
int fd = open(filename->data, O_CREAT | O_TRUNC | O_WRONLY, 0777);
|
||||
close(fd);
|
||||
}
|
||||
|
||||
/** Create named directory. */
|
||||
//%
|
||||
void __mkdir(String filename) {
|
||||
::mkdir(filename->data, 0777);
|
||||
}
|
||||
|
||||
} // namespace storage
|
11
libs/storage/storage.ts
Normal file
11
libs/storage/storage.ts
Normal file
@ -0,0 +1,11 @@
|
||||
namespace storage {
|
||||
// automatically send console output to temp storage
|
||||
storage.temporary.remove("console.txt");
|
||||
console.addListener(function(line) {
|
||||
const fn = "console.txt";
|
||||
const mxs = 65536;
|
||||
const t = control.millis();
|
||||
storage.temporary.appendLine(fn, `${t}> ${line}`);
|
||||
storage.temporary.limit(fn, 65536);
|
||||
})
|
||||
}
|
2
package-lock.json
generated
2
package-lock.json
generated
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "pxt-ev3",
|
||||
"version": "0.0.59",
|
||||
"version": "0.0.63",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "pxt-ev3",
|
||||
"version": "0.0.59",
|
||||
"version": "0.0.63",
|
||||
"description": "LEGO Mindstorms EV3 for Microsoft MakeCode",
|
||||
"private": true,
|
||||
"keywords": [
|
||||
@ -44,8 +44,8 @@
|
||||
"webfonts-generator": "^0.4.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"pxt-common-packages": "0.15.4",
|
||||
"pxt-core": "3.0.8"
|
||||
"pxt-common-packages": "0.15.5",
|
||||
"pxt-core": "3.0.11"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "node node_modules/pxt-core/built/pxt.js travis"
|
||||
|
@ -17,6 +17,8 @@
|
||||
"libs/gyro-sensor",
|
||||
"libs/chassis",
|
||||
"libs/ev3",
|
||||
"libs/storage",
|
||||
"libs/datalog",
|
||||
"libs/tests",
|
||||
"libs/behaviors"
|
||||
],
|
||||
|
@ -30,8 +30,8 @@ namespace pxsim {
|
||||
data,
|
||||
beforeMemRead: () => {
|
||||
//console.log("analog before read");
|
||||
data[AnalogOff.BatteryTemp] = 21; // TODO simulate this
|
||||
data[AnalogOff.BatteryCurrent] = 100; // TODO simulate this
|
||||
util.map16Bit(data, AnalogOff.BatteryTemp, 21);
|
||||
util.map16Bit(data, AnalogOff.BatteryCurrent, 900);
|
||||
const inputNodes = ev3board().getInputNodes();
|
||||
for (let port = 0; port < DAL.NUM_INPUTS; port++) {
|
||||
const node = inputNodes[port];
|
||||
|
@ -8,6 +8,7 @@ namespace pxsim.MMapMethods {
|
||||
read?: (d: Buffer) => number;
|
||||
write?: (d: Buffer) => number;
|
||||
ioctl?: (id: number, d: Buffer) => number;
|
||||
lseek?: (offset: number, whence: number) => number;
|
||||
}
|
||||
|
||||
import BM = pxsim.BufferMethods
|
||||
@ -23,6 +24,7 @@ namespace pxsim.MMapMethods {
|
||||
if (!impl.read) impl.read = () => 0
|
||||
if (!impl.write) impl.write = () => 0
|
||||
if (!impl.ioctl) impl.ioctl = () => -1
|
||||
if (!impl.lseek) impl.lseek = (offset, whence) => -1
|
||||
}
|
||||
destroy() {
|
||||
}
|
||||
@ -68,6 +70,10 @@ namespace pxsim.MMapMethods {
|
||||
export function read(m: MMap, data: Buffer): number {
|
||||
return m.impl.read(data)
|
||||
}
|
||||
|
||||
export function lseek(m: MMap, offset: number, whence: number): number {
|
||||
return m.impl.lseek(offset, whence);
|
||||
}
|
||||
}
|
||||
|
||||
namespace pxsim.control {
|
||||
|
@ -17,6 +17,8 @@ namespace pxsim {
|
||||
private speedCmdTime: number;
|
||||
private _synchedMotor: MotorNode; // non-null if synchronized
|
||||
|
||||
private manualSpeed: number = undefined;
|
||||
|
||||
constructor(port: number, large: boolean) {
|
||||
super(port);
|
||||
this.setLarge(large);
|
||||
@ -42,7 +44,7 @@ namespace pxsim {
|
||||
setSpeedCmd(cmd: DAL, values: number[]) {
|
||||
if (this.speedCmd != cmd ||
|
||||
JSON.stringify(this.speedCmdValues) != JSON.stringify(values))
|
||||
this.setChangedState();
|
||||
this.setChangedState();
|
||||
// new command TODO: values
|
||||
this.speedCmd = cmd;
|
||||
this.speedCmdValues = values;
|
||||
@ -96,6 +98,17 @@ namespace pxsim {
|
||||
this.started = true;
|
||||
}
|
||||
|
||||
manualMotorDown() {
|
||||
}
|
||||
|
||||
manualMotorMove(speed: number) {
|
||||
this.manualSpeed = speed;
|
||||
}
|
||||
|
||||
manualMotorUp() {
|
||||
this.manualSpeed = undefined;
|
||||
}
|
||||
|
||||
updateState(elapsed: number) {
|
||||
//console.log(`motor: ${elapsed}ms - ${this.speed}% - ${this.angle}> - ${this.tacho}|`)
|
||||
const interval = Math.min(20, elapsed);
|
||||
@ -109,79 +122,84 @@ namespace pxsim {
|
||||
}
|
||||
|
||||
private updateStateStep(elapsed: number) {
|
||||
// compute new speed
|
||||
switch (this.speedCmd) {
|
||||
case DAL.opOutputSpeed:
|
||||
case DAL.opOutputPower:
|
||||
// assume power == speed
|
||||
// TODO: PID
|
||||
this.speed = this.speedCmdValues[0];
|
||||
break;
|
||||
case DAL.opOutputTimeSpeed:
|
||||
case DAL.opOutputTimePower:
|
||||
case DAL.opOutputStepPower:
|
||||
case DAL.opOutputStepSpeed: {
|
||||
// ramp up, run, ramp down, <brake> using time
|
||||
const speed = this.speedCmdValues[0];
|
||||
const step1 = this.speedCmdValues[1];
|
||||
const step2 = this.speedCmdValues[2];
|
||||
const step3 = this.speedCmdValues[3];
|
||||
const brake = this.speedCmdValues[4];
|
||||
const dstep = (this.speedCmd == DAL.opOutputTimePower || this.speedCmd == DAL.opOutputTimeSpeed)
|
||||
? pxsim.U.now() - this.speedCmdTime
|
||||
: this.tacho - this.speedCmdTacho;
|
||||
if (dstep < step1) // rampup
|
||||
this.speed = speed * dstep / step1;
|
||||
else if (dstep < step1 + step2) // run
|
||||
this.speed = speed;
|
||||
else if (dstep < step1 + step2 + step3)
|
||||
this.speed = speed * (step1 + step2 + step3 - dstep) / (step1 + step2 + step3);
|
||||
else {
|
||||
if (brake) this.speed = 0;
|
||||
this.clearSpeedCmd();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DAL.opOutputStepSync:
|
||||
case DAL.opOutputTimeSync: {
|
||||
const otherMotor = this._synchedMotor;
|
||||
if (otherMotor.port < this.port) // handled in other motor code
|
||||
if (!this.manualSpeed) {
|
||||
// compute new speed
|
||||
switch (this.speedCmd) {
|
||||
case DAL.opOutputSpeed:
|
||||
case DAL.opOutputPower:
|
||||
// assume power == speed
|
||||
// TODO: PID
|
||||
this.speed = this.speedCmdValues[0];
|
||||
break;
|
||||
case DAL.opOutputTimeSpeed:
|
||||
case DAL.opOutputTimePower:
|
||||
case DAL.opOutputStepPower:
|
||||
case DAL.opOutputStepSpeed: {
|
||||
// ramp up, run, ramp down, <brake> using time
|
||||
const speed = this.speedCmdValues[0];
|
||||
const step1 = this.speedCmdValues[1];
|
||||
const step2 = this.speedCmdValues[2];
|
||||
const step3 = this.speedCmdValues[3];
|
||||
const brake = this.speedCmdValues[4];
|
||||
const dstep = (this.speedCmd == DAL.opOutputTimePower || this.speedCmd == DAL.opOutputTimeSpeed)
|
||||
? pxsim.U.now() - this.speedCmdTime
|
||||
: this.tacho - this.speedCmdTacho;
|
||||
if (dstep < step1) // rampup
|
||||
this.speed = speed * dstep / step1;
|
||||
else if (dstep < step1 + step2) // run
|
||||
this.speed = speed;
|
||||
else if (dstep < step1 + step2 + step3)
|
||||
this.speed = speed * (step1 + step2 + step3 - dstep) / (step1 + step2 + step3);
|
||||
else {
|
||||
if (brake) this.speed = 0;
|
||||
this.clearSpeedCmd();
|
||||
}
|
||||
break;
|
||||
|
||||
const speed = this.speedCmdValues[0];
|
||||
const turnRatio = this.speedCmdValues[1];
|
||||
const stepsOrTime = this.speedCmdValues[2];
|
||||
const brake = this.speedCmdValues[3];
|
||||
const dstep = this.speedCmd == DAL.opOutputTimeSync
|
||||
? pxsim.U.now() - this.speedCmdTime
|
||||
: this.tacho - this.speedCmdTacho;
|
||||
// 0 is special case, run infinite
|
||||
if (!stepsOrTime || dstep < stepsOrTime)
|
||||
this.speed = speed;
|
||||
else {
|
||||
if (brake) this.speed = 0;
|
||||
this.clearSpeedCmd();
|
||||
}
|
||||
case DAL.opOutputStepSync:
|
||||
case DAL.opOutputTimeSync: {
|
||||
const otherMotor = this._synchedMotor;
|
||||
if (otherMotor.port < this.port) // handled in other motor code
|
||||
break;
|
||||
|
||||
// turn ratio is a bit weird to interpret
|
||||
// see https://communities.theiet.org/blogs/698/1706
|
||||
if (turnRatio < 0) {
|
||||
otherMotor.speed = speed;
|
||||
this.speed *= (100 + turnRatio) / 100;
|
||||
} else {
|
||||
otherMotor.speed = this.speed * (100 - turnRatio) / 100;
|
||||
const speed = this.speedCmdValues[0];
|
||||
const turnRatio = this.speedCmdValues[1];
|
||||
const stepsOrTime = this.speedCmdValues[2];
|
||||
const brake = this.speedCmdValues[3];
|
||||
const dstep = this.speedCmd == DAL.opOutputTimeSync
|
||||
? pxsim.U.now() - this.speedCmdTime
|
||||
: this.tacho - this.speedCmdTacho;
|
||||
// 0 is special case, run infinite
|
||||
if (!stepsOrTime || dstep < stepsOrTime)
|
||||
this.speed = speed;
|
||||
else {
|
||||
if (brake) this.speed = 0;
|
||||
this.clearSpeedCmd();
|
||||
}
|
||||
|
||||
// turn ratio is a bit weird to interpret
|
||||
// see https://communities.theiet.org/blogs/698/1706
|
||||
if (turnRatio < 0) {
|
||||
otherMotor.speed = speed;
|
||||
this.speed *= (100 + turnRatio) / 100;
|
||||
} else {
|
||||
otherMotor.speed = this.speed * (100 - turnRatio) / 100;
|
||||
}
|
||||
|
||||
// clamp
|
||||
this.speed = Math.max(-100, Math.min(100, this.speed >> 0));
|
||||
otherMotor.speed = Math.max(-100, Math.min(100, otherMotor.speed >> 0));;
|
||||
|
||||
// stop other motor if needed
|
||||
if (!this._synchedMotor)
|
||||
otherMotor.clearSpeedCmd();
|
||||
break;
|
||||
}
|
||||
|
||||
// clamp
|
||||
this.speed = Math.max(-100, Math.min(100, this.speed >> 0));
|
||||
otherMotor.speed = Math.max(-100, Math.min(100, otherMotor.speed >> 0));;
|
||||
|
||||
// stop other motor if needed
|
||||
if (!this._synchedMotor)
|
||||
otherMotor.clearSpeedCmd();
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.speed = this.manualSpeed;
|
||||
}
|
||||
this.speed = Math.round(this.speed); // integer only
|
||||
|
||||
// compute delta angle
|
||||
|
@ -97,6 +97,7 @@ namespace pxsim {
|
||||
motors.forEach(motor => motor.stop());
|
||||
return 2;
|
||||
}
|
||||
case DAL.opOutputPower:
|
||||
case DAL.opOutputSpeed: {
|
||||
// setSpeed
|
||||
const port = buf.data[1];
|
||||
|
23
sim/state/storage.ts
Normal file
23
sim/state/storage.ts
Normal file
@ -0,0 +1,23 @@
|
||||
namespace pxsim.storage {
|
||||
export function __stringToBuffer(s: string): RefBuffer {
|
||||
// TODO
|
||||
return new RefBuffer(new Uint8Array([]));
|
||||
}
|
||||
|
||||
export function __bufferToString(b: RefBuffer): string {
|
||||
// TODO
|
||||
return "";
|
||||
}
|
||||
|
||||
export function __mkdir(fn: string) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
export function __unlink(filename: string): void {
|
||||
// TODO
|
||||
}
|
||||
|
||||
export function __truncate(filename: string): void {
|
||||
// TODO
|
||||
}
|
||||
}
|
@ -44,7 +44,7 @@ namespace pxsim.visuals {
|
||||
}
|
||||
.sim-text.number {
|
||||
font-family: Courier, Lato, Work Sans, PT Serif, Source Serif Pro;
|
||||
font-weight: bold;
|
||||
/*font-weight: bold;*/
|
||||
}
|
||||
.sim-text.inverted {
|
||||
fill:#5A5A5A;
|
||||
@ -71,6 +71,15 @@ namespace pxsim.visuals {
|
||||
fill: gray !important;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Motor slider */
|
||||
.sim-motor-btn {
|
||||
cursor: pointer;
|
||||
}
|
||||
.sim-motor-btn:hover .btn {
|
||||
stroke-width: 2px;
|
||||
stroke: black !important;
|
||||
}
|
||||
`;
|
||||
|
||||
const EV3_WIDTH = 99.984346;
|
||||
@ -222,9 +231,8 @@ namespace pxsim.visuals {
|
||||
}
|
||||
case NodeType.MediumMotor:
|
||||
case NodeType.LargeMotor: {
|
||||
// TODO: figure out if the motor is in "input" or "output" mode
|
||||
const state = ev3board().getMotors()[port];
|
||||
view = new MotorReporterControl(this.element, this.defs, state, port);
|
||||
view = new MotorSliderControl(this.element, this.defs, state, port);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -27,8 +27,8 @@ namespace pxsim.visuals {
|
||||
this.group = svg.elt("g") as SVGGElement;
|
||||
|
||||
const reporterGroup = pxsim.svg.child(this.group, "g");
|
||||
reporterGroup.setAttribute("transform", `translate(31, 42)`);
|
||||
this.reporter = pxsim.svg.child(reporterGroup, "text", { 'x': 0, 'y': '0', 'class': 'sim-text number large inverted' }) as SVGTextElement;
|
||||
reporterGroup.setAttribute("transform", `translate(${this.getWidth() / 2}, 42)`);
|
||||
this.reporter = pxsim.svg.child(reporterGroup, "text", { 'text-anchor': 'middle', 'x': 0, 'y': '0', 'class': 'sim-text number large inverted' }) as SVGTextElement;
|
||||
|
||||
const sliderGroup = pxsim.svg.child(this.group, "g");
|
||||
sliderGroup.setAttribute("transform", `translate(${this.getWidth() / 2 - this.getSliderWidth() / 2}, ${this.getReporterHeight()})`)
|
||||
@ -60,12 +60,15 @@ namespace pxsim.visuals {
|
||||
}, ev => {
|
||||
captured = true;
|
||||
if ((ev as MouseEvent).clientY != undefined) {
|
||||
dragSurface.setAttribute('cursor', '-webkit-grabbing');
|
||||
this.updateSliderValue(pt, parent, ev as MouseEvent);
|
||||
}
|
||||
}, () => {
|
||||
captured = false;
|
||||
dragSurface.setAttribute('cursor', '-webkit-grab');
|
||||
}, () => {
|
||||
captured = false;
|
||||
dragSurface.setAttribute('cursor', '-webkit-grab');
|
||||
})
|
||||
|
||||
return this.group;
|
||||
|
167
sim/visuals/controls/motorSlider.ts
Normal file
167
sim/visuals/controls/motorSlider.ts
Normal file
@ -0,0 +1,167 @@
|
||||
|
||||
|
||||
namespace pxsim.visuals {
|
||||
|
||||
export class MotorSliderControl extends ControlView<MotorNode> {
|
||||
private group: SVGGElement;
|
||||
private gradient: SVGLinearGradientElement;
|
||||
private slider: SVGGElement;
|
||||
|
||||
private reporter: SVGTextElement;
|
||||
|
||||
private dial: SVGGElement;
|
||||
|
||||
private static SLIDER_RADIUS = 100;
|
||||
|
||||
private internalSpeed: number = 0;
|
||||
|
||||
getInnerView(parent: SVGSVGElement, globalDefs: SVGDefsElement) {
|
||||
this.group = svg.elt("g") as SVGGElement;
|
||||
|
||||
const slider = pxsim.svg.child(this.group, 'g', { 'transform': 'translate(25,25)' })
|
||||
const outerCircle = pxsim.svg.child(slider, "circle", {
|
||||
'stroke-dasharray': '565.48', 'stroke-dashoffset': '0',
|
||||
'cx': 100, 'cy': 100, 'r': '90', 'style': `fill:transparent;`,
|
||||
'stroke': '#a8aaa8', 'stroke-width': '1rem'
|
||||
}) as SVGCircleElement;
|
||||
|
||||
this.reporter = pxsim.svg.child(this.group, "text", {
|
||||
'x': this.getInnerWidth() / 2, 'y': this.getInnerHeight() / 2,
|
||||
'text-anchor': 'middle', 'alignment-baseline': 'middle',
|
||||
'style': 'font-size: 50px',
|
||||
'class': 'sim-text inverted number'
|
||||
}) as SVGTextElement;
|
||||
|
||||
this.dial = pxsim.svg.child(slider, "g", { 'cursor': '-webkit-grab' }) as SVGGElement;
|
||||
const handleInner = pxsim.svg.child(this.dial, "g");
|
||||
pxsim.svg.child(handleInner, "circle", { 'cx': 0, 'cy': 0, 'r': 30, 'style': 'fill: #f12a21;' });
|
||||
pxsim.svg.child(handleInner, "circle", { 'cx': 0, 'cy': 0, 'r': 29.5, 'style': 'fill: none;stroke: #b32e29' });
|
||||
|
||||
this.updateDial();
|
||||
|
||||
let pt = parent.createSVGPoint();
|
||||
let captured = false;
|
||||
|
||||
const dragSurface = svg.child(this.group, "rect", {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: this.getInnerWidth(),
|
||||
height: this.getInnerHeight(),
|
||||
opacity: 0,
|
||||
cursor: '-webkit-grab'
|
||||
})
|
||||
|
||||
touchEvents(dragSurface, ev => {
|
||||
if (captured && (ev as MouseEvent).clientY != undefined) {
|
||||
ev.preventDefault();
|
||||
this.updateSliderValue(pt, parent, ev as MouseEvent);
|
||||
this.handleSliderMove();
|
||||
}
|
||||
}, ev => {
|
||||
captured = true;
|
||||
if ((ev as MouseEvent).clientY != undefined) {
|
||||
this.updateSliderValue(pt, parent, ev as MouseEvent);
|
||||
this.handleSliderDown();
|
||||
}
|
||||
}, () => {
|
||||
captured = false;
|
||||
this.handleSliderUp();
|
||||
}, () => {
|
||||
captured = false;
|
||||
this.handleSliderUp();
|
||||
})
|
||||
|
||||
return this.group;
|
||||
}
|
||||
|
||||
getInnerWidth() {
|
||||
return 250;
|
||||
}
|
||||
|
||||
getInnerHeight() {
|
||||
return 250;
|
||||
}
|
||||
|
||||
private lastPosition: number;
|
||||
private prevVal: number;
|
||||
private updateSliderValue(pt: SVGPoint, parent: SVGSVGElement, ev: MouseEvent) {
|
||||
let cur = svg.cursorPoint(pt, parent, ev);
|
||||
const coords = {
|
||||
x: cur.x / this.scaleFactor - this.left / this.scaleFactor,
|
||||
y: cur.y / this.scaleFactor - this.top / this.scaleFactor
|
||||
};
|
||||
const radius = MotorSliderControl.SLIDER_RADIUS / 2;
|
||||
const dx = coords.x - radius;
|
||||
const dy = coords.y - radius;
|
||||
const atan = Math.atan(-dy / dx);
|
||||
let deg = Math.ceil(atan * (180 / Math.PI));
|
||||
|
||||
if (dx < 0) {
|
||||
deg -= 270;
|
||||
} else if (dy > 0) {
|
||||
deg -= 450;
|
||||
} else if (dx >= 0 && dy <= 0) {
|
||||
deg = 90 - deg;
|
||||
}
|
||||
const value = Math.abs(Math.ceil((deg % 360) / 360 * this.getMax()));
|
||||
|
||||
this.internalSpeed = value;
|
||||
this.updateDial();
|
||||
|
||||
this.prevVal = deg;
|
||||
this.lastPosition = cur.x;
|
||||
}
|
||||
|
||||
private handleSliderDown() {
|
||||
const state = this.state;
|
||||
state.manualMotorDown();
|
||||
}
|
||||
|
||||
private handleSliderMove() {
|
||||
this.dial.setAttribute('cursor', '-webkit-grabbing');
|
||||
const state = this.state;
|
||||
state.manualMotorMove(this.internalSpeed);
|
||||
}
|
||||
|
||||
private handleSliderUp() {
|
||||
this.dial.setAttribute('cursor', '-webkit-grab');
|
||||
const state = this.state;
|
||||
state.manualMotorUp();
|
||||
|
||||
this.internalSpeed = 0;
|
||||
this.updateDial();
|
||||
}
|
||||
|
||||
private updateDial() {
|
||||
let speed = this.internalSpeed;
|
||||
|
||||
// Update dial position
|
||||
const deg = speed / this.getMax() * 360; // degrees
|
||||
const radius = MotorSliderControl.SLIDER_RADIUS;
|
||||
const dialRadius = 5;
|
||||
const x = Math.ceil((radius - dialRadius) * Math.sin(deg * Math.PI / 180)) + radius;
|
||||
const y = Math.ceil((radius - dialRadius) * -Math.cos(deg * Math.PI / 180)) + radius;
|
||||
this.dial.setAttribute('transform', `translate(${x}, ${y})`);
|
||||
}
|
||||
|
||||
updateState() {
|
||||
if (!this.visible) {
|
||||
return;
|
||||
}
|
||||
const node = this.state;
|
||||
const speed = node.getSpeed();
|
||||
|
||||
// Update reporter
|
||||
this.reporter.textContent = `${speed}`;
|
||||
}
|
||||
|
||||
private getMin() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
private getMax() {
|
||||
return 100;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user