├── .classpath
├── .gitignore
├── .project
├── .settings
├── org.eclipse.core.resources.prefs
├── org.eclipse.jdt.core.prefs
└── org.eclipse.m2e.core.prefs
├── .travis.yml
├── LICENSE
├── README.md
├── pom.xml
├── screenshots
├── 1.png
└── 2.png
├── shortcuts.sh
└── src
├── main
├── assembly
│ └── assembly.xml
├── java
│ ├── module-info.java
│ └── ru
│ │ └── r2cloud
│ │ └── rtlspectrum
│ │ ├── BinData.java
│ │ ├── BinDataParser.java
│ │ ├── Controller.java
│ │ ├── FrequencyFormatter.java
│ │ ├── Legend.java
│ │ ├── LineChartWithMarkers.java
│ │ ├── LoadFile.java
│ │ ├── MacOsUtil.java
│ │ ├── Main.java
│ │ ├── MainApplication.java
│ │ ├── PowerFormatter.java
│ │ ├── RtlPowerProgress.java
│ │ ├── RunRtlPower.java
│ │ ├── SaveTask.java
│ │ ├── StatusBar.java
│ │ ├── StatusBarTask.java
│ │ └── SubtractFile.java
└── resources
│ ├── dark.css
│ ├── layout.fxml
│ └── statusBar.fxml
└── test
├── java
└── ru
│ └── r2cloud
│ └── rtlspectrum
│ ├── BinDataParserTest.java
│ ├── FrequencyFormatterTest.java
│ ├── MacOsUtilTest.java
│ ├── PowerFormatterTest.java
│ └── UITest.java
└── resources
├── defaults_empty.sh
├── defaults_fail.sh
├── defaults_mock.sh
├── subtract.csv
└── test.csv
/.classpath:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Compiled class file
2 | *.class
3 |
4 | # Log file
5 | *.log
6 |
7 | # BlueJ files
8 | *.ctxt
9 |
10 | # Mobile Tools for Java (J2ME)
11 | .mtj.tmp/
12 |
13 | # Package Files #
14 | *.jar
15 | *.war
16 | *.nar
17 | *.ear
18 | *.zip
19 | *.tar.gz
20 | *.rar
21 |
22 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
23 | hs_err_pid*
24 | /target/
25 |
--------------------------------------------------------------------------------
/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | rtlSpectrum
4 |
5 |
6 |
7 |
8 |
9 | org.eclipse.jdt.core.javabuilder
10 |
11 |
12 |
13 |
14 | org.eclipse.m2e.core.maven2Builder
15 |
16 |
17 |
18 |
19 |
20 | org.eclipse.jdt.core.javanature
21 | org.eclipse.m2e.core.maven2Nature
22 |
23 |
24 |
--------------------------------------------------------------------------------
/.settings/org.eclipse.core.resources.prefs:
--------------------------------------------------------------------------------
1 | eclipse.preferences.version=1
2 | encoding//src/main/java=UTF-8
3 | encoding//src/main/resources=UTF-8
4 | encoding//src/test/java=UTF-8
5 | encoding//src/test/resources=UTF-8
6 | encoding/=UTF-8
7 |
--------------------------------------------------------------------------------
/.settings/org.eclipse.jdt.core.prefs:
--------------------------------------------------------------------------------
1 | eclipse.preferences.version=1
2 | org.eclipse.jdt.core.compiler.annotation.inheritNullAnnotations=disabled
3 | org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore
4 | org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jdt.annotation.NonNull
5 | org.eclipse.jdt.core.compiler.annotation.nonnull.secondary=
6 | org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jdt.annotation.NonNullByDefault
7 | org.eclipse.jdt.core.compiler.annotation.nonnullbydefault.secondary=
8 | org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable
9 | org.eclipse.jdt.core.compiler.annotation.nullable.secondary=
10 | org.eclipse.jdt.core.compiler.annotation.nullanalysis=enabled
11 | org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
12 | org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate
13 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=11
14 | org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
15 | org.eclipse.jdt.core.compiler.compliance=11
16 | org.eclipse.jdt.core.compiler.debug.lineNumber=generate
17 | org.eclipse.jdt.core.compiler.debug.localVariable=generate
18 | org.eclipse.jdt.core.compiler.debug.sourceFile=generate
19 | org.eclipse.jdt.core.compiler.problem.APILeak=warning
20 | org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning
21 | org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
22 | org.eclipse.jdt.core.compiler.problem.autoboxing=ignore
23 | org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning
24 | org.eclipse.jdt.core.compiler.problem.deadCode=warning
25 | org.eclipse.jdt.core.compiler.problem.deprecation=warning
26 | org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled
27 | org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled
28 | org.eclipse.jdt.core.compiler.problem.discouragedReference=ignore
29 | org.eclipse.jdt.core.compiler.problem.emptyStatement=warning
30 | org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled
31 | org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
32 | org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=ignore
33 | org.eclipse.jdt.core.compiler.problem.fallthroughCase=ignore
34 | org.eclipse.jdt.core.compiler.problem.fatalOptionalError=enabled
35 | org.eclipse.jdt.core.compiler.problem.fieldHiding=ignore
36 | org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning
37 | org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning
38 | org.eclipse.jdt.core.compiler.problem.forbiddenReference=ignore
39 | org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning
40 | org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts=disabled
41 | org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning
42 | org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=warning
43 | org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore
44 | org.eclipse.jdt.core.compiler.problem.localVariableHiding=ignore
45 | org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning
46 | org.eclipse.jdt.core.compiler.problem.missingDefaultCase=ignore
47 | org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=ignore
48 | org.eclipse.jdt.core.compiler.problem.missingEnumCaseDespiteDefault=disabled
49 | org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=warning
50 | org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=warning
51 | org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled
52 | org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning
53 | org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore
54 | org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning
55 | org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning
56 | org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore
57 | org.eclipse.jdt.core.compiler.problem.nonnullParameterAnnotationDropped=warning
58 | org.eclipse.jdt.core.compiler.problem.nonnullTypeVariableFromLegacyInvocation=warning
59 | org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=error
60 | org.eclipse.jdt.core.compiler.problem.nullReference=error
61 | org.eclipse.jdt.core.compiler.problem.nullSpecViolation=error
62 | org.eclipse.jdt.core.compiler.problem.nullUncheckedConversion=warning
63 | org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning
64 | org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore
65 | org.eclipse.jdt.core.compiler.problem.pessimisticNullAnalysisForFreeTypeVariables=warning
66 | org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=warning
67 | org.eclipse.jdt.core.compiler.problem.potentialNullReference=warning
68 | org.eclipse.jdt.core.compiler.problem.potentiallyUnclosedCloseable=ignore
69 | org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning
70 | org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning
71 | org.eclipse.jdt.core.compiler.problem.redundantNullCheck=warning
72 | org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=ignore
73 | org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=warning
74 | org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore
75 | org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=warning
76 | org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning
77 | org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled
78 | org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning
79 | org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled
80 | org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled
81 | org.eclipse.jdt.core.compiler.problem.syntacticNullAnalysisForFields=disabled
82 | org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore
83 | org.eclipse.jdt.core.compiler.problem.terminalDeprecation=warning
84 | org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning
85 | org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=enabled
86 | org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning
87 | org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning
88 | org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=warning
89 | org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning
90 | org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentType=warning
91 | org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentTypeStrict=disabled
92 | org.eclipse.jdt.core.compiler.problem.unlikelyEqualsArgumentType=info
93 | org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore
94 | org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=warning
95 | org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore
96 | org.eclipse.jdt.core.compiler.problem.unstableAutoModuleName=warning
97 | org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=warning
98 | org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled
99 | org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled
100 | org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled
101 | org.eclipse.jdt.core.compiler.problem.unusedExceptionParameter=ignore
102 | org.eclipse.jdt.core.compiler.problem.unusedImport=warning
103 | org.eclipse.jdt.core.compiler.problem.unusedLabel=warning
104 | org.eclipse.jdt.core.compiler.problem.unusedLocal=warning
105 | org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=warning
106 | org.eclipse.jdt.core.compiler.problem.unusedParameter=warning
107 | org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled
108 | org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled
109 | org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled
110 | org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning
111 | org.eclipse.jdt.core.compiler.problem.unusedTypeParameter=warning
112 | org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning
113 | org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning
114 | org.eclipse.jdt.core.compiler.release=enabled
115 | org.eclipse.jdt.core.compiler.source=11
116 |
--------------------------------------------------------------------------------
/.settings/org.eclipse.m2e.core.prefs:
--------------------------------------------------------------------------------
1 | activeProfiles=
2 | eclipse.preferences.version=1
3 | resolveWorkspaceProjects=true
4 | version=1
5 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: java
2 | bundler_args: --retry 1
3 | os: linux
4 | sudo: false
5 |
6 | addons:
7 | sonarcloud:
8 | organization: "dernasherbrezon-github"
9 | token:
10 | secure: "ajg6cm5798rx2DHmwhie9VQko8J6OIiDDRwrOoP1zHLodmcjtJEhtvAnPpSEHjEINunLpOyhVncYpIPiSQW8Fq5L9FMtmDHaQpXPB1RZX24VGEVdB1BjUAQqe1c9FrNHjP7c/UdR6xXUkI83mejTlZI9VgVXpTpfG5geElo/2Ewhl/VsWinRKPDeE9fqdRZrw7eKfrNNOBn9PCqxBTdIsY/ed3eKQBXSdYsM5bIk/VE2UL3v7SYagKP5/B0sDDEbxLps4mVPC57yAx8DFm2LegqwXxh0RXNjnfuyCPLrx7CmP3p5P5Q/oDXaX48/wQd9bqfIxCfnLwaYjHpki8d6XJ85J3DLU5QgqQkUuJjDwvTbs5k1Z8QY5y2gTnEiw9EmAzDbi3NyTjfBwgioULB9I02+vGIv/SNuENxRaODbnGDCdw0071J9TiLGF5bzezAvXDgdGdQD/uGWbaCtaUhzHCbpbgEc7DkEgSmz96rORlR45JB/SBBb0KWi6Cf5I/nwCLsYCNaqI9fricpwU9YfB19YE/xPFY7YirkPHAiQRhL8fgD+u473R/b5PjHpYgQF9nfZG5tZXGRFdaVm4RcVawy1n2MXmiWxg8ZUS3gB5pgErpapH9S4LLk8ESpa/ciPYfMmWcAurv6ziSkdnh7oE8zgNodFDt/StHRrxeEndV4="
11 |
12 | install: true
13 |
14 | script:
15 | - mvn clean verify sonar:sonar -Pcoverage -Dsonar.scm.disabled=true -Dsonar.language=java
16 |
17 | cache:
18 | directories:
19 | - $HOME/.m2/repository
20 | - $HOME/.sonar/cache
21 |
22 | before_cache:
23 | - rm -rf $HOME/.m2/repository/ru/r2cloud/
24 |
25 | before_deploy:
26 | - mvn -Djavafx.platform=mac-aarch64 assembly:single
27 | - mvn -Djavafx.platform=mac assembly:single
28 | - mvn -Djavafx.platform=linux assembly:single
29 | - mvn -Djavafx.platform=win assembly:single
30 |
31 | deploy:
32 | provider: releases
33 | api_key:
34 | secure: "xydSkI4RpT7KrrUqnL325tqQHtkQl/UrGKylaWaoO3i9KrMkWGlPDen9tJ9M4WQGC0Kw8OmgOxUGQRabVJIphQdMrQXiLm2wRs01YlsIQSyoAY1JuQxEBnDizfLqDSjIm7nlp4HqOFy0HsPRztcezkxgiQO7uivr9NP+mR7WsNQDsnq/s0egq8/Ae0frr9SJJ7KUjK/ZRKjmNaGjbbCg7T+jjboORd1gUc+5+W6okfePSiH14S3ZHud++KA4P+RRyBmFhmfaFSmj3083+j2YP8y/FtFmNbqtbT5Pq/6HjY6LMOLW+oV17U203sy7Fh7FhF3hl8zlaDJl1fGHEADt9HcybWYmY8m22h0xJlrRrEBaUp5yISpFzfEIL0lS4HBqObYGmGTYt+oJ8f31cGbtug3DPOkioE+u7FyrihOzXUi2OJ1rpHdWLe+56T+GCMNOYwfDVZ6L6mAS0tZHqKe2UVc3vr8cWDqk2tSTGth+zK1fWlMveCxoJ9fIDAiJMSeoWfNV2SOJb7h9R5lpi1XNaf4GRcT8uM5qyOHDxNF8bpt6+lfo7kSaXXvS5ks9+AgikFgArRkJCAO7VKLweSo5MW321024dfSOZF1m3GaSWHpcF40l6bko6yXKM35av29ZRI65RojrU+dbmaYiWkHPQmAo6E/2GsejUOungE+6u3Q="
35 | file_glob: true
36 | file: "target/rtlSpectrum_*.jar"
37 | skip_cleanup: true
38 | on:
39 | tags: true
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # rtlSpectrum [](https://app.travis-ci.com/github/dernasherbrezon/rtlSpectrum) [](https://sonarcloud.io/dashboard?id=ru.r2cloud%3ArtlSpectrum)
2 |
3 | Analyze spectrograms created by rtl_power
4 |
5 | # Screenshots
6 |
7 | 
8 | 
9 |
10 | # Features
11 |
12 | * load from .csv file produced by rtl_power
13 | * run rtl_power directly. it should be available in the $PATH
14 | * add multiple graphs for analysis
15 | * substract one graph from another
16 | * save/export graph in the rtl_power based format
17 |
18 | # Installation
19 |
20 | * Ensure you have java installed. Required version is Java 11+
21 | * Go to [Releases](https://github.com/dernasherbrezon/rtlSpectrum/releases) tab
22 | * Download the latest .jar file for your operating system. Supported operating systems are:
23 | * Linux 64bit
24 | * Windows 64bit
25 | * MacOS Intel 64bit
26 | * MacOS M1
27 | * Run it using the command ```java -jar rtlSpectrum_win.jar```
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 | 4.0.0
3 | ru.r2cloud
4 | rtlSpectrum
5 | 1.9-SNAPSHOT
6 | 2019
7 | rtlSpectrum
8 | Analyze spectrograms created by rtl_power
9 |
10 |
11 | UTF-8
12 | 18-ea+6
13 |
14 |
15 |
16 |
17 | org.testfx
18 | testfx-junit
19 | 4.0.15-alpha
20 | test
21 |
22 |
23 | org.openjfx
24 | javafx-controls
25 | ${javafx-version}
26 |
27 |
28 | org.openjfx
29 | javafx-fxml
30 | ${javafx-version}
31 |
32 |
33 | org.openjfx
34 | javafx-graphics
35 | ${javafx-version}
36 |
37 |
38 |
39 |
40 |
41 |
42 | maven-compiler-plugin
43 | 3.8.0
44 |
45 | 11
46 |
47 |
48 |
49 | maven-assembly-plugin
50 | 2.2-beta-5
51 |
52 |
53 |
54 | ru.r2cloud.rtlspectrum.Main
55 |
56 |
57 |
58 | jar-with-dependencies
59 |
60 | rtlSpectrum_${javafx.platform}
61 | false
62 |
63 |
64 |
65 | maven-deploy-plugin
66 | 2.7
67 |
68 | true
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 | coverage
77 |
78 |
79 |
80 | org.jacoco
81 | jacoco-maven-plugin
82 | 0.8.3
83 |
84 |
85 | prepare-agent
86 |
87 | prepare-agent
88 |
89 |
90 |
91 | report
92 |
93 | report
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 | release
103 |
104 |
105 |
106 | org.apache.maven.plugins
107 | maven-release-plugin
108 | 2.5.1
109 |
110 | true
111 | false
112 | release
113 | deploy
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 | The Apache Software License, Version 2.0
124 | http://www.apache.org/licenses/LICENSE-2.0.txt
125 |
126 |
127 |
128 |
129 | DErNasherBrezson
130 | DErNasherBrezson
131 | dernasherbrezon@gmail.com
132 |
133 |
134 |
135 |
136 | https://github.com/dernasherbrezon/rtlSpectrum
137 | scm:git:git@github.com/dernasherbrezon/rtlSpectrum.git
138 | scm:git:git@github.com:dernasherbrezon/rtlSpectrum.git
139 | rtlSpectrum-1.6
140 |
141 |
142 |
143 | GitHub Issues
144 | https://github.com/dernasherbrezon/rtlSpectrum/issues
145 |
146 |
--------------------------------------------------------------------------------
/screenshots/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dernasherbrezon/rtlSpectrum/572d556d602eb0daff43ab9894190862d26c4ead/screenshots/1.png
--------------------------------------------------------------------------------
/screenshots/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dernasherbrezon/rtlSpectrum/572d556d602eb0daff43ab9894190862d26c4ead/screenshots/2.png
--------------------------------------------------------------------------------
/shortcuts.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | mvn -P release release:clean release:prepare
4 | mvn -P release release:perform
--------------------------------------------------------------------------------
/src/main/assembly/assembly.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 | internalresources
6 |
7 | dir
8 |
9 | .
10 | false
11 |
12 |
13 |
14 |
15 | *:*
16 |
17 | .
18 | false
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/src/main/java/module-info.java:
--------------------------------------------------------------------------------
1 | module rtlspectrum {
2 | requires javafx.controls;
3 | requires javafx.fxml;
4 |
5 | opens ru.r2cloud.rtlspectrum to javafx.graphics,javafx.fxml;
6 |
7 | }
--------------------------------------------------------------------------------
/src/main/java/ru/r2cloud/rtlspectrum/BinData.java:
--------------------------------------------------------------------------------
1 | package ru.r2cloud.rtlspectrum;
2 |
3 | import javafx.scene.chart.XYChart;
4 |
5 | public class BinData {
6 |
7 | private String date;
8 | private String time;
9 | private String frequencyStart;
10 | private long frequencyStartParsed;
11 | private String frequencyEnd;
12 | private String binSize;
13 | private String numberOfSamples;
14 | private XYChart.Data parsed;
15 | private double dbmAverage;
16 |
17 | private double dbmTotal;
18 | private int dbmCount;
19 |
20 | public BinData() {
21 | // do nothing
22 | }
23 |
24 | public BinData(BinData copy) {
25 | this.date = copy.date;
26 | this.time = copy.time;
27 | this.frequencyStart = copy.frequencyStart;
28 | this.frequencyStartParsed = copy.frequencyStartParsed;
29 | this.frequencyEnd = copy.frequencyEnd;
30 | this.binSize = copy.binSize;
31 | this.numberOfSamples = copy.numberOfSamples;
32 | this.parsed = copy.parsed;
33 | this.dbmAverage = copy.dbmAverage;
34 | this.dbmTotal = copy.dbmTotal;
35 | this.dbmCount = copy.dbmCount;
36 | }
37 |
38 | public long getFrequencyStartParsed() {
39 | return frequencyStartParsed;
40 | }
41 |
42 | public void setFrequencyStartParsed(long frequencyStartParsed) {
43 | this.frequencyStartParsed = frequencyStartParsed;
44 | }
45 |
46 | public int getDbmCount() {
47 | return dbmCount;
48 | }
49 |
50 | public void setDbmCount(int dbmCount) {
51 | this.dbmCount = dbmCount;
52 | }
53 |
54 | public double getDbmTotal() {
55 | return dbmTotal;
56 | }
57 |
58 | public void setDbmTotal(double dbmTotal) {
59 | this.dbmTotal = dbmTotal;
60 | }
61 |
62 | public XYChart.Data getParsed() {
63 | return parsed;
64 | }
65 |
66 | public void setParsed(XYChart.Data parsed) {
67 | this.parsed = parsed;
68 | }
69 |
70 | public String getDate() {
71 | return date;
72 | }
73 |
74 | public void setDate(String date) {
75 | this.date = date;
76 | }
77 |
78 | public String getTime() {
79 | return time;
80 | }
81 |
82 | public void setTime(String time) {
83 | this.time = time;
84 | }
85 |
86 | public String getFrequencyStart() {
87 | return frequencyStart;
88 | }
89 |
90 | public void setFrequencyStart(String frequencyStart) {
91 | this.frequencyStart = frequencyStart;
92 | }
93 |
94 | public String getFrequencyEnd() {
95 | return frequencyEnd;
96 | }
97 |
98 | public void setFrequencyEnd(String frequencyEnd) {
99 | this.frequencyEnd = frequencyEnd;
100 | }
101 |
102 | public String getBinSize() {
103 | return binSize;
104 | }
105 |
106 | public void setBinSize(String binSize) {
107 | this.binSize = binSize;
108 | }
109 |
110 | public String getNumberOfSamples() {
111 | return numberOfSamples;
112 | }
113 |
114 | public void setNumberOfSamples(String numberOfSamples) {
115 | this.numberOfSamples = numberOfSamples;
116 | }
117 |
118 | public void setDbmAverage(double dbmAverage) {
119 | this.dbmAverage = dbmAverage;
120 | }
121 |
122 | public double getDbmAverage() {
123 | return dbmAverage;
124 | }
125 |
126 | }
127 |
--------------------------------------------------------------------------------
/src/main/java/ru/r2cloud/rtlspectrum/BinDataParser.java:
--------------------------------------------------------------------------------
1 | package ru.r2cloud.rtlspectrum;
2 |
3 | import java.util.ArrayList;
4 | import java.util.Collections;
5 | import java.util.Comparator;
6 | import java.util.HashMap;
7 | import java.util.List;
8 | import java.util.Map;
9 | import java.util.Map.Entry;
10 | import java.util.regex.Pattern;
11 |
12 | import javafx.scene.chart.XYChart;
13 |
14 | public class BinDataParser {
15 |
16 | private static final Pattern COMMA = Pattern.compile(",");
17 | private final Map cache = new HashMap<>();
18 |
19 | public void addLine(String line) {
20 | Map cur = convertLine(line);
21 | if (cur.isEmpty()) {
22 | return;
23 | }
24 | for (Entry curEntry : cur.entrySet()) {
25 | BinData existingValue = cache.get(curEntry.getKey());
26 | if (existingValue == null) {
27 | cache.put(curEntry.getKey(), curEntry.getValue());
28 | continue;
29 | }
30 | existingValue.setDbmTotal(existingValue.getDbmTotal() + curEntry.getValue().getDbmTotal());
31 | existingValue.setDbmCount(existingValue.getDbmCount() + curEntry.getValue().getDbmCount());
32 | }
33 | }
34 |
35 | public List convert() {
36 | List result = new ArrayList<>(cache.values());
37 | Collections.sort(result, new Comparator() {
38 | @Override
39 | public int compare(BinData o1, BinData o2) {
40 | return Long.compare(o1.getFrequencyStartParsed(), o2.getFrequencyStartParsed());
41 | }
42 | });
43 | for (BinData cur : result) {
44 |
45 | XYChart.Data parsed = new XYChart.Data<>();
46 | parsed.setXValue(cur.getFrequencyStartParsed());
47 | parsed.setYValue(cur.getDbmTotal() / cur.getDbmCount());
48 |
49 | cur.setParsed(parsed);
50 | cur.setDbmAverage(cur.getDbmTotal() / cur.getDbmCount());
51 | }
52 | return result;
53 | }
54 |
55 | // format is: 2019-06-07, 19:44:45, 40000000, 41000000, 1000000.00, 1, -24.22,
56 | // -24.22, ...
57 | static Map convertLine(String line) {
58 | String[] parts = COMMA.split(line);
59 | if (parts.length < 7) {
60 | return Collections.emptyMap();
61 | }
62 | String date = parts[0].trim();
63 | String time = parts[1].trim();
64 | long frequencyStart = Long.parseLong(parts[2].trim());
65 | double step = Double.parseDouble(parts[4].trim());
66 | Map result = new HashMap<>();
67 | for (int i = 0; i < parts.length - 7 + 1; i++) {
68 | BinData cur = new BinData();
69 | cur.setDate(date);
70 | cur.setTime(time);
71 | cur.setFrequencyStartParsed((long) (frequencyStart + i * step));
72 | cur.setFrequencyStart(String.valueOf(cur.getFrequencyStartParsed()));
73 | cur.setFrequencyEnd(parts[4].trim());
74 | cur.setBinSize(parts[4].trim());
75 | cur.setNumberOfSamples(parts[5].trim());
76 | double value;
77 | try {
78 | value = Double.valueOf(parts[6 + i].trim());
79 | } catch (NumberFormatException e) {
80 | continue;
81 | }
82 |
83 | cur.setDbmTotal(value);
84 | cur.setDbmCount(1);
85 |
86 | result.put(cur.getFrequencyStart(), cur);
87 | }
88 | return result;
89 | }
90 |
91 | }
92 |
--------------------------------------------------------------------------------
/src/main/java/ru/r2cloud/rtlspectrum/Controller.java:
--------------------------------------------------------------------------------
1 | package ru.r2cloud.rtlspectrum;
2 |
3 | import java.io.File;
4 | import java.net.URL;
5 | import java.util.ArrayList;
6 | import java.util.List;
7 | import java.util.ResourceBundle;
8 | import java.util.UUID;
9 | import java.util.concurrent.ExecutorService;
10 | import java.util.concurrent.Executors;
11 |
12 | import javafx.fxml.FXML;
13 | import javafx.fxml.Initializable;
14 | import javafx.scene.chart.NumberAxis;
15 | import javafx.scene.chart.XYChart;
16 | import javafx.scene.control.Button;
17 | import javafx.scene.control.MenuBar;
18 | import javafx.scene.control.MenuItem;
19 | import javafx.scene.layout.HBox;
20 | import javafx.scene.layout.VBox;
21 | import javafx.stage.FileChooser;
22 | import javafx.stage.FileChooser.ExtensionFilter;
23 |
24 | public class Controller implements Initializable {
25 |
26 | @FXML
27 | private LineChartWithMarkers lineChart;
28 |
29 | @FXML
30 | private VBox welcomeMessage;
31 |
32 | @FXML
33 | private HBox statusBar;
34 | @FXML
35 | private StatusBar statusBarController;
36 |
37 | @FXML
38 | private Button runNowButton;
39 | @FXML
40 | private Button loadFileButton;
41 | @FXML
42 | private MenuItem loadFileMenu;
43 | @FXML
44 | private MenuItem runNowMenu;
45 | @FXML
46 | private MenuItem addFileMenu;
47 | @FXML
48 | private MenuItem subtractFileMenu;
49 | @FXML
50 | private MenuBar menuBar;
51 |
52 | private List> rawData = new ArrayList<>();
53 |
54 | private ExecutorService executorService;
55 | private RunRtlPower rtlPowerTask;
56 |
57 | @Override
58 | public void initialize(URL location, ResourceBundle resources) {
59 | executorService = Executors.newFixedThreadPool(2);
60 | ((NumberAxis) lineChart.getXAxis()).setTickLabelFormatter(new FrequencyFormatter());
61 | ((NumberAxis) lineChart.getYAxis()).setTickLabelFormatter(new PowerFormatter());
62 | if (System.getProperty("testfx.running") != null) {
63 | menuBar.setUseSystemMenuBar(false);
64 | }
65 | }
66 |
67 | @FXML
68 | public void runNow() {
69 | RtlPowerProgress progressTask = new RtlPowerProgress(statusBarController);
70 | rtlPowerTask = new RunRtlPower(progressTask);
71 | rtlPowerTask.setOnRunning(succeesesEvent -> statusBarController.beginTask("null"));
72 |
73 | disableButtons(true);
74 |
75 | rtlPowerTask.setOnSucceeded(succeededEvent -> {
76 | List result = rtlPowerTask.getValue();
77 | statusBarController.completeTask();
78 | welcomeMessage.setVisible(false);
79 | disableButtons(false);
80 | setupChart(result, false);
81 | });
82 | rtlPowerTask.setOnFailed(workerStateEvent -> {
83 | progressTask.cancel(true);
84 | statusBarController.completeError(rtlPowerTask.getException().getMessage());
85 | disableButtons(false);
86 | });
87 |
88 | executorService.execute(rtlPowerTask);
89 | executorService.execute(progressTask);
90 | }
91 |
92 | @FXML
93 | public void loadFile() {
94 | loadFile(false);
95 | }
96 |
97 | @FXML
98 | public void addFile() {
99 | loadFile(true);
100 | }
101 |
102 | @FXML
103 | public void save() {
104 | if (lineChart.getNoData() || rawData.isEmpty()) {
105 | return;
106 | }
107 |
108 | FileChooser fileChooser = new FileChooser();
109 | fileChooser.setTitle("Save file");
110 | fileChooser.getExtensionFilters().add(new ExtensionFilter("CSV files", "*.csv"));
111 | File selectedFile = fileChooser.showSaveDialog(welcomeMessage.getScene().getWindow());
112 | if (selectedFile == null) {
113 | return;
114 | }
115 |
116 | disableButtons(true);
117 |
118 | SaveTask saveTask = new SaveTask(statusBarController, selectedFile, rawData.get(0));
119 | saveTask.setOnRunning(succeesesEvent -> statusBarController.beginTask(selectedFile.getAbsolutePath()));
120 | saveTask.setOnSucceeded(succeededEvent -> {
121 | disableButtons(false);
122 | statusBarController.completeTask();
123 | });
124 | saveTask.setOnFailed(workerStateEvent -> {
125 | disableButtons(false);
126 | statusBarController.completeError(saveTask.getException().getMessage());
127 | });
128 |
129 | executorService.execute(saveTask);
130 | }
131 |
132 | @FXML
133 | public void clearChart() {
134 | lineChart.getData().clear();
135 | }
136 |
137 | @FXML
138 | public void subtractFile() {
139 | File selectedFile = requestFileForOpen();
140 | if (selectedFile == null) {
141 | return;
142 | }
143 |
144 | disableButtons(true);
145 |
146 | SubtractFile task = new SubtractFile(statusBarController, selectedFile, rawData);
147 | task.setOnRunning(succeesesEvent -> statusBarController.beginTask(selectedFile.getAbsolutePath()));
148 | task.setOnSucceeded(succeededEvent -> {
149 | List> result = task.getValue();
150 | statusBarController.completeTask();
151 | disableButtons(false);
152 |
153 | rawData = result;
154 | lineChart.getData().clear();
155 | for (List curGraph : result) {
156 | XYChart.Series series = new XYChart.Series<>();
157 | for (BinData cur : curGraph) {
158 | series.getData().add(cur.getParsed());
159 | }
160 | lineChart.getData().add(series);
161 | }
162 | lineChart.setVisible(true);
163 | });
164 | task.setOnFailed(workerStateEvent -> {
165 | disableButtons(false);
166 | statusBarController.completeError(task.getException().getMessage());
167 | });
168 |
169 | executorService.execute(task);
170 | }
171 |
172 | private void loadFile(boolean append) {
173 | File selectedFile = requestFileForOpen();
174 | if (selectedFile == null) {
175 | return;
176 | }
177 |
178 | disableButtons(true);
179 |
180 | LoadFile readTask = new LoadFile(statusBarController, selectedFile);
181 | readTask.setOnRunning(succeesesEvent -> statusBarController.beginTask(selectedFile.getAbsolutePath()));
182 | readTask.setOnSucceeded(succeededEvent -> {
183 | List result = readTask.getValue();
184 | statusBarController.completeTask();
185 | welcomeMessage.setVisible(false);
186 | disableButtons(false);
187 | setupChart(result, append);
188 | });
189 | readTask.setOnFailed(workerStateEvent -> {
190 | disableButtons(false);
191 | statusBarController.completeError(readTask.getException().getMessage());
192 | });
193 |
194 | executorService.execute(readTask);
195 | }
196 |
197 | private File requestFileForOpen() {
198 | FileChooser fileChooser = new FileChooser();
199 | fileChooser.setTitle("Open file");
200 | String defaultDirectory = System.getProperty("rtlSpectrum.defaultdirectory");
201 | if (defaultDirectory != null) {
202 | fileChooser.setInitialDirectory(new File(defaultDirectory));
203 | }
204 | fileChooser.getExtensionFilters().add(new ExtensionFilter("CSV files", "*.csv"));
205 | return fileChooser.showOpenDialog(welcomeMessage.getScene().getWindow());
206 | }
207 |
208 | private void setupChart(List data, boolean append) {
209 | XYChart.Series series = new XYChart.Series<>();
210 | for (BinData cur : data) {
211 | series.getData().add(cur.getParsed());
212 | series.setName(UUID.randomUUID().toString());
213 | }
214 | if (!append) {
215 | lineChart.getData().clear();
216 | rawData = new ArrayList<>();
217 | }
218 | rawData.add(data);
219 | lineChart.getData().add(series);
220 | lineChart.setVisible(true);
221 | }
222 |
223 | private void disableButtons(boolean value) {
224 | runNowButton.setDisable(value);
225 | loadFileButton.setDisable(value);
226 | loadFileMenu.setDisable(value);
227 | runNowMenu.setDisable(value);
228 | addFileMenu.setDisable(value);
229 | subtractFileMenu.setDisable(value);
230 | }
231 |
232 | public void stop() {
233 | if (executorService != null) {
234 | executorService.shutdownNow();
235 | // explicitly cancel this task as rtl_power is on native code and interruptions do not work there
236 | if (rtlPowerTask != null) {
237 | rtlPowerTask.cancel();
238 | }
239 | }
240 | }
241 | }
242 |
--------------------------------------------------------------------------------
/src/main/java/ru/r2cloud/rtlspectrum/FrequencyFormatter.java:
--------------------------------------------------------------------------------
1 | package ru.r2cloud.rtlspectrum;
2 |
3 | import java.text.DecimalFormat;
4 | import java.text.NumberFormat;
5 |
6 | import javafx.util.StringConverter;
7 |
8 | public class FrequencyFormatter extends StringConverter {
9 |
10 | private static final long ONE_KILOHERZ = 1_000;
11 | private static final long ONE_MEGAHERZ = 1_000_000;
12 | private static final long ONE_GIGAHERZ = 1_000_000_000;
13 |
14 | private final NumberFormat format;
15 |
16 | public FrequencyFormatter() {
17 | format = new DecimalFormat("#.#");
18 | }
19 |
20 | @Override
21 | public String toString(Number object) {
22 | if (object == null) {
23 | return "";
24 | }
25 | if (object.longValue() < 0) {
26 | return "";
27 | }
28 | if (object.longValue() < ONE_KILOHERZ) {
29 | return Long.valueOf(object.longValue()) + " Hz";
30 | }
31 | if (object.longValue() < ONE_MEGAHERZ) {
32 | return format.format(object.doubleValue() / ONE_KILOHERZ) + " KHz";
33 | }
34 | if (object.longValue() < ONE_GIGAHERZ) {
35 | return format.format(object.doubleValue() / ONE_MEGAHERZ) + " MHz";
36 | }
37 | return format.format(object.doubleValue() / ONE_GIGAHERZ) + " GHz";
38 | }
39 |
40 | @Override
41 | public Number fromString(String string) {
42 | return null;
43 | }
44 |
45 | }
46 |
--------------------------------------------------------------------------------
/src/main/java/ru/r2cloud/rtlspectrum/Legend.java:
--------------------------------------------------------------------------------
1 | package ru.r2cloud.rtlspectrum;
2 |
3 | import javafx.scene.control.Label;
4 | import javafx.scene.layout.BorderPane;
5 | import javafx.scene.layout.HBox;
6 | import javafx.scene.paint.Color;
7 | import javafx.scene.paint.Paint;
8 | import javafx.scene.shape.Rectangle;
9 |
10 | public class Legend extends HBox {
11 |
12 | private static final Color BLACK = Color.rgb(0, 0, 0);
13 | private final Rectangle rectangle = new Rectangle();
14 | private final Label label = new Label();
15 |
16 | public Legend() {
17 | rectangle.setHeight(10.0);
18 | rectangle.setWidth(10.0);
19 |
20 | label.setStyle("-fx-padding: 0; -fx-text-fill: black;");
21 |
22 | BorderPane pane = new BorderPane();
23 | pane.setCenter(rectangle);
24 | getChildren().add(pane);
25 | getChildren().add(label);
26 | setSpacing(5.0);
27 | }
28 |
29 | public void setStroke(Paint stroke) {
30 | rectangle.setStroke(stroke);
31 | rectangle.setFill(stroke);
32 | }
33 |
34 | public void setText(String text) {
35 | label.setText(text);
36 | }
37 |
38 | public boolean hasStroke() {
39 | if (rectangle.getStroke() == null) {
40 | return false;
41 | }
42 | if (!(rectangle.getStroke() instanceof Color)) {
43 | return false;
44 | }
45 | Color stroke = (Color) rectangle.getStroke();
46 | return !stroke.equals(BLACK);
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/main/java/ru/r2cloud/rtlspectrum/LineChartWithMarkers.java:
--------------------------------------------------------------------------------
1 | package ru.r2cloud.rtlspectrum;
2 |
3 | import java.util.ArrayList;
4 | import java.util.Collections;
5 | import java.util.HashMap;
6 | import java.util.Iterator;
7 | import java.util.List;
8 | import java.util.Map;
9 |
10 | import javafx.beans.NamedArg;
11 | import javafx.beans.property.BooleanProperty;
12 | import javafx.beans.property.SimpleBooleanProperty;
13 | import javafx.beans.value.ChangeListener;
14 | import javafx.beans.value.ObservableValue;
15 | import javafx.collections.ListChangeListener;
16 | import javafx.collections.ObservableList;
17 | import javafx.event.EventHandler;
18 | import javafx.geometry.Point2D;
19 | import javafx.scene.Node;
20 | import javafx.scene.chart.Axis;
21 | import javafx.scene.chart.LineChart;
22 | import javafx.scene.chart.NumberAxis;
23 | import javafx.scene.control.Label;
24 | import javafx.scene.input.MouseEvent;
25 | import javafx.scene.layout.Pane;
26 | import javafx.scene.paint.Color;
27 | import javafx.scene.shape.Line;
28 | import javafx.scene.shape.LineTo;
29 | import javafx.scene.shape.MoveTo;
30 | import javafx.scene.shape.Path;
31 | import javafx.scene.shape.PathElement;
32 | import javafx.util.StringConverter;
33 |
34 | public class LineChartWithMarkers extends LineChart {
35 |
36 | private static final int CURSOR_X_MARGIN = 12;
37 | private static final int CURSOR_Y_MARGIN = 20;
38 |
39 | private Line line = new Line();
40 | private StringConverter xConverter;
41 | private StringConverter yConverter;
42 |
43 | private final BooleanProperty noData = new SimpleBooleanProperty(this, "noData", true);
44 | private final Pane tooltip;
45 | private final Label tooltipXLabel;
46 | private Map legendByName = new HashMap<>();
47 |
48 | public LineChartWithMarkers(@NamedArg("xAxis") Axis xAxis, @NamedArg("yAxis") Axis yAxis, @NamedArg("tooltip") Pane tooltip) {
49 | super(xAxis, yAxis);
50 | this.tooltip = tooltip;
51 | line.setVisible(false);
52 | line.setStroke(Color.GREY);
53 | line.getStrokeDashArray().add(4d);
54 |
55 | tooltipXLabel = (Label) tooltip.lookup("#tooltipXLabel");
56 | getChildren().add(tooltip);
57 |
58 | if (xAxis instanceof NumberAxis) {
59 | NumberAxis numberedXAxis = (NumberAxis) xAxis;
60 | numberedXAxis.setForceZeroInRange(false);
61 | xConverter = numberedXAxis.getTickLabelFormatter();
62 | numberedXAxis.tickLabelFormatterProperty().addListener(new ChangeListener>() {
63 | @Override
64 | public void changed(ObservableValue extends StringConverter> observable, StringConverter oldValue, StringConverter newValue) {
65 | xConverter = newValue;
66 | }
67 | });
68 | }
69 | if (yAxis instanceof NumberAxis) {
70 | NumberAxis numberedYAxis = (NumberAxis) yAxis;
71 | yConverter = numberedYAxis.getTickLabelFormatter();
72 | numberedYAxis.tickLabelFormatterProperty().addListener(new ChangeListener>() {
73 |
74 | @Override
75 | public void changed(ObservableValue extends StringConverter> observable, StringConverter oldValue, StringConverter newValue) {
76 | yConverter = newValue;
77 | }
78 | });
79 | }
80 |
81 | final Node chartBackground2 = lookup(".chart-plot-background");
82 |
83 | chartBackground2.setOnMouseMoved(new EventHandler() {
84 | @Override
85 | public void handle(MouseEvent mouseEvent) {
86 | Point2D point = new Point2D(mouseEvent.getX(), mouseEvent.getY());
87 | String xLabel = getXLabel(point);
88 | if (xLabel.length() == 0) {
89 | return;
90 | }
91 |
92 | line.setStartX(point.getX());
93 | line.setEndX(line.getStartX());
94 | line.setStartY(0.0);
95 | line.setEndY(getBoundsInLocal().getHeight());
96 | line.setVisible(true);
97 |
98 | tooltip.setVisible(true);
99 | tooltipXLabel.setText(xLabel);
100 |
101 | double xWithinPlotArea = CURSOR_X_MARGIN + point.getX();
102 | double yWithinPlotArea = CURSOR_Y_MARGIN + point.getY();
103 |
104 | double newX = xWithinPlotArea + chartBackground2.getLayoutX();
105 | double newY = yWithinPlotArea + chartBackground2.getLayoutY();
106 |
107 | if (xWithinPlotArea + tooltip.getWidth() > chartBackground2.getBoundsInLocal().getMaxX()) {
108 | newX = point.getX() - tooltip.getWidth() - CURSOR_X_MARGIN + chartBackground2.getLayoutX();
109 | }
110 | if (yWithinPlotArea + tooltip.getHeight() > chartBackground2.getBoundsInLocal().getMaxY()) {
111 | newY = point.getY() - tooltip.getHeight();
112 | }
113 |
114 | tooltip.setTranslateX(newX);
115 | tooltip.setTranslateY(newY);
116 |
117 | Map yValues = getNearestYValue(getXAxis().getValueForDisplay(point.getX()));
118 | for (Series series : getData()) {
119 | Legend legend = legendByName.get(series.getName());
120 | if (legend == null) {
121 | continue;
122 | }
123 | Number yValue = yValues.get(series.getName());
124 | if (yValue == null || Double.isNaN(yValue.doubleValue())) {
125 | legend.setVisible(false);
126 | continue;
127 | }
128 | legend.setVisible(true);
129 | if (!legend.hasStroke()) {
130 | legend.setStroke(((Path) series.getNode().lookup(".chart-series-line")).getStroke());
131 | }
132 | if (yConverter != null) {
133 | legend.setText(yConverter.toString(yValue));
134 | } else {
135 | legend.setText(yValue.toString());
136 | }
137 | }
138 | }
139 |
140 | });
141 |
142 | line.setOnMouseMoved(new EventHandler() {
143 | @Override
144 | public void handle(MouseEvent mouseEvent) {
145 | chartBackground2.getOnMouseMoved().handle(mouseEvent);
146 | }
147 | });
148 |
149 | getPlotChildren().add(line);
150 |
151 | getData().addListener(new ListChangeListener>() {
152 | @Override
153 | public void onChanged(Change extends Series> c) {
154 | while (c.next()) {
155 | for (Series cur : c.getAddedSubList()) {
156 | Legend rec = new Legend();
157 | rec.setVisible(false);
158 | legendByName.put(cur.getName(), rec);
159 | tooltip.getChildren().add(rec);
160 | }
161 | for (Series cur : c.getRemoved()) {
162 | Legend previous = legendByName.remove(cur.getName());
163 | if (previous == null) {
164 | continue;
165 | }
166 | tooltip.getChildren().remove(previous);
167 | }
168 | }
169 | noData.set(c.getList().isEmpty());
170 | }
171 | });
172 | }
173 |
174 | @Override
175 | protected void layoutPlotChildren() {
176 | List constructedPath = new ArrayList<>(getData().size());
177 | for (int seriesIndex = 0; seriesIndex < getData().size(); seriesIndex++) {
178 | Series series = getData().get(seriesIndex);
179 | if (series.getNode() instanceof Path) {
180 | ObservableList seriesLine = ((Path) series.getNode()).getElements();
181 | seriesLine.clear();
182 | constructedPath.clear();
183 | MoveTo nextMoveTo = null;
184 | for (Iterator> it = getDisplayedDataIterator(series); it.hasNext();) {
185 | Data item = it.next();
186 | double x = getXAxis().getDisplayPosition(item.getXValue());
187 | double y = getYAxis().getDisplayPosition(getYAxis().toRealValue(getYAxis().toNumericValue(item.getYValue())));
188 | if (Double.isNaN(x) || Double.isNaN(y)) {
189 | int index = series.getData().indexOf(item);
190 | if (index < series.getData().size() - 1) {
191 | Data next = series.getData().get(index + 1);
192 | double nextX = getXAxis().getDisplayPosition(next.getXValue());
193 | double nextY = getYAxis().getDisplayPosition(getYAxis().toRealValue(getYAxis().toNumericValue(next.getYValue())));
194 | nextMoveTo = new MoveTo(nextX, nextY);
195 | }
196 | } else {
197 | if (nextMoveTo != null) {
198 | constructedPath.add(nextMoveTo);
199 | nextMoveTo = null;
200 | }
201 | constructedPath.add(new LineTo(x, y));
202 | Node symbol = item.getNode();
203 | if (symbol != null) {
204 | double w = symbol.prefWidth(-1);
205 | double h = symbol.prefHeight(-1);
206 | symbol.resizeRelocate(x - (w / 2), y - (h / 2), w, h);
207 | }
208 | }
209 | }
210 |
211 | if (!constructedPath.isEmpty()) {
212 | PathElement first = constructedPath.get(0);
213 | seriesLine.add(new MoveTo(getX(first), getY(first)));
214 | seriesLine.addAll(constructedPath);
215 | }
216 | }
217 | }
218 |
219 | }
220 |
221 | private static double getX(PathElement element) {
222 | if (element instanceof LineTo) {
223 | return ((LineTo) element).getX();
224 | } else if (element instanceof MoveTo) {
225 | return ((MoveTo) element).getX();
226 | } else {
227 | throw new IllegalArgumentException(element + " is not a valid type");
228 | }
229 | }
230 |
231 | private static double getY(PathElement element) {
232 | if (element instanceof LineTo) {
233 | return ((LineTo) element).getY();
234 | } else if (element instanceof MoveTo) {
235 | return ((MoveTo) element).getY();
236 | } else {
237 | throw new IllegalArgumentException(element + " is not a valid type");
238 | }
239 | }
240 |
241 | @Override
242 | protected void layoutChildren() {
243 | super.layoutChildren();
244 | final Pane chartContent = (Pane) lookup(".chart-content");
245 | double top = chartContent.getLayoutY();
246 | double left = chartContent.getLayoutX();
247 | double right = snappedRightInset();
248 | final double width = getWidth();
249 | // copy paste from legend
250 | final double legendHeight = snapSizeX(tooltip.prefHeight(width - left - right));
251 | final double legendWidth = boundedSize(snapSizeX(tooltip.prefWidth(legendHeight)), 0, width - left - right);
252 | tooltip.resizeRelocate(snapPositionX(left), snapPositionX(top), legendWidth, legendHeight);
253 | }
254 |
255 | private static double boundedSize(double value, double min, double max) {
256 | // if max < value, return max
257 | // if min > value, return min
258 | // if min > max, return min
259 | return Math.min(Math.max(value, min), Math.max(min, max));
260 | }
261 |
262 | private Map getNearestYValue(Number xValue) {
263 | if (getData().isEmpty()) {
264 | return Collections.emptyMap();
265 | }
266 | Map result = new HashMap<>();
267 | for (Series series : getData()) {
268 | if (series.getData().isEmpty()) {
269 | continue;
270 | }
271 | Number yValue = getNumber(xValue, series);
272 | if (yValue == null) {
273 | continue;
274 | }
275 | result.put(series.getName(), yValue);
276 | }
277 |
278 | return result;
279 | }
280 |
281 | private static Number getNumber(Number xValue, Series series) {
282 | Data previous = null;
283 | for (Data cur : series.getData()) {
284 | double curX = cur.getXValue().doubleValue();
285 | if (curX < xValue.doubleValue()) {
286 | previous = cur;
287 | continue;
288 | }
289 | if (curX == xValue.doubleValue()) {
290 | return cur.getYValue();
291 | }
292 |
293 | if (previous == null) {
294 | return null;
295 | }
296 | // interpolate linearly
297 | return previous.getYValue().doubleValue() + ((xValue.doubleValue() - previous.getXValue().doubleValue()) / (cur.getXValue().doubleValue() - previous.getXValue().doubleValue())) * (cur.getYValue().doubleValue() - previous.getYValue().doubleValue());
298 | }
299 | return null;
300 | }
301 |
302 | private String getXLabel(Point2D point) {
303 | Number xValue = getXAxis().getValueForDisplay(point.getX());
304 | String textValue = "";
305 | if (xConverter != null) {
306 | textValue += xConverter.toString(xValue);
307 | } else {
308 | textValue = xValue.toString();
309 | }
310 | return textValue.trim();
311 | }
312 |
313 | @Override
314 | protected void updateAxisRange() {
315 | final Axis xa = getXAxis();
316 | final Axis ya = getYAxis();
317 | List xData = null;
318 | List yData = null;
319 | if (xa.isAutoRanging())
320 | xData = new ArrayList<>();
321 | if (ya.isAutoRanging())
322 | yData = new ArrayList<>();
323 | if (xData != null || yData != null) {
324 | for (Series series : getData()) {
325 | for (Data data : series.getData()) {
326 | if (xData != null && !Double.isNaN(data.getXValue().doubleValue()))
327 | xData.add(data.getXValue());
328 | if (yData != null && !Double.isNaN(data.getYValue().doubleValue()))
329 | yData.add(data.getYValue());
330 | }
331 | }
332 | // RT-32838 No need to invalidate range if there is one data item - whose value
333 | // is zero.
334 | if (xData != null && !(xData.size() == 1 && getXAxis().toNumericValue(xData.get(0)) == 0)) {
335 | xa.invalidateRange(xData);
336 | }
337 | if (yData != null && !(yData.size() == 1 && getYAxis().toNumericValue(yData.get(0)) == 0)) {
338 | ya.invalidateRange(yData);
339 | }
340 |
341 | }
342 | }
343 |
344 | public BooleanProperty noDataProperty() {
345 | return noData;
346 | }
347 |
348 | public boolean getNoData() {
349 | return noData.get();
350 | }
351 |
352 | public void setNoData(boolean value) {
353 | noData.set(value);
354 | }
355 | }
356 |
--------------------------------------------------------------------------------
/src/main/java/ru/r2cloud/rtlspectrum/LoadFile.java:
--------------------------------------------------------------------------------
1 | package ru.r2cloud.rtlspectrum;
2 |
3 | import java.io.BufferedReader;
4 | import java.io.File;
5 | import java.io.FileReader;
6 | import java.util.List;
7 |
8 | public class LoadFile extends StatusBarTask> {
9 |
10 | private final File file;
11 |
12 | public LoadFile(StatusBar statusBar, File file) {
13 | super(statusBar);
14 | this.file = file;
15 | }
16 |
17 | @Override
18 | protected List call() throws Exception {
19 | updateMessage("Reading file: " + file.getAbsolutePath());
20 | BinDataParser parser = new BinDataParser();
21 | try (BufferedReader r = new BufferedReader(new FileReader(file))) {
22 | String curLine = null;
23 | while ((curLine = r.readLine()) != null) {
24 | parser.addLine(curLine);
25 | }
26 | }
27 | return parser.convert();
28 | }
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/src/main/java/ru/r2cloud/rtlspectrum/MacOsUtil.java:
--------------------------------------------------------------------------------
1 | package ru.r2cloud.rtlspectrum;
2 |
3 | import java.io.BufferedReader;
4 | import java.io.InputStreamReader;
5 | import java.nio.charset.StandardCharsets;
6 | import java.util.Locale;
7 |
8 | class MacOsUtil {
9 |
10 | static boolean isDark(String command) {
11 | ProcessBuilder builder = new ProcessBuilder().command(command, "read", "-g", "AppleInterfaceStyle");
12 | Process process;
13 | try {
14 | process = builder.start();
15 | int resultCode = process.waitFor();
16 | if (resultCode != 0) {
17 | return false;
18 | }
19 | } catch (InterruptedException e1) {
20 | Thread.currentThread().interrupt();
21 | e1.printStackTrace();
22 | return false;
23 | } catch (Exception e) {
24 | e.printStackTrace();
25 | return false;
26 | }
27 | try (BufferedReader r = new BufferedReader(new InputStreamReader(process.getInputStream(), StandardCharsets.ISO_8859_1))) {
28 | String result = r.readLine();
29 | if (result == null) {
30 | return false;
31 | }
32 | return result.toLowerCase(Locale.UK).contains("dark");
33 | } catch (Exception e) {
34 | e.printStackTrace();
35 | return false;
36 | }
37 | }
38 |
39 | private MacOsUtil() {
40 | // do nothing
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/main/java/ru/r2cloud/rtlspectrum/Main.java:
--------------------------------------------------------------------------------
1 | package ru.r2cloud.rtlspectrum;
2 |
3 | public class Main {
4 |
5 | public static void main(String[] args) {
6 | MainApplication.main(args);
7 | }
8 |
9 | }
10 |
--------------------------------------------------------------------------------
/src/main/java/ru/r2cloud/rtlspectrum/MainApplication.java:
--------------------------------------------------------------------------------
1 | package ru.r2cloud.rtlspectrum;
2 |
3 | import java.io.IOException;
4 | import java.io.InputStream;
5 | import java.util.Locale;
6 |
7 | import javafx.application.Application;
8 | import javafx.fxml.FXMLLoader;
9 | import javafx.scene.Parent;
10 | import javafx.scene.Scene;
11 | import javafx.stage.Stage;
12 |
13 | public class MainApplication extends Application {
14 |
15 | private Controller controller;
16 |
17 | @Override
18 | public void start(Stage stage) {
19 | Parent root;
20 | try (InputStream is = getClass().getClassLoader().getResourceAsStream("layout.fxml")) {
21 | FXMLLoader fxmlLoader = new FXMLLoader(getClass().getClassLoader().getResource("layout.fxml"));
22 | root = fxmlLoader.load(is);
23 | controller = fxmlLoader.getController();
24 | } catch (IOException e) {
25 | throw new IllegalStateException(e);
26 | }
27 |
28 | Scene scene = new Scene(root, 640, 480);
29 | String osName = System.getProperty("os.name");
30 | if (osName != null && osName.toLowerCase(Locale.UK).contains("mac") && MacOsUtil.isDark("/usr/bin/defaults")) {
31 | scene.getStylesheets().add("dark.css");
32 | }
33 | stage.setScene(scene);
34 | stage.setTitle("rtlSpectrum");
35 | stage.show();
36 | }
37 |
38 | @Override
39 | public void stop() throws Exception {
40 | if (controller != null) {
41 | controller.stop();
42 | }
43 | }
44 |
45 | public static void main(String[] args) {
46 | launch();
47 | }
48 |
49 | }
50 |
--------------------------------------------------------------------------------
/src/main/java/ru/r2cloud/rtlspectrum/PowerFormatter.java:
--------------------------------------------------------------------------------
1 | package ru.r2cloud.rtlspectrum;
2 |
3 | import java.text.DecimalFormat;
4 | import java.text.NumberFormat;
5 |
6 | import javafx.util.StringConverter;
7 |
8 | public class PowerFormatter extends StringConverter {
9 |
10 | private final NumberFormat format;
11 |
12 | public PowerFormatter() {
13 | format = new DecimalFormat("#.##");
14 | }
15 |
16 | @Override
17 | public String toString(Number object) {
18 | if (object == null || Double.isNaN(object.doubleValue())) {
19 | return "";
20 | }
21 | return format.format(object.doubleValue());
22 | }
23 |
24 | @Override
25 | public Number fromString(String string) {
26 | return null;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/main/java/ru/r2cloud/rtlspectrum/RtlPowerProgress.java:
--------------------------------------------------------------------------------
1 | package ru.r2cloud.rtlspectrum;
2 |
3 | public class RtlPowerProgress extends StatusBarTask {
4 |
5 | public RtlPowerProgress(StatusBar statusBar) {
6 | super(statusBar);
7 | }
8 |
9 | @Override
10 | protected Void call() throws Exception {
11 | // taken from the reference rtl-sdr mac book air 2013
12 | long averageNumberOfSecondsPerFullSpectrum = 121;
13 | long numberOfSeconds = ((long) Math.ceil((double) RunRtlPower.NUMBER_OF_SECONDS / averageNumberOfSecondsPerFullSpectrum)) * averageNumberOfSecondsPerFullSpectrum;
14 | for (long i = 0; i < numberOfSeconds; i++) {
15 | updateProgress("Running rtl_power ETA: " + formatETA((numberOfSeconds - i) * 1000), i, numberOfSeconds);
16 | Thread.sleep(1000);
17 | }
18 | updateProgress("Running rtl_power ETA: almost done", -1, -1);
19 | return null;
20 | }
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/src/main/java/ru/r2cloud/rtlspectrum/RunRtlPower.java:
--------------------------------------------------------------------------------
1 | package ru.r2cloud.rtlspectrum;
2 |
3 | import java.io.BufferedReader;
4 | import java.io.InputStreamReader;
5 | import java.nio.charset.StandardCharsets;
6 | import java.util.List;
7 | import java.util.concurrent.TimeUnit;
8 | import java.util.regex.Pattern;
9 |
10 | import javafx.concurrent.Task;
11 |
12 | public class RunRtlPower extends Task> {
13 |
14 | static final long MINIMUM_FREQ = 24_000_000;
15 | static final long MAXIMUM_FREQ = 1_700_000_000;
16 | static final long STEP = 1_000_000;
17 | static final long NUMBER_OF_SECONDS = TimeUnit.MINUTES.toSeconds(2);
18 |
19 | private static final Pattern SPACE = Pattern.compile("\\s");
20 |
21 | private Process process;
22 | private final RtlPowerProgress progressTask;
23 |
24 | public RunRtlPower(RtlPowerProgress progressTask) {
25 | this.progressTask = progressTask;
26 | }
27 |
28 | @Override
29 | protected List call() throws Exception {
30 | updateMessage("Running rtl_power");
31 | ProcessBuilder processBuilder = new ProcessBuilder(SPACE.split("rtl_power -f " + MINIMUM_FREQ + ":" + MAXIMUM_FREQ + ":" + STEP + " -i " + NUMBER_OF_SECONDS + " -g 0 -c 20% -1 -"));
32 | process = processBuilder.start();
33 | String curLine = null;
34 | BinDataParser parser = new BinDataParser();
35 | try (BufferedReader r = new BufferedReader(new InputStreamReader(process.getInputStream(), StandardCharsets.ISO_8859_1))) {
36 | while ((curLine = r.readLine()) != null && !Thread.currentThread().isInterrupted()) {
37 | if (isCancelled()) {
38 | break;
39 | }
40 | parser.addLine(curLine);
41 | }
42 | }
43 | try (BufferedReader r = new BufferedReader(new InputStreamReader(process.getErrorStream(), StandardCharsets.ISO_8859_1))) {
44 | while ((curLine = r.readLine()) != null && !Thread.currentThread().isInterrupted()) {
45 | if (curLine.equalsIgnoreCase("No supported devices found.")) {
46 | throw new IllegalStateException(curLine);
47 | }
48 | if (curLine.startsWith("usb_claim_interface")) {
49 | throw new IllegalStateException(curLine);
50 | }
51 | if (curLine.startsWith("stdbuf:")) {
52 | throw new IllegalStateException(curLine);
53 | }
54 | if (isCancelled()) {
55 | break;
56 | }
57 | }
58 | }
59 | progressTask.cancel(true);
60 | return parser.convert();
61 | }
62 |
63 | @Override
64 | protected void cancelled() {
65 | super.cancelled();
66 | if (process != null) {
67 | process.destroyForcibly();
68 | }
69 | }
70 |
71 | }
72 |
--------------------------------------------------------------------------------
/src/main/java/ru/r2cloud/rtlspectrum/SaveTask.java:
--------------------------------------------------------------------------------
1 | package ru.r2cloud.rtlspectrum;
2 |
3 | import java.io.BufferedWriter;
4 | import java.io.File;
5 | import java.io.FileWriter;
6 | import java.util.List;
7 |
8 | public class SaveTask extends StatusBarTask {
9 |
10 | private final File file;
11 | private final List data;
12 |
13 | public SaveTask(StatusBar statusBar, File file, List data) {
14 | super(statusBar);
15 | this.file = file;
16 | this.data = data;
17 | }
18 |
19 | @Override
20 | protected Void call() throws Exception {
21 | updateMessage("Saving to file: " + file.getAbsolutePath());
22 | try (BufferedWriter w = new BufferedWriter(new FileWriter(file))) {
23 | for (int i = 0; i < data.size(); i++) {
24 | BinData cur = data.get(i);
25 | w.append(cur.getDate()).append(',').append(cur.getTime()).append(',').append(cur.getFrequencyStart());
26 | w.append(',').append(cur.getFrequencyEnd()).append(',').append(cur.getBinSize()).append(',');
27 | w.append(cur.getNumberOfSamples());
28 | w.append(',').append(String.valueOf(cur.getDbmAverage()));
29 | w.append('\n');
30 | updateProgress(i, data.size());
31 | }
32 | }
33 | return null;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/main/java/ru/r2cloud/rtlspectrum/StatusBar.java:
--------------------------------------------------------------------------------
1 | package ru.r2cloud.rtlspectrum;
2 |
3 | import javafx.beans.property.DoubleProperty;
4 | import javafx.beans.property.StringProperty;
5 | import javafx.fxml.FXML;
6 | import javafx.scene.control.Label;
7 | import javafx.scene.control.ProgressBar;
8 |
9 | public class StatusBar {
10 |
11 | public static final String LAST_COMPLETED_TASK = "lastCompletedTask";
12 |
13 | @FXML
14 | private ProgressBar progressBar;
15 | @FXML
16 | private Label statusMessage;
17 |
18 | private String taskId;
19 |
20 | public void beginTask(String taskId) {
21 | progressBar.setVisible(true);
22 | synchronized (this) {
23 | this.taskId = taskId;
24 | }
25 | }
26 |
27 | public void completeTask() {
28 | completeTask(null);
29 | }
30 |
31 | public void completeError(String message) {
32 | completeTask("Error: " + message);
33 | }
34 |
35 | public void completeTask(String message) {
36 | statusMessage.textProperty().unbind();
37 | if (message != null) {
38 | statusMessage.textProperty().set(message);
39 | } else {
40 | statusMessage.textProperty().set("OK");
41 | }
42 | progressBar.setVisible(false);
43 | progressBar.progressProperty().unbind();
44 | progressBar.progressProperty().set(-1);
45 | synchronized (this) {
46 | progressBar.getProperties().put(LAST_COMPLETED_TASK, taskId);
47 | }
48 | }
49 |
50 | public DoubleProperty progressProperty() {
51 | return progressBar.progressProperty();
52 | }
53 |
54 | public StringProperty messageProperty() {
55 | return statusMessage.textProperty();
56 | }
57 |
58 | }
59 |
--------------------------------------------------------------------------------
/src/main/java/ru/r2cloud/rtlspectrum/StatusBarTask.java:
--------------------------------------------------------------------------------
1 | package ru.r2cloud.rtlspectrum;
2 |
3 | import java.text.SimpleDateFormat;
4 | import java.util.Date;
5 |
6 | import javafx.concurrent.Task;
7 |
8 | public abstract class StatusBarTask extends Task {
9 |
10 | private final SimpleDateFormat sdf = new SimpleDateFormat("mm'm':ss's'");
11 | protected final StatusBar statusBar;
12 |
13 | protected StatusBarTask(StatusBar statusBar) {
14 | this.statusBar = statusBar;
15 | this.statusBar.progressProperty().bind(progressProperty());
16 | this.statusBar.messageProperty().bind(messageProperty());
17 | }
18 |
19 | public void updateProgress(String message, long workDone, long max) {
20 | updateMessage(message);
21 | updateProgress(workDone, max);
22 | }
23 |
24 | public String formatETA(long time) {
25 | return sdf.format(new Date(time));
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/main/java/ru/r2cloud/rtlspectrum/SubtractFile.java:
--------------------------------------------------------------------------------
1 | package ru.r2cloud.rtlspectrum;
2 |
3 | import java.io.BufferedReader;
4 | import java.io.File;
5 | import java.io.FileReader;
6 | import java.util.ArrayList;
7 | import java.util.List;
8 |
9 | import javafx.scene.chart.XYChart;
10 |
11 | public class SubtractFile extends StatusBarTask>> {
12 |
13 | private final File file;
14 | private final List> rawData;
15 |
16 | public SubtractFile(StatusBar statusBar, File file, List> rawData) {
17 | super(statusBar);
18 | this.file = file;
19 | this.rawData = rawData;
20 | }
21 |
22 | @Override
23 | protected List> call() throws Exception {
24 | updateMessage("Reading file: " + file.getAbsolutePath());
25 | BinDataParser parser = new BinDataParser();
26 | try (BufferedReader r = new BufferedReader(new FileReader(file))) {
27 | String curLine = null;
28 | while ((curLine = r.readLine()) != null) {
29 | parser.addLine(curLine);
30 | }
31 | }
32 | List fileData = parser.convert();
33 | List> result = new ArrayList<>();
34 | for (List cur : rawData) {
35 | List resultGraph = new ArrayList<>();
36 | for (BinData curBin : cur) {
37 | BinData fileBin = findByFrequency(fileData, curBin.getFrequencyStart());
38 | if (fileBin == null) {
39 | // ignore missing buckets. They might contain NaN or mismatched frequencies.
40 | // keep only relevant buckets in the chart
41 | continue;
42 | }
43 |
44 | double dbm1 = curBin.getDbmAverage();
45 | double dbm2 = fileBin.getDbmAverage();
46 |
47 | double value = dbm1 - dbm2;
48 |
49 | XYChart.Data parsed = new XYChart.Data<>();
50 | parsed.setXValue(curBin.getFrequencyStartParsed());
51 | parsed.setYValue(value);
52 |
53 | BinData subtractedBin = new BinData(curBin);
54 | subtractedBin.setDbmAverage(value);
55 | subtractedBin.setDbmTotal(value);
56 | subtractedBin.setDbmCount(1);
57 | subtractedBin.setParsed(parsed);
58 | resultGraph.add(subtractedBin);
59 | }
60 | result.add(resultGraph);
61 | }
62 | return result;
63 | }
64 |
65 | private static BinData findByFrequency(List data, String frequency) {
66 | for (BinData cur : data) {
67 | if (cur.getFrequencyStart().equals(frequency)) {
68 | return cur;
69 | }
70 | }
71 | return null;
72 | }
73 |
74 | }
75 |
--------------------------------------------------------------------------------
/src/main/resources/dark.css:
--------------------------------------------------------------------------------
1 | .root {
2 | -fx-base: rgba(60, 63, 65, 255);
3 | }
--------------------------------------------------------------------------------
/src/main/resources/layout.fxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
16 |
17 |
18 |
19 |
28 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
--------------------------------------------------------------------------------
/src/main/resources/statusBar.fxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/src/test/java/ru/r2cloud/rtlspectrum/BinDataParserTest.java:
--------------------------------------------------------------------------------
1 | package ru.r2cloud.rtlspectrum;
2 |
3 | import static org.junit.Assert.assertEquals;
4 |
5 | import java.util.List;
6 |
7 | import org.junit.Before;
8 | import org.junit.Test;
9 |
10 | public class BinDataParserTest {
11 |
12 | private BinDataParser parser;
13 |
14 | @Test
15 | public void testMergeIntervals() {
16 | parser.addLine("2021-11-14, 20:27:18, 433006, 435994, 58.59, 342414, -27.70, nan");
17 | parser.addLine("2021-11-14, 20:27:18, 433006, 435994, 58.59, 342414, nan, -26.70");
18 |
19 | List result = parser.convert();
20 | assertEquals(2, result.size());
21 | assertBinData(result.get(0), "433006", -27.70);
22 | assertBinData(result.get(1), "433064", -26.70);
23 | }
24 |
25 | @Test
26 | public void testAverage() {
27 | parser.addLine("2021-11-14, 20:27:18, 433006, 435994, 58.59, 342414, -30.0, -60.0");
28 | parser.addLine("2021-11-14, 20:28:18, 433006, 435994, 58.59, 342414, -60.0, -30.0");
29 |
30 | List result = parser.convert();
31 | assertEquals(2, result.size());
32 | assertBinData(result.get(0), "433006", -45.0);
33 | assertBinData(result.get(1), "433064", -45.0);
34 | }
35 |
36 | private static void assertBinData(BinData actual, String frequencyStart, double value) {
37 | assertEquals(actual.getFrequencyStart(), frequencyStart);
38 | assertEquals(actual.getDbmAverage(), value, 0.0);
39 | }
40 |
41 | @Before
42 | public void start() {
43 | parser = new BinDataParser();
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/test/java/ru/r2cloud/rtlspectrum/FrequencyFormatterTest.java:
--------------------------------------------------------------------------------
1 | package ru.r2cloud.rtlspectrum;
2 |
3 | import static org.junit.Assert.assertEquals;
4 |
5 | import org.junit.Test;
6 |
7 | public class FrequencyFormatterTest {
8 |
9 | @Test
10 | public void formatNegative() {
11 | FrequencyFormatter formatter = new FrequencyFormatter();
12 | assertEquals("", formatter.toString(-1));
13 | }
14 |
15 | @Test
16 | public void formatHz() {
17 | FrequencyFormatter formatter = new FrequencyFormatter();
18 | assertEquals("10 Hz", formatter.toString(10));
19 | }
20 |
21 | @Test
22 | public void formatKHz() {
23 | FrequencyFormatter formatter = new FrequencyFormatter();
24 | assertEquals("10.1 KHz", formatter.toString(10100));
25 | }
26 |
27 | @Test
28 | public void formatMHz() {
29 | FrequencyFormatter formatter = new FrequencyFormatter();
30 | assertEquals("10 MHz", formatter.toString(10_001_000));
31 | }
32 |
33 | @Test
34 | public void formatGHz() {
35 | FrequencyFormatter formatter = new FrequencyFormatter();
36 | assertEquals("10.1 GHz", formatter.toString(10_101_000_000L));
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/src/test/java/ru/r2cloud/rtlspectrum/MacOsUtilTest.java:
--------------------------------------------------------------------------------
1 | package ru.r2cloud.rtlspectrum;
2 |
3 | import static org.junit.Assert.assertFalse;
4 | import static org.junit.Assert.assertTrue;
5 |
6 | import java.io.File;
7 | import java.io.FileNotFoundException;
8 | import java.io.FileOutputStream;
9 | import java.io.IOException;
10 | import java.io.InputStream;
11 | import java.util.UUID;
12 |
13 | import org.junit.Rule;
14 | import org.junit.Test;
15 | import org.junit.rules.TemporaryFolder;
16 |
17 | public class MacOsUtilTest {
18 |
19 | @Rule
20 | public TemporaryFolder tempFolder = new TemporaryFolder();
21 |
22 | private File command;
23 |
24 | @Test
25 | public void dark() throws Exception {
26 | setupCommand("defaults_mock.sh");
27 | assertTrue(MacOsUtil.isDark(command.getAbsolutePath()));
28 | }
29 |
30 | @Test
31 | public void white() {
32 | assertFalse(MacOsUtil.isDark("echo"));
33 | }
34 |
35 | @Test
36 | public void testInvalidCode() throws Exception {
37 | setupCommand("defaults_fail.sh");
38 | assertFalse(MacOsUtil.isDark(command.getAbsolutePath()));
39 | }
40 |
41 | @Test
42 | public void testUnknownCommand() {
43 | assertFalse(MacOsUtil.isDark("expected-unknown-command-" + UUID.randomUUID().toString()));
44 | }
45 |
46 | @Test
47 | public void testEmptyResponse() throws Exception {
48 | setupCommand("defaults_empty.sh");
49 | assertFalse(MacOsUtil.isDark(command.getAbsolutePath()));
50 | }
51 |
52 | private void setupCommand(String file) throws IOException, FileNotFoundException {
53 | command = new File(tempFolder.getRoot(), UUID.randomUUID().toString() + ".sh");
54 | try (FileOutputStream fos = new FileOutputStream(command); InputStream is = UITest.class.getClassLoader().getResourceAsStream(file)) {
55 | UITest.copy(is, fos);
56 | }
57 | command.setExecutable(true);
58 | }
59 |
60 | }
61 |
--------------------------------------------------------------------------------
/src/test/java/ru/r2cloud/rtlspectrum/PowerFormatterTest.java:
--------------------------------------------------------------------------------
1 | package ru.r2cloud.rtlspectrum;
2 |
3 | import static org.junit.Assert.assertEquals;
4 |
5 | import org.junit.Test;
6 |
7 | public class PowerFormatterTest {
8 |
9 | @Test
10 | public void formatNan() {
11 | assertEquals("", new PowerFormatter().toString(Double.NaN));
12 | }
13 |
14 | @Test
15 | public void format() {
16 | PowerFormatter formatter = new PowerFormatter();
17 | assertEquals("23.46", formatter.toString(23.4567));
18 | }
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/src/test/java/ru/r2cloud/rtlspectrum/UITest.java:
--------------------------------------------------------------------------------
1 | package ru.r2cloud.rtlspectrum;
2 |
3 | import static org.junit.Assert.assertEquals;
4 |
5 | import java.io.File;
6 | import java.io.FileOutputStream;
7 | import java.io.IOException;
8 | import java.io.InputStream;
9 | import java.io.OutputStream;
10 | import java.util.ArrayList;
11 | import java.util.List;
12 | import java.util.UUID;
13 |
14 | import org.junit.Ignore;
15 | import org.junit.Rule;
16 | import org.junit.Test;
17 | import org.junit.rules.TemporaryFolder;
18 | import org.testfx.framework.junit.ApplicationTest;
19 | import org.testfx.robot.Motion;
20 |
21 | import javafx.collections.ObservableList;
22 | import javafx.fxml.FXMLLoader;
23 | import javafx.scene.Parent;
24 | import javafx.scene.Scene;
25 | import javafx.scene.chart.LineChart;
26 | import javafx.scene.chart.XYChart;
27 | import javafx.scene.chart.XYChart.Data;
28 | import javafx.scene.control.ProgressBar;
29 | import javafx.scene.input.KeyCode;
30 | import javafx.stage.Stage;
31 |
32 | @Ignore
33 | public class UITest extends ApplicationTest {
34 |
35 | private static final long TIMEOUT = 100;
36 | private static final int MAX_RETRIES = 60;
37 |
38 | @Rule
39 | public TemporaryFolder tempFolder = new TemporaryFolder();
40 |
41 | @Test
42 | public void loadFile() throws Exception {
43 | File subtractData = new File(tempFolder.getRoot(), "2_" + UUID.randomUUID().toString() + ".csv");
44 | try (FileOutputStream fos = new FileOutputStream(subtractData); InputStream is = UITest.class.getClassLoader().getResourceAsStream("subtract.csv")) {
45 | copy(is, fos);
46 | }
47 | File testData = new File(tempFolder.getRoot(), "1_" + UUID.randomUUID().toString() + ".csv");
48 | try (FileOutputStream fos = new FileOutputStream(testData); InputStream is = UITest.class.getClassLoader().getResourceAsStream("test.csv")) {
49 | copy(is, fos);
50 | }
51 | clickOn("#loadFileButton");
52 | push(KeyCode.DIGIT1, KeyCode.ENTER);
53 | waitForCompletion(testData.getCanonicalPath());
54 |
55 | // verify load file
56 | List> file1Data = expectedDataFromFile1();
57 | assertData(file1Data);
58 |
59 | clickOn("#editMenu");
60 | clickOn("#subtractFileMenu", Motion.VERTICAL_FIRST);
61 | push(KeyCode.DIGIT2, KeyCode.ENTER);
62 | waitForCompletion(subtractData.getCanonicalPath());
63 |
64 | // verify subtract file
65 | List> expected = new ArrayList<>();
66 | expected.add(new Data(24000000L, -2.0));
67 | expected.add(new Data(25000000L, -2.0));
68 | expected.add(new Data(26000000L, 2.0));
69 | assertData(expected);
70 |
71 | clickOn("#fileMenu");
72 | clickOn("#addFileMenu", Motion.VERTICAL_FIRST);
73 | push(KeyCode.DIGIT1, KeyCode.ENTER);
74 | waitForCompletion(testData.getCanonicalPath());
75 | // verify add file
76 | assertData(expected, file1Data);
77 | }
78 |
79 | private static List> expectedDataFromFile1() {
80 | List> expected = new ArrayList<>();
81 | expected.add(new Data(24000000L, -24.14));
82 | expected.add(new Data(25000000L, -24.15));
83 | expected.add(new Data(26000000L, 14.07));
84 | return expected;
85 | }
86 |
87 | @Override
88 | public void start(Stage stage) {
89 | System.setProperty("rtlSpectrum.defaultdirectory", tempFolder.getRoot().getAbsolutePath());
90 | System.setProperty("testfx.running", "true");
91 | Parent root;
92 | try (InputStream is = getClass().getClassLoader().getResourceAsStream("layout.fxml")) {
93 | FXMLLoader fxmlLoader = new FXMLLoader(getClass().getClassLoader().getResource("layout.fxml"));
94 | root = fxmlLoader.load(is);
95 | } catch (IOException e) {
96 | throw new IllegalStateException(e);
97 | }
98 |
99 | Scene scene = new Scene(root, 640, 480);
100 | stage.setScene(scene);
101 | stage.show();
102 | }
103 |
104 | @SafeVarargs
105 | private final void assertData(List>... data) {
106 | LineChart chart = lookup("#lineChart").query();
107 | assertEquals(data.length, chart.getData().size());
108 | for (int j = 0; j < data.length; j++) {
109 | ObservableList> curSeries = chart.getData().get(j).getData();
110 | List> curExpected = data[j];
111 | assertEquals(curExpected.size(), curSeries.size());
112 | for (int i = 0; i < curExpected.size(); i++) {
113 | Data expected = curExpected.get(i);
114 | Data actual = curSeries.get(i);
115 | assertEquals(expected.getXValue(), actual.getXValue());
116 | assertEquals(expected.getYValue(), actual.getYValue());
117 | }
118 | }
119 | }
120 |
121 | private void waitForCompletion(String expectedTaskId) {
122 | ProgressBar bar = lookup("#progressBar").query();
123 | int curRetry = 0;
124 | while (curRetry < MAX_RETRIES && !Thread.currentThread().isInterrupted()) {
125 | String taskId = (String) bar.getProperties().get(StatusBar.LAST_COMPLETED_TASK);
126 | if (taskId != null && taskId.equals(expectedTaskId)) {
127 | return;
128 | }
129 | try {
130 | Thread.sleep(TIMEOUT);
131 | } catch (InterruptedException e) {
132 | Thread.currentThread().interrupt();
133 | break;
134 | }
135 | curRetry++;
136 | }
137 | throw new RuntimeException("task did not complete in time: " + expectedTaskId);
138 | }
139 |
140 | public static void copy(InputStream input, OutputStream output) throws IOException {
141 | byte[] buffer = new byte[1024 * 4];
142 | int n = 0;
143 | while (-1 != (n = input.read(buffer))) {
144 | output.write(buffer, 0, n);
145 | }
146 | }
147 | }
148 |
--------------------------------------------------------------------------------
/src/test/resources/defaults_empty.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
--------------------------------------------------------------------------------
/src/test/resources/defaults_fail.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | exit 1
--------------------------------------------------------------------------------
/src/test/resources/defaults_mock.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | echo "dark"
--------------------------------------------------------------------------------
/src/test/resources/subtract.csv:
--------------------------------------------------------------------------------
1 | 2019-06-16,23:10:56,24000000,25000000,1000000.00,1,-22.14,-22.14
2 | 2019-06-16,23:10:56,25000000,26000000,1000000.00,1,-22.15,-22.15
3 | 2019-06-16,23:10:56,26000000,27000000,1000000.00,1,12.07,12.07
--------------------------------------------------------------------------------
/src/test/resources/test.csv:
--------------------------------------------------------------------------------
1 | 2019-06-16,23:10:56,24000000,25000000,1000000.00,1,-24.14,-24.14
2 | 2019-06-16,23:10:56,25000000,26000000,1000000.00,1,-24.15,-24.15
3 | 2019-06-16,23:10:56,26000000,27000000,1000000.00,1,14.07,14.07
--------------------------------------------------------------------------------