├── .gitignore
├── .idea
└── gradle.xml
├── androidfskmodem
├── build.gradle
└── src
│ └── main
│ ├── AndroidManifest.xml
│ └── java
│ └── bg
│ └── cytec
│ └── android
│ └── fskmodem
│ ├── FSKConfig.java
│ ├── FSKDecoder.java
│ └── FSKEncoder.java
├── arduino
└── SoftTermExample
│ └── SoftTermExample.ino
├── build.gradle
├── fSKModemTerminal
├── build.gradle
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── bg
│ │ └── cytec
│ │ └── android
│ │ └── fskmodem
│ │ └── examples
│ │ └── terminal
│ │ └── MainActivity.java
│ └── res
│ ├── drawable-hdpi
│ └── ic_launcher.png
│ ├── drawable-mdpi
│ └── ic_launcher.png
│ ├── drawable-xhdpi
│ └── ic_launcher.png
│ ├── drawable-xxhdpi
│ └── ic_launcher.png
│ ├── layout
│ └── activity_main.xml
│ └── values
│ └── strings.xml
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── import-summary.txt
├── settings.gradle
└── waterlevel
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
├── androidTest
└── java
│ └── com
│ └── akg
│ └── sensprout
│ └── waterlevel
│ └── ApplicationTest.java
└── main
├── AndroidManifest.xml
├── java
└── com
│ └── akg
│ └── sensprout
│ └── waterlevel
│ └── MainActivity.java
└── res
├── layout
└── activity_main.xml
├── menu
└── menu_main.xml
├── mipmap-hdpi
└── ic_launcher.png
├── mipmap-mdpi
└── ic_launcher.png
├── mipmap-xhdpi
└── ic_launcher.png
├── mipmap-xxhdpi
└── ic_launcher.png
├── values-w820dp
└── dimens.xml
└── values
├── dimens.xml
├── strings.xml
└── styles.xml
/.gitignore:
--------------------------------------------------------------------------------
1 | # Built application files
2 | /*/build/
3 | build
4 |
5 | # Crashlytics configuations
6 | com_crashlytics_export_strings.xml
7 |
8 | # Local configuration file (sdk path, etc)
9 | local.properties
10 |
11 | # Gradle generated files
12 | .gradle/
13 |
14 | # Signing files
15 | .signing/
16 |
17 | # User-specific configurations
18 | .idea/libraries/
19 | .idea/workspace.xml
20 | .idea/tasks.xml
21 | .idea/.name
22 | .idea/compiler.xml
23 | .idea/copyright/profiles_settings.xml
24 | .idea/encodings.xml
25 | .idea/misc.xml
26 | .idea/modules.xml
27 | .idea/scopes/scope_settings.xml
28 | .idea/vcs.xml
29 | *.iml
30 |
31 | # OS-specific files
32 | .DS_Store
33 | .DS_Store?
34 | ._*
35 | .Spotlight-V100
36 | .Trashes
37 | ehthumbs.db
38 | Thumbs.db
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
20 |
21 |
--------------------------------------------------------------------------------
/androidfskmodem/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 |
3 | android {
4 | compileSdkVersion 19
5 | buildToolsVersion "23.0.0 rc2"
6 |
7 | defaultConfig {
8 | minSdkVersion 8
9 | targetSdkVersion 21
10 | }
11 |
12 | buildTypes {
13 | release {
14 | minifyEnabled false
15 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/androidfskmodem/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/androidfskmodem/src/main/java/bg/cytec/android/fskmodem/FSKConfig.java:
--------------------------------------------------------------------------------
1 | /**
2 | * This file is part of the FSKModem java/android library for
3 | * processing FSK audio signals.
4 | *
5 | * The FSKModem library is developed by Ivan Ganev, CEO at
6 | * Cytec BG Ltd.
7 | *
8 | * Copyright (C) 2014 Cytec BG Ltd. office@cytec.bg
9 | *
10 | * This program is free software: you can redistribute it and/or modify
11 | * it under the terms of the GNU General Public License as published by
12 | * the Free Software Foundation, either version 3 of the License, or
13 | * (at your option) any later version.
14 | *
15 | * This program is distributed in the hope that it will be useful,
16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 | * GNU General Public License for more details.
19 | *
20 | * You should have received a copy of the GNU General Public License
21 | * along with this program. If not, see .
22 | */
23 |
24 | package bg.cytec.android.fskmodem;
25 |
26 | import java.io.IOException;
27 |
28 | import android.media.AudioFormat;
29 |
30 | public class FSKConfig {
31 |
32 | public static final int SAMPLE_RATE_44100 = 44100; // LCMx2
33 | public static final int SAMPLE_RATE_22050 = 22050; // LEAST COMMON MULTIPLE
34 | // OF 2450, 1225, 630,
35 | // 315 and 126
36 | public static final int SAMPLE_RATE_29400 = 29400; // DEFAULT for 1225; 24
37 | // samples per bit
38 |
39 | public static final int PCM_8BIT = AudioFormat.ENCODING_PCM_8BIT;
40 | public static final int PCM_16BIT = AudioFormat.ENCODING_PCM_16BIT;
41 |
42 | public static final int CHANNELS_MONO = 1;
43 | public static final int CHANNELS_STEREO = 2;
44 |
45 | public static final int SOFT_MODEM_MODE_1 = 1;
46 | public static final int SOFT_MODEM_MODE_1_BAUD_RATE = 126;
47 | public static final int SOFT_MODEM_MODE_1_LOW_FREQ = 882;
48 | public static final int SOFT_MODEM_MODE_1_HIGH_FREQ = 1764;
49 |
50 | public static final int SOFT_MODEM_MODE_2 = 2;
51 | public static final int SOFT_MODEM_MODE_2_BAUD_RATE = 315;
52 | public static final int SOFT_MODEM_MODE_2_LOW_FREQ = 1575;
53 | public static final int SOFT_MODEM_MODE_2_HIGH_FREQ = 3150;
54 |
55 | public static final int SOFT_MODEM_MODE_3 = 3;
56 | public static final int SOFT_MODEM_MODE_3_BAUD_RATE = 630;
57 | public static final int SOFT_MODEM_MODE_3_LOW_FREQ = 3150;
58 | public static final int SOFT_MODEM_MODE_3_HIGH_FREQ = 6300;
59 |
60 | public static final int SOFT_MODEM_MODE_4 = 4;
61 | public static final int SOFT_MODEM_MODE_4_BAUD_RATE = 1225;
62 | public static final int SOFT_MODEM_MODE_4_LOW_FREQ = 4900;
63 | public static final int SOFT_MODEM_MODE_4_HIGH_FREQ = 7350;
64 |
65 | public static final int SOFT_MODEM_MODE_5 = 5;
66 | public static final int SOFT_MODEM_MODE_5_BAUD_RATE = 2450;
67 | public static final int SOFT_MODEM_MODE_5_LOW_FREQ = 4900;
68 | public static final int SOFT_MODEM_MODE_5_HIGH_FREQ = 7350;
69 |
70 | public static final int THRESHOLD_1P = 1; // above and under; sums 2%
71 | public static final int THRESHOLD_5P = 5; // above and under; sums 10%
72 | public static final int THRESHOLD_10P = 10; // above and under; sums 20%
73 | public static final int THRESHOLD_20P = 20; // above and under; sums 40%
74 |
75 | public static final int RMS_SILENCE_THRESHOLD_8BIT = 7;
76 | public static final int RMS_SILENCE_THRESHOLD_16BIT = 2000;
77 |
78 | public static final int DECODER_DATA_BUFFER_SIZE = 8;
79 | public static final int ENCODER_DATA_BUFFER_SIZE = 128;
80 |
81 | public static final int ENCODER_PRE_CARRIER_BITS = 3;
82 | public static final int ENCODER_POST_CARRIER_BITS = 1;
83 | public static final int ENCODER_SILENCE_BITS = 3;
84 |
85 | // /
86 |
87 | public int sampleRate;
88 | public int pcmFormat;
89 | public int channels;
90 | public int modemMode;
91 |
92 | // /
93 |
94 | public int samplesPerBit;
95 |
96 | public int modemBaudRate;
97 | public int modemFreqLow;
98 | public int modemFreqHigh;
99 |
100 | public int modemFreqLowThresholdHigh;
101 | public int modemFreqHighThresholdHigh;
102 |
103 | public int rmsSilenceThreshold;
104 |
105 | public FSKConfig(int sampleRate, int pcmFormat, int channels,
106 | int modemMode, int threshold) throws IOException {
107 | this.sampleRate = sampleRate;
108 | this.pcmFormat = pcmFormat;
109 | this.channels = channels;
110 | this.modemMode = modemMode;
111 |
112 | switch (modemMode) {
113 | case SOFT_MODEM_MODE_1:
114 |
115 | this.modemBaudRate = SOFT_MODEM_MODE_1_BAUD_RATE;
116 | this.modemFreqLow = SOFT_MODEM_MODE_1_LOW_FREQ;
117 | this.modemFreqHigh = SOFT_MODEM_MODE_1_HIGH_FREQ;
118 |
119 | break;
120 |
121 | case SOFT_MODEM_MODE_2:
122 |
123 | this.modemBaudRate = SOFT_MODEM_MODE_2_BAUD_RATE;
124 | this.modemFreqLow = SOFT_MODEM_MODE_2_LOW_FREQ;
125 | this.modemFreqHigh = SOFT_MODEM_MODE_2_HIGH_FREQ;
126 |
127 | break;
128 |
129 | case SOFT_MODEM_MODE_3:
130 |
131 | this.modemBaudRate = SOFT_MODEM_MODE_3_BAUD_RATE;
132 | this.modemFreqLow = SOFT_MODEM_MODE_3_LOW_FREQ;
133 | this.modemFreqHigh = SOFT_MODEM_MODE_3_HIGH_FREQ;
134 |
135 | break;
136 |
137 | case SOFT_MODEM_MODE_4:
138 |
139 | this.modemBaudRate = SOFT_MODEM_MODE_4_BAUD_RATE;
140 | this.modemFreqLow = SOFT_MODEM_MODE_4_LOW_FREQ;
141 | this.modemFreqHigh = SOFT_MODEM_MODE_4_HIGH_FREQ;
142 |
143 | break;
144 |
145 | case SOFT_MODEM_MODE_5:
146 |
147 | this.modemBaudRate = SOFT_MODEM_MODE_5_BAUD_RATE;
148 | this.modemFreqLow = SOFT_MODEM_MODE_5_LOW_FREQ;
149 | this.modemFreqHigh = SOFT_MODEM_MODE_5_HIGH_FREQ;
150 |
151 | break;
152 | }
153 |
154 | if (this.sampleRate % this.modemBaudRate > 0) {
155 | // wrong config
156 |
157 | throw new IOException("Invalid sample rate or baudrate");
158 | }
159 |
160 | this.samplesPerBit = this.sampleRate / this.modemBaudRate;
161 |
162 | this.modemFreqLowThresholdHigh = this.modemFreqLow
163 | + Math.round((this.modemFreqLow * threshold) / 100.0f);
164 | this.modemFreqHighThresholdHigh = this.modemFreqHigh
165 | + Math.round((this.modemFreqHigh * threshold) / 100.0f);
166 |
167 | if (this.pcmFormat == FSKConfig.PCM_8BIT) {
168 | this.rmsSilenceThreshold = FSKConfig.RMS_SILENCE_THRESHOLD_8BIT;
169 | } else if (this.pcmFormat == FSKConfig.PCM_16BIT) {
170 | this.rmsSilenceThreshold = FSKConfig.RMS_SILENCE_THRESHOLD_16BIT;
171 | }
172 | }
173 |
174 | }
175 |
--------------------------------------------------------------------------------
/androidfskmodem/src/main/java/bg/cytec/android/fskmodem/FSKDecoder.java:
--------------------------------------------------------------------------------
1 | /**
2 | * This file is part of the FSKModem java/android library for
3 | * processing FSK audio signals.
4 | *
5 | * The FSKModem library is developed by Ivan Ganev, CEO at
6 | * Cytec BG Ltd.
7 | *
8 | * Copyright (C) 2014 Cytec BG Ltd. office@cytec.bg
9 | *
10 | * This program is free software: you can redistribute it and/or modify
11 | * it under the terms of the GNU General Public License as published by
12 | * the Free Software Foundation, either version 3 of the License, or
13 | * (at your option) any later version.
14 | *
15 | * This program is distributed in the hope that it will be useful,
16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 | * GNU General Public License for more details.
19 | *
20 | * You should have received a copy of the GNU General Public License
21 | * along with this program. If not, see .
22 | */
23 |
24 | package bg.cytec.android.fskmodem;
25 |
26 | import java.nio.ByteBuffer;
27 | import java.nio.ShortBuffer;
28 |
29 | public class FSKDecoder {
30 |
31 | public interface FSKDecoderCallback {
32 | /**
33 | * This method will be called every time there is new data received
34 | * @param newData
35 | */
36 | public void decoded(byte[] newData);
37 | }
38 |
39 | /**
40 | * Decoding state-driven thread
41 | */
42 | protected Runnable mProcessor = new Runnable() {
43 |
44 | @Override
45 | public void run() {
46 |
47 | while (mRunning) {
48 |
49 | synchronized (mSignal) {
50 |
51 | switch (mDecoderStatus) {
52 | case IDLE:
53 | stop();
54 |
55 | break;
56 | case SEARCHING_SIGNAL:case SEARCHING_START_BIT:
57 | processIterationSearch();
58 |
59 | break;
60 |
61 | case DECODING:
62 | processIterationDecode();
63 |
64 | break;
65 |
66 | }
67 | }
68 | }
69 | }
70 | };
71 |
72 | ///
73 |
74 | protected enum STATE {
75 | HIGH, LOW, SILENCE, UNKNOWN
76 | }
77 |
78 | protected enum DecoderStatus {
79 | IDLE, SEARCHING_SIGNAL, SEARCHING_START_BIT, DECODING
80 | }
81 |
82 | // /
83 |
84 | protected FSKConfig mConfig;
85 |
86 | protected FSKDecoderCallback mCallback;
87 |
88 | protected Thread mThread;
89 |
90 | protected boolean mRunning = false;
91 |
92 | protected DecoderStatus mDecoderStatus = DecoderStatus.IDLE;
93 |
94 | protected DecoderStatus mDecoderStatusPaused = DecoderStatus.IDLE;
95 |
96 | // /
97 |
98 | protected ShortBuffer mSignal;
99 |
100 | protected ShortBuffer mFrame;
101 |
102 | protected StringBuffer mBitBuffer; // concat 0 and 1 to build a binary representation of a byte
103 |
104 | protected int mCurrentBit = 0;
105 |
106 | protected int mSignalEnd = 0; // where is the end of the signal in mSignal
107 |
108 | protected int mSignalPointer = 0; //where is the current processing poing in mSignal
109 |
110 | protected int mSignalBufferSize = 0;
111 |
112 | // /
113 |
114 | protected ByteBuffer mData;
115 |
116 | protected int mDataLength = 0;
117 |
118 | // /
119 |
120 | /**
121 | * Create FSK Decoder instance to feed with audio data
122 | * @param config
123 | * @param callback
124 | */
125 | public FSKDecoder(FSKConfig config, FSKDecoderCallback callback) {
126 | mConfig = config;
127 |
128 | mCallback = callback;
129 |
130 | mSignalBufferSize = mConfig.sampleRate; // 1 second buffer
131 |
132 | allocateBufferSignal();
133 |
134 | allocateBufferFrame();
135 |
136 | allocateBufferData();
137 | }
138 |
139 | @Override
140 | protected void finalize() throws Throwable {
141 |
142 | stop();
143 |
144 | super.finalize();
145 | }
146 |
147 | // /
148 |
149 | protected void notifyCallback(byte[] data) {
150 | if (mCallback != null) {
151 | mCallback.decoded(data);
152 | }
153 | }
154 |
155 | protected void start() {
156 | if (!mRunning) {
157 | if (!mDecoderStatusPaused.equals(DecoderStatus.IDLE)) {
158 | setStatus(mDecoderStatusPaused); //resume task
159 | }
160 | else {
161 | setStatus(DecoderStatus.SEARCHING_SIGNAL); //start new process
162 | }
163 |
164 | mRunning = true;
165 |
166 | mThread = new Thread(mProcessor);
167 | mThread.setPriority(Thread.MIN_PRIORITY);
168 | mThread.start();
169 | }
170 | }
171 |
172 | /**
173 | * Stop decoding process
174 | */
175 | public void stop() {
176 | if (mRunning) {
177 | if (mThread != null && mThread.isAlive()) {
178 | mRunning = false;
179 |
180 | mThread.interrupt();
181 | }
182 | }
183 | }
184 |
185 | protected void allocateBufferSignal() {
186 | mSignal = ShortBuffer.allocate(mSignalBufferSize);
187 | }
188 |
189 | protected void allocateBufferFrame() {
190 | mFrame = ShortBuffer.allocate(mConfig.samplesPerBit); // one frame contains one bit
191 | }
192 |
193 | protected void allocateBufferData() {
194 | mData = ByteBuffer.allocate(FSKConfig.DECODER_DATA_BUFFER_SIZE); // maximum bytes
195 | }
196 |
197 | protected void nextStatus() {
198 | switch (mDecoderStatus) {
199 | case IDLE:
200 | setStatus(DecoderStatus.SEARCHING_SIGNAL);
201 | break;
202 | case SEARCHING_SIGNAL:
203 | setStatus(DecoderStatus.SEARCHING_START_BIT);
204 | break;
205 | case SEARCHING_START_BIT:
206 | setStatus(DecoderStatus.DECODING);
207 | break;
208 | case DECODING:
209 | setStatus(DecoderStatus.IDLE);
210 | break;
211 | }
212 | }
213 |
214 | protected void setStatus(DecoderStatus status) {
215 | setStatus(status, DecoderStatus.IDLE);
216 | }
217 |
218 | protected void setStatus(DecoderStatus status, DecoderStatus paused) {
219 | mDecoderStatus = status;
220 | mDecoderStatusPaused = paused;
221 | }
222 |
223 | protected short[] byteArrayToShortArray(byte[] data) {
224 | int size = data.length;
225 | short[] result = new short[size];
226 |
227 | for (int i = 0; i < size; i++) {
228 | result[i] = (short) data[i];
229 | }
230 |
231 | return result;
232 | }
233 |
234 | protected short[] convertToMono(short[] data) {
235 |
236 | short[] monoData = new short[data.length/2];
237 |
238 | for (int i = 0; i < data.length-1; i+=2) {
239 | int mixed = ((int)(data[i] + data[i+1]))/2;
240 |
241 | monoData[i/2] = (short) mixed;
242 | }
243 |
244 | return monoData;
245 | }
246 |
247 | // /
248 |
249 | protected void trimSignal() {
250 |
251 | if (mSignalPointer <= mSignalEnd) {
252 |
253 | short[] currentData = mSignal.array();
254 | short[] remainingData = new short[mSignalEnd - mSignalPointer];
255 |
256 | for (int i = 0; i < remainingData.length; i++) {
257 | remainingData[i] = currentData[mSignalPointer+i];
258 | }
259 |
260 | allocateBufferSignal();
261 | mSignal.put(remainingData);
262 | mSignal.rewind();
263 |
264 | mSignalPointer = 0;
265 | mSignalEnd = remainingData.length;
266 | }
267 | else {
268 | clearSignal();
269 | }
270 | }
271 |
272 | /**
273 | * Use this method to feed 8bit PCM data to the decoder
274 | * @param data
275 | * @return samples space left in the buffer
276 | */
277 | public int appendSignal(byte[] data) {
278 | return appendSignal(byteArrayToShortArray(data));
279 | }
280 |
281 | /**
282 | * Use this method to feed 16bit PCM data to the decoder
283 | * @param data
284 | * @return samples space left in the buffer
285 | */
286 | public int appendSignal(short[] data) {
287 |
288 | synchronized (mSignal) {
289 | short[] monoData;
290 |
291 | if (mConfig.channels == FSKConfig.CHANNELS_STEREO) {
292 | monoData = convertToMono(data);
293 | }
294 | else {
295 | monoData = data;
296 | }
297 |
298 | if (mSignalEnd + monoData.length > mSignal.capacity()) {
299 | //the buffer will overflow... attempt to trim data
300 |
301 | if ((mSignalEnd + monoData.length)-mSignalPointer <= mSignal.capacity()) {
302 | //we can cut off part of the data to fit the rest
303 |
304 | trimSignal();
305 | }
306 | else {
307 | //the decoder is gagging
308 |
309 | return (mSignal.capacity() - (mSignalEnd + monoData.length)); // refuse data and tell the amount of overflow
310 | }
311 | }
312 |
313 | mSignal.position(mSignalEnd);
314 | mSignal.put(monoData);
315 |
316 | mSignalEnd += monoData.length;
317 |
318 | start(); //if idle
319 |
320 | return (mSignal.capacity() - mSignalEnd); //return the remaining amount of space in the buffer
321 | }
322 | }
323 |
324 | /**
325 | * Use this method to set 8bit PCM data to the decoder
326 | * @param data
327 | * @return samples space left in the buffer
328 | */
329 | public int setSignal(byte[] data) {
330 | allocateBufferData(); //reset data buffer
331 |
332 | clearSignal();
333 |
334 | return appendSignal(byteArrayToShortArray(data));
335 | }
336 |
337 | /**
338 | * Use this method to set 16bit PCM data to the decoder
339 | * @param data
340 | * @return samples space left in the buffer
341 | */
342 | public int setSignal(short[] data) {
343 | allocateBufferData(); //reset data buffer
344 |
345 | clearSignal();
346 |
347 | return appendSignal(data);
348 | }
349 |
350 | /**
351 | * Use this method to destroy all data currently queued for decoding
352 | * @return samples space left in the buffer
353 | */
354 | public int clearSignal() {
355 | synchronized (mSignal) {
356 | allocateBufferSignal();
357 |
358 | mSignalEnd = 0;
359 | mSignalPointer = 0;
360 |
361 | return mSignal.capacity();
362 | }
363 | }
364 |
365 | ///
366 |
367 | protected int calcFrequencyZerocrossing(short[] data) {
368 | int numSamples = data.length;
369 | int numCrossing = 0;
370 |
371 | for (int i = 0; i < numSamples-1; i++) {
372 | if ((data[i] > 0 && data[i+1] <= 0)
373 | || (data[i] < 0 && data[i+1] >= 0)) {
374 |
375 | numCrossing++;
376 | }
377 | }
378 |
379 | double numSecondsRecorded = (double) numSamples
380 | / (double) mConfig.sampleRate;
381 | double numCycles = numCrossing / 2;
382 | double frequency = numCycles / numSecondsRecorded;
383 |
384 | return (int) Math.round(frequency);
385 | }
386 |
387 | protected double rootMeanSquared(short[] data) {
388 | double ms = 0;
389 |
390 | for (int i = 0; i < data.length; i++) {
391 | ms += data[i] * data[i];
392 | }
393 |
394 | ms /= data.length;
395 |
396 | return Math.sqrt(ms);
397 | }
398 |
399 | protected STATE determineState(int frequency, double rms) {
400 |
401 | STATE state = STATE.UNKNOWN;
402 |
403 | if (rms <= mConfig.rmsSilenceThreshold) {
404 | state = STATE.SILENCE;
405 | }
406 | else if (frequency <= mConfig.modemFreqLowThresholdHigh) {
407 | state = STATE.LOW;
408 | }
409 | else if (frequency <= mConfig.modemFreqHighThresholdHigh) {
410 | state = STATE.HIGH;
411 | }
412 |
413 | return state;
414 | }
415 |
416 | protected short[] getFrameData(int position) {
417 | mSignal.position(position);
418 |
419 | allocateBufferFrame();
420 |
421 | for (int j = 0; j < mConfig.samplesPerBit; j++) {
422 | mFrame.put(j, mSignal.get());
423 | }
424 |
425 | return mFrame.array();
426 | }
427 |
428 | protected void flushData() {
429 | if (mDataLength > 0) {
430 | byte[] data = new byte[mDataLength];
431 |
432 | for (int i = 0; i < mDataLength; i++) {
433 | data[i] = mData.get(i);
434 | }
435 |
436 | allocateBufferData();
437 |
438 | mDataLength = 0;
439 |
440 | notifyCallback(data);
441 | }
442 | }
443 |
444 | ///
445 |
446 | protected void processIterationSearch() {
447 | if (mSignalPointer <= mSignalEnd-mConfig.samplesPerBit) {
448 |
449 | short[] frameData = getFrameData(mSignalPointer);
450 |
451 | int freq = calcFrequencyZerocrossing(frameData);
452 |
453 | STATE state = determineState(freq, rootMeanSquared(frameData));
454 |
455 | if (state.equals(STATE.HIGH) && mDecoderStatus.equals(DecoderStatus.SEARCHING_SIGNAL)) {
456 | //found pre-carrier bit
457 | nextStatus(); //start searching for start bit
458 | }
459 |
460 | if (mDecoderStatus.equals(DecoderStatus.SEARCHING_START_BIT) && freq == mConfig.modemFreqLow && state.equals(STATE.LOW)) {
461 | //found start bit
462 |
463 | mSignalPointer += (mConfig.samplesPerBit/2); //shift 0.5 period forward
464 |
465 | nextStatus(); //begin decoding
466 |
467 | return;
468 | }
469 |
470 | mSignalPointer++;
471 | }
472 | else {
473 | trimSignal(); //get rid of data that is already processed
474 |
475 | flushData();
476 |
477 | setStatus(DecoderStatus.IDLE);
478 | }
479 | }
480 |
481 | protected void processIterationDecode() {
482 |
483 | if (mSignalPointer <= mSignalEnd-mConfig.samplesPerBit) {
484 |
485 | short[] frameData = getFrameData(mSignalPointer);
486 |
487 | double rms = rootMeanSquared(frameData);
488 |
489 | int freq = calcFrequencyZerocrossing(frameData);
490 |
491 | STATE state = determineState(freq, rms);
492 |
493 | if (mCurrentBit == 0 && state.equals(STATE.LOW)) {
494 | //start bit
495 |
496 | //prepare buffers
497 | mBitBuffer = new StringBuffer();
498 | mCurrentBit++;
499 | }
500 | else if (mCurrentBit == 0 && state.equals(STATE.HIGH)) {
501 | //post-carrier bit(s)
502 |
503 | //go searching for a new transmission
504 | setStatus(DecoderStatus.SEARCHING_START_BIT);
505 | }
506 | else if (mCurrentBit == 9 && state.equals(STATE.HIGH)) {
507 | //end bit
508 |
509 | try {
510 | mData.put((byte) Integer.parseInt(mBitBuffer.toString(), 2));
511 |
512 | mDataLength++;
513 |
514 | if (mDataLength == mData.capacity()) {
515 | //the data buffer is full, puke back to application and cleanup
516 |
517 | flushData();
518 | }
519 | }
520 | catch (Exception e) {
521 | e.printStackTrace();
522 | }
523 |
524 | mCurrentBit = 0;
525 | }
526 | else if (mCurrentBit > 0 && mCurrentBit < 9 && (state.equals(STATE.HIGH) || state.equals(STATE.LOW))) {
527 |
528 | mBitBuffer.insert(0, (state.equals(STATE.HIGH) ? 1 : 0));
529 |
530 | mCurrentBit++;
531 | }
532 | else {
533 | //corrupted data, clear bit buffer
534 |
535 | mBitBuffer = new StringBuffer();
536 | mCurrentBit = 0;
537 |
538 | setStatus(DecoderStatus.SEARCHING_START_BIT);
539 | }
540 |
541 | mSignalPointer += mConfig.samplesPerBit;
542 | }
543 | else {
544 |
545 | trimSignal(); //get rid of data that is already processed
546 |
547 | flushData();
548 |
549 | setStatus(DecoderStatus.IDLE, DecoderStatus.DECODING); //we need to wait for more data to continue decoding
550 | }
551 | }
552 |
553 | }
554 |
--------------------------------------------------------------------------------
/androidfskmodem/src/main/java/bg/cytec/android/fskmodem/FSKEncoder.java:
--------------------------------------------------------------------------------
1 | /**
2 | * This file is part of the FSKModem java/android library for
3 | * processing FSK audio signals.
4 | *
5 | * The FSKModem library is developed by Ivan Ganev, CEO at
6 | * Cytec BG Ltd.
7 | *
8 | * Copyright (C) 2014 Cytec BG Ltd. office@cytec.bg
9 | *
10 | * This program is free software: you can redistribute it and/or modify
11 | * it under the terms of the GNU General Public License as published by
12 | * the Free Software Foundation, either version 3 of the License, or
13 | * (at your option) any later version.
14 | *
15 | * This program is distributed in the hope that it will be useful,
16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 | * GNU General Public License for more details.
19 | *
20 | * You should have received a copy of the GNU General Public License
21 | * along with this program. If not, see .
22 | */
23 |
24 | package bg.cytec.android.fskmodem;
25 |
26 | import java.nio.ByteBuffer;
27 | import java.nio.ShortBuffer;
28 |
29 | public class FSKEncoder {
30 |
31 | public interface FSKEncoderCallback {
32 | /**
33 | * This method will be called every time there is new data encoded
34 | * @param newData
35 | */
36 | public void encoded(byte[] pcm8, short[] pcm16);
37 | }
38 |
39 | /**
40 | * Encoding thread
41 | */
42 | protected Runnable mProcessor = new Runnable() {
43 |
44 | @Override
45 | public void run() {
46 |
47 | while (mRunning) {
48 | synchronized (mData) {
49 |
50 | switch (mEncoderStatus) {
51 | case IDLE:
52 | stop();
53 |
54 | break;
55 | case PRE_CARRIER:case POST_CARRIER:
56 | processIterationCarrier();
57 |
58 | break;
59 |
60 | case ENCODING:
61 | processIterationEncoding();
62 |
63 | break;
64 |
65 | case SILENCE:
66 | processIterationSilence();
67 |
68 | break;
69 |
70 | }
71 | }
72 | }
73 | }
74 | };
75 |
76 | ///
77 |
78 | protected enum STATE {
79 | HIGH, LOW, SILENCE
80 | }
81 |
82 | protected enum EncoderStatus {
83 | IDLE, PRE_CARRIER, ENCODING, POST_CARRIER, SILENCE
84 | }
85 |
86 | ///
87 |
88 | protected FSKConfig mConfig;
89 |
90 | protected FSKEncoderCallback mCallback;
91 |
92 | protected Thread mThread;
93 |
94 | protected boolean mRunning = false;
95 |
96 | protected EncoderStatus mEncoderStatus = EncoderStatus.IDLE;
97 |
98 | ///
99 |
100 | protected ShortBuffer mSignalPCM16;
101 |
102 | protected ByteBuffer mSignalPCM8;
103 |
104 | protected int mSignalLength = 0;
105 |
106 | ///
107 |
108 | protected ByteBuffer mData;
109 |
110 | protected int mDataLength = 0;
111 |
112 | protected int mDataPointer = 0;
113 |
114 | ///
115 |
116 | protected int mPreCarrierBits = 0;
117 |
118 | protected int mPostCarrierBits = 0;
119 |
120 | protected int mSilenceBits = 0;
121 |
122 | ///
123 |
124 | /**
125 | * Create FSK Encoder instance to feed with byte array data
126 | * @param config
127 | * @param callback
128 | */
129 | public FSKEncoder(FSKConfig config, FSKEncoderCallback callback) {
130 | mConfig = config;
131 |
132 | mCallback = callback;
133 |
134 | ///
135 |
136 | mPreCarrierBits = FSKConfig.ENCODER_PRE_CARRIER_BITS; //(int) Math.ceil(mConfig.sampleRate * 40.0f / 1000.0f / mConfig.samplesPerBit);
137 | mPostCarrierBits = FSKConfig.ENCODER_POST_CARRIER_BITS; //(int) Math.ceil(mConfig.sampleRate * 5.0f / 1000.0f / mConfig.samplesPerBit);
138 | mSilenceBits = FSKConfig.ENCODER_SILENCE_BITS;//(int) Math.ceil(mConfig.sampleRate * 5.0f / 1000.0f / mConfig.samplesPerBit);
139 |
140 | ///
141 |
142 | allocateBufferSignal();
143 |
144 | allocateBufferData();
145 | }
146 |
147 | @Override
148 | protected void finalize() throws Throwable {
149 |
150 | stop();
151 |
152 | super.finalize();
153 | }
154 |
155 | // /
156 |
157 | protected void notifyCallback(byte[] pcm8, short[] pcm16) {
158 | if (mCallback != null) {
159 | mCallback.encoded(pcm8, pcm16);
160 | }
161 | }
162 |
163 | protected void start() {
164 | if (!mRunning) {
165 |
166 | setStatus(EncoderStatus.PRE_CARRIER);
167 |
168 | mRunning = true;
169 |
170 | mThread = new Thread(mProcessor);
171 | mThread.setPriority(Thread.MIN_PRIORITY);
172 | mThread.start();
173 | }
174 | }
175 |
176 | /**
177 | * Stop decoding process
178 | */
179 | public void stop() {
180 | if (mRunning) {
181 | if (mThread != null && mThread.isAlive()) {
182 | mRunning = false;
183 |
184 | mThread.interrupt();
185 | }
186 | }
187 | }
188 |
189 | protected void nextStatus() {
190 | switch (mEncoderStatus) {
191 | case IDLE:
192 | setStatus(EncoderStatus.PRE_CARRIER);
193 | break;
194 | case PRE_CARRIER:
195 | setStatus(EncoderStatus.ENCODING);
196 | break;
197 | case ENCODING:
198 | setStatus(EncoderStatus.POST_CARRIER);
199 | break;
200 | case POST_CARRIER:
201 | setStatus(EncoderStatus.SILENCE);
202 | break;
203 | case SILENCE:
204 | setStatus(EncoderStatus.IDLE);
205 | break;
206 | }
207 | }
208 |
209 | protected void setStatus(EncoderStatus status) {
210 | mEncoderStatus = status;
211 | }
212 |
213 | protected void allocateBufferSignal() {
214 | if (mConfig.pcmFormat == FSKConfig.PCM_8BIT) {
215 | mSignalPCM8 = ByteBuffer.allocate(mConfig.sampleRate); //1 second buffer
216 | }
217 | else if (mConfig.pcmFormat == FSKConfig.PCM_16BIT) {
218 | mSignalPCM16 = ShortBuffer.allocate(mConfig.sampleRate); //1 second buffer
219 | }
220 | }
221 |
222 | protected void allocateBufferData() {
223 | mData = ByteBuffer.allocate(FSKConfig.ENCODER_DATA_BUFFER_SIZE);
224 | }
225 |
226 | protected void trimData() {
227 |
228 | if (mDataPointer <= mDataLength) {
229 |
230 | byte[] currentData = mData.array();
231 | byte[] remainingData = new byte[mDataLength - mDataPointer];
232 |
233 | for (int i = 0; i < remainingData.length; i++) {
234 | remainingData[i] = currentData[mDataPointer+i];
235 | }
236 |
237 | mData = ByteBuffer.allocate(FSKConfig.ENCODER_DATA_BUFFER_SIZE);
238 | mData.put(remainingData);
239 | mData.rewind();
240 |
241 | mDataPointer = 0;
242 | mDataLength = remainingData.length;
243 | }
244 | else {
245 | clearData();
246 | }
247 | }
248 |
249 | /**
250 | * Use this method to feed byte array data to the encoder
251 | * @param data
252 | * @return bytes left in the buffer
253 | */
254 | public int appendData(byte[] data) {
255 |
256 | synchronized (mData) {
257 |
258 | if (mDataLength + data.length > mData.capacity()) {
259 | //the buffer will overflow... attempt to trim data
260 |
261 | if ((mDataLength + data.length)-mDataPointer <= mData.capacity()) {
262 | //we can cut off part of the data to fit the rest
263 |
264 | trimData();
265 | }
266 | else {
267 | //the encoder is gagging
268 |
269 | return (mData.capacity() - (mDataLength + data.length)); // refuse data and tell the amount of overflow
270 | }
271 | }
272 |
273 | mData.position(mDataLength);
274 | mData.put(data);
275 |
276 | mDataLength += data.length;
277 |
278 | start(); //if idle
279 |
280 | return (mData.capacity() - mDataLength); //return the remaining amount of space in the buffer
281 | }
282 | }
283 |
284 | /**
285 | * Use this method to set byte array data to the encoder
286 | * @param data
287 | * @return bytes left in the buffer
288 | */
289 | public int setData(byte[] data) {
290 | allocateBufferSignal(); //reset signal output buffer
291 |
292 | clearData();
293 |
294 | return appendData(data);
295 | }
296 |
297 | /**
298 | * Use this method to destroy all data currently queued for encoding
299 | * @return bytes left in the buffer
300 | */
301 | public int clearData() {
302 | synchronized (mData) {
303 | allocateBufferSignal();
304 |
305 | mDataLength = 0;
306 | mDataPointer = 0;
307 |
308 | return mData.capacity();
309 | }
310 | }
311 |
312 | protected void flushSignal() {
313 | if (mSignalLength > 0) {
314 |
315 | if (mConfig.pcmFormat == FSKConfig.PCM_8BIT) {
316 | byte[] dataPCM8 = new byte[mSignalLength];
317 |
318 | for (int i = 0; i < mSignalLength; i++) {
319 | dataPCM8[i] = mSignalPCM8.get(i);
320 | }
321 |
322 | notifyCallback(dataPCM8, null);
323 | }
324 | else if (mConfig.pcmFormat == FSKConfig.PCM_16BIT) {
325 | short[] dataPCM16 = new short[mSignalLength];
326 |
327 | for (int i = 0; i < mSignalLength; i++) {
328 | dataPCM16[i] = mSignalPCM16.get(i);
329 | }
330 |
331 | notifyCallback(null, dataPCM16);
332 | }
333 |
334 | mSignalLength = 0;
335 |
336 | allocateBufferSignal();
337 | }
338 | }
339 |
340 | protected void checkSignalBuffer() {
341 | if (mConfig.pcmFormat == FSKConfig.PCM_8BIT) {
342 | if (mSignalLength >= mSignalPCM8.capacity() - 2) {
343 | //time to flush
344 |
345 | flushSignal();
346 | }
347 | }
348 | else {
349 | if (mSignalLength >= mSignalPCM16.capacity() - 2) {
350 | //time to flush
351 |
352 | flushSignal();
353 | }
354 | }
355 | }
356 |
357 | protected void modulate(STATE state) {
358 | if (!state.equals(STATE.SILENCE)) {
359 | //high or low bit
360 |
361 | if (mConfig.pcmFormat == FSKConfig.PCM_8BIT) {
362 | byte[] newData = modulate8(state);
363 |
364 | mSignalPCM8.position(mSignalLength);
365 |
366 | for (int i = 0; i < mConfig.samplesPerBit; i++) {
367 |
368 | mSignalPCM8.put(newData[i]);
369 |
370 | mSignalLength++;
371 |
372 | if (mConfig.channels == FSKConfig.CHANNELS_STEREO) {
373 | mSignalPCM8.put(newData[i]);
374 |
375 | mSignalLength++;
376 | }
377 |
378 | checkSignalBuffer();
379 | }
380 | }
381 | else if (mConfig.pcmFormat == FSKConfig.PCM_16BIT) {
382 | short[] newData = modulate16(state);
383 |
384 | for (int i = 0; i < mConfig.samplesPerBit; i++) {
385 | mSignalPCM16.put(newData[i]);
386 |
387 | mSignalLength++;
388 |
389 | if (mConfig.channels == FSKConfig.CHANNELS_STEREO) {
390 | mSignalPCM16.put(newData[i]);
391 |
392 | mSignalLength++;
393 | }
394 |
395 | checkSignalBuffer();
396 | }
397 | }
398 | }
399 | else {
400 | if (mConfig.pcmFormat == FSKConfig.PCM_8BIT) {
401 | mSignalPCM8.position(mSignalLength);
402 |
403 | for (int i = 0; i < mConfig.samplesPerBit; i++) {
404 | mSignalPCM8.put((byte) 0);
405 |
406 | mSignalLength++;
407 |
408 | if (mConfig.channels == FSKConfig.CHANNELS_STEREO) {
409 | mSignalPCM8.put((byte) 0);
410 |
411 | mSignalLength++;
412 | }
413 |
414 | checkSignalBuffer();
415 | }
416 | }
417 | else if (mConfig.pcmFormat == FSKConfig.PCM_16BIT) {
418 | mSignalPCM16.position(mSignalLength);
419 |
420 | for (int i = 0; i < mConfig.samplesPerBit; i++) {
421 | mSignalPCM16.put((short) 0);
422 |
423 | mSignalLength++;
424 |
425 | if (mConfig.channels == FSKConfig.CHANNELS_STEREO) {
426 | mSignalPCM16.put((short) 0);
427 |
428 | mSignalLength++;
429 | }
430 |
431 | checkSignalBuffer();
432 | }
433 | }
434 | }
435 | }
436 |
437 | protected byte[] modulate8(STATE state) {
438 | int freq = 0;
439 | byte[] buffer = new byte[mConfig.samplesPerBit];
440 |
441 | if (state.equals(STATE.HIGH)) {
442 | freq = mConfig.modemFreqHigh;
443 | }
444 | else {
445 | freq = mConfig.modemFreqLow;
446 | }
447 |
448 | for (int i = 0; i < buffer.length; i++) {
449 | buffer[i] = (byte) (128 + 127 * Math.sin((2 * Math.PI) * (i*1.0f / mConfig.sampleRate) * freq));
450 | }
451 |
452 | return buffer;
453 | }
454 |
455 | protected short[] modulate16(STATE state) {
456 | int freq = 0;
457 | short[] buffer = new short[mConfig.samplesPerBit];
458 |
459 | if (state.equals(STATE.HIGH)) {
460 | freq = mConfig.modemFreqHigh;
461 | }
462 | else {
463 | freq = mConfig.modemFreqLow;
464 | }
465 |
466 | for (int i = 0; i < buffer.length; i++) {
467 | buffer[i] = (short) (32767 * Math.sin((2 * Math.PI) * (i*1.0f / mConfig.sampleRate) * freq));
468 | }
469 |
470 | return buffer;
471 | }
472 |
473 | protected void processIterationCarrier() {
474 | if (mEncoderStatus.equals(EncoderStatus.PRE_CARRIER)) {
475 | //40ms HIGH signal
476 |
477 | for (int i = 0; i < mPreCarrierBits; i++) {
478 | modulate(STATE.HIGH);
479 | }
480 |
481 | }
482 | else if (mEncoderStatus.equals(EncoderStatus.POST_CARRIER)) {
483 | //5ms HIGH signal as a push bit to end transmission
484 |
485 | for (int i = 0; i < mPostCarrierBits; i++) {
486 | modulate(STATE.HIGH);
487 | }
488 | }
489 |
490 | nextStatus();
491 | }
492 |
493 | protected void processIterationEncoding() {
494 | if (mDataPointer < mDataLength) {
495 | mData.position(mDataPointer);
496 |
497 | byte data = mData.get();
498 |
499 | modulate(STATE.LOW); //start bit
500 |
501 | //modulate data bits
502 | for(byte mask = 1; mask != 0; mask <<= 1) {
503 | if((data & mask) > 0){
504 | modulate(STATE.HIGH);
505 | }
506 | else{
507 | modulate(STATE.LOW);
508 | }
509 | }
510 |
511 | modulate(STATE.HIGH); //end bit
512 |
513 | mDataPointer++;
514 | }
515 | else {
516 | nextStatus();
517 | }
518 | }
519 |
520 | protected void processIterationSilence() {
521 | for (int i = 0; i < mSilenceBits; i++) {
522 | modulate(STATE.SILENCE);
523 | }
524 |
525 | flushSignal();
526 |
527 | nextStatus();
528 | }
529 |
530 | }
531 |
--------------------------------------------------------------------------------
/arduino/SoftTermExample/SoftTermExample.ino:
--------------------------------------------------------------------------------
1 | #include //ライブラリをインクルード
2 | #include
3 |
4 | SoftModem modem; //SoftModemのインスタンスを作る
5 | //int delayTime = 1000;
6 | int txLed = 13;
7 | int rxLed = 12;
8 |
9 | void setup()
10 | {
11 | Serial.begin(57600);
12 | modem.begin(); // setup()でbeginをコールする
13 | pinMode(txLed, OUTPUT);
14 | pinMode(rxLed, OUTPUT);
15 | }
16 |
17 | void loop()
18 | {
19 | while (modem.available()) { //iPhoneからデータ受信しているか確認
20 | blinkLed(rxLed);
21 | int c = modem.read(); //1byteリード
22 | if (isprint(c)) {
23 | Serial.println((char)c); //PCに送信
24 | }
25 | else {
26 | Serial.print("("); //表示できない文字はHexで表示
27 | Serial.print(c, HEX);
28 | Serial.print(")");
29 | }
30 | }
31 | while (Serial.available()) { //PCからデータを受信しているか確認
32 | blinkLed(txLed);
33 | char c = Serial.read(); //1byteリード
34 | modem.write(c); //iPhoneに送信
35 |
36 | }
37 |
38 | // blinkLed();
39 | // modem.write('a');
40 | // delayMicroseconds(delayTime);
41 | // modem.write('b');
42 | // delayMicroseconds(delayTime);
43 | // modem.write('c');
44 | // delayMicroseconds(delayTime);
45 | // modem.write('d');
46 | // delayMicroseconds(delayTime);
47 | // modem.write('e');
48 | // delayMicroseconds(delayTime);
49 | // modem.write(' ');
50 | // delay(100);
51 | }
52 |
53 | void blinkLed(int led){
54 | digitalWrite(led, HIGH);
55 | delay(100);
56 | digitalWrite(led, LOW);
57 | delay(100);
58 | }
59 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 | buildscript {
3 | repositories {
4 | jcenter()
5 | }
6 | dependencies {
7 | classpath 'com.android.tools.build:gradle:1.2.3'
8 | }
9 | }
10 |
11 | allprojects {
12 | repositories {
13 | jcenter()
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/fSKModemTerminal/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 19
5 | buildToolsVersion "23.0.0 rc2"
6 |
7 | defaultConfig {
8 | applicationId "bg.cytec.android.fskmodem.examples.terminal"
9 | minSdkVersion 8
10 | targetSdkVersion 19
11 | }
12 |
13 | buildTypes {
14 | release {
15 | minifyEnabled false
16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
17 | }
18 | }
19 | }
20 |
21 | dependencies {
22 | compile project(':androidfskmodem')
23 | compile 'com.android.support:appcompat-v7:19.1.0'
24 | compile 'com.android.support:support-v4:19.1.0'
25 | }
26 |
--------------------------------------------------------------------------------
/fSKModemTerminal/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
10 |
11 |
12 |
13 |
18 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/fSKModemTerminal/src/main/java/bg/cytec/android/fskmodem/examples/terminal/MainActivity.java:
--------------------------------------------------------------------------------
1 | /**
2 | * This file is part of the FSKModem java/android library for
3 | * processing FSK audio signals.
4 | *
5 | * The FSKModem library is developed by Ivan Ganev, CEO at
6 | * Cytec BG Ltd.
7 | *
8 | * Copyright (C) 2014 Cytec BG Ltd. office@cytec.bg
9 | *
10 | * This program is free software: you can redistribute it and/or modify
11 | * it under the terms of the GNU General Public License as published by
12 | * the Free Software Foundation, either version 3 of the License, or
13 | * (at your option) any later version.
14 | *
15 | * This program is distributed in the hope that it will be useful,
16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 | * GNU General Public License for more details.
19 | *
20 | * You should have received a copy of the GNU General Public License
21 | * along with this program. If not, see .
22 | */
23 |
24 | package bg.cytec.android.fskmodem.examples.terminal;
25 |
26 | import java.io.IOException;
27 |
28 | import bg.cytec.android.fskmodem.FSKConfig;
29 | import bg.cytec.android.fskmodem.FSKDecoder;
30 | import bg.cytec.android.fskmodem.FSKDecoder.FSKDecoderCallback;
31 | import bg.cytec.android.fskmodem.FSKEncoder.FSKEncoderCallback;
32 | import bg.cytec.android.fskmodem.FSKEncoder;
33 | import android.support.v7.app.ActionBarActivity;
34 | import android.util.Log;
35 | import android.view.KeyEvent;
36 | import android.view.MotionEvent;
37 | import android.view.View;
38 | import android.view.View.OnTouchListener;
39 | import android.view.inputmethod.EditorInfo;
40 | import android.widget.EditText;
41 | import android.widget.ScrollView;
42 | import android.widget.TextView;
43 | import android.widget.TextView.OnEditorActionListener;
44 | import android.annotation.SuppressLint;
45 | import android.media.AudioFormat;
46 | import android.media.AudioManager;
47 | import android.media.AudioRecord;
48 | import android.media.AudioTrack;
49 | import android.media.MediaRecorder.AudioSource;
50 | import android.os.Bundle;
51 |
52 | public class MainActivity extends ActionBarActivity {
53 |
54 | protected FSKConfig mConfig;
55 | protected FSKEncoder mEncoder;
56 | protected FSKDecoder mDecoder;
57 |
58 | protected AudioTrack mAudioTrack;
59 |
60 | protected AudioRecord mRecorder;
61 |
62 | protected int mBufferSize = 0;
63 |
64 | protected boolean mScrollLock = true;
65 |
66 | protected ScrollView mScroll;
67 | protected TextView mTerminal;
68 | protected EditText mInput;
69 |
70 | protected Runnable mRecordFeed = new Runnable() {
71 |
72 | @Override
73 | public void run() {
74 |
75 | while (mRecorder.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING) {
76 |
77 | short[] data = new short[mBufferSize/2]; //the buffer size is in bytes
78 |
79 | // gets the audio output from microphone to short array samples
80 | mRecorder.read(data, 0, mBufferSize/2);
81 |
82 | mDecoder.appendSignal(data);
83 | }
84 | }
85 | };
86 |
87 | @Override
88 | protected void onCreate(Bundle savedInstanceState) {
89 | super.onCreate(savedInstanceState);
90 | setContentView(R.layout.activity_main);
91 |
92 | // INIT VIEWS
93 |
94 | mTerminal = (TextView) findViewById(R.id.terminal);
95 | mInput = (EditText) findViewById(R.id.input);
96 |
97 | mInput.setOnEditorActionListener(new OnEditorActionListener() {
98 |
99 | @Override
100 | public boolean onEditorAction(TextView view, int actionId, KeyEvent event) {
101 |
102 | if (actionId == EditorInfo.IME_ACTION_SEND) {
103 |
104 | String text = view.getText().toString()+"\n";
105 |
106 | mEncoder.appendData(text.getBytes());
107 |
108 | mTerminal.setText(mTerminal.getText()+ "\nSend: " + text);
109 |
110 | mInput.setText("");
111 |
112 | if (mScrollLock) {
113 | mScroll.fullScroll(ScrollView.FOCUS_DOWN);
114 | }
115 |
116 | return true;
117 | }
118 |
119 | return false;
120 | }
121 | });
122 |
123 | mScroll = (ScrollView) findViewById(R.id.scrollview);
124 |
125 | mScroll.setOnTouchListener(new OnTouchListener() {
126 |
127 | @SuppressLint("ClickableViewAccessibility")
128 | @Override
129 | public boolean onTouch(View view, MotionEvent event) {
130 |
131 | if (event.getAction() == MotionEvent.ACTION_UP) {
132 | //finger released, detect if view is scrolled to bottom
133 |
134 | int diff = (mScroll.getScrollY()+mScroll.getHeight()) - mTerminal.getHeight();
135 |
136 | if (diff == 0) {
137 | mScrollLock = true;
138 | }
139 | }
140 | else if (event.getAction() == MotionEvent.ACTION_DOWN) {
141 | //finger placed down, lock scroll
142 |
143 | mScrollLock = false;
144 | }
145 |
146 | return false;
147 | }
148 | });
149 |
150 | /// INIT FSK CONFIG
151 |
152 | try {
153 | mConfig = new FSKConfig(FSKConfig.SAMPLE_RATE_44100, FSKConfig.PCM_16BIT, FSKConfig.CHANNELS_MONO, FSKConfig.SOFT_MODEM_MODE_4, FSKConfig.THRESHOLD_20P);
154 | } catch (IOException e1) {
155 | e1.printStackTrace();
156 | }
157 |
158 | /// INIT FSK DECODER
159 |
160 | mDecoder = new FSKDecoder(mConfig, new FSKDecoderCallback() {
161 |
162 | @Override
163 | public void decoded(byte[] newData) {
164 |
165 | final String text = new String(newData);
166 |
167 | runOnUiThread(new Runnable() {
168 | public void run() {
169 |
170 | mTerminal.setText(mTerminal.getText()+text);
171 |
172 | if (mScrollLock) {
173 | mScroll.fullScroll(ScrollView.FOCUS_DOWN);
174 | }
175 | }
176 | });
177 | }
178 | });
179 |
180 | /// INIT FSK ENCODER
181 |
182 | mEncoder = new FSKEncoder(mConfig, new FSKEncoderCallback() {
183 |
184 | @Override
185 | public void encoded(byte[] pcm8, short[] pcm16) {
186 | if (mConfig.pcmFormat == FSKConfig.PCM_16BIT) {
187 | //16bit buffer is populated, 8bit buffer is null
188 |
189 | mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC,
190 | mConfig.sampleRate, AudioFormat.CHANNEL_OUT_MONO,
191 | AudioFormat.ENCODING_PCM_16BIT, pcm16.length*2,
192 | AudioTrack.MODE_STATIC);
193 |
194 | mAudioTrack.write(pcm16, 0, pcm16.length);
195 |
196 | mAudioTrack.play();
197 | }
198 | }
199 | });
200 |
201 | ///
202 |
203 | //make sure that the settings of the recorder match the settings of the decoder
204 | //most devices cant record anything but 44100 samples in 16bit PCM format...
205 | mBufferSize = AudioRecord.getMinBufferSize(FSKConfig.SAMPLE_RATE_44100, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT);
206 |
207 | //scale up the buffer... reading larger amounts of data
208 | //minimizes the chance of missing data because of thread priority
209 | mBufferSize *= 10;
210 |
211 | //again, make sure the recorder settings match the decoder settings
212 | mRecorder = new AudioRecord(AudioSource.MIC, FSKConfig.SAMPLE_RATE_44100, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, mBufferSize);
213 |
214 | if (mRecorder.getState() == AudioRecord.STATE_INITIALIZED) {
215 | mRecorder.startRecording();
216 |
217 | //start a thread to read the audio data
218 | Thread thread = new Thread(mRecordFeed);
219 | thread.setPriority(Thread.MAX_PRIORITY);
220 | thread.start();
221 | }
222 | else {
223 | Log.i("FSKDecoder", "Please check the recorder settings, something is wrong!");
224 | }
225 | }
226 |
227 | @Override
228 | protected void onDestroy() {
229 |
230 | mDecoder.stop();
231 | mEncoder.stop();
232 |
233 | if (mRecorder != null && mRecorder.getState() == AudioRecord.STATE_INITIALIZED)
234 | {
235 | mRecorder.stop();
236 | mRecorder.release();
237 | }
238 |
239 | if (mAudioTrack != null && mAudioTrack.getPlayState() == AudioTrack.STATE_INITIALIZED)
240 | {
241 | mAudioTrack.stop();
242 | mAudioTrack.release();
243 | }
244 |
245 | super.onDestroy();
246 | }
247 |
248 | }
249 |
--------------------------------------------------------------------------------
/fSKModemTerminal/src/main/res/drawable-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tatung/FSKModemTerminal/ccabe56a4911e74210c44a646052a211c155d87b/fSKModemTerminal/src/main/res/drawable-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/fSKModemTerminal/src/main/res/drawable-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tatung/FSKModemTerminal/ccabe56a4911e74210c44a646052a211c155d87b/fSKModemTerminal/src/main/res/drawable-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/fSKModemTerminal/src/main/res/drawable-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tatung/FSKModemTerminal/ccabe56a4911e74210c44a646052a211c155d87b/fSKModemTerminal/src/main/res/drawable-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/fSKModemTerminal/src/main/res/drawable-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tatung/FSKModemTerminal/ccabe56a4911e74210c44a646052a211c155d87b/fSKModemTerminal/src/main/res/drawable-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/fSKModemTerminal/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
11 |
12 |
18 |
19 |
24 |
25 |
26 |
27 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/fSKModemTerminal/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | FSKModem Terminal
5 |
6 | Input
7 |
8 |
9 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tatung/FSKModemTerminal/ccabe56a4911e74210c44a646052a211c155d87b/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed Apr 10 15:27:10 PDT 2013
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # For Cygwin, ensure paths are in UNIX format before anything is touched.
46 | if $cygwin ; then
47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
48 | fi
49 |
50 | # Attempt to set APP_HOME
51 | # Resolve links: $0 may be a link
52 | PRG="$0"
53 | # Need this for relative symlinks.
54 | while [ -h "$PRG" ] ; do
55 | ls=`ls -ld "$PRG"`
56 | link=`expr "$ls" : '.*-> \(.*\)$'`
57 | if expr "$link" : '/.*' > /dev/null; then
58 | PRG="$link"
59 | else
60 | PRG=`dirname "$PRG"`"/$link"
61 | fi
62 | done
63 | SAVED="`pwd`"
64 | cd "`dirname \"$PRG\"`/" >&-
65 | APP_HOME="`pwd -P`"
66 | cd "$SAVED" >&-
67 |
68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
69 |
70 | # Determine the Java command to use to start the JVM.
71 | if [ -n "$JAVA_HOME" ] ; then
72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
73 | # IBM's JDK on AIX uses strange locations for the executables
74 | JAVACMD="$JAVA_HOME/jre/sh/java"
75 | else
76 | JAVACMD="$JAVA_HOME/bin/java"
77 | fi
78 | if [ ! -x "$JAVACMD" ] ; then
79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
80 |
81 | Please set the JAVA_HOME variable in your environment to match the
82 | location of your Java installation."
83 | fi
84 | else
85 | JAVACMD="java"
86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
87 |
88 | Please set the JAVA_HOME variable in your environment to match the
89 | location of your Java installation."
90 | fi
91 |
92 | # Increase the maximum file descriptors if we can.
93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
94 | MAX_FD_LIMIT=`ulimit -H -n`
95 | if [ $? -eq 0 ] ; then
96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
97 | MAX_FD="$MAX_FD_LIMIT"
98 | fi
99 | ulimit -n $MAX_FD
100 | if [ $? -ne 0 ] ; then
101 | warn "Could not set maximum file descriptor limit: $MAX_FD"
102 | fi
103 | else
104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
105 | fi
106 | fi
107 |
108 | # For Darwin, add options to specify how the application appears in the dock
109 | if $darwin; then
110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
111 | fi
112 |
113 | # For Cygwin, switch paths to Windows format before running java
114 | if $cygwin ; then
115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
158 | function splitJvmOpts() {
159 | JVM_OPTS=("$@")
160 | }
161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
163 |
164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
165 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/import-summary.txt:
--------------------------------------------------------------------------------
1 | ECLIPSE ANDROID PROJECT IMPORT SUMMARY
2 | ======================================
3 |
4 | Manifest Merging:
5 | -----------------
6 | Your project uses libraries that provide manifests, and your Eclipse
7 | project did not explicitly turn on manifest merging. In Android Gradle
8 | projects, manifests are always merged (meaning that contents from your
9 | libraries' manifests will be merged into the app manifest. If you had
10 | manually copied contents from library manifests into your app manifest
11 | you may need to remove these for the app to build correctly.
12 |
13 | Ignored Files:
14 | --------------
15 | The following files were *not* copied into the new Gradle project; you
16 | should evaluate whether these are still needed in your project and if
17 | so manually move them:
18 |
19 | From FSKModemTerminal:
20 | * ic_launcher-web.png
21 | * proguard-project.txt
22 | From android.fskmodem:
23 | * .directory
24 | * proguard-project.txt
25 |
26 | Replaced Jars with Dependencies:
27 | --------------------------------
28 | The importer recognized the following .jar files as third party
29 | libraries and replaced them with Gradle dependencies instead. This has
30 | the advantage that more explicit version information is known, and the
31 | libraries can be updated automatically. However, it is possible that
32 | the .jar file in your project was of an older version than the
33 | dependency we picked, which could render the project not compileable.
34 | You can disable the jar replacement in the import wizard and try again:
35 |
36 | android-support-v4.jar => com.android.support:support-v4:19.1.0
37 |
38 | Replaced Libraries with Dependencies:
39 | -------------------------------------
40 | The importer recognized the following library projects as third party
41 | libraries and replaced them with Gradle dependencies instead. This has
42 | the advantage that more explicit version information is known, and the
43 | libraries can be updated automatically. However, it is possible that
44 | the source files in your project were of an older version than the
45 | dependency we picked, which could render the project not compileable.
46 | You can disable the library replacement in the import wizard and try
47 | again:
48 |
49 | appcompat-v7 => [com.android.support:appcompat-v7:19.1.0]
50 |
51 | Moved Files:
52 | ------------
53 | Android Gradle projects use a different directory structure than ADT
54 | Eclipse projects. Here's how the projects were restructured:
55 |
56 | In android.fskmodem:
57 | * AndroidManifest.xml => androidfskmodem/src/main/AndroidManifest.xml
58 | * assets/ => androidfskmodem/src/main/assets
59 | * res/ => androidfskmodem/src/main/res
60 | * src/ => androidfskmodem/src/main/java/
61 | In FSKModemTerminal:
62 | * AndroidManifest.xml => fSKModemTerminal/src/main/AndroidManifest.xml
63 | * res/ => fSKModemTerminal/src/main/res/
64 | * src/ => fSKModemTerminal/src/main/java/
65 |
66 | Next Steps:
67 | -----------
68 | You can now build the project. The Gradle project needs network
69 | connectivity to download dependencies.
70 |
71 | Bugs:
72 | -----
73 | If for some reason your project does not build, and you determine that
74 | it is due to a bug or limitation of the Eclipse to Gradle importer,
75 | please file a bug at http://b.android.com with category
76 | Component-Tools.
77 |
78 | (This import summary is for your information only, and can be deleted
79 | after import once you are satisfied with the results.)
80 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':androidfskmodem', ':waterlevel'
2 | include ':fSKModemTerminal'
3 |
--------------------------------------------------------------------------------
/waterlevel/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/waterlevel/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 22
5 | buildToolsVersion "23.0.0 rc2"
6 |
7 | defaultConfig {
8 | applicationId "com.akg.sensprout.waterlevel"
9 | minSdkVersion 8
10 | targetSdkVersion 22
11 | versionCode 1
12 | versionName "1.0"
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | }
21 |
22 | dependencies {
23 | compile fileTree(dir: 'libs', include: ['*.jar'])
24 | compile 'com.android.support:appcompat-v7:22.2.0'
25 | compile project(':androidfskmodem')
26 | }
27 |
--------------------------------------------------------------------------------
/waterlevel/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Volumes/Data/bin/adt-bundle-mac-x86_64-20140702/sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/waterlevel/src/androidTest/java/com/akg/sensprout/waterlevel/ApplicationTest.java:
--------------------------------------------------------------------------------
1 | package com.akg.sensprout.waterlevel;
2 |
3 | import android.app.Application;
4 | import android.test.ApplicationTestCase;
5 |
6 | /**
7 | * Testing Fundamentals
8 | */
9 | public class ApplicationTest extends ApplicationTestCase {
10 | public ApplicationTest() {
11 | super(Application.class);
12 | }
13 | }
--------------------------------------------------------------------------------
/waterlevel/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
10 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/waterlevel/src/main/java/com/akg/sensprout/waterlevel/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.akg.sensprout.waterlevel;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.media.AudioFormat;
5 | import android.media.AudioManager;
6 | import android.media.AudioRecord;
7 | import android.media.AudioTrack;
8 | import android.media.MediaRecorder;
9 | import android.support.v7.app.ActionBarActivity;
10 | import android.os.Bundle;
11 | import android.util.Log;
12 | import android.view.KeyEvent;
13 | import android.view.Menu;
14 | import android.view.MenuItem;
15 | import android.view.MotionEvent;
16 | import android.view.View;
17 | import android.view.inputmethod.EditorInfo;
18 | import android.widget.EditText;
19 | import android.widget.ScrollView;
20 | import android.widget.TextView;
21 |
22 | import java.io.IOException;
23 |
24 | import bg.cytec.android.fskmodem.FSKConfig;
25 | import bg.cytec.android.fskmodem.FSKDecoder;
26 | import bg.cytec.android.fskmodem.FSKEncoder;
27 |
28 |
29 | public class MainActivity extends ActionBarActivity {
30 |
31 | protected FSKConfig mConfig;
32 | protected FSKEncoder mEncoder;
33 | protected FSKDecoder mDecoder;
34 |
35 | protected AudioTrack mAudioTrack;
36 |
37 | protected AudioRecord mRecorder;
38 |
39 | protected int mBufferSize = 0;
40 |
41 | protected boolean mScrollLock = true;
42 |
43 | protected ScrollView mScroll;
44 | protected TextView mTerminal;
45 | protected EditText mInput;
46 |
47 | protected Runnable mRecordFeed = new Runnable() {
48 |
49 | @Override
50 | public void run() {
51 |
52 | while (mRecorder.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING) {
53 |
54 | short[] data = new short[mBufferSize / 2]; //the buffer size is in bytes
55 |
56 | // gets the audio output from microphone to short array samples
57 | mRecorder.read(data, 0, mBufferSize / 2);
58 |
59 | mDecoder.appendSignal(data);
60 | }
61 | }
62 | };
63 |
64 | @Override
65 | protected void onCreate(Bundle savedInstanceState) {
66 | super.onCreate(savedInstanceState);
67 | setContentView(R.layout.activity_main);
68 |
69 | // INIT VIEWS
70 |
71 | mTerminal = (TextView) findViewById(R.id.lblSense);
72 | mInput = (EditText) findViewById(R.id.txtInput);
73 |
74 | mInput.setOnEditorActionListener(new TextView.OnEditorActionListener() {
75 |
76 | @Override
77 | public boolean onEditorAction(TextView view, int actionId, KeyEvent event) {
78 |
79 | if (actionId == EditorInfo.IME_ACTION_SEND) {
80 |
81 | String text = view.getText().toString() + "\n";
82 |
83 | mEncoder.appendData(text.getBytes());
84 |
85 | mTerminal.setText(mTerminal.getText() + "\nSend: " + text);
86 |
87 | mInput.setText("");
88 |
89 | if (mScrollLock) {
90 | mScroll.fullScroll(ScrollView.FOCUS_DOWN);
91 | }
92 |
93 | return true;
94 | }
95 |
96 | return false;
97 | }
98 | });
99 |
100 | mScroll = (ScrollView) findViewById(R.id.scrollView);
101 |
102 | mScroll.setOnTouchListener(new View.OnTouchListener() {
103 |
104 | @SuppressLint("ClickableViewAccessibility")
105 | @Override
106 | public boolean onTouch(View view, MotionEvent event) {
107 |
108 | if (event.getAction() == MotionEvent.ACTION_UP) {
109 | //finger released, detect if view is scrolled to bottom
110 |
111 | int diff = (mScroll.getScrollY() + mScroll.getHeight()) - mTerminal.getHeight();
112 |
113 | if (diff == 0) {
114 | mScrollLock = true;
115 | }
116 | } else if (event.getAction() == MotionEvent.ACTION_DOWN) {
117 | //finger placed down, lock scroll
118 |
119 | mScrollLock = false;
120 | }
121 |
122 | return false;
123 | }
124 | });
125 |
126 | /// INIT FSK CONFIG
127 |
128 | try {
129 | mConfig = new FSKConfig(FSKConfig.SAMPLE_RATE_44100, FSKConfig.PCM_16BIT, FSKConfig.CHANNELS_MONO, FSKConfig.SOFT_MODEM_MODE_4, FSKConfig.THRESHOLD_20P);
130 | } catch (IOException e1) {
131 | e1.printStackTrace();
132 | }
133 |
134 | /// INIT FSK DECODER
135 |
136 | mDecoder = new FSKDecoder(mConfig, new FSKDecoder.FSKDecoderCallback() {
137 |
138 | @Override
139 | public void decoded(byte[] newData) {
140 |
141 | final String text = new String(newData);
142 |
143 | runOnUiThread(new Runnable() {
144 | public void run() {
145 |
146 | mTerminal.setText(mTerminal.getText() + text);
147 |
148 | if (mScrollLock) {
149 | mScroll.fullScroll(ScrollView.FOCUS_DOWN);
150 | }
151 | }
152 | });
153 | }
154 | });
155 |
156 | /// INIT FSK ENCODER
157 |
158 | mEncoder = new FSKEncoder(mConfig, new FSKEncoder.FSKEncoderCallback() {
159 |
160 | @Override
161 | public void encoded(byte[] pcm8, short[] pcm16) {
162 | if (mConfig.pcmFormat == FSKConfig.PCM_16BIT) {
163 | //16bit buffer is populated, 8bit buffer is null
164 |
165 | mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC,
166 | mConfig.sampleRate, AudioFormat.CHANNEL_OUT_MONO,
167 | AudioFormat.ENCODING_PCM_16BIT, pcm16.length * 2,
168 | AudioTrack.MODE_STATIC);
169 |
170 | mAudioTrack.write(pcm16, 0, pcm16.length);
171 |
172 | mAudioTrack.play();
173 | }
174 | }
175 | });
176 |
177 | ///
178 |
179 | //make sure that the settings of the recorder match the settings of the decoder
180 | //most devices cant record anything but 44100 samples in 16bit PCM format...
181 | mBufferSize = AudioRecord.getMinBufferSize(FSKConfig.SAMPLE_RATE_44100, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT);
182 |
183 | //scale up the buffer... reading larger amounts of data
184 | //minimizes the chance of missing data because of thread priority
185 | mBufferSize *= 10;
186 |
187 | //again, make sure the recorder settings match the decoder settings
188 | mRecorder = new AudioRecord(MediaRecorder.AudioSource.MIC, FSKConfig.SAMPLE_RATE_44100, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, mBufferSize);
189 |
190 | if (mRecorder.getState() == AudioRecord.STATE_INITIALIZED) {
191 | mRecorder.startRecording();
192 |
193 | //start a thread to read the audio data
194 | Thread thread = new Thread(mRecordFeed);
195 | thread.setPriority(Thread.MAX_PRIORITY);
196 | thread.start();
197 | } else {
198 | Log.i("FSKDecoder", "Please check the recorder settings, something is wrong!");
199 | }
200 | }
201 |
202 | @Override
203 | public boolean onCreateOptionsMenu(Menu menu) {
204 | // Inflate the menu; this adds items to the action bar if it is present.
205 | getMenuInflater().inflate(R.menu.menu_main, menu);
206 | return true;
207 | }
208 |
209 | @Override
210 | public boolean onOptionsItemSelected(MenuItem item) {
211 | // Handle action bar item clicks here. The action bar will
212 | // automatically handle clicks on the Home/Up button, so long
213 | // as you specify a parent activity in AndroidManifest.xml.
214 | int id = item.getItemId();
215 |
216 | //noinspection SimplifiableIfStatement
217 | if (id == R.id.action_settings) {
218 | return true;
219 | }
220 |
221 | return super.onOptionsItemSelected(item);
222 | }
223 |
224 | @Override
225 | protected void onDestroy() {
226 |
227 | mDecoder.stop();
228 | mEncoder.stop();
229 |
230 | if (mRecorder != null && mRecorder.getState() == AudioRecord.STATE_INITIALIZED) {
231 | mRecorder.stop();
232 | mRecorder.release();
233 | }
234 |
235 | if (mAudioTrack != null && mAudioTrack.getPlayState() == AudioTrack.STATE_INITIALIZED) {
236 | mAudioTrack.stop();
237 | mAudioTrack.release();
238 | }
239 |
240 | super.onDestroy();
241 | }
242 | }
--------------------------------------------------------------------------------
/waterlevel/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
11 |
12 |
13 |
18 |
19 |
24 |
25 |
26 |
27 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/waterlevel/src/main/res/menu/menu_main.xml:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/waterlevel/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tatung/FSKModemTerminal/ccabe56a4911e74210c44a646052a211c155d87b/waterlevel/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/waterlevel/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tatung/FSKModemTerminal/ccabe56a4911e74210c44a646052a211c155d87b/waterlevel/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/waterlevel/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tatung/FSKModemTerminal/ccabe56a4911e74210c44a646052a211c155d87b/waterlevel/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/waterlevel/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tatung/FSKModemTerminal/ccabe56a4911e74210c44a646052a211c155d87b/waterlevel/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/waterlevel/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/waterlevel/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 |
6 |
--------------------------------------------------------------------------------
/waterlevel/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Water Level
3 |
4 | Hello world!
5 | Settings
6 |
7 |
--------------------------------------------------------------------------------
/waterlevel/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
9 |
--------------------------------------------------------------------------------