Porównaj commity

...

155 Commity
1.42 ... master

Autor SHA1 Wiadomość Data
sh123 1b15e9a029 Update version 2024-02-22 19:59:48 +02:00
sh123 f6bfff12a2 Add raspberry pi pico device filter 2024-02-20 20:45:03 +02:00
sh123 cdb787d922 Update version and some strings 2024-02-20 18:05:35 +02:00
sh123 8edf786869 Added custom prefix as protocol 2024-02-19 22:28:53 +02:00
sh123 579c27f931 Cleanup 2024-02-19 21:52:57 +02:00
sh123 fb809eb47f Remove previous feature, needs another approach 2024-02-19 21:45:20 +02:00
sh123 ad6e79044b Add option to prefix USB packet with transmission target 2024-02-19 19:04:24 +02:00
sh123 83cffeaeea Fix issue with exact super frame size giving smaller packet 2024-02-18 22:30:57 +02:00
sh123 532686c3cf Fix NDK working version 2024-02-18 22:16:23 +02:00
sh123 dda3677b00 Increase version, do not use super frame size for OPUS 2023-12-25 12:51:56 +02:00
sh123 b19dcb8ca3 Change from /usr/bin/cmake to cmake 2023-12-11 17:39:14 +02:00
sh123 aa1087cd9e Increase version 2023-12-10 18:43:59 +02:00
sh123 4d0eb259b3 Added .gitignore 2023-12-10 08:38:07 +02:00
sh123 4e10b023dc Update readme 2023-12-09 21:28:56 +02:00
sh123 49f8060635 Optimizations 2023-12-09 21:08:35 +02:00
sh123 4b8321e425 OPUS fixes 2023-12-09 21:07:39 +02:00
sh123 800af35e8f Logging 2023-12-09 20:28:36 +02:00
sh123 d2711d807b Added missing file 2023-12-09 19:54:54 +02:00
sh123 cac9a17885 Title fixes 2023-12-09 19:53:49 +02:00
sh123 e88a94e9be OPUS init 2023-12-09 19:43:55 +02:00
sh123 f5ce9e7bbd Add opus preferences 2023-12-09 17:21:50 +02:00
sh123 083e996ae6 Update Opus.java with defines 2023-12-09 16:34:51 +02:00
sh123 6fcf2f38ff Refactoring 2023-12-09 16:17:25 +02:00
sh123 cc83f5add8 Simplify protocol interface 2023-12-09 14:27:49 +02:00
sh123 2f949aa501 Update codec2 related interfaces 2023-12-09 13:33:32 +02:00
sh123 4d56b32c55 Implement java class 2023-12-09 12:14:47 +02:00
sh123 52c41ec2a0 Implement opus jni 2023-12-09 11:49:28 +02:00
sh123 d8eb26d6fe Include libopus-android 2023-12-08 22:55:25 +02:00
sh123 5128480b6c Add headers 2023-12-08 22:47:54 +02:00
sh123 bb9abbc379 Initial version of OPUS 2023-12-08 22:22:32 +02:00
sh123 4d8ec2998b
Update README.md 2023-12-08 10:49:54 +02:00
sh123 b53785f261 Limit length of the message to 67 characters 2023-12-07 14:31:38 +02:00
sh123 2c7eb04b69 Fix hang issue with messages longer than 67 characters 2023-12-07 14:26:07 +02:00
sh123 df80a982de Tune timeouts 2023-11-17 14:53:35 +02:00
sh123 9886bccaf4 Display speed based on modulation 2023-11-17 13:19:59 +02:00
sh123 3346003d2d Escape control command data 2023-11-16 23:26:22 +02:00
sh123 9dab9acad0 Small settings update 2023-11-16 21:23:54 +02:00
sh123 561325757b Added settings for FSK 2023-11-16 21:16:05 +02:00
sh123 3a4438e80c FSK support for set hardware 2023-11-16 20:32:19 +02:00
sh123 64e6b7f265 Add option to specify APRS-IS port 2023-11-12 16:42:08 +02:00
sh123 854bd629da Handle auto reply messages 2023-07-16 11:12:55 +03:00
sh123 1edf1c4c51 Message ack id parsing 2023-07-15 16:25:48 +03:00
sh123 e6a05091d9 Flip own icon 2023-07-06 17:13:13 +03:00
sh123 6c3700809a Show location info when following location 2023-07-06 15:59:01 +03:00
sh123 d79c24f602 Proper direction of own icon 2023-07-06 13:52:42 +03:00
sh123 7e6e0bc43e Move map with own position changes 2023-07-04 21:25:02 +03:00
sh123 968a213bc6 Show aprs destination in map popup 2023-07-04 20:33:26 +03:00
sh123 2c7e3e80b5 Third party packets handling 2023-07-03 18:53:02 +03:00
sh123 ef7f4a8dcd Callsign formatting 2023-07-02 23:09:05 +03:00
sh123 8499df6e18 Add rf heard list 2023-07-02 22:47:27 +03:00
sh123 e842032cad Aprs heard list 2023-07-02 22:33:40 +03:00
sh123 dee32ec97f Refactor 2023-07-02 12:37:01 +03:00
sh123 09d231e118 Warning fixes 2023-07-02 11:12:53 +03:00
sh123 710afa0b82 Third party packet handling 2023-07-02 11:10:45 +03:00
sh123 45af1ff8c3 Comments 2023-07-01 15:40:25 +03:00
sh123 19d5d8adbd Refactor 2023-07-01 15:04:33 +03:00
sh123 93f98cc26b TX igate third party wrap logic 2023-07-01 14:59:41 +03:00
sh123 2db9c0154e Handle third party header with rx gating 2023-07-01 12:25:13 +03:00
sh123 55997a703e Own position and RX gate sending fixes 2023-06-30 21:19:26 +03:00
sh123 837dcc2e4c Additional rules for gating 2023-06-30 14:53:20 +03:00
sh123 8ecc9bafe5 Updated APRS device id 2023-06-30 14:19:18 +03:00
sh123 dea6304258 Additional guarding of APRS-IS with loopback transport 2023-06-30 08:46:35 +03:00
sh123 32d6b92960 Disable TX igate 2023-06-29 23:28:23 +03:00
sh123 a80af0fad9 Supply APRS-IS version from build config version name 2023-06-29 23:01:08 +03:00
sh123 2fe55655b8 Update version APRS-IS string 2023-06-29 22:51:14 +03:00
sh123 4bfc4532a9 Fix issue with received packets retransmission back to aprs-is 2023-06-29 22:43:02 +03:00
sh123 7588f6b0ba Fix issue with Bitmap.Config being null 2023-05-08 17:26:40 +03:00
sh123 d697657684 Merge branch 'master' of https://github.com/sh123/codec2_talkie 2023-05-08 08:30:59 +03:00
sh123 a59a908c88 Prevent crash when aprs symbol cannot be selected 2023-05-08 08:30:29 +03:00
sh123 77c307ca3c
Update README.md 2023-04-24 20:40:45 +03:00
sh123 2ed8a4074d Merge branch 'master' of https://github.com/sh123/codec2_talkie 2023-04-18 22:02:16 +03:00
sh123 a9ee325c47 Lora aprs text packets compatible mode 2023-04-18 22:01:58 +03:00
sh123 8bc35767fd
Update README.md 2023-03-17 20:41:40 +02:00
sh123 28146a9ff7
Update README.md 2023-03-17 20:40:23 +02:00
sh123 aca71d54d5
Update README.md 2023-03-17 20:38:38 +02:00
sh123 95bc0fb812
Update README.md 2023-03-17 20:38:05 +02:00
sh123 970ca74b92 Added modems image 2023-03-17 20:37:15 +02:00
sh123 2062e2ee19 More supported USB devices 2023-03-13 22:11:17 +02:00
sh123 084af64d50
Update README.md 2023-03-12 19:01:27 +02:00
sh123 6780839fa7 Merge branch 'master' of https://github.com/sh123/codec2_talkie 2023-03-12 18:27:44 +02:00
sh123 bb82360e3f Fix timeout 2023-03-12 18:22:38 +02:00
sh123 f6334c580f Added 1b4f:9203 2023-03-12 12:56:50 +02:00
sh123 169eaf1000
Update README.md 2022-12-21 18:14:10 +02:00
sh123 0c70d6353c
Update README.md 2022-12-21 18:10:28 +02:00
sh123 19eb6fcc09 Add ability to display modem voltage level 2022-12-17 23:58:44 +02:00
sh123 1b3beb9f04
Update README.md 2022-12-04 09:10:50 +02:00
sh123 bb0b7d0e89
Update README.md 2022-12-04 09:08:20 +02:00
sh123 12193f5f8e Fix BLE transmit 2022-12-03 23:49:23 +02:00
sh123 4e2b8bbc17
Update README.md 2022-09-25 10:29:25 +03:00
sh123 03be0feb2d Performance optimization 2022-09-24 16:48:03 +03:00
sh123 ad6aa8c0f8 Increase version 2022-09-23 23:02:36 +03:00
sh123 332f4627e1 Update matcher params 2022-09-12 18:44:28 +03:00
sh123 1995051f89 Do not treat telemetry message as IM 2022-09-12 18:35:35 +03:00
sh123 ec9dee33c7
Update README.md 2022-09-11 20:20:31 +03:00
sh123 93846cb976
Update README.md 2022-09-09 14:16:38 +03:00
sh123 59b309e4bb
Update README.md 2022-09-08 18:03:54 +03:00
sh123 74f782feb5 Add map option to show moving stations only 2022-09-07 21:17:02 +03:00
sh123 eba020cbfe Prefix OFDM HF AX25 APRS packets with packet length 2022-09-06 22:54:47 +03:00
sh123 ce069d92f2 Support for UTF-8 in comments 2022-09-06 18:55:52 +03:00
sh123 6b119b892d Refactoring 2022-09-05 18:04:46 +03:00
sh123 370ef3f2db Cleanup 2022-09-05 13:36:32 +03:00
sh123 69c09be2aa Refactor map stations into separate class 2022-09-05 13:32:19 +03:00
sh123 febe7a5fdb Refactor track into separate class 2022-09-05 13:17:32 +03:00
sh123 9ff42c834d Include digipath into map info window 2022-09-05 11:31:21 +03:00
sh123 64a8e18ce1 Add missing icon 2022-09-05 10:32:28 +03:00
sh123 c2768dbd76 Log cleanup improvements 2022-09-05 10:20:01 +03:00
sh123 ab54a49d00 Additional storage cache purge 2022-09-04 15:36:51 +03:00
sh123 7968612cf2 Show station track together with info window 2022-09-04 15:26:59 +03:00
sh123 4dcd4c5c4c Fixe bugs 2022-09-03 19:02:25 +03:00
sh123 f0aa85cefd Cleanup 2022-09-03 18:47:47 +03:00
sh123 0025b790b6 Upsert for position items 2022-09-03 18:24:43 +03:00
sh123 4530c58811 Run station upsert as single transaction 2022-09-03 18:16:49 +03:00
sh123 6428b8263b Contents update 2022-09-03 17:38:29 +03:00
sh123 024697ff04 Disable extensive logging 2022-09-03 17:22:28 +03:00
sh123 20aab8d253 Improve upsert 2022-09-03 17:17:59 +03:00
sh123 e0fd23d401 Station items cleanup 2022-09-03 16:10:53 +03:00
sh123 f6613bfb6e Database refactoring 2022-09-03 15:55:08 +03:00
sh123 48a9ac86d2 Indices 2022-09-03 15:41:39 +03:00
sh123 bbf4000579 Refactoring 2022-09-03 15:08:22 +03:00
sh123 5cee15769f Refactoring 2022-09-03 14:23:15 +03:00
sh123 b5ed6a011f Refactoring 2022-09-03 14:11:29 +03:00
sh123 df999f0e58 Refactoring 2022-09-03 12:28:47 +03:00
sh123 d463c2e767 Refactor 2022-09-03 12:15:04 +03:00
sh123 a47801fae6 Refactor 2022-09-03 12:11:03 +03:00
sh123 373fec8c91 Refactor 2022-09-03 12:06:35 +03:00
sh123 60bab818f0 Refactor log item group to station 2022-09-03 12:01:45 +03:00
sh123 ec2ac02d84 Tracks 2022-09-03 11:58:55 +03:00
sh123 90ddf20a7e Refactoring 2022-09-02 21:03:16 +03:00
sh123 30cda1b159 Remove excessive logging 2022-09-02 16:05:56 +03:00
sh123 4f92c99139 Rotate relevant APRS icons in the direction of movement 2022-09-02 16:05:20 +03:00
sh123 5951a7c0f6 Update item position insead of inserting if it has not been changed 2022-09-02 14:26:21 +03:00
sh123 6f09a0b0e6 Process station range information and add option to draw range circles 2022-09-01 19:18:31 +03:00
sh123 19c2937ced Add support for items 2022-08-31 22:07:30 +03:00
sh123 701fe71af5 Show objects on the map and optimize items drawing 2022-08-31 21:28:17 +03:00
sh123 fad1c6654c Show raw log line for stations, which do not have position 2022-08-30 22:05:31 +03:00
sh123 eef3a0c469 Fix bug with wrong status for stations without any positions 2022-08-30 21:58:55 +03:00
sh123 974e6cf9ee Increase version 2022-08-30 20:39:21 +03:00
sh123 83a87fa6e9 Improve log auto scrolling 2022-08-30 20:36:36 +03:00
sh123 d77f79f89a Comments, logging 2022-08-29 20:20:48 +03:00
sh123 a91a11a999 Switch to Marker API with better info window 2022-08-29 20:18:13 +03:00
sh123 50450ac0af Add option to rotate aprs map with compass 2022-08-28 16:45:55 +03:00
sh123 09b38e65f7 Add option to clean aprs log older than hours 2022-08-28 14:59:23 +03:00
sh123 4ce3820baa Increase version 2022-08-28 10:24:17 +03:00
sh123 b67d298587
Update README.md 2022-08-27 18:44:59 +03:00
sh123 0470cc317a Increase version 2022-08-27 16:13:27 +03:00
sh123 47782cb1dc Clear tile cache menu item 2022-08-27 16:07:36 +03:00
sh123 1c25d65d59 Improve map 2022-08-27 15:54:47 +03:00
sh123 9d0f06a6af Draw text under icon 2022-08-27 11:10:46 +03:00
sh123 742ca8184d Own icon 2022-08-26 18:34:01 +03:00
sh123 6d01acbc0f Icon overlay 2022-08-26 17:43:15 +03:00
sh123 9669142de8 Overlays 2022-08-26 16:57:12 +03:00
sh123 3c2bdb6128 Added map view 2022-08-26 16:03:51 +03:00
sh123 424e3c87fa Addtional permissions and osmdroid dependency 2022-08-26 15:34:56 +03:00
sh123 9656016a29 Update some of libraries 2022-08-26 15:25:01 +03:00
sh123 067ff7318b Clear buffers on flush 2022-08-25 20:41:26 +03:00
527 zmienionych plików z 144364 dodań i 647 usunięć

38
README.md vendored
Wyświetl plik

@ -2,11 +2,15 @@
![APK](https://img.shields.io/endpoint?url=https://apt.izzysoft.de/fdroid/api/v1/shield/com.radio.codec2talkie)
# Introduction
**Turn your Android phone into real Amateur Radio HF/VHF/UHF APRS enabled Codec2 DV (digital voice) and/or FreeDV handheld transceiver.**
**Turn your Android phone into real Amateur Radio HF/VHF/UHF APRS enabled Codec2/OPUS DV (digital voice) and/or FreeDV handheld transceiver.**
**Requires additional hardware (e.g. AFSK/LoRa), software (e.g. Direwolf) radio modem or analog transceiver with USB audio + VOX/USB CAT PTT control, such as MCHF or ICOM**
For more information visit project [Wiki](https://github.com/sh123/codec2_talkie/wiki)
For more information about FreeDV and Codec2 visit https://github.com/drowe67/codec2
For more information about OPUS codec visit https://opus-codec.org/
For detailed information about project [Wiki](https://github.com/sh123/codec2_talkie/wiki)
![alt text](images/diagram.png)
@ -16,14 +20,20 @@ For more information visit project [Wiki](https://github.com/sh123/codec2_talkie
# Short Description
What you can do with this app:
- Voice communication:
- Send and receive voice over FreeDV modes
- Send and receive Codec2 voice over KISS
- Send and receive Codec2 voice encapsulated into APRS UI frames
- Send and receive Codec2 voice over FreeDV modes
- Send and receive Codec2/OPUS voice over KISS
- Send and receive Codec2/OPUS voice encapsulated into APRS UI frames
- Data communication
- Send and receive APRS position reports over FSK 300 (HF, TX only), AFSK1200 (VHF), FreeDV OFDM (HF)
- Use APRS over FSK 300 (HF, TX only), AFSK1200 (VHF), FreeDV OFDM (HF) or APRS-IS (Internet)
- Send and receive APRS position reports
- Send and receive APRS messages
- APRS log with raw APRS data
- APRS station hub with stations grouped by callsign and their log
- APRS map with ability to see station info, station track with information about each geo position
- Use application as APRS digirepeater
- Use application as APRS-IS RX/TX iGate
- Use application as APRS-IS RX/TX iGate to forward packets to/from APRS-IS (Internet)
- Use application as APRS-IS internet tracker to send/receive APRS data over Internet
- Send and receive text packets in lora-aprs format over KISS
- Integrate with hardware/software
- Use it with your KISS Bluetooth/BLE/USB/TCPIP hardware modem, such as LoRa/FSK/AFSK/etc, control its parameters by using "set hardware" KISS command
- Use it with KISS software modem using TCPIP, such as Direwolf
@ -35,8 +45,6 @@ What you can do with this app:
# Requirements
- Android 7.0 (API 24) or higher
- Application could also be used with your Android network radio, such as Inrico TM-7, apk just needs to be installed over USB, see [Discussion](https://github.com/sh123/codec2_talkie/issues/4)
- Android 5.0, 5.1, 6.0 (API 21, 22, 23)
- Separate apk package is released with "legacy" suffix from legacy branch
- Modem, radio module or transceiver which supports [KISS protocol](https://en.wikipedia.org/wiki/KISS_(TNC)) or can process KISS or raw Codec2 audio frames over serial Bluetooth, BLE, USB or TCP/IP
- Analog transceiver with built-in or external USB audio adapter and VOX or USB CAT PTT control (such as MCHF or iCom IC-7x00 series)
@ -44,12 +52,16 @@ What you can do with this app:
- Source code is integrated into this project for easier building and customization:
- Codec2 codec: https://github.com/drowe67/codec2
- Android Codec2 wrapper code: https://github.com/UstadMobile/Codec2-Android
- OPUS codec: https://opus-codec.org
- Fetched with gradle as dependency:
- Android USB serial: https://github.com/mik3y/usb-serial-for-android
# Other similar or related projects
- Hardware
- ESP32 LoRa APRS modem (used with this application for testing): https://github.com/sh123/esp32_loraprs
- ESP32 LoRa DV transceiver: https://github.com/sh123/esp32_loradv
- Arduno Micro KISS modem and APRS AX.25 digirepeater: https://github.com/sh123/micro_loraprs
- ESP32 Arduino Codec2 library (ESP32 i2s walkie talkie example interoperable with this application): https://github.com/sh123/esp32_codec2_arduino
- Minimal Arduino LoRa KISS modem: https://github.com/sh123/lora_arduino_kiss_modem
- Minimal Arduino NRF24 KISS modem: https://github.com/sh123/nrf24l01_arduino_kiss_modem
- Software:
@ -60,3 +72,11 @@ What you can do with this app:
- Codec2 iOS wrapper: https://github.com/Beartooth/codec2-ios
- Other interesting projects:
- LoRa mesh text GPS communicator: https://github.com/meshtastic/Meshtastic-device
# Prototypes used with this application
- ESP32 LoRa Bluetooth headless APRS modem (no screen and external controls): https://github.com/sh123/esp32_loraprs
- ESP32 LoRa DV handheld transceiver (with screen and controls): https://github.com/sh123/esp32_loradv
- Arduno Micro USB KISS modem (no screen and external controls): https://github.com/sh123/micro_loraprs
![alt text](images/modems.png)

Wyświetl plik

@ -3,6 +3,7 @@ plugins {
}
android {
android.ndkVersion "21.4.7075529"
compileSdkVersion 30
buildToolsVersion "30.0.2"
@ -10,8 +11,8 @@ android {
applicationId "com.radio.codec2talkie"
minSdkVersion 23
targetSdkVersion 30
versionCode 142
versionName "1.42"
versionCode 172
versionName "1.72"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
@ -31,6 +32,7 @@ android {
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation project(":libcodec2-android")
implementation project(":libopus-android")
implementation 'androidx.preference:preference:1.2.0'
implementation 'androidx.appcompat:appcompat:1.2.0'
@ -38,12 +40,15 @@ dependencies {
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'com.github.mik3y:usb-serial-for-android:3.4.3'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'androidx.recyclerview:recyclerview:1.2.0'
implementation 'androidx.recyclerview:recyclerview:1.2.1'
implementation 'androidx.lifecycle:lifecycle-livedata:2.5.1'
testImplementation 'junit:junit:4.13.1'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
implementation "androidx.room:room-runtime:2.4.2"
annotationProcessor "androidx.room:room-compiler:2.4.2"
implementation "androidx.room:room-runtime:2.4.3"
annotationProcessor "androidx.room:room-compiler:2.4.3"
implementation "org.osmdroid:osmdroid-android:6.1.13"
}

Wyświetl plik

@ -13,12 +13,15 @@
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-feature android:name="android.hardware.bluetooth_le"
android:required="true" />
<application
android:allowBackup="true"
android:fullBackupOnly="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
@ -56,6 +59,9 @@
<activity
android:name=".storage.message.MessageItemActivity"
android:configChanges="orientation|screenSize" />
<activity
android:name=".maps.MapActivity"
android:configChanges="orientation|screenSize" />
<activity
android:name=".MainActivity"
android:configChanges="orientation|screenSize">

Wyświetl plik

@ -53,6 +53,7 @@ import com.radio.codec2talkie.connect.BleConnectActivity;
import com.radio.codec2talkie.connect.BluetoothConnectActivity;
import com.radio.codec2talkie.connect.BluetoothSocketHandler;
import com.radio.codec2talkie.connect.TcpIpConnectActivity;
import com.radio.codec2talkie.maps.MapActivity;
import com.radio.codec2talkie.settings.SettingsWrapper;
import com.radio.codec2talkie.storage.log.LogItemActivity;
import com.radio.codec2talkie.protocol.ProtocolFactory;
@ -88,7 +89,8 @@ public class MainActivity extends AppCompatActivity implements ServiceConnection
Manifest.permission.BLUETOOTH_ADMIN,
Manifest.permission.RECORD_AUDIO,
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.WRITE_EXTERNAL_STORAGE
};
private AppService _appService;
@ -100,6 +102,7 @@ public class MainActivity extends AppCompatActivity implements ServiceConnection
private TextView _textConnInfo;
private TextView _textStatus;
private TextView _textTelemetry;
private TextView _textCodecMode;
private TextView _textRssi;
private ProgressBar _progressAudioLevel;
@ -131,6 +134,7 @@ public class MainActivity extends AppCompatActivity implements ServiceConnection
_textConnInfo = findViewById(R.id.textBtName);
_textStatus = findViewById(R.id.textStatus);
_textTelemetry = findViewById(R.id.textTelemetry);
_textRssi = findViewById(R.id.textRssi);
// UV bar
@ -378,6 +382,9 @@ public class MainActivity extends AppCompatActivity implements ServiceConnection
_logViewActivityLauncher.launch(new Intent(this, LogItemActivity.class));
}
protected void startMapViewActivity() {
_logViewActivityLauncher.launch(new Intent(this, MapActivity.class));
}
private final ActivityResultLauncher<Intent> _settingsActivityLauncher = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(), result -> restartApplication());
@ -450,8 +457,6 @@ public class MainActivity extends AppCompatActivity implements ServiceConnection
}
private void updateStatusText(ProtocolFactory.ProtocolType protocolType) {
String codec2ModeName = _sharedPreferences.getString(PreferenceKeys.CODEC2_MODE, getResources().getStringArray(R.array.codec2_modes)[0]);
// protocol
String status = "";
@ -484,12 +489,18 @@ public class MainActivity extends AppCompatActivity implements ServiceConnection
status += getString(R.string.voax25_label);
}
// Lora aprs text packets
boolean textPacketsEnabled = SettingsWrapper.isTextPacketsEnabled(_sharedPreferences);
if (textPacketsEnabled) {
status += getString(R.string.text_packets_label);
}
// Digirepeater
boolean isDigirepeaterEnabled = _sharedPreferences.getBoolean(PreferenceKeys.AX25_DIGIREPEATER_ENABLED, false);
if (isDigirepeaterEnabled) {
status += getString(R.string.digirepeater_label);
}
// APRSIS
boolean aprsisEnabled = SettingsWrapper.isAprsIsEnabled(_sharedPreferences);
if (aprsisEnabled) {
status += getString(R.string.aprsis_label);
@ -505,7 +516,7 @@ public class MainActivity extends AppCompatActivity implements ServiceConnection
status = status.length() == 0 ? protocolType.toString() : protocolType.toString() + " " + status;
String statusLine = AudioTools.getSpeedStatusText(codec2ModeName, _sharedPreferences) + ", " + status;
String statusLine = AudioTools.getSpeedStatusText(_sharedPreferences, getResources()) + ", " + status;
_textCodecMode.setText(statusLine);
}
@ -604,6 +615,10 @@ public class MainActivity extends AppCompatActivity implements ServiceConnection
startLogViewActivity();
return true;
}
else if (itemId == R.id.aprs_map) {
startMapViewActivity();
return true;
}
return super.onOptionsItemSelected(item);
}
@ -803,6 +818,12 @@ public class MainActivity extends AppCompatActivity implements ServiceConnection
_progressRssi.setProgress(msg.arg1 - S_METER_S0_VALUE_DB);
}
break;
case EV_TELEMETRY:
if (msg.arg1 > 0) {
// NOTE, reuse status indicator for voltage
_textTelemetry.setText(String.format(Locale.getDefault(), "%2.2fV", (double)msg.arg1 / 100.0));
}
break;
// same progress bar is reused for rx and tx levels
case EV_RX_LEVEL:
case EV_TX_LEVEL:

Wyświetl plik

@ -18,16 +18,17 @@ public enum AppMessage {
EV_RX_ERROR(13),
EV_TX_ERROR(14),
EV_RX_RADIO_LEVEL(15),
EV_STARTED_TRACKING(16),
EV_STOPPED_TRACKING(17),
EV_TELEMETRY(16),
EV_STARTED_TRACKING(17),
EV_STOPPED_TRACKING(18),
// commands
CMD_SEND_LOCATION_TO_TNC(18),
CMD_PROCESS(19),
CMD_QUIT(20),
CMD_START_TRACKING(21),
CMD_STOP_TRACKING(22),
CMD_SEND_SINGLE_TRACKING(23),
CMD_SEND_MESSAGE(24);
CMD_SEND_LOCATION_TO_TNC(19),
CMD_PROCESS(20),
CMD_QUIT(21),
CMD_START_TRACKING(22),
CMD_STOP_TRACKING(23),
CMD_SEND_SINGLE_TRACKING(24),
CMD_SEND_MESSAGE(25);
private final int _value;

Wyświetl plik

@ -21,7 +21,7 @@ import java.io.IOException;
import java.util.Timer;
import java.util.TimerTask;
import com.radio.codec2talkie.R;
import com.radio.codec2talkie.protocol.aprs.tools.AprsIsData;
import com.radio.codec2talkie.protocol.message.TextMessage;
import com.radio.codec2talkie.storage.log.LogItem;
import com.radio.codec2talkie.storage.log.LogItemRepository;
@ -32,6 +32,7 @@ import com.radio.codec2talkie.protocol.position.Position;
import com.radio.codec2talkie.settings.PreferenceKeys;
import com.radio.codec2talkie.storage.message.MessageItemRepository;
import com.radio.codec2talkie.storage.position.PositionItemRepository;
import com.radio.codec2talkie.storage.station.StationItemRepository;
import com.radio.codec2talkie.tools.AudioTools;
import com.radio.codec2talkie.transport.Transport;
import com.radio.codec2talkie.transport.TransportFactory;
@ -44,7 +45,7 @@ public class AppWorker extends Thread {
private static final int AUDIO_MAX_LEVEL = 0;
private static final int AUDIO_SAMPLE_SIZE = 8000;
private static final int PROCESS_INTERVAL_MS = 20;
private static final int PROCESS_INTERVAL_MS = 10;
private static final int LISTEN_AFTER_MS = 1500;
private boolean _needTransmission = false;
@ -53,8 +54,6 @@ public class AppWorker extends Thread {
private final Protocol _protocol;
private final Transport _transport;
private final int _codec2Mode;
// input data, bt -> audio
private AudioTrack _systemAudioPlayer;
@ -74,6 +73,7 @@ public class AppWorker extends Thread {
private final LogItemRepository _logItemRepository;
private final MessageItemRepository _messageItemRepository;
private final PositionItemRepository _positionItemRepository;
private final StationItemRepository _stationItemRepository;
private final Context _context;
private final SharedPreferences _sharedPreferences;
@ -85,15 +85,13 @@ public class AppWorker extends Thread {
_context = context;
_sharedPreferences = PreferenceManager.getDefaultSharedPreferences(_context);
String codec2ModeName = _sharedPreferences.getString(PreferenceKeys.CODEC2_MODE, _context.getResources().getStringArray(R.array.codec2_modes)[0]);
_codec2Mode = AudioTools.extractCodec2ModeId(codec2ModeName);
_logItemRepository = new LogItemRepository((Application)context);
_messageItemRepository = new MessageItemRepository((Application)context);
_positionItemRepository = new PositionItemRepository((Application)context);
_stationItemRepository = new StationItemRepository((Application)context);
_transport = TransportFactory.create(transportType, context);
_protocol = ProtocolFactory.create(_codec2Mode, context);
_protocol = ProtocolFactory.create(context);
_processPeriodicTimer = new Timer();
@ -234,6 +232,12 @@ public class AppWorker extends Thread {
_onWorkerStateChanged.sendMessage(msg);
}
private void sendTelemetryUpdate(int batVoltage) {
Message msg = Message.obtain();
msg.what = AppMessage.EV_TELEMETRY.toInt();
msg.arg1 = batVoltage;
_onWorkerStateChanged.sendMessage(msg);
}
private void sendRxAudioLevelUpdate(short [] pcmAudioSamples) {
Message msg = Message.obtain();
msg.what = AppMessage.EV_RX_LEVEL.toInt();
@ -250,17 +254,18 @@ public class AppWorker extends Thread {
private void recordAndSendAudioFrame() throws IOException {
_systemAudioRecorder.read(_recordAudioBuffer, 0, _recordAudioBuffer.length);
_protocol.sendPcmAudio(null, null, _codec2Mode, _recordAudioBuffer);
_protocol.sendPcmAudio(null, null, _recordAudioBuffer);
}
private final ProtocolCallback _protocolCallback = new ProtocolCallback() {
@Override
protected void onReceivePosition(Position position) {
Log.i(TAG, String.format("Position received: %s, lat: %f, lon: %f, course: %f, speed: %f, alt: %f, sym: %s, status: %s, comment: %s",
position.maidenHead, position.latitude, position.longitude,
Log.i(TAG, String.format("Position received: %s→%s, %s, lat: %f, lon: %f, course: %f, speed: %f, alt: %f, sym: %s, range: %.2f, status: %s, comment: %s",
position.srcCallsign, position.dstCallsign, position.maidenHead, position.latitude, position.longitude,
position.bearingDegrees, position.speedMetersPerSecond, position.altitudeMeters,
position.symbolCode, position.status, position.comment));
_positionItemRepository.insertPositionItem(position.toPositionItem(false));
position.symbolCode, position.rangeMiles, position.status, position.comment));
_positionItemRepository.upsertPositionItem(position.toPositionItem(false));
_stationItemRepository.upsertStationItem(position.toStationItem());
String note = (position.srcCallsign == null ? "UNK" : position.srcCallsign) + "→" +
(position.dstCallsign == null ? "UNK" : position.dstCallsign);
@ -268,7 +273,7 @@ public class AppWorker extends Thread {
}
@Override
protected void onReceivePcmAudio(String src, String dst, int codec, short[] pcmFrame) {
protected void onReceivePcmAudio(String src, String dst, short[] pcmFrame) {
String note = (src == null ? "UNK" : src) + "→" + (dst == null ? "UNK" : dst);
sendStatusUpdate(AppMessage.EV_VOICE_RECEIVED, note);
sendRxAudioLevelUpdate(pcmFrame);
@ -279,7 +284,7 @@ public class AppWorker extends Thread {
}
@Override
protected void onReceiveCompressedAudio(String src, String dst, int codec2Mode, byte[] audioFrame) {
protected void onReceiveCompressedAudio(String src, String dst, byte[] audioFrame) {
throw new UnsupportedOperationException();
}
@ -288,7 +293,11 @@ public class AppWorker extends Thread {
String note = (textMessage.src == null ? "UNK" : textMessage.src) + "→" +
(textMessage.dst == null ? "UNK" : textMessage.dst);
sendStatusUpdate(AppMessage.EV_TEXT_MESSAGE_RECEIVED, note + ": " + textMessage.text);
_messageItemRepository.insertMessageItem(textMessage.toMessageItem(false));
if (textMessage.isAutoReply()) {
// TODO, acknowledge or reject message with the given (src, dst, ackId)
} else {
_messageItemRepository.insertMessageItem(textMessage.toMessageItem(false));
}
Log.i(TAG, "message received: " + textMessage.text);
}
@ -303,6 +312,11 @@ public class AppWorker extends Thread {
sendRxRadioLevelUpdate(rssi, snr);
}
@Override
protected void onReceiveTelemetry(int batVoltage) {
sendTelemetryUpdate(batVoltage);
}
@Override
protected void onReceiveLog(String logData) {
Log.i(TAG, "RX-LOG: " + logData);
@ -310,14 +324,14 @@ public class AppWorker extends Thread {
}
@Override
protected void onTransmitPcmAudio(String src, String dst, int codec, short[] frame) {
protected void onTransmitPcmAudio(String src, String dst, short[] frame) {
String note = (src == null ? "UNK" : src) + "→" + (dst == null ? "UNK" : dst);
sendStatusUpdate(AppMessage.EV_TRANSMITTED_VOICE, note);
sendTxAudioLevelUpdate(frame);
}
@Override
protected void onTransmitCompressedAudio(String src, String dst, int codec, byte[] frame) {
protected void onTransmitCompressedAudio(String src, String dst, byte[] frame) {
throw new UnsupportedOperationException();
}
@ -326,12 +340,15 @@ public class AppWorker extends Thread {
String note = (textMessage.src == null ? "UNK" : textMessage.src) + "→" +
(textMessage.dst == null ? "UNK" : textMessage.dst);
sendStatusUpdate(AppMessage.EV_TEXT_MESSAGE_TRANSMITTED, note);
_messageItemRepository.insertMessageItem(textMessage.toMessageItem(true));
if (!textMessage.isAutoReply()) {
_messageItemRepository.insertMessageItem(textMessage.toMessageItem(true));
}
}
@Override
protected void onTransmitPosition(Position position) {
_positionItemRepository.insertPositionItem(position.toPositionItem(true));
_positionItemRepository.upsertPositionItem(position.toPositionItem(true));
_stationItemRepository.upsertStationItem(position.toStationItem());
}
@Override
@ -359,16 +376,25 @@ public class AppWorker extends Thread {
}
};
void storeLogData(String logData, boolean isTransmit) {
// TODO, parse through aprs data
String[] callsignData = logData.split(">");
if (callsignData.length >= 2) {
private void storeLogData(String logData, boolean isTransmit) {
AprsIsData aprsIsData = AprsIsData.fromString(logData);
if (aprsIsData != null) {
LogItem logItem = new LogItem();
logItem.setTimestampEpoch(System.currentTimeMillis());
logItem.setSrcCallsign(callsignData[0]);
logItem.setLogLine(logData);
logItem.setIsTransmit(isTransmit);
logItem.setSrcCallsign(aprsIsData.src);
logItem.setLogLine(logData);
_logItemRepository.insertLogItem(logItem);
_stationItemRepository.upsertStationItem(logItem.toStationItem());
if (aprsIsData.hasThirdParty()) {
LogItem logItemThirdParty = new LogItem();
logItemThirdParty.setTimestampEpoch(System.currentTimeMillis());
logItemThirdParty.setIsTransmit(isTransmit);
logItemThirdParty.setSrcCallsign(aprsIsData.thirdParty.src);
logItemThirdParty.setLogLine(aprsIsData.thirdParty.convertToString(true));
_logItemRepository.insertLogItem(logItemThirdParty);
_stationItemRepository.upsertStationItem(logItemThirdParty.toStationItem());
}
}
}
@ -425,10 +451,14 @@ public class AppWorker extends Thread {
private void cleanup() {
Log.i(TAG, "cleanup() started");
_systemAudioRecorder.stop();
try {
_systemAudioRecorder.stop();
} catch (IllegalStateException ignored) {}
_systemAudioRecorder.release();
_systemAudioPlayer.stop();
try {
_systemAudioPlayer.stop();
} catch (IllegalStateException ignored) {}
_systemAudioPlayer.release();
try {

Wyświetl plik

@ -26,6 +26,8 @@ public class BleGattWrapper extends BluetoothGattCallback {
public static final UUID BT_KISS_CHARACTERISTIC_RX_UUID = UUID.fromString("00000003-ba2a-46c9-ae49-01b0961f68bb");
public static final UUID BT_NOTIFY_UUID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
private static final int DefaultMtuSize = 16;
private BluetoothGatt _gatt;
private BluetoothGattCharacteristic _rxCharacteristic;
private BluetoothGattCharacteristic _txCharacteristic;
@ -63,9 +65,7 @@ public class BleGattWrapper extends BluetoothGattCallback {
synchronized (_readBuffer) {
// nothing to read
if (_readBuffer.position() == 0) return 0;
_readBuffer.flip();
int countRead = 0;
try {
for (int i = 0; i < data.length; i++) {
@ -85,27 +85,34 @@ public class BleGattWrapper extends BluetoothGattCallback {
public int write(byte[] data) throws IOException {
if (!_isConnected) throw new IOException();
synchronized (_writeBuffer) {
_writeBuffer.put(data);
_writeBuffer.flip();
byte[] arr = new byte[_writeBuffer.limit()];
_writeBuffer.get(arr);
_txCharacteristic.setValue(arr);
if (_gatt.writeCharacteristic(_txCharacteristic)) {
// written successfully
_writeBuffer.clear();
} else {
// redo
_writeBuffer.position(_writeBuffer.limit());
_writeBuffer.limit(_writeBuffer.capacity());
}
writeCharacteristic(_txCharacteristic);
return data.length;
}
}
private void writeCharacteristic(BluetoothGattCharacteristic characteristic) {
_writeBuffer.flip();
int cntRemaining = _writeBuffer.remaining();
if (cntRemaining > 0) {
int cntWrite = Math.min(cntRemaining, DefaultMtuSize);
byte[] arr = new byte[cntWrite];
_writeBuffer.get(arr);
characteristic.setValue(arr);
if (!_gatt.writeCharacteristic(characteristic)) {
Log.d(TAG, "GATT write delayed");
_writeBuffer.position(_writeBuffer.position() - cntWrite);
}
}
_writeBuffer.compact();
}
@Override
public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
super.onMtuChanged(gatt, mtu, status);
}
@Override
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
super.onCharacteristicRead(gatt, characteristic, status);
@ -129,18 +136,12 @@ public class BleGattWrapper extends BluetoothGattCallback {
@Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
super.onCharacteristicWrite(gatt, characteristic, status);
if (status == BluetoothGatt.GATT_SUCCESS && _writeBuffer.position() > 0) {
if (status == BluetoothGatt.GATT_SUCCESS) {
synchronized (_writeBuffer) {
_writeBuffer.flip();
byte[] arr = new byte[_writeBuffer.limit()];
_writeBuffer.get(arr);
characteristic.setValue(arr);
_gatt.writeCharacteristic(characteristic);
_writeBuffer.clear();
writeCharacteristic(_txCharacteristic);
}
} else {
Log.e(TAG, "GATT write failure " + status);
}
}

Wyświetl plik

@ -22,7 +22,9 @@ import androidx.appcompat.app.AppCompatActivity;
import androidx.preference.PreferenceManager;
import com.hoho.android.usbserial.driver.CdcAcmSerialDriver;
import com.hoho.android.usbserial.driver.Ch34xSerialDriver;
import com.hoho.android.usbserial.driver.Cp21xxSerialDriver;
import com.hoho.android.usbserial.driver.FtdiSerialDriver;
import com.hoho.android.usbserial.driver.ProbeTable;
import com.hoho.android.usbserial.driver.UsbSerialDriver;
import com.hoho.android.usbserial.driver.UsbSerialPort;
@ -92,13 +94,33 @@ public class UsbConnectActivity extends AppCompatActivity {
private UsbSerialProber getCustomProber() {
ProbeTable customTable = new ProbeTable();
// Spark Fun
customTable.addProduct(0x1b4f, 0x9203, CdcAcmSerialDriver.class);
customTable.addProduct(0x1b4f, 0x9204, CdcAcmSerialDriver.class);
// Arduino Due
customTable.addProduct(0x2341, 0x003d, CdcAcmSerialDriver.class);
// Arduino Uno/Nano (CH34x)
customTable.addProduct(0x1a86, 0x5523, Ch34xSerialDriver.class);
customTable.addProduct(0x1a86, 0x7523, Ch34xSerialDriver.class);
// STM, MCHF
customTable.addProduct(0x0483, 0x5732, CdcAcmSerialDriver.class);
// CP2102/2109, iCom
customTable.addProduct(0x10c4, 0xea60, Cp21xxSerialDriver.class);
customTable.addProduct(0x10c4, 0xea70, Cp21xxSerialDriver.class);
customTable.addProduct(0x10c4, 0xea71, Cp21xxSerialDriver.class);
// FTDI
customTable.addProduct(0x0403, 0x6001, FtdiSerialDriver.class);
customTable.addProduct(0x0403, 0x6010, FtdiSerialDriver.class);
customTable.addProduct(0x0403, 0x6011, FtdiSerialDriver.class);
customTable.addProduct(0x0403, 0x6014, FtdiSerialDriver.class);
customTable.addProduct(0x0403, 0x6015, FtdiSerialDriver.class);
// Raspberry PI Pico
customTable.addProduct(0x2e8a, 0x0004, CdcAcmSerialDriver.class);
customTable.addProduct(0x2e8a, 0x0005, CdcAcmSerialDriver.class);
customTable.addProduct(0x2e8a, 0x000a, CdcAcmSerialDriver.class);
customTable.addProduct(0x2e8a, 0x000b, CdcAcmSerialDriver.class);
customTable.addProduct(0x2e8a, 0x000c, CdcAcmSerialDriver.class);
customTable.addProduct(0x2e8a, 0x000d, CdcAcmSerialDriver.class);
customTable.addProduct(0x2e8a, 0x000e, CdcAcmSerialDriver.class);
return new UsbSerialProber(customTable);
}

Wyświetl plik

@ -0,0 +1,256 @@
package com.radio.codec2talkie.maps;
import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Rect;
import android.hardware.SensorEvent;
import android.location.Location;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.preference.PreferenceManager;
import com.radio.codec2talkie.BuildConfig;
import com.radio.codec2talkie.R;
import com.radio.codec2talkie.protocol.Aprs;
import com.radio.codec2talkie.protocol.aprs.tools.AprsSymbolTable;
import com.radio.codec2talkie.settings.PreferenceKeys;
import com.radio.codec2talkie.tools.UnitTools;
import org.osmdroid.api.IMapController;
import org.osmdroid.config.Configuration;
import org.osmdroid.tileprovider.modules.SqlTileWriter;
import org.osmdroid.tileprovider.tilesource.TileSourceFactory;
import org.osmdroid.views.MapView;
import org.osmdroid.views.overlay.compass.CompassOverlay;
import org.osmdroid.views.overlay.compass.InternalCompassOrientationProvider;
import org.osmdroid.views.overlay.mylocation.GpsMyLocationProvider;
import org.osmdroid.views.overlay.mylocation.IMyLocationProvider;
import org.osmdroid.views.overlay.mylocation.MyLocationNewOverlay;
import java.util.Locale;
public class MapActivity extends AppCompatActivity {
private static final String TAG = MapActivity.class.getSimpleName();
private static final double MAP_STARTUP_ZOOM = 5.0;
private static final double MAP_FOLLOW_ZOOM = 14.0;
private MapView _mapView;
private IMapController _mapController;
private MyLocationNewOverlay _myLocationNewOverlay;
private MapStations _mapStations;
private boolean _rotateMap = false;
private boolean _shouldFollowLocation = false;
private String _positionInfo;
private double _prevBearing = 0.0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setTitle(R.string.menu_aprs_map);
setContentView(R.layout.activity_map_view);
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) actionBar.setDisplayHomeAsUpEnabled(true);
Context context = getApplicationContext();
Configuration.getInstance().setUserAgentValue(Aprs.APRS_ID + " " + BuildConfig.VERSION_NAME);
// map
_mapView = findViewById(R.id.map);
_mapView.setTileSource(TileSourceFactory.MAPNIK);
_mapView.setMultiTouchControls(true);
// controller
_mapController = _mapView.getController();
_mapController.zoomTo(MAP_STARTUP_ZOOM);
// compass
InternalCompassOrientationProvider compassOrientationProvider = new InternalCompassOrientationProvider(context) {
@Override
public void onSensorChanged(SensorEvent sensorEvent) {
if (_rotateMap) {
_mapView.setMapOrientation(-sensorEvent.values[0]);
}
super.onSensorChanged(sensorEvent);
}
};
CompassOverlay compassOverlay = new CompassOverlay(context, compassOrientationProvider, _mapView);
compassOverlay.enableCompass();
_mapView.getOverlays().add(compassOverlay);
// my location
_myLocationNewOverlay = new MyLocationNewOverlay(new GpsMyLocationProvider(context), _mapView) {
@Override
public void onLocationChanged(Location location, IMyLocationProvider source) {
super.onLocationChanged(location, source);
_positionInfo = String.format(Locale.US, "%dkm/h, %d°, %dm, %s, %f, %f",
UnitTools.metersPerSecondToKilometersPerHour((int)location.getSpeed()),
(int)location.getBearing(),
(int)location.getAltitude(),
UnitTools.decimalToMaidenhead(location.getLatitude(), location.getLongitude()),
location.getLatitude(),
location.getLongitude()
);
double currentBearing = location.getBearing();
if (_prevBearing > 180 && currentBearing <= 180)
updateMyIcon(false);
else if (_prevBearing <= 180 && currentBearing > 180)
updateMyIcon(true);
_prevBearing = currentBearing;
}
@Override
public void draw(Canvas pCanvas, MapView pMapView, boolean pShadow) {
super.draw(pCanvas, pMapView, pShadow);
if (_positionInfo == null || !_shouldFollowLocation) return;
// create paint
Paint paint = new Paint();
paint.setStyle(Paint.Style.FILL);
paint.setTextSize(24);
// query bounds from text
Rect bounds = new Rect();
paint.getTextBounds(_positionInfo, 0, _positionInfo.length(), bounds);
// draw background
paint.setColor(Color.WHITE);
paint.setAlpha(200);
pCanvas.drawRect(pCanvas.getWidth() - bounds.width(),
0,
pCanvas.getWidth(),
bounds.height(),
paint);
// draw text
paint.setColor(Color.BLACK);
paint.setAlpha(255);
paint.setFlags(Paint.ANTI_ALIAS_FLAG);
pCanvas.drawText(_positionInfo, pCanvas.getWidth() - bounds.width(), bounds.height(), paint);
}
};
// set own icon
updateMyIcon(false);
// my location overlay
_myLocationNewOverlay.enableMyLocation();
_myLocationNewOverlay.runOnFirstFix(() -> runOnUiThread(() -> {
_mapController.setCenter(_myLocationNewOverlay.getMyLocation());
_mapController.animateTo(_myLocationNewOverlay.getMyLocation());
}));
_mapView.getOverlays().add(_myLocationNewOverlay);
// stations
_mapStations = new MapStations(context, _mapView, this);
}
public void updateMyIcon(boolean shouldFlip) {
Context context = getApplicationContext();
Configuration.getInstance().setUserAgentValue(Aprs.APRS_ID + " " + BuildConfig.VERSION_NAME);
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
// my symbol
AprsSymbolTable aprsSymbolTable = AprsSymbolTable.getInstance(context);
String mySymbolCode = sharedPreferences.getString(PreferenceKeys.APRS_SYMBOL, "/[");
Bitmap myBitmapIcon = aprsSymbolTable.bitmapFromSymbol(mySymbolCode, true);
if (AprsSymbolTable.needsRotation(mySymbolCode)) {
Matrix matrix = new Matrix();
matrix.postRotate(-90);
if (shouldFlip) {
matrix.postScale(-1, 1, myBitmapIcon.getWidth() / 2f, myBitmapIcon.getHeight() / 2f);
}
myBitmapIcon = Bitmap.createBitmap(myBitmapIcon, 0, 0, myBitmapIcon.getWidth(), myBitmapIcon.getHeight(), matrix, true);
}
_myLocationNewOverlay.setDirectionIcon(myBitmapIcon);
_myLocationNewOverlay.setPersonIcon(myBitmapIcon);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.map_menu, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int itemId = item.getItemId();
if (itemId == android.R.id.home) {
finish();
return true;
} else if (itemId == R.id.map_menu_clear_cache) {
new Thread(() -> {
_mapView.getTileProvider().clearTileCache();
SqlTileWriter sqlTileWriter = new SqlTileWriter();
boolean isCleared = sqlTileWriter.purgeCache(_mapView.getTileProvider().getTileSource().name());
if (isCleared)
Log.i(TAG, "Cleanup completed");
else
Log.e(TAG, "Cache was not cleared");
}).start();
return true;
} else if (itemId == R.id.map_menu_rotate_map) {
if (item.isChecked()) {
item.setChecked(false);
_rotateMap = false;
_mapView.setMapOrientation(0);
} else {
item.setChecked(true);
_rotateMap = true;
}
return true;
} else if (itemId == R.id.map_menu_show_range) {
boolean showCircles = false;
if (item.isChecked()) {
item.setChecked(false);
} else {
item.setChecked(true);
showCircles = true;
}
_mapStations.showRangeCircles(showCircles);
return true;
} else if (itemId == R.id.map_menu_show_moving) {
boolean showMoving = false;
if (item.isChecked()) {
item.setChecked(false);
} else {
item.setChecked(true);
showMoving = true;
}
_mapStations.showMovingStations(showMoving);
return true;
} else if (itemId == R.id.map_menu_move_map) {
if (item.isChecked()) {
item.setChecked(false);
_shouldFollowLocation = false;
_myLocationNewOverlay.disableFollowLocation();
} else {
item.setChecked(true);
_shouldFollowLocation = true;
_myLocationNewOverlay.enableFollowLocation();
_mapController.zoomTo(MAP_FOLLOW_ZOOM);
}
return true;
}
return super.onOptionsItemSelected(item);
}
}

Wyświetl plik

@ -0,0 +1,215 @@
package com.radio.codec2talkie.maps;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.util.DisplayMetrics;
import android.util.Log;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.ViewModelProvider;
import androidx.lifecycle.ViewModelStoreOwner;
import com.radio.codec2talkie.R;
import com.radio.codec2talkie.protocol.aprs.tools.AprsSymbolTable;
import com.radio.codec2talkie.protocol.position.Position;
import com.radio.codec2talkie.storage.position.PositionItemViewModel;
import com.radio.codec2talkie.storage.station.StationItem;
import com.radio.codec2talkie.storage.station.StationItemViewModel;
import com.radio.codec2talkie.tools.DateTools;
import com.radio.codec2talkie.tools.UnitTools;
import org.osmdroid.util.GeoPoint;
import org.osmdroid.views.MapView;
import org.osmdroid.views.overlay.Marker;
import org.osmdroid.views.overlay.Polygon;
import org.osmdroid.views.overlay.infowindow.MarkerInfoWindow;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
public class MapStations {
private static final String TAG = MapTrack.class.getSimpleName();
private final Context _context;
private final AprsSymbolTable _aprsSymbolTable;
private final PositionItemViewModel _positionItemViewModel;
private final ViewModelStoreOwner _owner;
private final MapView _mapView;
private final MarkerInfoWindow _infoWindow;
private LiveData<List<StationItem>> _stationItemLiveData;
private final StationItemViewModel _stationItemViewModel;
private final HashMap<String, Marker> _objectOverlayItems = new HashMap<>();
private final HashMap<String, Polygon> _objectOverlayRangeCircles = new HashMap<>();
private boolean _showCircles = false;
private boolean _showMoving = false;
private final MapTrack _activeTrack;
public MapStations(Context context, MapView mapView, ViewModelStoreOwner owner) {
_context = context;
_owner = owner;
_mapView = mapView;
_positionItemViewModel = new ViewModelProvider(_owner).get(PositionItemViewModel.class);
_aprsSymbolTable = AprsSymbolTable.getInstance(context);
_infoWindow = new MarkerInfoWindow(R.layout.bonuspack_bubble, _mapView);
_activeTrack = new MapTrack(_context, _mapView, _owner);
_stationItemViewModel = new ViewModelProvider(_owner).get(StationItemViewModel.class);
loadStations(_showMoving);
}
private void loadStations(boolean movingOnly) {
if (_stationItemLiveData != null)
_stationItemLiveData.removeObservers((LifecycleOwner) _owner);
removePositionMarkers();
_stationItemLiveData = _stationItemViewModel.getAllStationItems(movingOnly);
// FIXME, room livedata sends all list if one item changed event with distinctUntilChanged
_stationItemLiveData.observe((LifecycleOwner) _owner, allStations -> {
Log.i(TAG, "add stations " + allStations.size());
for (StationItem station : allStations) {
//Log.i(TAG, "new position " + station.getSrcCallsign() + ">" +
// station.getDstCallsign() + " " + station.getLatitude() + " " + station.getLongitude());
// do not add items without coordinate
if (station.getMaidenHead() == null) continue;
if (addStationPositionIcon(station)) {
addRangeCircle(station);
}
}
});
}
private void removePositionMarkers() {
for (Marker marker : _objectOverlayItems.values()) {
marker.remove(_mapView);
}
_objectOverlayItems.clear();
for (Polygon circle : _objectOverlayRangeCircles.values()) {
_mapView.getOverlays().remove(circle);
}
_objectOverlayRangeCircles.clear();
}
public void showMovingStations(boolean isMoving) {
_showMoving = isMoving;
loadStations(_showMoving);
}
public void showRangeCircles(boolean isVisible) {
_showCircles = isVisible;
for (Polygon polygon : _objectOverlayRangeCircles.values()) {
polygon.setVisible(isVisible);
}
}
private void addRangeCircle(StationItem group) {
if (group.getRangeMiles() == 0) return;
String callsign = group.getSrcCallsign();
Polygon polygon = null;
if (_objectOverlayRangeCircles.containsKey(callsign)) {
polygon = _objectOverlayRangeCircles.get(callsign);
assert polygon != null;
}
if (polygon == null) {
polygon = new Polygon();
polygon.setVisible(_showCircles);
Paint p = polygon.getOutlinePaint();
p.setStrokeWidth(1);
_mapView.getOverlayManager().add(0, polygon);
_objectOverlayRangeCircles.put(callsign, polygon);
}
ArrayList<GeoPoint> circlePoints = new ArrayList<>();
for (float f = 0; f < 360; f += 6) {
circlePoints.add(new GeoPoint(group.getLatitude(), group.getLongitude()).destinationPoint(1000 * UnitTools.milesToKilometers(group.getRangeMiles()), f));
}
polygon.setPoints(circlePoints);
}
private boolean addStationPositionIcon(StationItem group) {
String callsign = group.getSrcCallsign();
Marker marker = null;
String newTitle = DateTools.epochToIso8601(group.getTimestampEpoch()) + " " + callsign;
String newSnippet = getStatus(group);
// find old marker
if (_objectOverlayItems.containsKey(callsign)) {
marker = _objectOverlayItems.get(callsign);
assert marker != null;
// skip if unchanged
GeoPoint oldPosition = marker.getPosition();
if (oldPosition.getLatitude() == group.getLatitude() &&
oldPosition.getLongitude() == group.getLongitude() &&
marker.getTitle().equals(newTitle) &&
marker.getSnippet().equals(newSnippet)) {
return false;
}
}
// create new marker
if (marker == null) {
Bitmap bitmapInfoIcon = _aprsSymbolTable.bitmapFromSymbol(group.getSymbolCode(), true);
if (bitmapInfoIcon == null) return false;
// add marker
BitmapDrawable drawableText = group.drawLabelWithIcon(_context, 12);
BitmapDrawable drawableInfoIcon = new BitmapDrawable(_context.getResources(), bitmapInfoIcon);
marker = new Marker(_mapView);
marker.setId(callsign);
if (drawableText == null)
Log.e(TAG, "Cannot load icon for " + callsign);
else
marker.setIcon(drawableText);
marker.setImage(drawableInfoIcon);
marker.setOnMarkerClickListener((monitoredStationMarker, mapView) -> {
GeoPoint markerPoint = monitoredStationMarker.getPosition();
_infoWindow.open(monitoredStationMarker, new GeoPoint(markerPoint.getLatitude(), markerPoint.getLongitude()), 0, -64);
_activeTrack.drawForStationMarker(monitoredStationMarker);
return false;
});
_mapView.getOverlays().add(marker);
_objectOverlayItems.put(callsign, marker);
}
marker.setPosition(new GeoPoint(group.getLatitude(), group.getLongitude()));
marker.setTitle(newTitle);
marker.setSnippet(newSnippet);
return true;
}
private String getStatus(StationItem station) {
double range = UnitTools.milesToKilometers(station.getRangeMiles());
return String.format(Locale.US, "%s %s<br>%s %f %f<br>%03d° %03dkm/h %04dm %.2fkm<br>%s %s",
station.getDstCallsign(),
station.getDigipath(),
station.getMaidenHead(), station.getLatitude(), station.getLongitude(),
(int)station.getBearingDegrees(),
UnitTools.metersPerSecondToKilometersPerHour((int)station.getSpeedMetersPerSecond()),
(int)station.getAltitudeMeters(),
range == 0 ? UnitTools.milesToKilometers(Position.DEFAULT_RANGE_MILES): range,
station.getStatus(),
station.getComment());
}
}

Wyświetl plik

@ -0,0 +1,133 @@
package com.radio.codec2talkie.maps;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.DashPathEffect;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.util.DisplayMetrics;
import android.util.Log;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.ViewModelProvider;
import androidx.lifecycle.ViewModelStoreOwner;
import com.radio.codec2talkie.protocol.position.Position;
import com.radio.codec2talkie.storage.position.PositionItem;
import com.radio.codec2talkie.storage.position.PositionItemViewModel;
import com.radio.codec2talkie.storage.station.StationItem;
import com.radio.codec2talkie.tools.BitmapTools;
import com.radio.codec2talkie.tools.DateTools;
import com.radio.codec2talkie.tools.UnitTools;
import org.osmdroid.util.GeoPoint;
import org.osmdroid.views.MapView;
import org.osmdroid.views.overlay.Marker;
import org.osmdroid.views.overlay.Polyline;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
public class MapTrack {
private static final String TAG = MapTrack.class.getSimpleName();
private final PositionItemViewModel _positionItemViewModel;
private final ViewModelStoreOwner _owner;
private final MapView _mapView;
private final Context _context;
// track db data
private LiveData<List<PositionItem>> _activeTrackLiveData;
// track data
private final HashSet<Long> _activeTrackTimestamps = new HashSet<>();
private final List<GeoPoint> _activeTrackPoints = new ArrayList<>();
private final Polyline _activeTrackLine = new Polyline();
// track points
private final HashSet<Marker> _trackMarkers = new HashSet<>();
public MapTrack(Context context, MapView mapView, ViewModelStoreOwner owner) {
_owner = owner;
_mapView = mapView;
_context = context;
_positionItemViewModel = new ViewModelProvider(_owner).get(PositionItemViewModel.class);
// initialize track
Paint p = _activeTrackLine.getOutlinePaint();
p.setStrokeWidth(8);
p.setColor(Color.RED);
p.setStyle(Paint.Style.STROKE);
p.setPathEffect(new DashPathEffect(new float[] {10f, 10f}, 0f));
_mapView.getOverlayManager().add(_activeTrackLine);
}
public void drawForStationMarker(Marker marker) {
if (_activeTrackLiveData != null)
_activeTrackLiveData.removeObservers((LifecycleOwner) _owner);
for (Marker trackMarker : _trackMarkers) {
_mapView.getOverlays().remove(trackMarker);
}
_mapView.getOverlays().remove(_activeTrackLine);
_activeTrackPoints.clear();
_activeTrackTimestamps.clear();
_trackMarkers.clear();
_activeTrackLine.setPoints(_activeTrackPoints);
_activeTrackLine.setVisible(false);
_mapView.getOverlays().add(_activeTrackLine);
// FIXME, room livedata sends all list if one item changed event with distinctUntilChanged
_activeTrackLiveData = _positionItemViewModel.getPositionItems(marker.getId());
_activeTrackLiveData.observe((LifecycleOwner) _owner, this::addTrack);
}
private void addTrack(List<PositionItem> positions) {
boolean shouldSet = false;
for (PositionItem trackPoint : positions) {
if (!_activeTrackTimestamps.contains(trackPoint.getTimestampEpoch())) {
long pointTimestamp = trackPoint.getTimestampEpoch();
Log.i(TAG, "addPoint " + trackPoint.getTimestampEpoch() + " " + trackPoint.getLatitude() + " " + trackPoint.getLongitude());
// add point into the line
GeoPoint point = new GeoPoint(trackPoint.getLatitude(), trackPoint.getLongitude());
_activeTrackPoints.add(point);
_activeTrackTimestamps.add(pointTimestamp);
if (_activeTrackPoints.size() > 1)
_activeTrackLine.setVisible(true);
// draw point marker with time
Marker marker = new Marker(_mapView);
marker.setIcon(BitmapTools.drawLabel(_context, DateTools.epochToIso8601Time(pointTimestamp), 11));
marker.setTitle(DateTools.epochToIso8601(pointTimestamp) + " " + trackPoint.getSrcCallsign());
marker.setSnippet(getStatus(trackPoint));
marker.setPosition(point);
_trackMarkers.add(marker);
_mapView.getOverlays().add(marker);
shouldSet = true;
}
}
if (shouldSet)
_activeTrackLine.setPoints(_activeTrackPoints);
}
private String getStatus(PositionItem position) {
return String.format(Locale.US, "%s<br>%s %f %f<br>%03d° %03dkm/h %04dm<br>%s",
position.getDigipath(),
position.getMaidenHead(), position.getLatitude(), position.getLongitude(),
(int)position.getBearingDegrees(),
UnitTools.metersPerSecondToKilometersPerHour((int)position.getSpeedMetersPerSecond()),
(int)position.getAltitudeMeters(),
position.getComment());
}
}

Wyświetl plik

@ -20,6 +20,8 @@ import com.radio.codec2talkie.transport.Transport;
import java.io.IOException;
public class Aprs implements Protocol {
public static final String APRS_ID = "APCTLK";
private static final String TAG = Aprs.class.getSimpleName();
private final Protocol _childProtocol;
@ -55,7 +57,7 @@ public class Aprs implements Protocol {
_srcCallsign = AX25Callsign.formatCallsign(
sharedPreferences.getString(PreferenceKeys.AX25_CALLSIGN, "NOCALL").toUpperCase(),
sharedPreferences.getString(PreferenceKeys.AX25_SSID, "0"));
_dstCallsign = "APZMDM";
_dstCallsign = APRS_ID;
_symbolCode = sharedPreferences.getString(PreferenceKeys.APRS_SYMBOL, "/[");
String packetFormat = sharedPreferences.getString(PreferenceKeys.APRS_LOCATION_PACKET_FORMAT, "uncompressed");
@ -78,8 +80,8 @@ public class Aprs implements Protocol {
}
@Override
public void sendCompressedAudio(String src, String dst, int codec2Mode, byte[] frame) throws IOException {
_childProtocol.sendCompressedAudio(src, dst, codec2Mode, frame);
public void sendCompressedAudio(String src, String dst, byte[] frame) throws IOException {
_childProtocol.sendCompressedAudio(src, dst, frame);
}
@Override
@ -99,11 +101,11 @@ public class Aprs implements Protocol {
}
@Override
public void sendPcmAudio(String src, String dst, int codec2Mode, short[] pcmFrame) throws IOException {
public void sendPcmAudio(String src, String dst, short[] pcmFrame) throws IOException {
if (_isVoax25Enabled) {
_childProtocol.sendPcmAudio(src == null ? _srcCallsign : src, dst == null ? _dstCallsign : dst, codec2Mode, pcmFrame);
_childProtocol.sendPcmAudio(src == null ? _srcCallsign : src, dst == null ? _dstCallsign : dst, pcmFrame);
} else {
_childProtocol.sendPcmAudio(src, dst, codec2Mode, pcmFrame);
_childProtocol.sendPcmAudio(src, dst, pcmFrame);
}
}
@ -124,14 +126,14 @@ public class Aprs implements Protocol {
}
@Override
protected void onReceivePcmAudio(String src, String dst, int codec, short[] pcmFrame) {
protected void onReceivePcmAudio(String src, String dst, short[] pcmFrame) {
String dstCallsign = new AprsCallsign(dst).isSoftware() ? "*" : dst;
_parentProtocolCallback.onReceivePcmAudio(src, dstCallsign, codec, pcmFrame);
_parentProtocolCallback.onReceivePcmAudio(src, dstCallsign, pcmFrame);
}
@Override
protected void onReceiveCompressedAudio(String src, String dst, int codec2Mode, byte[] audioFrame) {
_parentProtocolCallback.onReceiveCompressedAudio(src, dst, codec2Mode, audioFrame);
protected void onReceiveCompressedAudio(String src, String dst, byte[] audioFrame) {
_parentProtocolCallback.onReceiveCompressedAudio(src, dst, audioFrame);
}
@Override
@ -142,14 +144,13 @@ public class Aprs implements Protocol {
@Override
protected void onReceiveData(String src, String dst, String path, byte[] data) {
if (data.length == 0) return;
AprsDataType dataType = new AprsDataType((char)data[0]);
AprsData aprsData = AprsDataFactory.fromBinary(src, dst, path, data);
if (aprsData != null && aprsData.isValid()) {
if (dataType.isTextMessage()) {
if (aprsData.isTextMessage()) {
TextMessage textMessage = aprsData.toTextMessage();
_parentProtocolCallback.onReceiveTextMessage(textMessage);
return;
} else if (dataType.isPositionReport()) {
} else if (aprsData.isPositionReport()) {
Position position = aprsData.toPosition();
if (position != null) {
_parentProtocolCallback.onReceivePosition(position);
@ -165,20 +166,25 @@ public class Aprs implements Protocol {
_parentProtocolCallback.onReceiveSignalLevel(rssi, snr);
}
@Override
protected void onReceiveTelemetry(int batVoltage) {
_parentProtocolCallback.onReceiveTelemetry(batVoltage);
}
@Override
protected void onReceiveLog(String logData) {
_parentProtocolCallback.onReceiveLog(logData);
}
@Override
protected void onTransmitPcmAudio(String src, String dst, int codec, short[] frame) {
protected void onTransmitPcmAudio(String src, String dst, short[] frame) {
String dstCallsign = new AprsCallsign(dst).isSoftware() ? "*" : dst;
_parentProtocolCallback.onTransmitPcmAudio(src, dstCallsign, codec, frame);
_parentProtocolCallback.onTransmitPcmAudio(src, dstCallsign, frame);
}
@Override
protected void onTransmitCompressedAudio(String src, String dst, int codec, byte[] frame) {
_parentProtocolCallback.onTransmitCompressedAudio(src, dst, codec, frame);
protected void onTransmitCompressedAudio(String src, String dst, byte[] frame) {
_parentProtocolCallback.onTransmitCompressedAudio(src, dst, frame);
}
@Override

Wyświetl plik

@ -4,18 +4,20 @@ import android.content.Context;
import android.content.SharedPreferences;
import android.os.Looper;
import android.util.Log;
import android.util.Xml;
import android.widget.Toast;
import androidx.preference.PreferenceManager;
import com.radio.codec2talkie.MainActivity;
import com.radio.codec2talkie.BuildConfig;
import com.radio.codec2talkie.R;
import com.radio.codec2talkie.protocol.aprs.AprsCallsign;
import com.radio.codec2talkie.protocol.aprs.tools.AprsHeardList;
import com.radio.codec2talkie.protocol.aprs.tools.AprsIsData;
import com.radio.codec2talkie.protocol.ax25.AX25Callsign;
import com.radio.codec2talkie.protocol.message.TextMessage;
import com.radio.codec2talkie.protocol.position.Position;
import com.radio.codec2talkie.settings.PreferenceKeys;
import com.radio.codec2talkie.settings.SettingsWrapper;
import com.radio.codec2talkie.tools.DebugTools;
import com.radio.codec2talkie.tools.TextTools;
import com.radio.codec2talkie.transport.TcpIp;
@ -27,8 +29,8 @@ import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.BufferOverflowException;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.Locale;
import kotlin.text.MatchGroup;
import kotlin.text.MatchResult;
@ -38,7 +40,8 @@ public class AprsIs implements Protocol, Runnable {
private static final String TAG = AprsIs.class.getSimpleName();
private static final int APRSIS_RETRY_WAIT_MS = 10000;
private static final int APRSIS_DEFAULT_PORT = 14580;
private static final int HEARD_LIST_DURATION_SECONDS = 60;
private final Protocol _childProtocol;
private Context _context;
@ -50,23 +53,28 @@ public class AprsIs implements Protocol, Runnable {
private boolean _isSelfEnabled;
private boolean _isRxGateEnabled;
private boolean _isTxGateEnabled;
private boolean _isLoopbackTransport;
private String _callsign;
private String _digipath;
private String _ssid;
private int _filterRadius;
private String _filter;
private int _defaultPort;
private final ByteBuffer _rxQueue;
private final ByteBuffer _txQueue;
private final ByteBuffer _fromAprsIsQueue;
private final ByteBuffer _toAprsIsQueue;
private final byte[] _rxBuf;
private final AprsHeardList _rfHeardList = new AprsHeardList(HEARD_LIST_DURATION_SECONDS);
protected boolean _isRunning = true;
private boolean _isConnected = false;
public AprsIs(Protocol childProtocol) {
_childProtocol = childProtocol;
_rxQueue = ByteBuffer.allocate(4096);
_txQueue = ByteBuffer.allocate(4096);
_fromAprsIsQueue = ByteBuffer.allocate(4096);
_toAprsIsQueue = ByteBuffer.allocate(4096);
_rxBuf = new byte[4096];
}
@ -80,12 +88,15 @@ public class AprsIs implements Protocol, Runnable {
_isRxGateEnabled = sharedPreferences.getBoolean(PreferenceKeys.APRS_IS_ENABLE_RX_GATE, false);
_isTxGateEnabled = sharedPreferences.getBoolean(PreferenceKeys.APRS_IS_ENABLE_TX_GATE, false);
_isSelfEnabled = sharedPreferences.getBoolean(PreferenceKeys.APRS_IS_ENABLE_SELF, false);
_callsign = sharedPreferences.getString(PreferenceKeys.AX25_CALLSIGN, "N0CALL");
_callsign = sharedPreferences.getString(PreferenceKeys.AX25_CALLSIGN, "N0CALL").toUpperCase(Locale.ROOT);
_digipath = sharedPreferences.getString(PreferenceKeys.AX25_DIGIPATH, "").toUpperCase();
_ssid = sharedPreferences.getString(PreferenceKeys.AX25_SSID, "0");
_passcode = sharedPreferences.getString(PreferenceKeys.APRS_IS_CODE, "");
_server = sharedPreferences.getString(PreferenceKeys.APRS_IS_TCPIP_SERVER, "euro.aprs2.net");
_filterRadius = Integer.parseInt(sharedPreferences.getString(PreferenceKeys.APRS_IS_RADIUS, "10"));
_filter = sharedPreferences.getString(PreferenceKeys.APRS_IS_FILTER, "");
_isLoopbackTransport = SettingsWrapper.isLoopbackTransport(sharedPreferences);
_defaultPort = Integer.parseInt(sharedPreferences.getString(PreferenceKeys.APRS_IS_TCPIP_SERVER_PORT, "14580"));
Log.i(TAG, "AprsIs RX gate: " + _isTxGateEnabled + ", TX gate: " + _isTxGateEnabled + ", server: " + _server);
@ -98,8 +109,8 @@ public class AprsIs implements Protocol, Runnable {
}
@Override
public void sendCompressedAudio(String src, String dst, int codec2Mode, byte[] frame) throws IOException {
_childProtocol.sendCompressedAudio(src, dst, codec2Mode, frame);
public void sendCompressedAudio(String src, String dst, byte[] frame) throws IOException {
_childProtocol.sendCompressedAudio(src, dst, frame);
}
@Override
@ -108,34 +119,51 @@ public class AprsIs implements Protocol, Runnable {
}
@Override
public void sendPcmAudio(String src, String dst, int codec2Mode, short[] pcmFrame) throws IOException {
_childProtocol.sendPcmAudio(src, dst, codec2Mode, pcmFrame);
public void sendPcmAudio(String src, String dst, short[] pcmFrame) throws IOException {
_childProtocol.sendPcmAudio(src, dst, pcmFrame);
}
@Override
public void sendData(String src, String dst, String path, byte[] data) throws IOException {
if (_isSelfEnabled) {
AprsIsData aprsIsData = new AprsIsData(src, dst, path, new String(data));
synchronized (_txQueue) {
_txQueue.put(aprsIsData.toString().getBytes());
synchronized (_toAprsIsQueue) {
String rawData = aprsIsData.convertToString(false) + "\n";
_toAprsIsQueue.put(rawData.getBytes());
}
}
_childProtocol.sendData(src, dst, path, data);
}
private boolean isEligibleForTxGate(AprsIsData aprsIsData) {
AprsCallsign aprsCallsign = new AprsCallsign(aprsIsData.src);
return _isTxGateEnabled &&
aprsCallsign.isValid &&
!_isLoopbackTransport &&
!_rfHeardList.contains(aprsIsData.src) &&
aprsIsData.isEligibleForTxGate();
}
private byte[] thirdPartyWrap(AprsIsData aprsIsData) {
// wrap into third party, https://aprs-is.net/IGateDetails.aspx
aprsIsData.digipath = "TCPIP," + _callsign + "*";
String txData = "}" + aprsIsData.convertToString(false);
return txData.getBytes();
}
@Override
public boolean receive() throws IOException {
String line;
synchronized (_rxQueue) {
line = TextTools.getString(_rxQueue);
synchronized (_fromAprsIsQueue) {
line = TextTools.getString(_fromAprsIsQueue);
}
if (line.length() > 0) {
Log.d(TAG, "APRS-RX: " + DebugTools.bytesToDebugString(line.getBytes()));
AprsIsData aprsIsData = AprsIsData.fromString(line);
if (aprsIsData != null) {
_parentProtocolCallback.onReceiveData(aprsIsData.src, aprsIsData.dst, aprsIsData.path, aprsIsData.data.getBytes());
if (_isTxGateEnabled && new AprsCallsign(aprsIsData.src).isValid) {
sendData(aprsIsData.src, aprsIsData.dst, aprsIsData.path, aprsIsData.data.getBytes());
_parentProtocolCallback.onReceiveData(aprsIsData.src, aprsIsData.dst, aprsIsData.rawDigipath, aprsIsData.data.getBytes());
if (isEligibleForTxGate(aprsIsData)) {
_childProtocol.sendData(new AX25Callsign(_callsign, _ssid).toString(), Aprs.APRS_ID, _digipath, thirdPartyWrap(aprsIsData));
}
}
_parentProtocolCallback.onReceiveLog(line);
@ -150,13 +178,13 @@ public class AprsIs implements Protocol, Runnable {
}
@Override
protected void onReceivePcmAudio(String src, String dst, int codec, short[] pcmFrame) {
_parentProtocolCallback.onReceivePcmAudio(src, dst, codec, pcmFrame);
protected void onReceivePcmAudio(String src, String dst, short[] pcmFrame) {
_parentProtocolCallback.onReceivePcmAudio(src, dst, pcmFrame);
}
@Override
protected void onReceiveCompressedAudio(String src, String dst, int codec2Mode, byte[] audioFrame) {
_parentProtocolCallback.onReceiveCompressedAudio(src, dst, codec2Mode, audioFrame);
protected void onReceiveCompressedAudio(String src, String dst, byte[] audioFrame) {
_parentProtocolCallback.onReceiveCompressedAudio(src, dst, audioFrame);
}
@Override
@ -166,10 +194,19 @@ public class AprsIs implements Protocol, Runnable {
@Override
protected void onReceiveData(String src, String dst, String path, byte[] data) {
if (_isRxGateEnabled) {
_rfHeardList.add(src);
if (_isRxGateEnabled && !_isLoopbackTransport) {
// NOTE, https://aprs-is.net/IGateDetails.aspx
AprsIsData aprsIsData = new AprsIsData(src, dst, path, new String(data));
synchronized (_txQueue) {
_txQueue.put(aprsIsData.toString().getBytes());
if (aprsIsData.isEligibleForRxGate()) {
// strip "rf header" for third party packets before gating
if (aprsIsData.hasThirdParty()) {
aprsIsData = aprsIsData.thirdParty;
}
String rawData = aprsIsData.convertToString(false) + "\n";
synchronized (_toAprsIsQueue) {
_toAprsIsQueue.put(rawData.getBytes());
}
}
}
_parentProtocolCallback.onReceiveData(src, dst, path, data);
@ -180,19 +217,24 @@ public class AprsIs implements Protocol, Runnable {
_parentProtocolCallback.onReceiveSignalLevel(rssi, snr);
}
@Override
protected void onReceiveTelemetry(int batVoltage) {
_parentProtocolCallback.onReceiveTelemetry(batVoltage);
}
@Override
protected void onReceiveLog(String logData) {
_parentProtocolCallback.onReceiveLog(logData);
}
@Override
protected void onTransmitPcmAudio(String src, String dst, int codec, short[] frame) {
_parentProtocolCallback.onTransmitPcmAudio(src, dst, codec, frame);
protected void onTransmitPcmAudio(String src, String dst, short[] frame) {
_parentProtocolCallback.onTransmitPcmAudio(src, dst, frame);
}
@Override
protected void onTransmitCompressedAudio(String src, String dst, int codec, byte[] frame) {
_parentProtocolCallback.onTransmitCompressedAudio(src, dst, codec, frame);
protected void onTransmitCompressedAudio(String src, String dst, byte[] frame) {
_parentProtocolCallback.onTransmitCompressedAudio(src, dst, frame);
}
@Override
@ -271,7 +313,7 @@ public class AprsIs implements Protocol, Runnable {
}
private String getLoginCommand() {
String cmd = "user " + _callsign + "-" + _ssid + " pass " + _passcode + " vers " + "C2T 1.0";
String cmd = "user " + new AX25Callsign(_callsign, _ssid).toString() + " pass " + _passcode + " vers " + Aprs.APRS_ID + " " + BuildConfig.VERSION_NAME;
if (_filterRadius > 0) {
cmd += " filter m/" + _filterRadius;
}
@ -288,7 +330,7 @@ public class AprsIs implements Protocol, Runnable {
private TcpIp runConnect() {
Socket socket = new Socket();
try {
socket.connect(new InetSocketAddress(_server, APRSIS_DEFAULT_PORT));
socket.connect(new InetSocketAddress(_server, _defaultPort));
TcpIp tcpIp = new TcpIp(socket, "aprsis");
String loginCmd = getLoginCommand();
Log.i(TAG, "Login command " + loginCmd);
@ -312,11 +354,12 @@ public class AprsIs implements Protocol, Runnable {
}
private void runWrite(TcpIp tcpIp) {
synchronized (_txQueue) {
String line = TextTools.getString(_txQueue);
synchronized (_toAprsIsQueue) {
String line = TextTools.getString(_toAprsIsQueue);
if (line.length() > 0) {
Log.d(TAG, "APRS-IS TX: " + DebugTools.bytesToDebugString(line.getBytes()));
try {
line += "\n";
tcpIp.write(line.getBytes());
} catch (IOException e) {
Log.w(TAG, "Lost connection on transmit");
@ -368,12 +411,12 @@ public class AprsIs implements Protocol, Runnable {
}
// data
} else {
synchronized (_rxQueue) {
synchronized (_fromAprsIsQueue) {
try {
_rxQueue.put(Arrays.copyOf(_rxBuf, bytesRead));
_fromAprsIsQueue.put(Arrays.copyOf(_rxBuf, bytesRead));
} catch (BufferOverflowException e) {
e.printStackTrace();
_rxQueue.clear();
_fromAprsIsQueue.clear();
}
}
}

Wyświetl plik

@ -1,9 +1,13 @@
package com.radio.codec2talkie.protocol;
import android.content.Context;
import android.content.SharedPreferences;
import com.radio.codec2talkie.R;
import com.radio.codec2talkie.protocol.message.TextMessage;
import com.radio.codec2talkie.protocol.position.Position;
import com.radio.codec2talkie.settings.PreferenceKeys;
import com.radio.codec2talkie.tools.AudioTools;
import com.radio.codec2talkie.transport.Transport;
import com.ustadmobile.codec2.Codec2;
@ -19,18 +23,23 @@ public class AudioCodec2 implements Protocol {
private char[] _recordAudioEncodedBuffer;
private short[] _playbackAudioBuffer;
private final SharedPreferences _sharedPreferences;
private ProtocolCallback _parentProtocolCallback;
public AudioCodec2(Protocol childProtocol, int codec2ModeId) {
public AudioCodec2(Protocol childProtocol, SharedPreferences sharedPreferences) {
_childProtocol = childProtocol;
_codec2Con = 0;
constructCodec2(codec2ModeId);
_sharedPreferences = sharedPreferences;
}
@Override
public void initialize(Transport transport, Context context, ProtocolCallback protocolCallback) throws IOException {
_parentProtocolCallback = protocolCallback;
_childProtocol.initialize(transport, context, _protocolCallback);
String codec2ModeName = _sharedPreferences.getString(PreferenceKeys.CODEC2_MODE, context.getResources().getStringArray(R.array.codec2_modes)[0]);
int codec2ModeId = AudioTools.extractCodec2ModeId(codec2ModeName);
constructCodec2(codec2ModeId);
}
@Override
@ -39,8 +48,8 @@ public class AudioCodec2 implements Protocol {
}
@Override
public void sendCompressedAudio(String src, String dst, int codec2Mode, byte[] frame) throws IOException {
_childProtocol.sendCompressedAudio(src, dst, codec2Mode, frame);
public void sendCompressedAudio(String src, String dst, byte[] frame) throws IOException {
_childProtocol.sendCompressedAudio(src, dst, frame);
}
@Override
@ -49,8 +58,8 @@ public class AudioCodec2 implements Protocol {
}
@Override
public void sendPcmAudio(String src, String dst, int codec2Mode, short[] pcmFrame) throws IOException {
_parentProtocolCallback.onTransmitPcmAudio(src, dst, codec2Mode, pcmFrame);
public void sendPcmAudio(String src, String dst, short[] pcmFrame) throws IOException {
_parentProtocolCallback.onTransmitPcmAudio(src, dst, pcmFrame);
Codec2.encode(_codec2Con, pcmFrame, _recordAudioEncodedBuffer);
@ -59,7 +68,7 @@ public class AudioCodec2 implements Protocol {
for (int i = 0; i < _recordAudioEncodedBuffer.length; i++) {
frame[i] = (byte)_recordAudioEncodedBuffer[i];
}
_childProtocol.sendCompressedAudio(src, dst, codec2Mode, frame);
_childProtocol.sendCompressedAudio(src, dst, frame);
}
@Override
@ -79,14 +88,14 @@ public class AudioCodec2 implements Protocol {
}
@Override
protected void onReceivePcmAudio(String src, String dst, int codec, short[] pcmFrame) {
_parentProtocolCallback.onReceivePcmAudio(src, dst, codec, pcmFrame);
protected void onReceivePcmAudio(String src, String dst, short[] pcmFrame) {
_parentProtocolCallback.onReceivePcmAudio(src, dst, pcmFrame);
}
@Override
protected void onReceiveCompressedAudio(String src, String dst, int codec2Mode, byte[] audioFrame) {
protected void onReceiveCompressedAudio(String src, String dst, byte[] audioFrame) {
Codec2.decode(_codec2Con, _playbackAudioBuffer, audioFrame);
_parentProtocolCallback.onReceivePcmAudio(src, dst, codec2Mode, _playbackAudioBuffer);
_parentProtocolCallback.onReceivePcmAudio(src, dst, _playbackAudioBuffer);
}
@Override
@ -104,19 +113,24 @@ public class AudioCodec2 implements Protocol {
_parentProtocolCallback.onReceiveSignalLevel(rssi, snr);
}
@Override
protected void onReceiveTelemetry(int batVoltage) {
_parentProtocolCallback.onReceiveTelemetry(batVoltage);
}
@Override
protected void onReceiveLog(String logData) {
_parentProtocolCallback.onReceiveLog(logData);
}
@Override
protected void onTransmitPcmAudio(String src, String dst, int codec, short[] frame) {
_parentProtocolCallback.onTransmitPcmAudio(src, dst, codec, frame);
protected void onTransmitPcmAudio(String src, String dst, short[] frame) {
_parentProtocolCallback.onTransmitPcmAudio(src, dst, frame);
}
@Override
protected void onTransmitCompressedAudio(String src, String dst, int codec, byte[] frame) {
_parentProtocolCallback.onTransmitCompressedAudio(src, dst, codec, frame);
protected void onTransmitCompressedAudio(String src, String dst, byte[] frame) {
_parentProtocolCallback.onTransmitCompressedAudio(src, dst, frame);
}
@Override

Wyświetl plik

@ -6,18 +6,20 @@ import android.util.Log;
import androidx.preference.PreferenceManager;
import com.radio.codec2talkie.R;
import com.radio.codec2talkie.protocol.message.TextMessage;
import com.radio.codec2talkie.protocol.position.Position;
import com.radio.codec2talkie.settings.PreferenceKeys;
import com.radio.codec2talkie.tools.AudioTools;
import com.radio.codec2talkie.transport.Transport;
import com.ustadmobile.codec2.Codec2;
import java.io.IOException;
import java.util.Arrays;
public class AudioFrameAggregator implements Protocol {
public class AudioCodec2FrameAggregator implements Protocol {
private static final String TAG = AudioFrameAggregator.class.getSimpleName();
private static final String TAG = AudioCodec2FrameAggregator.class.getSimpleName();
private final Protocol _childProtocol;
@ -25,19 +27,17 @@ public class AudioFrameAggregator implements Protocol {
private int _outputBufferPos;
private byte[] _outputBuffer;
private final int _codec2FrameSize;
private final int _codec2ModeId;
private int _codec2FrameSize;
private String _lastSrc;
private String _lastDst;
private int _lastCodec2Mode;
private final SharedPreferences _sharedPreferences;
private ProtocolCallback _parentProtocolCallback;
public AudioFrameAggregator(Protocol childProtocol, int codec2ModeId) {
public AudioCodec2FrameAggregator(Protocol childProtocol, SharedPreferences sharedPreferences) {
_childProtocol = childProtocol;
_codec2ModeId = codec2ModeId;
_codec2FrameSize = getPcmAudioBufferSize(codec2ModeId);
_sharedPreferences = sharedPreferences;
}
@Override
@ -48,6 +48,10 @@ public class AudioFrameAggregator implements Protocol {
_outputBuffer = new byte[_outputBufferSize];
_outputBufferPos = 0;
_childProtocol.initialize(transport, context, _protocolCallback);
String codec2ModeName = _sharedPreferences.getString(PreferenceKeys.CODEC2_MODE, context.getResources().getStringArray(R.array.codec2_modes)[0]);
int codec2ModeId = AudioTools.extractCodec2ModeId(codec2ModeName);
_codec2FrameSize = getPcmAudioBufferSize(codec2ModeId);
}
@Override
@ -63,14 +67,13 @@ public class AudioFrameAggregator implements Protocol {
}
@Override
public void sendCompressedAudio(String src, String dst, int codec2Mode, byte[] frame) throws IOException {
if ( _outputBufferPos + frame.length >= _outputBufferSize) {
_childProtocol.sendCompressedAudio(src, dst, codec2Mode, Arrays.copyOf(_outputBuffer, _outputBufferPos));
public void sendCompressedAudio(String src, String dst, byte[] frame) throws IOException {
if ( _outputBufferPos + frame.length > _outputBufferSize) {
_childProtocol.sendCompressedAudio(src, dst, Arrays.copyOf(_outputBuffer, _outputBufferPos));
_outputBufferPos = 0;
}
_lastSrc = src;
_lastDst = dst;
_lastCodec2Mode = codec2Mode;
System.arraycopy(frame, 0, _outputBuffer, _outputBufferPos, frame.length);
_outputBufferPos += frame.length;
}
@ -81,8 +84,8 @@ public class AudioFrameAggregator implements Protocol {
}
@Override
public void sendPcmAudio(String src, String dst, int codec, short[] pcmFrame) throws IOException {
_childProtocol.sendPcmAudio(src, dst, codec, pcmFrame);
public void sendPcmAudio(String src, String dst, short[] pcmFrame) throws IOException {
_childProtocol.sendPcmAudio(src, dst, pcmFrame);
}
@Override
@ -102,12 +105,12 @@ public class AudioFrameAggregator implements Protocol {
}
@Override
protected void onReceivePcmAudio(String src, String dst, int codec, short[] pcmFrame) {
_parentProtocolCallback.onReceivePcmAudio(src, dst, codec, pcmFrame);
protected void onReceivePcmAudio(String src, String dst, short[] pcmFrame) {
_parentProtocolCallback.onReceivePcmAudio(src, dst, pcmFrame);
}
@Override
protected void onReceiveCompressedAudio(String src, String dst, int codec2Mode, byte[] audioFrames) {
protected void onReceiveCompressedAudio(String src, String dst, byte[] audioFrames) {
if (audioFrames.length % _codec2FrameSize != 0) {
Log.e(TAG, "Ignoring audio frame of wrong size: " + audioFrames.length);
_parentProtocolCallback.onProtocolRxError();
@ -118,7 +121,7 @@ public class AudioFrameAggregator implements Protocol {
for (int j = 0; j < _codec2FrameSize && (j + i) < audioFrames.length; j++) {
audioFrame[j] = audioFrames[i + j];
}
_parentProtocolCallback.onReceiveCompressedAudio(src, dst, codec2Mode, audioFrame);
_parentProtocolCallback.onReceiveCompressedAudio(src, dst, audioFrame);
}
}
}
@ -138,19 +141,24 @@ public class AudioFrameAggregator implements Protocol {
_parentProtocolCallback.onReceiveSignalLevel(rssi, snr);
}
@Override
protected void onReceiveTelemetry(int batVoltage) {
_parentProtocolCallback.onReceiveTelemetry(batVoltage);
}
@Override
protected void onReceiveLog(String logData) {
_parentProtocolCallback.onReceiveLog(logData);
}
@Override
protected void onTransmitPcmAudio(String src, String dst, int codec, short[] frame) {
_parentProtocolCallback.onTransmitPcmAudio(src, dst, codec, frame);
protected void onTransmitPcmAudio(String src, String dst, short[] frame) {
_parentProtocolCallback.onTransmitPcmAudio(src, dst, frame);
}
@Override
protected void onTransmitCompressedAudio(String src, String dst, int codec, byte[] frame) {
_parentProtocolCallback.onTransmitCompressedAudio(src, dst, codec, frame);
protected void onTransmitCompressedAudio(String src, String dst, byte[] frame) {
_parentProtocolCallback.onTransmitCompressedAudio(src, dst, frame);
}
@Override
@ -192,7 +200,7 @@ public class AudioFrameAggregator implements Protocol {
@Override
public void flush() throws IOException {
if (_outputBufferPos > 0) {
_childProtocol.sendCompressedAudio(_lastSrc, _lastDst, _lastCodec2Mode, Arrays.copyOf(_outputBuffer, _outputBufferPos));
_childProtocol.sendCompressedAudio(_lastSrc, _lastDst, Arrays.copyOf(_outputBuffer, _outputBufferPos));
_outputBufferPos = 0;
}
_childProtocol.flush();

Wyświetl plik

@ -0,0 +1,216 @@
package com.radio.codec2talkie.protocol;
import android.content.Context;
import android.content.SharedPreferences;
import android.util.Log;
import androidx.preference.PreferenceManager;
import com.radio.codec2talkie.protocol.message.TextMessage;
import com.radio.codec2talkie.protocol.position.Position;
import com.radio.codec2talkie.settings.PreferenceKeys;
import com.radio.codec2talkie.transport.Transport;
import com.radio.opus.Opus;
import java.io.IOException;
public class AudioOpus implements Protocol {
private static final String TAG = AudioOpus.class.getSimpleName();
private final Protocol _childProtocol;
private static final int SAMPLE_RATE = 8000;
private int _pcmFrameSize;
private byte[] _recordAudioEncodedBuffer;
private short[] _playbackAudioBuffer;
private ProtocolCallback _parentProtocolCallback;
private long _opusCon;
private int _audioBufferSize;
public AudioOpus(Protocol childProtocol) {
_childProtocol = childProtocol;
}
@Override
public void initialize(Transport transport, Context context, ProtocolCallback protocolCallback) throws IOException {
_parentProtocolCallback = protocolCallback;
_childProtocol.initialize(transport, context, _protocolCallback);
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
int bitRate = Integer.parseInt(sharedPreferences.getString(PreferenceKeys.OPUS_BIT_RATE, "3200"));
int complexity = Integer.parseInt(sharedPreferences.getString(PreferenceKeys.OPUS_COMPLEXITY, "5"));
float pcmFrameDuration = Float.parseFloat(sharedPreferences.getString(PreferenceKeys.OPUS_FRAME_SIZE, "40"));
_pcmFrameSize = (int)(SAMPLE_RATE / 1000 * pcmFrameDuration);
_audioBufferSize = 10*_pcmFrameSize;
_playbackAudioBuffer = new short[_audioBufferSize];
_recordAudioEncodedBuffer = new byte[_audioBufferSize];
_opusCon = Opus.create(SAMPLE_RATE, 1, Opus.OPUS_APPLICATION_VOIP, bitRate, complexity);
if (_opusCon == 0) {
Log.e(TAG, "Failed to create opus");
}
Log.i(TAG, "Opus is initialized, pcm frame size: " + _pcmFrameSize + ", buffer size: " + _audioBufferSize);
}
@Override
public int getPcmAudioRecordBufferSize() {
return _pcmFrameSize;
}
@Override
public void sendCompressedAudio(String src, String dst, byte[] frame) throws IOException {
_childProtocol.sendCompressedAudio(src, dst, frame);
}
@Override
public void sendTextMessage(TextMessage textMessage) throws IOException {
_childProtocol.sendTextMessage(textMessage);
}
@Override
public void sendPcmAudio(String src, String dst, short[] pcmFrame) throws IOException {
_parentProtocolCallback.onTransmitPcmAudio(src, dst, pcmFrame);
int encodedBytesCnt = Opus.encode(_opusCon, pcmFrame, _pcmFrameSize, _recordAudioEncodedBuffer);
if (encodedBytesCnt == 0) {
Log.w(TAG, "Nothing was encoded");
return;
}
if (encodedBytesCnt > 0) {
byte[] frame = new byte[encodedBytesCnt];
System.arraycopy(_recordAudioEncodedBuffer, 0, frame, 0, encodedBytesCnt);
Log.v(TAG, "pcm count: " + pcmFrame.length + ", encoded bytes count: " + encodedBytesCnt);
_childProtocol.sendCompressedAudio(src, dst, frame);
} else {
Log.e(TAG, "Encode error: " + encodedBytesCnt);
_parentProtocolCallback.onProtocolTxError();
}
}
@Override
public void sendData(String src, String dst, String path, byte[] dataPacket) throws IOException {
_childProtocol.sendData(src, dst, path, dataPacket);
}
@Override
public boolean receive() throws IOException {
return _childProtocol.receive();
}
ProtocolCallback _protocolCallback = new ProtocolCallback() {
@Override
protected void onReceivePosition(Position position) {
_parentProtocolCallback.onReceivePosition(position);
}
@Override
protected void onReceivePcmAudio(String src, String dst, short[] pcmFrame) {
_parentProtocolCallback.onReceivePcmAudio(src, dst, pcmFrame);
}
@Override
protected void onReceiveCompressedAudio(String src, String dst, byte[] audioEncodedFrame) {
int decodedSamplesCnt = Opus.decode(_opusCon, audioEncodedFrame, _playbackAudioBuffer, _audioBufferSize);
Log.v(TAG, "encoded frame size: " + audioEncodedFrame.length + ", decoded samples count:" + decodedSamplesCnt);
if (decodedSamplesCnt == 0) {
Log.w(TAG, "Nothing was decoded");
return;
}
short [] decodedSamples = new short[decodedSamplesCnt];
if (decodedSamplesCnt > 0) {
System.arraycopy(_playbackAudioBuffer, 0, decodedSamples, 0, decodedSamplesCnt);
} else {
Log.e(TAG, "Decode error: " + decodedSamplesCnt);
_parentProtocolCallback.onProtocolRxError();
}
_parentProtocolCallback.onReceivePcmAudio(src, dst, decodedSamples);
}
@Override
protected void onReceiveTextMessage(TextMessage textMessage) {
_parentProtocolCallback.onReceiveTextMessage(textMessage);
}
@Override
protected void onReceiveData(String src, String dst, String path, byte[] data) {
_parentProtocolCallback.onReceiveData(src, dst, path, data);
}
@Override
protected void onReceiveSignalLevel(short rssi, short snr) {
_parentProtocolCallback.onReceiveSignalLevel(rssi, snr);
}
@Override
protected void onReceiveTelemetry(int batVoltage) {
_parentProtocolCallback.onReceiveTelemetry(batVoltage);
}
@Override
protected void onReceiveLog(String logData) {
_parentProtocolCallback.onReceiveLog(logData);
}
@Override
protected void onTransmitPcmAudio(String src, String dst, short[] frame) {
_parentProtocolCallback.onTransmitPcmAudio(src, dst, frame);
}
@Override
protected void onTransmitCompressedAudio(String src, String dst, byte[] frame) {
_parentProtocolCallback.onTransmitCompressedAudio(src, dst, frame);
}
@Override
protected void onTransmitTextMessage(TextMessage textMessage) {
_parentProtocolCallback.onTransmitTextMessage(textMessage);
}
@Override
protected void onTransmitPosition(Position position) {
_parentProtocolCallback.onTransmitPosition(position);
}
@Override
protected void onTransmitData(String src, String dst, String path, byte[] data) {
_parentProtocolCallback.onTransmitData(src, dst, path, data);
}
@Override
protected void onTransmitLog(String logData) {
_parentProtocolCallback.onTransmitLog(logData);
}
@Override
protected void onProtocolRxError() {
_parentProtocolCallback.onProtocolRxError();
}
@Override
protected void onProtocolTxError() {
_parentProtocolCallback.onProtocolTxError();
}
};
@Override
public void sendPosition(Position position) throws IOException {
_childProtocol.sendPosition(position);
}
@Override
public void flush() throws IOException {
_childProtocol.flush();
}
@Override
public void close() {
Opus.destroy(_opusCon);
_childProtocol.close();
}
}

Wyświetl plik

@ -26,6 +26,7 @@ public class Ax25 implements Protocol {
private String _digipath;
private boolean _isVoax25Enabled;
private boolean _isDigiRepeaterEnabled;
private boolean _useTextPackets;
private ProtocolCallback _parentProtocolCallback;
@ -44,6 +45,7 @@ public class Ax25 implements Protocol {
// NOTE, may need to pass through sendData/sendAudio
_digipath = sharedPreferences.getString(PreferenceKeys.AX25_DIGIPATH, "").toUpperCase();
_isVoax25Enabled = SettingsWrapper.isVoax25Enabled(sharedPreferences);
_useTextPackets = SettingsWrapper.isTextPacketsEnabled(sharedPreferences);
_isDigiRepeaterEnabled = sharedPreferences.getBoolean(PreferenceKeys.AX25_DIGIREPEATER_ENABLED, false);
}
@ -53,13 +55,12 @@ public class Ax25 implements Protocol {
}
@Override
public void sendCompressedAudio(String src, String dst, int codec2Mode, byte[] frame) throws IOException {
public void sendCompressedAudio(String src, String dst, byte[] frame) throws IOException {
if (_isVoax25Enabled) {
AX25Packet ax25Packet = new AX25Packet();
ax25Packet.src = src;
ax25Packet.dst = dst;
ax25Packet.digipath = _digipath;
ax25Packet.codec2Mode = codec2Mode;
ax25Packet.isAudio = true;
ax25Packet.rawData = frame;
byte[] ax25Frame = ax25Packet.toBinary();
@ -67,10 +68,10 @@ public class Ax25 implements Protocol {
Log.e(TAG, "Cannot convert AX.25 voice packet to binary");
_parentProtocolCallback.onProtocolTxError();
} else {
_childProtocol.sendCompressedAudio(src, dst, codec2Mode, ax25Frame);
_childProtocol.sendCompressedAudio(src, dst, ax25Frame);
}
} else {
_childProtocol.sendCompressedAudio(src, dst, codec2Mode, frame);
_childProtocol.sendCompressedAudio(src, dst, frame);
}
}
@ -80,8 +81,8 @@ public class Ax25 implements Protocol {
}
@Override
public void sendPcmAudio(String src, String dst, int codec, short[] pcmFrame) throws IOException {
_childProtocol.sendPcmAudio(src, dst, codec, pcmFrame);
public void sendPcmAudio(String src, String dst, short[] pcmFrame) throws IOException {
_childProtocol.sendPcmAudio(src, dst, pcmFrame);
}
@Override
@ -93,7 +94,7 @@ public class Ax25 implements Protocol {
ax25Packet.digipath = path == null ? _digipath : path;
ax25Packet.isAudio = false;
ax25Packet.rawData = dataPacket;
byte[] ax25Frame = ax25Packet.toBinary();
byte[] ax25Frame = _useTextPackets ? ax25Packet.toTextBinary() : ax25Packet.toBinary();
if (ax25Frame == null) {
Log.e(TAG, "Cannot convert AX.25 data packet to binary");
_parentProtocolCallback.onProtocolTxError();
@ -115,25 +116,25 @@ public class Ax25 implements Protocol {
}
@Override
protected void onReceivePcmAudio(String src, String dst, int codec, short[] pcmFrame) {
_parentProtocolCallback.onReceivePcmAudio(src, dst, codec, pcmFrame);
protected void onReceivePcmAudio(String src, String dst, short[] pcmFrame) {
_parentProtocolCallback.onReceivePcmAudio(src, dst, pcmFrame);
}
@Override
protected void onReceiveCompressedAudio(String src, String dst, int codec2Mode, byte[] audioFrames) {
protected void onReceiveCompressedAudio(String src, String dst, byte[] audioFrames) {
AX25Packet ax25Data = new AX25Packet();
ax25Data.fromBinary(audioFrames);
if (ax25Data.isValid) {
if (ax25Data.isAudio) {
_parentProtocolCallback.onReceiveCompressedAudio(ax25Data.src, ax25Data.dst, ax25Data.codec2Mode, ax25Data.rawData);
_parentProtocolCallback.onReceiveCompressedAudio(ax25Data.src, ax25Data.dst, ax25Data.rawData);
} else {
_parentProtocolCallback.onReceiveData(ax25Data.src, ax25Data.dst, ax25Data.digipath, ax25Data.rawData);
_parentProtocolCallback.onReceiveLog(ax25Data.toString());
_parentProtocolCallback.onReceiveData(ax25Data.src, ax25Data.dst, ax25Data.digipath, ax25Data.rawData);
if (_isDigiRepeaterEnabled) digiRepeat(ax25Data);
}
} else {
// fallback to raw audio if ax25 frame is invalid
_parentProtocolCallback.onReceiveCompressedAudio(src, dst, codec2Mode, audioFrames);
_parentProtocolCallback.onReceiveCompressedAudio(src, dst, audioFrames);
}
}
@ -152,19 +153,24 @@ public class Ax25 implements Protocol {
_parentProtocolCallback.onReceiveSignalLevel(rssi, snr);
}
@Override
protected void onReceiveTelemetry(int batVoltage) {
_parentProtocolCallback.onReceiveTelemetry(batVoltage);
}
@Override
protected void onReceiveLog(String logData) {
_parentProtocolCallback.onReceiveLog(logData);
}
@Override
protected void onTransmitPcmAudio(String src, String dst, int codec, short[] frame) {
_parentProtocolCallback.onTransmitPcmAudio(src, dst, codec, frame);
protected void onTransmitPcmAudio(String src, String dst, short[] frame) {
_parentProtocolCallback.onTransmitPcmAudio(src, dst, frame);
}
@Override
protected void onTransmitCompressedAudio(String src, String dst, int codec, byte[] frame) {
_parentProtocolCallback.onTransmitCompressedAudio(src, dst, codec, frame);
protected void onTransmitCompressedAudio(String src, String dst, byte[] frame) {
_parentProtocolCallback.onTransmitCompressedAudio(src, dst, frame);
}
@Override

Wyświetl plik

@ -0,0 +1,167 @@
package com.radio.codec2talkie.protocol;
import android.content.Context;
import android.content.SharedPreferences;
import com.radio.codec2talkie.protocol.message.TextMessage;
import com.radio.codec2talkie.protocol.position.Position;
import com.radio.codec2talkie.settings.PreferenceKeys;
import com.radio.codec2talkie.tools.TextTools;
import com.radio.codec2talkie.transport.Transport;
import java.io.IOException;
import java.nio.ByteBuffer;
public class CustomDataPrefix implements Protocol {
private final Protocol _childProtocol;
private ProtocolCallback _parentProtocolCallback;
private final byte[] _bytePrefix;
public CustomDataPrefix(Protocol childProtocol, SharedPreferences sharedPreferences) {
_childProtocol = childProtocol;
String prefix = sharedPreferences.getString(PreferenceKeys.CUSTOM_PREFIX, "");
_bytePrefix = TextTools.hexStringToByteArray(prefix);
}
@Override
public void initialize(Transport transport, Context context, ProtocolCallback protocolCallback) throws IOException {
_parentProtocolCallback = protocolCallback;
_childProtocol.initialize(transport, context, _protocolCallback);
}
@Override
public int getPcmAudioRecordBufferSize() {
return -1;
}
@Override
public void sendCompressedAudio(String src, String dst, byte[] frame) throws IOException {
byte[] prefixedData = ByteBuffer.allocate(_bytePrefix.length + frame.length)
.put(_bytePrefix)
.put(frame)
.array();
_childProtocol.sendCompressedAudio(src, dst, prefixedData);
}
@Override
public void sendTextMessage(TextMessage textMessage) throws IOException {
_childProtocol.sendTextMessage(textMessage);
}
@Override
public void sendPcmAudio(String src, String dst, short[] pcmFrame) throws IOException {
_childProtocol.sendPcmAudio(src, dst, pcmFrame);
}
@Override
public void sendData(String src, String dst, String path, byte[] dataPacket) throws IOException {
byte[] prefixedData = ByteBuffer.allocate(_bytePrefix.length + dataPacket.length)
.put(_bytePrefix)
.put(dataPacket)
.array();
_childProtocol.sendData(src, dst, path, prefixedData);
}
@Override
public boolean receive() throws IOException {
return _childProtocol.receive();
}
@Override
public void sendPosition(Position position) throws IOException {
_childProtocol.sendPosition(position);
}
@Override
public void flush() throws IOException {
_childProtocol.flush();
}
@Override
public void close() {
_childProtocol.close();
}
ProtocolCallback _protocolCallback = new ProtocolCallback() {
@Override
protected void onReceivePosition(Position position) {
_parentProtocolCallback.onReceivePosition(position);
}
@Override
protected void onReceivePcmAudio(String src, String dst, short[] pcmFrame) {
_parentProtocolCallback.onReceivePcmAudio(src, dst, pcmFrame);
}
@Override
protected void onReceiveCompressedAudio(String src, String dst, byte[] audioFrame) {
_parentProtocolCallback.onReceiveCompressedAudio(src, dst, audioFrame);
}
@Override
protected void onReceiveTextMessage(TextMessage textMessage) {
_parentProtocolCallback.onReceiveTextMessage(textMessage);
}
@Override
protected void onReceiveData(String src, String dst, String path, byte[] data) {
_parentProtocolCallback.onReceiveData(src, dst, path, data);
}
@Override
protected void onReceiveSignalLevel(short rssi, short snr) {
_parentProtocolCallback.onReceiveSignalLevel(rssi, snr);
}
@Override
protected void onReceiveTelemetry(int batVoltage) {
_parentProtocolCallback.onReceiveTelemetry(batVoltage);
}
@Override
protected void onReceiveLog(String logData) {
_parentProtocolCallback.onReceiveLog(logData);
}
@Override
protected void onTransmitPcmAudio(String src, String dst, short[] frame) {
_parentProtocolCallback.onTransmitPcmAudio(src, dst, frame);
}
@Override
protected void onTransmitCompressedAudio(String src, String dst, byte[] frame) {
_parentProtocolCallback.onTransmitCompressedAudio(src, dst, frame);
}
@Override
protected void onTransmitTextMessage(TextMessage textMessage) {
_parentProtocolCallback.onTransmitTextMessage(textMessage);
}
@Override
protected void onTransmitPosition(Position position) {
_parentProtocolCallback.onTransmitPosition(position);
}
@Override
protected void onTransmitData(String src, String dst, String path, byte[] data) {
_parentProtocolCallback.onTransmitData(src, dst, path, data);
}
@Override
protected void onTransmitLog(String logData) {
_parentProtocolCallback.onTransmitLog(logData);
}
@Override
protected void onProtocolRxError() {
_parentProtocolCallback.onProtocolRxError();
}
@Override
protected void onProtocolTxError() {
_parentProtocolCallback.onProtocolTxError();
}
};
}

Wyświetl plik

@ -22,6 +22,9 @@ import java.util.Arrays;
public class Freedv implements Protocol {
private static final String TAG = Freedv.class.getSimpleName();
private static final int CRC_LENGTH = 2;
private static final int PKT_SIZE_LENGTH = 2;
private ProtocolCallback _parentProtocolCallback;
private Transport _transport;
@ -70,15 +73,15 @@ public class Freedv implements Protocol {
}
@Override
public void sendPcmAudio(String src, String dst, int codec, short[] pcmFrame) throws IOException {
public void sendPcmAudio(String src, String dst, short[] pcmFrame) throws IOException {
Codec2.freedvTx(_freedv, _modemTxBuffer, pcmFrame);
//Log.i(TAG, "send pcm " + _modemTxBuffer.length);
_transport.write(_modemTxBuffer);
_parentProtocolCallback.onTransmitPcmAudio(src, dst, codec, pcmFrame);
_parentProtocolCallback.onTransmitPcmAudio(src, dst, pcmFrame);
}
@Override
public void sendCompressedAudio(String src, String dst, int codec, byte[] frame) throws IOException {
public void sendCompressedAudio(String src, String dst, byte[] frame) throws IOException {
Log.w(TAG, "sendCompressedAudio() is not supported");
}
@ -89,7 +92,7 @@ public class Freedv implements Protocol {
@Override
public void sendData(String src, String dst, String path, byte[] dataPacket) throws IOException {
if (dataPacket.length > _dataBuffer.length - 2) {
if (dataPacket.length > _dataBuffer.length - CRC_LENGTH - PKT_SIZE_LENGTH) {
Log.e(TAG, "Too large packet " + dataPacket.length + " > " + _dataBuffer.length);
return;
}
@ -97,7 +100,10 @@ public class Freedv implements Protocol {
_transport.write(Arrays.copyOf(_dataSamplesBuffer, (int) cnt));
Arrays.fill(_dataBuffer, (byte) 0);
System.arraycopy(dataPacket, 0, _dataBuffer, 0, dataPacket.length);
// transmit packet size first
_dataBuffer[0] = (byte)((dataPacket.length >> 8) & 0xff);
_dataBuffer[1] = (byte)(dataPacket.length & 0xff);
System.arraycopy(dataPacket, 0, _dataBuffer, 2, dataPacket.length);
Codec2.freedvRawDataTx(_freedvData, _dataSamplesBuffer, _dataBuffer);
_transport.write(_dataSamplesBuffer);
@ -134,7 +140,7 @@ public class Freedv implements Protocol {
long cntRead = Codec2.freedvRx(_freedv, _speechRxBuffer, samplesSpeech);
if (cntRead > 0) {
//Log.i(TAG, "receive " + cntRead);
_parentProtocolCallback.onReceivePcmAudio(null, null, -1, Arrays.copyOf(_speechRxBuffer, (int) cntRead));
_parentProtocolCallback.onReceivePcmAudio(null, null, Arrays.copyOf(_speechRxBuffer, (int) cntRead));
float snr = Codec2.freedvGetModemStat(_freedv);
_parentProtocolCallback.onReceiveSignalLevel((short) 0, (short)(100 * snr));
isRead = true;
@ -151,8 +157,12 @@ public class Freedv implements Protocol {
long cntRead = Codec2.freedvRawDataRx(_freedvData, _dataBuffer, samplesData);
if (cntRead > 0) {
Log.i(TAG, "receive " + cntRead);
// TODO, refactor, use onReceiveData
_parentProtocolCallback.onReceiveCompressedAudio(null, null, -1, _dataBuffer);
// extract packet length
int pktLen = (((int)_dataBuffer[0] & 0xff) << 8) | ((int)_dataBuffer[1] & 0xff);
byte [] pkt = new byte[pktLen];
System.arraycopy(_dataBuffer, 2, pkt, 0, pktLen);
// NOTE, default data is compressed audio, upper layer should distinguish
_parentProtocolCallback.onReceiveCompressedAudio(null, null, pkt);
float snr = Codec2.freedvGetModemStat(_freedvData);
_parentProtocolCallback.onReceiveSignalLevel((short) 0, (short)(100 * snr));
isRead = true;
@ -170,7 +180,8 @@ public class Freedv implements Protocol {
@Override
public void flush() throws IOException {
// TODO, check if need to flush buffers
_speechSamples.clear();
_dataSamples.clear();
}
@Override

Wyświetl plik

@ -55,12 +55,12 @@ public class Hdlc implements Protocol {
}
@Override
public void sendPcmAudio(String src, String dst, int codec, short[] pcmFrame) throws IOException {
public void sendPcmAudio(String src, String dst, short[] pcmFrame) throws IOException {
Log.w(TAG, "sendPcmAudio() is not supported");
}
@Override
public void sendCompressedAudio(String src, String dst, int codec, byte[] frame) throws IOException {
public void sendCompressedAudio(String src, String dst, byte[] frame) throws IOException {
_transport.write(hdlcEncode(frame));
}
@ -111,7 +111,8 @@ public class Hdlc implements Protocol {
if (calculatedCrc == packetCrc) {
//Log.v(TAG, DebugTools.byteBitsToString(packetBits));
//Log.i(TAG, "RX: " + DebugTools.bytesToHex(packetBytes));
_parentProtocolCallback.onReceiveCompressedAudio(null, null, -1, contentBytes);
// NOTE, default data is compressed audio, upper layer should distinguish
_parentProtocolCallback.onReceiveCompressedAudio(null, null, contentBytes);
}
}
}

Wyświetl plik

@ -30,7 +30,7 @@ public class Kiss implements Protocol {
private static final int FRAME_OUTPUT_BUFFER_SIZE = 1024;
private static final int KISS_CMD_BUFFER_SIZE = 128;
private static final int KISS_RADIO_CONTROL_COMMAND_SIZE = 17;
private static final int KISS_RADIO_CONTROL_COMMAND_SIZE = 34;
private static final byte KISS_FEND = (byte)0xc0;
private static final byte KISS_FESC = (byte)0xdb;
@ -46,6 +46,7 @@ public class Kiss implements Protocol {
private static final byte KISS_CMD_SET_HARDWARE = (byte)0x06;
private static final byte KISS_CMD_SIGNAL_REPORT = (byte)0x07;
private static final byte KISS_CMD_REBOOT = (byte)0x08;
private static final byte KISS_CMD_TELEMETRY = (byte)0x09;
private static final byte KISS_CMD_NOCMD = (byte)0x80;
private static final byte CSMA_PERSISTENCE = (byte)0xff;
@ -54,6 +55,7 @@ public class Kiss implements Protocol {
private static final byte TX_TAIL_10MS_UNITS = (byte)(500 / 10);
private static final int SIGNAL_LEVEL_EVENT_SIZE = 4;
private static final int TELEMETRY_EVENT_SIZE = 2;
private enum State {
GET_START,
@ -65,7 +67,8 @@ public class Kiss implements Protocol {
private enum DataType {
RAW,
SIGNAL_REPORT
SIGNAL_REPORT,
TELEMETRY
}
private DataType _kissDataType = DataType.RAW;
@ -152,41 +155,52 @@ public class Kiss implements Protocol {
private void initializeExtended() throws IOException {
/*
struct LoraControlCommand {
uint32_t freq;
struct SetHardware {
uint32_t freqRx;
uint32_t freqTx;
uint8_t modType;
uint16_t pwr;
uint32_t bw;
uint16_t sf;
uint16_t cr;
uint16_t pwr;
uint16_t sync;
uint8_t crc;
} __attribute__((packed));
uint32_t fskBitRate;
uint32_t fskFreqDev;
uint32_t fskRxBw;
} __attribute__((packed));
*/
String freq = _sharedPreferences.getString(PreferenceKeys.KISS_EXTENSIONS_RADIO_FREQUENCY, "433775000");
String freqTx = _sharedPreferences.getString(PreferenceKeys.KISS_EXTENSIONS_RADIO_FREQUENCY_TX, "433775000");
if (!_sharedPreferences.getBoolean(PreferenceKeys.KISS_EXTENSIONS_RADIO_SPLIT_FREQ, false)) freqTx = freq;
String modType = _sharedPreferences.getString(PreferenceKeys.KISS_EXTENSIONS_RADIO_MOD, "0");
String bw = _sharedPreferences.getString(PreferenceKeys.KISS_EXTENSIONS_RADIO_BANDWIDTH, "125000");
String sf = _sharedPreferences.getString(PreferenceKeys.KISS_EXTENSIONS_RADIO_SF, "7");
String cr = _sharedPreferences.getString(PreferenceKeys.KISS_EXTENSIONS_RADIO_CR, "6");
String pwr = _sharedPreferences.getString(PreferenceKeys.KISS_EXTENSIONS_RADIO_POWER, "20");
String sync = _sharedPreferences.getString(PreferenceKeys.KISS_EXTENSIONS_RADIO_SYNC, "34");
byte crc = (byte)(_sharedPreferences.getBoolean(PreferenceKeys.KISS_EXTENSIONS_RADIO_CRC, true) ? 1 : 0);
String fskBitRate = _sharedPreferences.getString(PreferenceKeys.KISS_EXTENSIONS_RADIO_FSK_BIT_RATE, "4800");
String fskFreqDev = _sharedPreferences.getString(PreferenceKeys.KISS_EXTENSIONS_RADIO_FSK_FREQ_DEV, "1200");
String fskRxBw = _sharedPreferences.getString(PreferenceKeys.KISS_EXTENSIONS_RADIO_FSK_RX_BW, "9700");
ByteBuffer rawBuffer = ByteBuffer.allocate(KISS_RADIO_CONTROL_COMMAND_SIZE);
rawBuffer.putInt(Integer.parseInt(freq))
.putInt(Integer.parseInt(freqTx))
.put(Byte.parseByte(modType))
.putShort(Short.parseShort(pwr))
.putInt(Integer.parseInt(bw))
.putShort(Short.parseShort(sf))
.putShort(Short.parseShort(cr))
.putShort(Short.parseShort(pwr))
.putShort(Short.parseShort(sync, 16))
.put(crc)
.putInt(Integer.parseInt(fskBitRate))
.putInt(Integer.parseInt(fskFreqDev))
.putInt(Integer.parseInt(fskRxBw))
.rewind();
startKissPacket(KISS_CMD_SET_HARDWARE);
for (byte b: rawBuffer.array()) {
sendKissByte(b);
}
completeKissPacket();
send(KISS_CMD_SET_HARDWARE, rawBuffer.array());
_context.registerReceiver(onModemRebootRequested, new IntentFilter(PreferenceKeys.KISS_EXTENSIONS_ACTION_REBOOT_REQUESTED));
}
@ -205,9 +219,9 @@ public class Kiss implements Protocol {
};
@Override
public void sendCompressedAudio(String src, String dst, int codec, byte[] frame) throws IOException {
public void sendCompressedAudio(String src, String dst, byte[] frame) throws IOException {
// NOTE, KISS does not distinguish between audio and data packet, upper layer should decide
send(frame);
send(KISS_CMD_DATA, frame);
}
@Override
@ -216,14 +230,14 @@ public class Kiss implements Protocol {
}
@Override
public void sendPcmAudio(String src, String dst, int codec, short[] pcmFrame) {
public void sendPcmAudio(String src, String dst, short[] pcmFrame) {
Log.w(TAG, "sendPcmAudio() is not supported");
}
@Override
public void sendData(String src, String dst, String path, byte[] dataPacket) throws IOException {
// NOTE, KISS does not distinguish between audio and data packet, upper layer should decide
send(dataPacket);
send(KISS_CMD_DATA, dataPacket);
}
@Override
@ -255,14 +269,14 @@ public class Kiss implements Protocol {
}
}
private void send(byte[] data) throws IOException {
private void send(byte commandCode, byte[] data) throws IOException {
// escape
ByteBuffer escapedFrame = escape(data);
int escapedFrameSize = escapedFrame.position();
escapedFrame.rewind();
// send
startKissPacket(KISS_CMD_DATA);
startKissPacket(commandCode);
while (escapedFrame.position() < escapedFrameSize) {
sendKissByte(escapedFrame.get());
}
@ -280,6 +294,11 @@ public class Kiss implements Protocol {
_kissDataType = DataType.SIGNAL_REPORT;
_kissCmdBufferPos = 0;
break;
case KISS_CMD_TELEMETRY:
_kissState = State.GET_DATA;
_kissDataType = DataType.TELEMETRY;
_kissCmdBufferPos = 0;
break;
case KISS_FEND:
break;
default:
@ -296,9 +315,8 @@ public class Kiss implements Protocol {
break;
case KISS_FEND:
if (_kissDataType == DataType.RAW) {
// NOTE, KISS does not distinguish between audio and data packets, upper layer should decide
// TODO, refactor, use onReceiveData
protocolCallback.onReceiveCompressedAudio(null, null, -1, Arrays.copyOf(_frameOutputBuffer, _frameOutputBufferPos));
// NOTE, default data is compressed audio, KISS does not distinguish between audio and data packets, upper layer should decide
protocolCallback.onReceiveCompressedAudio(null, null, Arrays.copyOf(_frameOutputBuffer, _frameOutputBufferPos));
} else if (_kissDataType == DataType.SIGNAL_REPORT && _isExtendedMode) {
byte[] signalLevelRaw = Arrays.copyOf(_kissCmdBuffer, _kissCmdBufferPos);
ByteBuffer data = ByteBuffer.wrap(signalLevelRaw);
@ -311,13 +329,24 @@ public class Kiss implements Protocol {
Log.e(TAG, "Signal event of wrong size");
}
_kissCmdBufferPos = 0;
} else if (_kissDataType == DataType.TELEMETRY && _isExtendedMode) {
byte[] telemetryRaw = Arrays.copyOf(_kissCmdBuffer, _kissCmdBufferPos);
ByteBuffer data = ByteBuffer.wrap(telemetryRaw);
if (telemetryRaw.length == TELEMETRY_EVENT_SIZE) {
short batVoltage = data.getShort();
protocolCallback.onReceiveTelemetry(batVoltage);
} else {
protocolCallback.onProtocolRxError();
Log.e(TAG, "Telemetry event of wrong size " + telemetryRaw.length + " vs " + TELEMETRY_EVENT_SIZE);
}
_kissCmdBufferPos = 0;
}
resetState();
break;
default:
if (_kissDataType == DataType.RAW) {
receiveFrameByte(b);
} else if (_kissDataType == DataType.SIGNAL_REPORT) {
} else if (_kissDataType == DataType.SIGNAL_REPORT || _kissDataType == DataType.TELEMETRY) {
_kissCmdBuffer[_kissCmdBufferPos++] = b;
}
break;
@ -393,6 +422,8 @@ public class Kiss implements Protocol {
private void completeKissPacket() throws IOException {
if (_transportOutputBufferPos > 0) {
sendKissByte(KISS_FEND);
//byte[] d = Arrays.copyOf(_transportOutputBuffer, _transportOutputBufferPos);
//Log.i(TAG, DebugTools.bytesToHex(d));
_transport.write(Arrays.copyOf(_transportOutputBuffer, _transportOutputBufferPos));
_transportOutputBufferPos = 0;
}

Wyświetl plik

@ -13,8 +13,8 @@ public interface Protocol {
void initialize(Transport transport, Context context, ProtocolCallback protocolCallback) throws IOException;
// audio
int getPcmAudioRecordBufferSize();
void sendPcmAudio(String src, String dst, int codec, short[] pcmFrame) throws IOException;
void sendCompressedAudio(String src, String dst, int codec, byte[] frame) throws IOException;
void sendPcmAudio(String src, String dst, short[] pcmFrame) throws IOException;
void sendCompressedAudio(String src, String dst, byte[] frame) throws IOException;
// messaging
void sendTextMessage(TextMessage textMessage) throws IOException;
// data

Wyświetl plik

@ -6,16 +6,17 @@ import com.radio.codec2talkie.protocol.position.Position;
public abstract class ProtocolCallback {
// receive
abstract protected void onReceivePosition(Position position);
abstract protected void onReceivePcmAudio(String src, String dst, int codec, short[] pcmFrame);
abstract protected void onReceiveCompressedAudio(String src, String dst, int codec, byte[] frame);
abstract protected void onReceivePcmAudio(String src, String dst, short[] pcmFrame);
abstract protected void onReceiveCompressedAudio(String src, String dst, byte[] frame);
abstract protected void onReceiveTextMessage(TextMessage textMessage);
abstract protected void onReceiveData(String src, String dst, String path, byte[] data);
abstract protected void onReceiveSignalLevel(short rssi, short snr);
abstract protected void onReceiveTelemetry(int batVoltage);
abstract protected void onReceiveLog(String logData);
// transmit
abstract protected void onTransmitPcmAudio(String src, String dst, int codec, short[] frame);
abstract protected void onTransmitCompressedAudio(String src, String dst, int codec, byte[] frame);
abstract protected void onTransmitPcmAudio(String src, String dst, short[] frame);
abstract protected void onTransmitCompressedAudio(String src, String dst, byte[] frame);
abstract protected void onTransmitTextMessage(TextMessage textMessage);
abstract protected void onTransmitPosition(Position position);
abstract protected void onTransmitData(String src, String dst, String path, byte[] data);

Wyświetl plik

@ -3,9 +3,9 @@ package com.radio.codec2talkie.protocol;
import android.content.Context;
import android.content.SharedPreferences;
import androidx.annotation.NonNull;
import androidx.preference.PreferenceManager;
import com.radio.codec2talkie.settings.PreferenceKeys;
import com.radio.codec2talkie.settings.SettingsWrapper;
public class ProtocolFactory {
@ -24,6 +24,7 @@ public class ProtocolFactory {
_name = name;
}
@NonNull
@Override
public String toString() {
return _name;
@ -58,7 +59,7 @@ public class ProtocolFactory {
return protocolType;
}
public static Protocol create(int codec2ModeId, Context context) {
public static Protocol create(Context context) {
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
@ -70,6 +71,8 @@ public class ProtocolFactory {
boolean aprsEnabled = SettingsWrapper.isAprsEnabled(sharedPreferences);
boolean aprsIsEnabled = SettingsWrapper.isAprsIsEnabled(sharedPreferences);
boolean freedvEnabled = SettingsWrapper.isFreeDvSoundModemModulation(sharedPreferences);
boolean codec2Enabled = SettingsWrapper.isCodec2Enabled(sharedPreferences);
boolean isCustomPrefixEnabled = SettingsWrapper.isCustomPrefixEnabled(sharedPreferences);
// "root" protocol
Protocol proto;
@ -95,6 +98,9 @@ public class ProtocolFactory {
break;
}
if (isCustomPrefixEnabled) {
proto = new CustomDataPrefix(proto, sharedPreferences);
}
if (scramblingEnabled) {
proto = new Scrambler(proto, scramblingKey);
}
@ -102,12 +108,15 @@ public class ProtocolFactory {
proto = new Ax25(proto);
}
if (!freedvEnabled) {
if (recordingEnabled) {
proto = new Recorder(proto, codec2ModeId);
if (codec2Enabled) {
if (recordingEnabled) {
proto = new Recorder(proto, sharedPreferences);
}
proto = new AudioCodec2FrameAggregator(proto, sharedPreferences);
proto = new AudioCodec2(proto, sharedPreferences);
} else {
proto = new AudioOpus(proto);
}
proto = new AudioFrameAggregator(proto, codec2ModeId);
proto = new AudioCodec2(proto, codec2ModeId);
}
if (aprsEnabled) {

Wyświetl plik

@ -37,7 +37,7 @@ public class Raw implements Protocol {
}
@Override
public void sendCompressedAudio(String src, String dst, int codec2Mode, byte[] frame) throws IOException {
public void sendCompressedAudio(String src, String dst, byte[] frame) throws IOException {
_transport.write(Arrays.copyOf(frame, frame.length));
}
@ -47,7 +47,7 @@ public class Raw implements Protocol {
}
@Override
public void sendPcmAudio(String src, String dst, int codec, short[] pcmFrame) {
public void sendPcmAudio(String src, String dst, short[] pcmFrame) {
Log.w(TAG, "sendPcmAudio() is not supported");
}
@ -60,7 +60,8 @@ public class Raw implements Protocol {
public boolean receive() throws IOException {
int bytesRead = _transport.read(_rxDataBuffer);
if (bytesRead > 0) {
_parentProtocolCallback.onReceiveCompressedAudio(null, null, -1, Arrays.copyOf(_rxDataBuffer, bytesRead));
// NOTE, default data is compressed audio, upper layer should distinguish
_parentProtocolCallback.onReceiveCompressedAudio(null, null, Arrays.copyOf(_rxDataBuffer, bytesRead));
return true;
}
return false;

Wyświetl plik

@ -1,12 +1,16 @@
package com.radio.codec2talkie.protocol;
import android.content.Context;
import android.content.SharedPreferences;
import android.text.TextUtils;
import android.util.Log;
import com.radio.codec2talkie.MainActivity;
import com.radio.codec2talkie.R;
import com.radio.codec2talkie.protocol.message.TextMessage;
import com.radio.codec2talkie.protocol.position.Position;
import com.radio.codec2talkie.settings.PreferenceKeys;
import com.radio.codec2talkie.tools.AudioTools;
import com.radio.codec2talkie.tools.StorageTools;
import com.radio.codec2talkie.transport.Transport;
@ -31,16 +35,17 @@ public class Recorder implements Protocol {
private Timer _fileRotationTimer;
final Protocol _childProtocol;
final int _codec2ModeId;
int _codec2ModeId;
private String _prevSrcCallsign;
private String _prevDstCallsign;
private final SharedPreferences _sharedPreferences;
private ProtocolCallback _parentProtocolCallback;
public Recorder(Protocol childProtocol, int codec2ModeId) {
public Recorder(Protocol childProtocol, SharedPreferences sharedPreferences) {
_childProtocol = childProtocol;
_codec2ModeId = codec2ModeId;
_sharedPreferences = sharedPreferences;
_prevSrcCallsign = null;
_prevDstCallsign = null;
@ -51,6 +56,9 @@ public class Recorder implements Protocol {
_parentProtocolCallback = protocolCallback;
_storage = StorageTools.getStorage(context);
_childProtocol.initialize(transport, context, _protocolCallback);
String codec2ModeName = _sharedPreferences.getString(PreferenceKeys.CODEC2_MODE, context.getResources().getStringArray(R.array.codec2_modes)[0]);
_codec2ModeId = AudioTools.extractCodec2ModeId(codec2ModeName);
}
@Override
@ -59,10 +67,10 @@ public class Recorder implements Protocol {
}
@Override
public void sendCompressedAudio(String src, String dst, int codec2Mode, byte[] frame) throws IOException {
public void sendCompressedAudio(String src, String dst, byte[] frame) throws IOException {
rotateIfNewSrcOrDstCallsign(src, dst);
writeToFile(src, dst, codec2Mode, frame);
_childProtocol.sendCompressedAudio(src, dst, codec2Mode, frame);
writeToFile(src, dst, frame);
_childProtocol.sendCompressedAudio(src, dst, frame);
}
@Override
@ -71,8 +79,8 @@ public class Recorder implements Protocol {
}
@Override
public void sendPcmAudio(String src, String dst, int codec, short[] pcmFrame) throws IOException {
_childProtocol.sendPcmAudio(src, dst, codec, pcmFrame);
public void sendPcmAudio(String src, String dst, short[] pcmFrame) throws IOException {
_childProtocol.sendPcmAudio(src, dst, pcmFrame);
}
@Override
@ -92,15 +100,15 @@ public class Recorder implements Protocol {
}
@Override
protected void onReceivePcmAudio(String src, String dst, int codec, short[] pcmFrame) {
_parentProtocolCallback.onReceivePcmAudio(src, dst, codec, pcmFrame);
protected void onReceivePcmAudio(String src, String dst, short[] pcmFrame) {
_parentProtocolCallback.onReceivePcmAudio(src, dst, pcmFrame);
}
@Override
protected void onReceiveCompressedAudio(String src, String dst, int codec2Mode, byte[] audioFrames) {
protected void onReceiveCompressedAudio(String src, String dst, byte[] audioFrames) {
rotateIfNewSrcOrDstCallsign(src, dst);
_parentProtocolCallback.onReceiveCompressedAudio(src, dst, codec2Mode, audioFrames);
writeToFile(src, dst, codec2Mode, audioFrames);
_parentProtocolCallback.onReceiveCompressedAudio(src, dst, audioFrames);
writeToFile(src, dst, audioFrames);
}
@Override
@ -118,19 +126,24 @@ public class Recorder implements Protocol {
_parentProtocolCallback.onReceiveSignalLevel(rssi, snr);
}
@Override
protected void onReceiveTelemetry(int batVoltage) {
_parentProtocolCallback.onReceiveTelemetry(batVoltage);
}
@Override
protected void onReceiveLog(String logData) {
_parentProtocolCallback.onReceiveLog(logData);
}
@Override
protected void onTransmitPcmAudio(String src, String dst, int codec, short[] frame) {
_parentProtocolCallback.onTransmitPcmAudio(src, dst, codec, frame);
protected void onTransmitPcmAudio(String src, String dst, short[] frame) {
_parentProtocolCallback.onTransmitPcmAudio(src, dst, frame);
}
@Override
protected void onTransmitCompressedAudio(String src, String dst, int codec, byte[] frame) {
_parentProtocolCallback.onTransmitCompressedAudio(src, dst, codec, frame);
protected void onTransmitCompressedAudio(String src, String dst, byte[] frame) {
_parentProtocolCallback.onTransmitCompressedAudio(src, dst, frame);
}
@Override
@ -179,9 +192,9 @@ public class Recorder implements Protocol {
_childProtocol.close();
}
private void writeToFile(String src, String dst, int codec2Mode, byte[] rawData) {
private void writeToFile(String src, String dst, byte[] rawData) {
stopRotationTimer();
createStreamIfNotExists(src, dst, codec2Mode);
createStreamIfNotExists(src, dst);
writeToStream(rawData);
startRotationTimer();
}
@ -196,7 +209,7 @@ public class Recorder implements Protocol {
}
}
private void createStreamIfNotExists(String src, String dst, int codec2Mode) {
private void createStreamIfNotExists(String src, String dst) {
if (_activeStream == null) {
try {
Date date = new Date();
@ -204,7 +217,7 @@ public class Recorder implements Protocol {
if (!newDirectory.exists() && !newDirectory.mkdirs()) {
Log.e(TAG, "Failed to create directory for voicemails");
}
File newAudioFile = new File(newDirectory, getNewFileName(date, src, dst, codec2Mode));
File newAudioFile = new File(newDirectory, getNewFileName(date, src, dst));
_activeStream = new FileOutputStream(newAudioFile);
} catch (FileNotFoundException e) {
e.printStackTrace();
@ -217,13 +230,9 @@ public class Recorder implements Protocol {
return df.format(date);
}
private String getNewFileName(Date date, String src, String dst, int codec2Mode) {
int mode = codec2Mode;
if (mode == -1) {
mode = _codec2ModeId;
}
private String getNewFileName(Date date, String src, String dst) {
SimpleDateFormat tf = new SimpleDateFormat("HHmmss", Locale.ENGLISH);
String codec2mode = String.format(Locale.ENGLISH, "%02d", mode);
String codec2mode = String.format(Locale.ENGLISH, "%02d", _codec2ModeId);
String fileName = codec2mode + "_" + tf.format(date);
if (src != null && dst != null) {
fileName += "_" + src + "_" + dst;

Wyświetl plik

@ -51,7 +51,7 @@ public class Scrambler implements Protocol {
}
@Override
public void sendCompressedAudio(String src, String dst, int codec2Mode, byte[] audioFrame) throws IOException {
public void sendCompressedAudio(String src, String dst, byte[] audioFrame) throws IOException {
byte[] result = scramble(audioFrame);
if (result == null) {
_parentProtocolCallback.onProtocolTxError();
@ -66,8 +66,8 @@ public class Scrambler implements Protocol {
}
@Override
public void sendPcmAudio(String src, String dst, int codec, short[] pcmFrame) throws IOException {
_childProtocol.sendPcmAudio(src, dst, codec, pcmFrame);
public void sendPcmAudio(String src, String dst, short[] pcmFrame) throws IOException {
_childProtocol.sendPcmAudio(src, dst, pcmFrame);
}
@Override
@ -92,18 +92,18 @@ public class Scrambler implements Protocol {
}
@Override
protected void onReceivePcmAudio(String src, String dst, int codec, short[] pcmFrame) {
_parentProtocolCallback.onReceivePcmAudio(src, dst, codec, pcmFrame);
protected void onReceivePcmAudio(String src, String dst, short[] pcmFrame) {
_parentProtocolCallback.onReceivePcmAudio(src, dst, pcmFrame);
}
@Override
protected void onReceiveCompressedAudio(String src, String dst, int codec2Mode, byte[] scrambledFrame) {
protected void onReceiveCompressedAudio(String src, String dst, byte[] scrambledFrame) {
byte[] audioFrames = unscramble(scrambledFrame);
if (audioFrames == null) {
_parentProtocolCallback.onProtocolRxError();
} else {
_parentProtocolCallback.onReceiveCompressedAudio(src, dst, codec2Mode, audioFrames);
_parentProtocolCallback.onReceiveCompressedAudio(src, dst, audioFrames);
}
}
@ -127,19 +127,24 @@ public class Scrambler implements Protocol {
_parentProtocolCallback.onReceiveSignalLevel(rssi, snr);
}
@Override
protected void onReceiveTelemetry(int batVoltage) {
_parentProtocolCallback.onReceiveTelemetry(batVoltage);
}
@Override
protected void onReceiveLog(String logData) {
_parentProtocolCallback.onReceiveLog(logData);
}
@Override
protected void onTransmitPcmAudio(String src, String dst, int codec, short[] frame) {
_parentProtocolCallback.onTransmitPcmAudio(src, dst, codec, frame);
protected void onTransmitPcmAudio(String src, String dst, short[] frame) {
_parentProtocolCallback.onTransmitPcmAudio(src, dst, frame);
}
@Override
protected void onTransmitCompressedAudio(String src, String dst, int codec, byte[] frame) {
_parentProtocolCallback.onTransmitCompressedAudio(src, dst, codec, frame);
protected void onTransmitCompressedAudio(String src, String dst, byte[] frame) {
_parentProtocolCallback.onTransmitCompressedAudio(src, dst, frame);
}
@Override

Wyświetl plik

@ -4,6 +4,8 @@ import com.radio.codec2talkie.protocol.message.TextMessage;
import com.radio.codec2talkie.protocol.position.Position;
public interface AprsData {
boolean isPositionReport();
boolean isTextMessage();
void fromPosition(Position position);
void fromTextMessage(TextMessage textMessage);
Position toPosition();

Wyświetl plik

@ -16,6 +16,12 @@ public class AprsDataFactory {
return new AprsDataPositionReport();
case MESSAGE:
return new AprsDataTextMessage();
case OBJECT:
return new AprsObject();
case ITEM:
return new AprsItem();
case THIRD_PARTY:
return new AprsThirdParty();
}
return null;
}

Wyświetl plik

@ -11,6 +11,7 @@ import com.radio.codec2talkie.tools.TextTools;
import com.radio.codec2talkie.tools.UnitTools;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@ -22,6 +23,16 @@ public class AprsDataPositionReport implements AprsData {
private byte[] _binary;
private boolean _isValid;
@Override
public boolean isPositionReport() {
return true;
}
@Override
public boolean isTextMessage() {
return false;
}
@Override
public void fromPosition(Position position) {
_isValid = false;
@ -143,7 +154,7 @@ public class AprsDataPositionReport implements AprsData {
byte[] tail = new byte[buffer.remaining()];
buffer.get(tail);
String strTail = new String(tail);
String strTail = new String(tail, StandardCharsets.UTF_8);
Pattern latLonPattern = Pattern.compile("^([\\\\/])(\\S{4})(\\S{4})(\\S)(.\\S)?(\\S)?(.*)$", Pattern.DOTALL);
Matcher latLonMatcher = latLonPattern.matcher(strTail);
if (!latLonMatcher.matches()) {
@ -215,7 +226,7 @@ public class AprsDataPositionReport implements AprsData {
// read latitude/symbol_table/longitude/symbol
byte[] tail = new byte[buffer.remaining()];
buffer.get(tail);
String strTail = new String(tail);
String strTail = new String(tail, StandardCharsets.UTF_8);
Pattern latLonPattern = Pattern.compile(
"^" +
"(?:.*)?" + // optional timestamp
@ -284,6 +295,31 @@ public class AprsDataPositionReport implements AprsData {
_position.isAltitudeEnabled = false;
_position.hasAltitude = false;
}
// read PHG range
Pattern phgPattern = Pattern.compile("^.*(PHG\\d{4}).*$", Pattern.DOTALL);
Matcher phgMatcher = phgPattern.matcher(strTail);
if (phgMatcher.matches()) {
String phg = phgMatcher.group(1);
if (phg != null) {
strTail = strTail.replaceAll(phg, "");
_position.directivityDeg = AprsTools.phgToDirectivityDegrees(phg);
_position.rangeMiles = AprsTools.phgToRangeMiles(phg);
}
}
// read RNG range
Pattern rngPattern = Pattern.compile("^.*(RNG\\d{4}).*$", Pattern.DOTALL);
Matcher rngMatcher = rngPattern.matcher(strTail);
if (rngMatcher.matches()) {
String rng = rngMatcher.group(1);
if (rng != null) {
strTail = strTail.replaceAll(rng, "");
_position.rangeMiles = Double.parseDouble(rng.substring(3));
_position.directivityDeg = 0;
}
}
// read comment until the end
_position.comment = TextTools.stripNulls(strTail);
return true;

Wyświetl plik

@ -7,6 +7,7 @@ import com.radio.codec2talkie.tools.TextTools;
import com.radio.codec2talkie.tools.UnitTools;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Locale;
@ -62,6 +63,16 @@ public class AprsDataPositionReportMicE implements AprsData {
put(0b001, "custom_6");
}};
@Override
public boolean isPositionReport() {
return true;
}
@Override
public boolean isTextMessage() {
return false;
}
@Override
public void fromPosition(Position position) {
_isValid = false;
@ -222,9 +233,9 @@ public class AprsDataPositionReportMicE implements AprsData {
if (infoData.length > 11 && infoData[11] == '}') {
_position.hasAltitude = true;
_position.altitudeMeters = ((infoData[8] - 33) * 91 * 91 + (infoData[9] - 33) * 91 + (infoData[10] - 33)) - 10000;
_position.comment = TextTools.stripNulls(new String(Arrays.copyOfRange(infoData, 12, infoData.length)));
_position.comment = TextTools.stripNulls(new String(Arrays.copyOfRange(infoData, 12, infoData.length), StandardCharsets.UTF_8));
} else {
_position.comment = TextTools.stripNulls(new String(Arrays.copyOfRange(infoData, 8, infoData.length)));
_position.comment = TextTools.stripNulls(new String(Arrays.copyOfRange(infoData, 8, infoData.length), StandardCharsets.UTF_8));
}
_position.maidenHead = UnitTools.decimalToMaidenhead(_position.latitude, _position.longitude);

Wyświetl plik

@ -4,6 +4,10 @@ import com.radio.codec2talkie.protocol.message.TextMessage;
import com.radio.codec2talkie.protocol.position.Position;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class AprsDataTextMessage implements AprsData {
@ -11,9 +15,20 @@ public class AprsDataTextMessage implements AprsData {
public String dstCallsign;
public String digipath;
public String textMessage;
public Integer ackId;
private boolean _isValid;
@Override
public boolean isPositionReport() {
return false;
}
@Override
public boolean isTextMessage() {
return true;
}
@Override
public void fromPosition(Position position) {
_isValid = false;
@ -24,6 +39,7 @@ public class AprsDataTextMessage implements AprsData {
this.dstCallsign = textMessage.dst;
this.textMessage = textMessage.text;
this.digipath = textMessage.digipath;
this.ackId = textMessage.ackId;
_isValid = true;
}
@ -39,6 +55,7 @@ public class AprsDataTextMessage implements AprsData {
textMessage.dst = this.dstCallsign;
textMessage.digipath = this.digipath;
textMessage.text = this.textMessage;
textMessage.ackId = this.ackId;
return textMessage;
}
@ -49,28 +66,66 @@ public class AprsDataTextMessage implements AprsData {
this.digipath = digipath;
this.srcCallsign = srcCallsign;
ByteBuffer buffer = ByteBuffer.wrap(infoData);
// callsign, trim ending spaces
byte[] callsign = new byte[9];
buffer.get(callsign);
this.dstCallsign = new String(callsign).replaceAll("\\s+$", "");
// ':' separator
byte b = buffer.get();
if (b != ':') return;
// message
byte[] message = new byte[buffer.remaining()];
buffer.get(message);
textMessage = new String(message);
// TODO, message id: {xxxxx
_isValid = true;
String stringMessage = new String(message, StandardCharsets.UTF_8);
// ack/rej message
this.ackId = 0;
Pattern p = Pattern.compile("^(ack|rej)(\n+){1,5}$", Pattern.DOTALL);
Matcher m = p.matcher(stringMessage);
if (m.find()) {
String type = m.group(1);
if (type != null) {
String ackIdStr = m.group(2);
if (ackIdStr != null)
this.ackId = Integer.parseInt(ackIdStr);
}
} else {
// message requires acknowledge {xxxxx (for auto ack)
p = Pattern.compile("^.+[{](\\d+){1,5}$", Pattern.DOTALL);
m = p.matcher(stringMessage);
if (m.find()) {
this.textMessage = m.group(1);
String ackNumStr = m.group(2);
if (ackNumStr != null)
this.ackId = Integer.parseInt(ackNumStr);
} else {
this.textMessage = stringMessage;
}
}
// TODO, telemetry, make subclass from message, extend and extract values
if (this.textMessage != null)
_isValid = !isTelemetry(this.textMessage);
}
@Override
public byte[] toBinary() {
return String.format(":%-9s:%s", dstCallsign, textMessage).getBytes();
return (ackId > 0)
? String.format(Locale.US, ":%-9s:%s{%d", dstCallsign, textMessage, ackId).getBytes()
: String.format(":%-9s:%s", dstCallsign, textMessage).getBytes();
}
@Override
public boolean isValid() {
return _isValid;
}
private boolean isTelemetry(String textMessage) {
Pattern p = Pattern.compile("^(EQNS|PARM|UNIT|BITS)[.].+$", Pattern.DOTALL);
Matcher m = p.matcher(textMessage);
return m.matches();
}
}

Wyświetl plik

@ -11,7 +11,9 @@ public class AprsDataType {
POSITION_WITH_TIMESTAMP_NO_MSG,
POSITION_WITHOUT_TIMESTAMP_NO_MSG,
OBJECT,
STATUS
ITEM,
STATUS,
THIRD_PARTY
}
private final DataType _dataType;
@ -40,7 +42,12 @@ public class AprsDataType {
_dataType == DataType.POSITION_WITH_TIMESTAMP_MSG ||
_dataType == DataType.POSITION_WITHOUT_TIMESTAMP_MSG ||
_dataType == DataType.POSITION_WITH_TIMESTAMP_NO_MSG ||
_dataType == DataType.POSITION_WITHOUT_TIMESTAMP_NO_MSG);
_dataType == DataType.POSITION_WITHOUT_TIMESTAMP_NO_MSG ||
_dataType == DataType.OBJECT);
}
public boolean isObject() {
return _dataType == DataType.OBJECT;
}
public boolean isTextMessage() {
@ -62,8 +69,12 @@ public class AprsDataType {
return DataType.POSITION_WITH_TIMESTAMP_NO_MSG;
} else if (ident == ';') {
return DataType.OBJECT;
} else if (ident == ')') {
return DataType.ITEM;
} else if (ident == '>') {
return DataType.STATUS;
} else if (ident == '}') {
return DataType.THIRD_PARTY;
} else {
return DataType.UNKNOWN;
}
@ -84,8 +95,12 @@ public class AprsDataType {
return '/';
} else if (dataType == DataType.OBJECT) {
return ';';
} else if (dataType == DataType.ITEM) {
return ')';
} else if (dataType == DataType.STATUS) {
return '>';
} else if (dataType == DataType.THIRD_PARTY) {
return '}';
} else {
return 0;
}

Wyświetl plik

@ -0,0 +1,28 @@
package com.radio.codec2talkie.protocol.aprs;
import android.util.Log;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class AprsItem extends AprsDataPositionReport {
private static final String TAG = AprsItem.class.getSimpleName();
@Override
public void fromBinary(String srcCallsign, String dstCallsign, String digipath, byte[] infoData) {
String info = new String(infoData);
Pattern itemPattern = Pattern.compile("^([^_!]{3,9})(!)(.+)$", Pattern.DOTALL);
Matcher itemMatcher = itemPattern.matcher(info);
if (!itemMatcher.matches()) return;
String posSrcCallsign = itemMatcher.group(1);
if (posSrcCallsign == null) return;
String itemState = itemMatcher.group(2);
if (itemState == null) return;
String positionInfoData = itemMatcher.group(3);
if (positionInfoData == null) return;
super.fromBinary(posSrcCallsign, dstCallsign, digipath, positionInfoData.getBytes());
}
}

Wyświetl plik

@ -0,0 +1,30 @@
package com.radio.codec2talkie.protocol.aprs;
import android.util.Log;
import java.nio.ByteBuffer;
public class AprsObject extends AprsDataPositionReport {
private static final String TAG = AprsObject.class.getSimpleName();
@Override
public void fromBinary(String srcCallsign, String dstCallsign, String digipath, byte[] infoData) {
if (infoData.length <= 9) return;
ByteBuffer buffer = ByteBuffer.wrap(infoData);
// callsign
byte[] objectCallsign = new byte[9];
buffer.get(objectCallsign, 0, 9);
// process only live objects
byte isLive = buffer.get();
if (isLive != '*') return;
byte[] positionInfoData = new byte[buffer.remaining()];
buffer.get(positionInfoData);
String positionSrcCallsign = new String(objectCallsign).trim();
super.fromBinary(positionSrcCallsign, dstCallsign, digipath, positionInfoData);
}
}

Wyświetl plik

@ -0,0 +1,61 @@
package com.radio.codec2talkie.protocol.aprs;
import com.radio.codec2talkie.protocol.aprs.tools.AprsIsData;
import com.radio.codec2talkie.protocol.message.TextMessage;
import com.radio.codec2talkie.protocol.position.Position;
public class AprsThirdParty implements AprsData {
private AprsData _aprsData;
@Override
public boolean isPositionReport() {
return _aprsData.isPositionReport();
}
@Override
public boolean isTextMessage() {
return _aprsData.isTextMessage();
}
@Override
public void fromPosition(Position position) {
_aprsData.fromPosition(position);
}
@Override
public void fromTextMessage(TextMessage textMessage) {
_aprsData.fromTextMessage(textMessage);
}
@Override
public Position toPosition() {
return _aprsData.toPosition();
}
@Override
public TextMessage toTextMessage() {
return _aprsData.toTextMessage();
}
@Override
public void fromBinary(String srcCallsign, String dstCallsign, String digipath, byte[] infoData) {
AprsIsData data = AprsIsData.fromString(new String(infoData));
if (data == null) return;
AprsDataType aprsDataType = new AprsDataType((char)data.data.charAt(0));
_aprsData = AprsDataFactory.create(aprsDataType);
if (_aprsData == null) return;
_aprsData.fromBinary(data.src, data.dst, data.rawDigipath, data.data.getBytes());
}
@Override
public byte[] toBinary() {
return _aprsData.toBinary();
}
@Override
public boolean isValid() {
if (_aprsData == null) return false;
return _aprsData.isValid();
}
}

Wyświetl plik

@ -0,0 +1,53 @@
package com.radio.codec2talkie.protocol.aprs.tools;
import java.util.Map;
import java.util.TreeMap;
public class AprsHeardList {
private static class AprsHeardListItem {
public long timestamp;
public String callsign;
public AprsHeardListItem(long timestamp, String callsign) {
this.timestamp = timestamp;
this.callsign = callsign;
}
}
private final int _keepSeconds;
private final TreeMap<String, AprsHeardListItem> _data = new TreeMap<>();
public AprsHeardList(int keepSeconds) {
_keepSeconds = keepSeconds;
}
public void add(String callsign) {
synchronized (_data) {
AprsHeardListItem heardItem = _data.get(callsign);
if (heardItem == null) {
AprsHeardListItem newHeardItem = new AprsHeardListItem(System.currentTimeMillis(), callsign);
_data.put(callsign, newHeardItem);
} else {
heardItem.timestamp = System.currentTimeMillis();
}
cleanup();
}
}
public boolean contains(String callsign) {
synchronized (_data) {
cleanup();
return _data.containsKey(callsign);
}
}
private void cleanup() {
long removeOlderThan = System.currentTimeMillis() - _keepSeconds * 1000L;
for (Map.Entry<String, AprsHeardListItem> entryElement : _data.entrySet()) {
if (entryElement.getValue().timestamp < removeOlderThan) {
_data.remove(entryElement.getKey());
}
}
}
}

Wyświetl plik

@ -1,7 +1,5 @@
package com.radio.codec2talkie.protocol.aprs.tools;
import android.util.Log;
import androidx.annotation.NonNull;
import kotlin.text.Regex;
@ -10,8 +8,10 @@ import kotlin.text.RegexOption;
public class AprsIsData {
public String src;
public String dst;
public String path;
public String digipath;
public String rawDigipath;
public String data;
public AprsIsData thirdParty;
public AprsIsData() {
}
@ -19,21 +19,51 @@ public class AprsIsData {
public AprsIsData(String src, String dst, String path, String data) {
this.src = src;
this.dst = dst;
this.path = path;
this.digipath = path;
this.data = data;
// handle third party packet
if (data.length() > 10 && data.startsWith("}")) {
thirdParty = AprsIsData.fromString(data.substring(1));
}
}
public boolean hasThirdParty() {
return thirdParty != null;
}
@NonNull
public String toString() {
public String convertToString(boolean useRawPath) {
String result = src + ">";
if (dst != null && dst.length() > 0)
result += dst;
if (path != null && path.length() > 0)
result += "," + path;
if (useRawPath && rawDigipath != null && rawDigipath.length() > 0)
result += "," + rawDigipath;
else if (digipath != null && digipath.length() > 0)
result += "," + digipath;
result += ":" + data;
return result;
}
public boolean isEligibleForRxGate() {
boolean hasNoGate = rawDigipath.contains("TCPIP") ||
rawDigipath.contains("TCPXX") ||
rawDigipath.contains("NOGATE") ||
rawDigipath.contains("RFONLY");
boolean thirdPartyHasNoGate = thirdParty != null &&
(thirdParty.rawDigipath.contains("TCPIP") ||
thirdParty.rawDigipath.contains("TCPXX"));
// do not gate TCPIP/NOGATE, queries and third party tcp ip packets
return !hasNoGate && !data.startsWith("?") && !thirdPartyHasNoGate;
}
public boolean isEligibleForTxGate() {
return !(rawDigipath.contains("TCPXX") ||
rawDigipath.contains("NOGATE") ||
rawDigipath.contains("RFONLY"));
}
public static AprsIsData fromString(String textData) {
AprsIsData aprsIsData = new AprsIsData();
// N0CALL>PATH:DATA
@ -47,8 +77,12 @@ public class AprsIsData {
String[] path = digipathData[0].split(",");
if (path.length == 0) return null;
aprsIsData.dst = path[0];
aprsIsData.path = joinTail(path, ",", "^WIDE.+$");
aprsIsData.digipath = joinTail(path, ",", "^WIDE.+$");
aprsIsData.rawDigipath = joinTail(path, ",", ".*");
aprsIsData.data = joinTail(digipathData, ":", ".*");
if (aprsIsData.data.length() > 10 && aprsIsData.data.startsWith("}")) {
aprsIsData.thirdParty = AprsIsData.fromString(aprsIsData.data.substring(1));
}
return aprsIsData;
}

Wyświetl plik

@ -5,6 +5,7 @@ import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.drawable.BitmapDrawable;
import android.media.audiofx.DynamicsProcessing;
import android.util.DisplayMetrics;
import android.widget.ImageView;
@ -12,6 +13,8 @@ import com.radio.codec2talkie.R;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
public class AprsSymbolTable {
@ -24,11 +27,11 @@ public class AprsSymbolTable {
private final ArrayList<Bitmap> _secondaryTableIconsLarge;
private final ArrayList<Bitmap> _overlayTableIconsLarge;
private final int _cellWidth = 24;
private final int _cellHeight = 24;
private static final int _cellWidth = 24;
private static final int _cellHeight = 24;
private final int _cellWidthLarge = 64;
private final int _cellHeightLarge = 64;
private static final int _cellWidthLarge = 64;
private static final int _cellHeightLarge = 64;
private static final int _cntWidth = 16;
private static final int _cntHeight = 6;
@ -39,6 +42,10 @@ public class AprsSymbolTable {
private static AprsSymbolTable _symbolTable;
private static final List<String> _symbolsToRotate = Arrays.asList("/'", "/(", "/*", "/<", "/=",
"/C", "/F", "/P", "/U", "/X", "/Y", "/[", "/^", "/a", "/b", "/e", "/f", "/g", "/j",
"/k", "/p", "/s", "/u", "/v", "/>", "\\k", "\\u", "\\v", "\\>");
public static AprsSymbolTable getInstance(Context context) {
if (_symbolTable == null) {
synchronized (AprsSymbolTable.class) {
@ -89,17 +96,22 @@ public class AprsSymbolTable {
if (symbolIconIndex < 0 || symbolIconIndex >= (_cntWidth * _cntHeight)) return null;
if (table == '/') {
return _primaryTable.get(symbolIconIndex);
} else if (table == '\\') {
return _secondaryTable.get(symbolIconIndex);
try {
if (table == '/') {
return _primaryTable.get(symbolIconIndex);
} else if (table == '\\') {
return _secondaryTable.get(symbolIconIndex);
}
} catch (IndexOutOfBoundsException e) {
e.printStackTrace();
return null;
}
if (overlayIconIndex < 0 || overlayIconIndex >= (_cntWidth * _cntHeight)) return null;
Bitmap icon = _secondaryTable.get(symbolIconIndex);
Bitmap overlayIcon = _overlayTable.get(overlayIconIndex);
Bitmap bmOverlay = Bitmap.createBitmap(icon.getWidth(), icon.getHeight(), null);
Bitmap bmOverlay = Bitmap.createBitmap(icon.getWidth(), icon.getHeight(), Bitmap.Config.ARGB_8888);
bmOverlay.setDensity(DisplayMetrics.DENSITY_DEFAULT);
Canvas canvas = new Canvas(bmOverlay);
@ -154,7 +166,7 @@ public class AprsSymbolTable {
ArrayList<Bitmap> secondaryTableIcons = Load(imageViewSecondary, _selectorIconDim, _selectorIconDim, _cntWidth, _cntHeight);
primaryTableIcons.addAll(secondaryTableIcons);
Bitmap bmOverlay = Bitmap.createBitmap(_selectorIconDim*cntX, _selectorIconDim*cntY*2, null);
Bitmap bmOverlay = Bitmap.createBitmap(_selectorIconDim*cntX, _selectorIconDim*cntY*2, Bitmap.Config.ARGB_8888);
bmOverlay.setDensity(DisplayMetrics.DENSITY_DEFAULT);
Canvas canvas = new Canvas(bmOverlay);
Paint paint = new Paint(Paint.FILTER_BITMAP_FLAG);
@ -169,4 +181,8 @@ public class AprsSymbolTable {
}
return bmOverlay;
}
public static boolean needsRotation(String symbol) {
return _symbolsToRotate.contains(symbol);
}
}

Wyświetl plik

@ -11,4 +11,20 @@ public class AprsTools {
}
return new String(buffer);
}
public static int phgToDirectivityDegrees(String phg) {
int d = phg.charAt(6) - '0';
if (d > 8) return 0;
return 45*d;
}
public static double phgToRangeMiles(String phg) {
int p = phg.charAt(3) - '0';
double power = p*p;
int h = phg.charAt(4) - '0';
double haat = 10.0 * Math.pow(2.0, h);
int g = phg.charAt(5) - '0';
double gain = Math.pow(10.0, g / 10.0);
return Math.sqrt(2.0 * haat * Math.sqrt((power / 10.0) * (gain / 2.0)));
}
}

Wyświetl plik

@ -12,6 +12,13 @@ public class AX25Callsign {
public boolean isValid;
public boolean isLast = false;
public AX25Callsign() {}
public AX25Callsign(String callsign, String ssid) {
this.callsign = callsign;
this.ssid = Integer.parseInt(ssid);
}
public static String formatCallsign(String callsign, String ssid) {
return String.format("%s-%s", callsign, ssid);
}

Wyświetl plik

@ -2,11 +2,14 @@ package com.radio.codec2talkie.protocol.ax25;
import androidx.annotation.NonNull;
import com.radio.codec2talkie.protocol.aprs.tools.AprsIsData;
import com.radio.codec2talkie.tools.DebugTools;
import com.radio.codec2talkie.tools.TextTools;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
public class AX25Packet {
@ -16,7 +19,6 @@ public class AX25Packet {
public String src;
public String dst;
public String digipath;
public int codec2Mode;
public boolean isAudio;
public byte[] rawData;
public boolean isValid;
@ -28,6 +30,21 @@ public class AX25Packet {
public void fromBinary(byte[] data) {
isValid = false;
if (data == null) return;
// lora text packet with 0x3c,0xff,0x01 prefix
if (data.length > 3 && data[0] == (byte)0x3c && data[1] == (byte)0xff && data[2] == (byte)0x01) {
String rawText = new String(Arrays.copyOfRange(data, 3, data.length), StandardCharsets.US_ASCII);
AprsIsData textPacket = AprsIsData.fromString(rawText);
if (textPacket != null) {
src = textPacket.src;
dst = textPacket.dst;
digipath = textPacket.rawDigipath;
rawData = textPacket.data.getBytes(StandardCharsets.US_ASCII);
isAudio = false;
isValid = true;
return;
}
}
// binary packet
ByteBuffer buffer = ByteBuffer.wrap(data);
try {
// dst
@ -66,7 +83,6 @@ public class AX25Packet {
byte ax25Pid = buffer.get();
if (ax25Pid == AX25PID_AUDIO) {
isAudio = true;
codec2Mode = buffer.get();
} else if (ax25Pid == AX25PID_NO_LAYER3) {
isAudio = false;
} else {
@ -81,6 +97,18 @@ public class AX25Packet {
}
}
public byte[] toTextBinary() {
byte[] packetContent = toString().getBytes(StandardCharsets.US_ASCII);
// lora aprs prefix 0x3c,0xff,0x01
ByteBuffer textPacketBuffer = ByteBuffer.allocateDirect(packetContent.length + 3);
textPacketBuffer.put((byte)0x3c).put((byte)0xff).put((byte)0x01);
textPacketBuffer.put(packetContent);
textPacketBuffer.flip();
byte[] ax25Frame = new byte[textPacketBuffer.remaining()];
textPacketBuffer.get(ax25Frame);
return ax25Frame;
}
public byte[] toBinary() {
ByteBuffer buffer = ByteBuffer.allocate(MaximumSize);
String[] rptCallsigns = new String[] {};
@ -113,7 +141,6 @@ public class AX25Packet {
buffer.put(AX25CTRL_UI);
if (isAudio) {
buffer.put(AX25PID_AUDIO);
buffer.put((byte)codec2Mode);
} else {
buffer.put(AX25PID_NO_LAYER3);
}

Wyświetl plik

@ -2,20 +2,41 @@ package com.radio.codec2talkie.protocol.message;
import com.radio.codec2talkie.storage.message.MessageItem;
import java.util.Locale;
public class TextMessage {
public String src;
public String dst;
public String digipath;
public String text;
public Integer ackId;
public MessageItem toMessageItem(boolean isTransmit) {
MessageItem messageItem = new MessageItem();
messageItem.setTimestampEpoch(System.currentTimeMillis());
messageItem.setNeedsAck(false); // TODO
messageItem.setIsTransmit(isTransmit);
messageItem.setSrcCallsign(this.src);
messageItem.setDstCallsign(this.dst);
messageItem.setMessage(this.text);
messageItem.setAckId(this.ackId);
messageItem.setIsAcknowledged(false);
messageItem.setRetryCnt(0);
return messageItem;
}
public boolean isAck() {
return this.text != null &&
this.text.toLowerCase(Locale.ROOT).equals("ack") &&
this.ackId > 0;
}
public boolean isRej() {
return this.text != null &&
this.text.toLowerCase(Locale.ROOT).equals("rej") &&
this.ackId > 0;
}
public boolean isAutoReply() {
return isAck() || isRej();
}
}

Wyświetl plik

@ -3,9 +3,13 @@ package com.radio.codec2talkie.protocol.position;
import android.location.Location;
import com.radio.codec2talkie.storage.position.PositionItem;
import com.radio.codec2talkie.storage.station.StationItem;
import com.radio.codec2talkie.tools.UnitTools;
public class Position {
public final static double DEFAULT_RANGE_MILES = 6.0;
public long timestampEpochMs;
public String srcCallsign;
public String dstCallsign;
@ -26,6 +30,8 @@ public class Position {
public boolean isAltitudeEnabled;
public boolean hasBearing;
public boolean hasAltitude;
public double rangeMiles;
public int directivityDeg;
public boolean hasSpeed;
public static Position fromLocation(Location location) {
@ -40,6 +46,8 @@ public class Position {
position.hasAltitude = location.hasAltitude();
position.hasSpeed = location.hasSpeed();
position.maidenHead = UnitTools.decimalToMaidenhead(position.latitude, position.longitude);
position.rangeMiles = 0.0;
position.directivityDeg = 0; // 0 - omni
return position;
}
@ -92,6 +100,7 @@ public class Position {
positionItem.setIsTransmit(isTransmit);
positionItem.setSrcCallsign(srcCallsign);
positionItem.setDstCallsign(dstCallsign);
positionItem.setDigipath(digipath);
positionItem.setLatitude(latitude);
positionItem.setLongitude(longitude);
positionItem.setMaidenHead(maidenHead);
@ -102,6 +111,28 @@ public class Position {
positionItem.setComment(comment);
positionItem.setSymbolCode(symbolCode);
positionItem.setPrivacyLevel(privacyLevel);
positionItem.setDirectivityDeg(directivityDeg);
positionItem.setRangeMiles(rangeMiles);
return positionItem;
}
public StationItem toStationItem() {
StationItem stationItem = new StationItem(srcCallsign);
stationItem.setTimestampEpoch(System.currentTimeMillis());
stationItem.setDstCallsign(dstCallsign);
stationItem.setDigipath(digipath);
stationItem.setLatitude(latitude);
stationItem.setLongitude(longitude);
stationItem.setMaidenHead(maidenHead);
stationItem.setAltitudeMeters(altitudeMeters);
stationItem.setBearingDegrees(bearingDegrees);
stationItem.setSpeedMetersPerSecond(speedMetersPerSecond);
stationItem.setStatus(status);
stationItem.setComment(comment);
stationItem.setSymbolCode(symbolCode);
stationItem.setPrivacyLevel(privacyLevel);
stationItem.setDirectivityDeg(directivityDeg);
stationItem.setRangeMiles(rangeMiles);
return stationItem;
}
}

Wyświetl plik

@ -22,6 +22,8 @@ import com.radio.codec2talkie.protocol.aprs.tools.AprsSymbolTable;
public class AprsSymbolSelectionActivity extends AppCompatActivity {
private static final String TAG = AprsSymbolSelectionActivity.class.getSimpleName();
private SharedPreferences _sharedPreferences;
private ImageView _currentSelectionView;
@ -68,9 +70,12 @@ public class AprsSymbolSelectionActivity extends AppCompatActivity {
float y = event.getY();
_currentSymbolCode = AprsSymbolTable.getSymbolFromCoordinate(x, y, v.getWidth(), v.getHeight());
_currentSelectionText.setText(_currentSymbolCode);
Log.i("---", _currentSymbolCode);
Log.i(TAG, "Selected symbol: " + _currentSymbolCode);
Bitmap currentSymbolBitmap = AprsSymbolTable.getInstance(this).bitmapFromSymbol(_currentSymbolCode, true);
_currentSelectionView.setImageBitmap(currentSymbolBitmap);
if (currentSymbolBitmap == null)
Log.e(TAG, "Cannot select symbol");
else
_currentSelectionView.setImageBitmap(currentSymbolBitmap);
v.performClick();
}
return true;

Wyświetl plik

@ -9,7 +9,6 @@ public final class PreferenceKeys {
public static String PORTS_USB_PARITY = "ports_usb_parity";
public static String PORTS_USB_DTR = "ports_usb_dtr";
public static String PORTS_USB_RTS = "ports_usb_rts";
public static String PORTS_BT_CLIENT_NAME = "ports_bt_client_name";
public static String PORTS_TCP_IP_ADDRESS = "ports_tcp_ip_address";
@ -30,10 +29,19 @@ public final class PreferenceKeys {
public static String PORTS_SOUND_MODEM_FREEDV_SQUELCH_SNR="ports_sound_modem_freedv_squelch_snr";
public static String PORTS_SOUND_MODEM_FREEDV_DATA_MODE="ports_sound_modem_freedv_data_mode";
public static String CODEC2_MODE = "codec2_mode";
public static String CUSTOM_PREFIX_ENABLED = "custom_prefix_enabled";
public static String CUSTOM_PREFIX = "custom_prefix";
public static String CODEC_TYPE = "codec_type";
public static String CODEC2_RECORDING_ENABLED = "codec2_recording_enabled";
public static String CODEC2_MODE = "codec2_mode";
public static String CODEC2_TX_FRAME_MAX_SIZE = "codec2_tx_frame_max_size";
public static String OPUS_BIT_RATE = "opus_bit_rate";
public static String OPUS_FRAME_SIZE = "opus_frame_size";
public static String OPUS_COMPLEXITY = "opus_complexity";
public static String KISS_ENABLED = "kiss_enable";
public static String KISS_BUFFERED_ENABLED = "kiss_buffered_enable";
public static String KISS_PARROT = "kiss_parrot_enable";
@ -47,13 +55,19 @@ public final class PreferenceKeys {
public static String KISS_SCRAMBLER_ITERATIONS = "kiss_scrambler_iterations";
public static String KISS_EXTENSIONS_ENABLED = "kiss_extensions_enable";
public static String KISS_EXTENSIONS_RADIO_MOD = "kiss_extension_radio_mod";
public static String KISS_EXTENSIONS_RADIO_SPLIT_FREQ = "kiss_extension_radio_split_freq";
public static String KISS_EXTENSIONS_RADIO_FREQUENCY = "kiss_extension_radio_frequency";
public static String KISS_EXTENSIONS_RADIO_FREQUENCY_TX = "kiss_extension_radio_frequency_tx";
public static String KISS_EXTENSIONS_RADIO_BANDWIDTH = "kiss_extension_radio_bandwidth";
public static String KISS_EXTENSIONS_RADIO_POWER = "kiss_extension_radio_power";
public static String KISS_EXTENSIONS_RADIO_SF = "kiss_extension_radio_sf";
public static String KISS_EXTENSIONS_RADIO_CR = "kiss_extension_radio_cr";
public static String KISS_EXTENSIONS_RADIO_SYNC = "kiss_extension_radio_sync";
public static String KISS_EXTENSIONS_RADIO_CRC = "kiss_extension_radio_crc";
public static String KISS_EXTENSIONS_RADIO_FSK_BIT_RATE = "kiss_extension_radio_fsk_bit_rate";
public static String KISS_EXTENSIONS_RADIO_FSK_FREQ_DEV = "kiss_extension_radio_fsk_freq_dev";
public static String KISS_EXTENSIONS_RADIO_FSK_RX_BW = "kiss_extension_radio_fsk_rx_bw";
public static String KISS_EXTENSIONS_ACTION_REBOOT_REQUESTED = "com.radio.codec2talkie.MODEM_REBOOT";
@ -67,6 +81,7 @@ public final class PreferenceKeys {
public static String APP_AUDIO_DESTINATION = "app_audio_destination";
public static String AX25_VOAX25_ENABLE = "aprs_voax25_enable";
public static String AX25_TEXT_PACKETS_ENABLE = "aprs_text_packets_enable";
public static String AX25_CALLSIGN = "aprs_callsign";
public static String AX25_SSID = "aprs_ssid";
public static String AX25_DIGIPATH = "aprs_digipath";
@ -100,6 +115,7 @@ public final class PreferenceKeys {
public static String APRS_IS_ENABLE="aprs_is_enable";
public static String APRS_IS_CODE = "aprs_is_code";
public static String APRS_IS_TCPIP_SERVER = "aprs_is_tcpip_server";
public static String APRS_IS_TCPIP_SERVER_PORT = "aprs_is_tcpip_server_port";
public static String APRS_IS_ENABLE_RX_GATE = "aprs_is_enable_rx_gate";
public static String APRS_IS_ENABLE_TX_GATE = "aprs_is_enable_tx_gate";
public static String APRS_IS_ENABLE_SELF = "aprs_is_enable_self";

Wyświetl plik

@ -38,7 +38,9 @@ public class SettingsActivity extends AppCompatActivity
"ports_tcp_ip_retry_count",
"ports_tcp_ip_retry_delay",
"ports_sound_modem_preamble",
"ports_sound_modem_ptt_off_delay_ms"
"ports_sound_modem_ptt_off_delay_ms",
"aprs_is_tcpip_server_port",
"opus_bit_rate"
};
private static final String[] _signedDecimalSettings = {
@ -115,6 +117,15 @@ public class SettingsActivity extends AppCompatActivity
}
}
public static class SettingsTncExtendedFragment extends PreferenceFragmentCompat
{
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
setPreferencesFromResource(R.xml.preferences_tnc_extended, null);
setNumberInputType(getPreferenceManager());
}
}
public static class SettingsTcpIpFragment extends PreferenceFragmentCompat
{
@Override
@ -142,6 +153,15 @@ public class SettingsActivity extends AppCompatActivity
}
}
public static class SettingsCodecFragment extends PreferenceFragmentCompat
{
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
setPreferencesFromResource(R.xml.preferences_codec, null);
setNumberInputType(getPreferenceManager());
}
}
public static class SettingsAprsLocationFragment extends PreferenceFragmentCompat
{
@Override

Wyświetl plik

@ -40,6 +40,14 @@ public class SettingsWrapper {
sharedPreferences.getString(PreferenceKeys.PORTS_SOUND_MODEM_TYPE, "1200").startsWith("F");
}
public static boolean isCodec2Enabled(SharedPreferences sharedPreferences) {
return sharedPreferences.getString(PreferenceKeys.CODEC_TYPE, "Codec2").equals("Codec2");
}
public static boolean isCustomPrefixEnabled(SharedPreferences sharedPreferences) {
return sharedPreferences.getBoolean(PreferenceKeys.CUSTOM_PREFIX_ENABLED, false);
}
public static int getFreeDvSoundModemModulation(SharedPreferences sharedPreferences) {
String modemType = sharedPreferences.getString(PreferenceKeys.PORTS_SOUND_MODEM_TYPE, "1200");
if (modemType.startsWith("F")) {
@ -93,6 +101,10 @@ public class SettingsWrapper {
!isFreeDvSoundModemModulation(sharedPreferences); // no voax25 in freedv
}
public static boolean isTextPacketsEnabled(SharedPreferences sharedPreferences) {
return sharedPreferences.getBoolean(PreferenceKeys.AX25_TEXT_PACKETS_ENABLE, false);
}
public static boolean isAprsIsEnabled(SharedPreferences sharedPreferences) {
return sharedPreferences.getBoolean(PreferenceKeys.APRS_IS_ENABLE, false);
}

Wyświetl plik

@ -13,13 +13,15 @@ import com.radio.codec2talkie.storage.message.MessageItem;
import com.radio.codec2talkie.storage.message.MessageItemDao;
import com.radio.codec2talkie.storage.position.PositionItem;
import com.radio.codec2talkie.storage.position.PositionItemDao;
import com.radio.codec2talkie.storage.station.StationItem;
import com.radio.codec2talkie.storage.station.StationItemDao;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@androidx.room.Database(
version = 4,
entities = {LogItem.class, MessageItem.class, PositionItem.class},
version = 13,
entities = {LogItem.class, MessageItem.class, PositionItem.class, StationItem.class},
exportSchema = false
)
public abstract class AppDatabase extends RoomDatabase {
@ -29,6 +31,7 @@ public abstract class AppDatabase extends RoomDatabase {
public abstract LogItemDao logItemDao();
public abstract MessageItemDao messageItemDao();
public abstract PositionItemDao positionItemDao();
public abstract StationItemDao stationitemDao();
private static AppDatabase _db;
private static ExecutorService _executor;

Wyświetl plik

@ -4,7 +4,9 @@ import androidx.room.Entity;
import androidx.room.Index;
import androidx.room.PrimaryKey;
@Entity
import com.radio.codec2talkie.storage.station.StationItem;
@Entity(indices = {@Index(value = {"id", "srcCallsign"}, unique = true)})
public class LogItem {
@PrimaryKey(autoGenerate = true)
@ -45,4 +47,12 @@ public class LogItem {
public void setLogLine(String logLine) { this.logLine = logLine; }
public void setIsTransmit(boolean isTransmit) { this.isTransmit = isTransmit; }
public StationItem toStationItem() {
StationItem stationItem = new StationItem(srcCallsign);
stationItem.setTimestampEpoch(System.currentTimeMillis());
stationItem.setDstCallsign(stationItem.dstCallsign);
stationItem.setLogLine(logLine);
return stationItem;
}
}

Wyświetl plik

@ -3,7 +3,6 @@ package com.radio.codec2talkie.storage.log;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
@ -18,19 +17,20 @@ import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.radio.codec2talkie.MainActivity;
import com.radio.codec2talkie.R;
import com.radio.codec2talkie.storage.log.group.LogItemGroupAdapter;
import com.radio.codec2talkie.storage.station.StationItemAdapter;
import com.radio.codec2talkie.storage.position.PositionItemViewModel;
import com.radio.codec2talkie.storage.station.StationItemViewModel;
import java.util.List;
public class LogItemActivity extends AppCompatActivity {
private static final String TAG = LogItemActivity.class.getSimpleName();
private String _groupName;
private String _stationName;
private LogItemViewModel _logItemViewModel;
private PositionItemViewModel _positionItemViewModel;
private StationItemViewModel _stationItemViewModel;
private LiveData<List<LogItem>> _logItemLiveData;
@ -38,66 +38,67 @@ public class LogItemActivity extends AppCompatActivity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_log_view);
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) actionBar.setDisplayHomeAsUpEnabled(true);
// get group name to decide if filtering should be enabled
// get station name to decide if filtering should be enabled
Bundle bundle = getIntent().getExtras();
_groupName = null;
_stationName = null;
if (bundle != null) {
_groupName = (String)bundle.get("groupName");
_stationName = (String)bundle.get("stationName");
}
// view models
_logItemViewModel = new ViewModelProvider(this).get(LogItemViewModel.class);
_positionItemViewModel = new ViewModelProvider(this).get(PositionItemViewModel.class);
_stationItemViewModel = new ViewModelProvider(this).get(StationItemViewModel.class);
// log items
RecyclerView logItemRecyclerView = findViewById(R.id.log_item_recyclerview);
logItemRecyclerView.setHasFixedSize(true);
// log lines list adapter
final LogItemAdapter adapter = new LogItemAdapter(new LogItemAdapter.LogItemDiff(), _groupName == null);
final LogItemAdapter adapter = new LogItemAdapter(new LogItemAdapter.LogItemDiff(), _stationName == null);
logItemRecyclerView.setAdapter(adapter);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
linearLayoutManager.setReverseLayout(true);
logItemRecyclerView.setLayoutManager(linearLayoutManager);
logItemRecyclerView.addItemDecoration(new DividerItemDecoration(logItemRecyclerView.getContext(), DividerItemDecoration.VERTICAL));
// log groups
RecyclerView logItemGroupRecyclerView = findViewById(R.id.log_item_group_recyclerview);
logItemGroupRecyclerView.setHasFixedSize(true);
// stations
RecyclerView stationsRecyclerView = findViewById(R.id.log_item_group_recyclerview);
stationsRecyclerView.setHasFixedSize(true);
// groups adapter
final LogItemGroupAdapter adapterGroup = new LogItemGroupAdapter(new LogItemGroupAdapter.LogItemGroupDiff());
adapterGroup.setClickListener(v -> {
// stations adapter
final StationItemAdapter stationsAdapter = new StationItemAdapter(new StationItemAdapter.StationItemDiff());
stationsAdapter.setClickListener(v -> {
TextView itemView = v.findViewById(R.id.log_view_group_item_title);
//_logItemLiveData.removeObserver(adapter::submitList);
_logItemLiveData.removeObservers(this);
_groupName = itemView.getText().toString();
_logItemLiveData = _logItemViewModel.getData(_groupName);
_stationName = itemView.getText().toString();
_logItemLiveData = _logItemViewModel.getData(_stationName);
_logItemLiveData.observe(this, adapter::submitList);
setTitle(_groupName);
setTitle(_stationName);
});
logItemGroupRecyclerView.setAdapter(adapterGroup);
LinearLayoutManager linearLayoutManagerGroup = new LinearLayoutManager(this);
logItemGroupRecyclerView.setLayoutManager(linearLayoutManagerGroup);
logItemGroupRecyclerView.addItemDecoration(new DividerItemDecoration(logItemGroupRecyclerView.getContext(), DividerItemDecoration.VERTICAL));
stationsRecyclerView.setAdapter(stationsAdapter);
LinearLayoutManager linearLayoutManagerStations = new LinearLayoutManager(this);
stationsRecyclerView.setLayoutManager(linearLayoutManagerStations);
stationsRecyclerView.addItemDecoration(new DividerItemDecoration(stationsRecyclerView.getContext(), DividerItemDecoration.VERTICAL));
_logItemViewModel.getGroups().observe(this, adapterGroup::submitList);
_stationItemViewModel.getAllStationItems(false).observe(this, stationsAdapter::submitList);
// launch with filter if group name is provided
if (_groupName == null) {
logItemGroupRecyclerView.setVisibility(View.GONE);
// launch with filter if station name is provided
if (_stationName == null) {
stationsRecyclerView.setVisibility(View.GONE);
findViewById(R.id.log_item_textview).setVisibility(View.GONE);
findViewById(R.id.log_item_group_textview).setVisibility(View.GONE);
_logItemLiveData = _logItemViewModel.getAllData();
_logItemLiveData.observe(this, adapter::submitList);
setTitle(R.string.aprs_log_view_title);
} else {
_logItemLiveData = _logItemViewModel.getData(_groupName);
_logItemLiveData = _logItemViewModel.getData(_stationName);
_logItemLiveData.observe(this, adapter::submitList);
setTitle(_groupName);
setTitle(_stationName);
}
// register live scroll
@ -106,8 +107,9 @@ public class LogItemActivity extends AppCompatActivity {
public void onItemRangeInserted(int positionStart, int itemCount) {
int msgCount = adapter.getItemCount();
int lastVisiblePosition = linearLayoutManager.findLastCompletelyVisibleItemPosition();
if (lastVisiblePosition == RecyclerView.NO_POSITION || positionStart == msgCount - 1 && lastVisiblePosition == positionStart - 1) {
logItemRecyclerView.scrollToPosition(positionStart);
//Log.i(TAG, " " + positionStart + " " + itemCount + " " + lastVisiblePosition + " " + msgCount);
if (lastVisiblePosition == RecyclerView.NO_POSITION || (positionStart == msgCount - itemCount && lastVisiblePosition == positionStart - 1)) {
logItemRecyclerView.scrollToPosition(msgCount - 1);
}
}
});
@ -121,7 +123,7 @@ public class LogItemActivity extends AppCompatActivity {
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
if (_groupName != null) {
if (_stationName != null) {
menu.findItem(R.id.log_view_menu_stations).setVisible(false);
}
return super.onPrepareOptionsMenu(menu);
@ -135,35 +137,45 @@ public class LogItemActivity extends AppCompatActivity {
if (itemId == android.R.id.home) {
finish();
return true;
}
else if (itemId == R.id.log_view_menu_clear) {
deleteAll();
} else if (itemId == R.id.log_view_menu_clear_all) {
deleteLogItems(-1);
return true;
} else if (itemId == R.id.log_view_menu_clear_1h) {
deleteLogItems(1);
return true;
} else if (itemId == R.id.log_view_menu_clear_12h) {
deleteLogItems(12);
return true;
} else if (itemId == R.id.log_view_menu_clear_1d) {
deleteLogItems(24);
return true;
} else if (itemId == R.id.log_view_menu_clear_7d) {
deleteLogItems(24*7);
return true;
} else if (itemId == R.id.log_view_menu_stations) {
Intent logItemIntent = new Intent(this, LogItemActivity.class);
logItemIntent.putExtra("groupName", getString(R.string.log_view_station_history));
logItemIntent.putExtra("stationName", getString(R.string.log_view_station_history));
startActivity(logItemIntent);
return true;
}
return super.onOptionsItemSelected(item);
}
private void deleteAll() {
private void deleteLogItems(int hours) {
DialogInterface.OnClickListener deleteAllDialogClickListener = (dialog, which) -> {
if (which == DialogInterface.BUTTON_POSITIVE) {
if (_groupName == null) {
_logItemViewModel.deleteAllLogItems();
_positionItemViewModel.deleteAllPositionItems();
} else {
_logItemViewModel.deleteLogItems(_groupName);
_positionItemViewModel.deletePositionItems(_groupName);
}
_logItemViewModel.deleteLogItems(_stationName, hours);
_positionItemViewModel.deletePositionItems(_stationName, hours);
_stationItemViewModel.deleteStationItems(_stationName, hours);
}
};
String alertMessage = getString(R.string.log_item_activity_delete_all_title);
if (_groupName != null) {
if (hours != -1) {
alertMessage = String.format(getString(R.string.log_item_activity_delete_hours_title), hours);
}
if (_stationName != null) {
alertMessage = getString(R.string.log_item_activity_delete_group_title);
alertMessage = String.format(alertMessage, _groupName);
alertMessage = String.format(alertMessage, _stationName);
}
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage(alertMessage)

Wyświetl plik

@ -6,7 +6,7 @@ import androidx.room.Insert;
import androidx.room.OnConflictStrategy;
import androidx.room.Query;
import com.radio.codec2talkie.storage.log.group.LogItemGroup;
import com.radio.codec2talkie.storage.station.StationItem;
import java.util.List;
@ -16,25 +16,6 @@ public interface LogItemDao {
@Insert(onConflict = OnConflictStrategy.IGNORE)
void insertLogItem(LogItem logItem);
@Query("SELECT pos.timestampEpoch AS timestampEpoch, " +
"log.srcCallsign AS srcCallsign, " +
"pos.dstCallsign AS dstCallsign, " +
"pos.latitude AS latitude, " +
"pos.longitude AS longitude, " +
"pos.maidenHead AS maidenHead, " +
"pos.altitudeMeters AS altitudeMeters, " +
"pos.bearingDegrees AS bearingDegrees, " +
"pos.speedMetersPerSecond AS speedMetersPerSecond, " +
"pos.status AS status, " +
"pos.comment AS comment, " +
"pos.symbolCode AS symbolCode, " +
"pos.privacyLevel AS privacyLevel, " +
"MAX(pos.timestampEpoch)" +
"FROM LogItem log " +
"LEFT OUTER JOIN PositionItem pos ON (log.srcCallsign = pos.srcCallsign)" +
"GROUP BY log.srcCallsign")
LiveData<List<LogItemGroup>> getGroups();
@Query("SELECT * FROM LogItem ORDER by timestampEpoch ASC")
LiveData<List<LogItem>> getAllLogItems();
@ -42,8 +23,14 @@ public interface LogItemDao {
LiveData<List<LogItem>> getLogItems(String srcCallsign);
@Query("DELETE FROM LogItem WHERE srcCallsign = :srcCallsign")
void deleteLogItems(String srcCallsign);
void deleteLogItemsFromCallsign(String srcCallsign);
@Query("DELETE FROM LogItem")
void deleteAllLogItems();
@Query("DELETE FROM LogItem WHERE timestampEpoch < :timestampEpoch")
void deleteLogItemsOlderThanTimestamp(long timestampEpoch);
@Query("DELETE FROM LogItem WHERE srcCallsign = :srcCallsign AND timestampEpoch < :timestampEpoch")
void deleteLogItems(String srcCallsign, long timestampEpoch);
}

Wyświetl plik

@ -33,7 +33,8 @@ public class LogItemHolder extends RecyclerView.ViewHolder implements View.OnCli
DateTools.epochToIso8601(timestamp),
isTransmitting ? "→" : "←",
srcCallsign));
_logItemViewMessage.setText(TextTools.addZeroWidthSpaces(text));
if (text != null)
_logItemViewMessage.setText(TextTools.addZeroWidthSpaces(text));
}
static LogItemHolder create(ViewGroup parent, boolean isClickable) {
@ -46,7 +47,7 @@ public class LogItemHolder extends RecyclerView.ViewHolder implements View.OnCli
public void onClick(View v) {
if (!_isClickable) return;
Intent logItemIntent = new Intent(v.getContext(), LogItemActivity.class);
logItemIntent.putExtra("groupName", _srcCallsign);
logItemIntent.putExtra("stationName", _srcCallsign);
v.getContext().startActivity(logItemIntent);
}
}

Wyświetl plik

@ -5,7 +5,7 @@ import android.app.Application;
import androidx.lifecycle.LiveData;
import com.radio.codec2talkie.storage.AppDatabase;
import com.radio.codec2talkie.storage.log.group.LogItemGroup;
import com.radio.codec2talkie.tools.DateTools;
import java.util.List;
@ -13,22 +13,17 @@ public class LogItemRepository {
private final LogItemDao _logItemDao;
private final LiveData<List<LogItem>> _logItemLiveData;
private LiveData<List<LogItem>> _logItemGroupLiveData;
private final LiveData<List<LogItemGroup>> _logItemGroups;
public LogItemRepository(Application application) {
AppDatabase appDatabase = AppDatabase.getDatabase(application);
_logItemDao = appDatabase.logItemDao();
_logItemLiveData = _logItemDao.getAllLogItems();
_logItemGroups = _logItemDao.getGroups();
}
public LiveData<List<LogItem>> getAllLogItems() {
return _logItemLiveData;
}
public LiveData<List<LogItemGroup>> getGroups() { return _logItemGroups; }
public LiveData<List<LogItem>> getLogItems(String groupName) {
return _logItemDao.getLogItems(groupName);
}
@ -37,11 +32,16 @@ public class LogItemRepository {
AppDatabase.getDatabaseExecutor().execute(() -> _logItemDao.insertLogItem(logItem));
}
public void deleteAllLogItems() {
AppDatabase.getDatabaseExecutor().execute(_logItemDao::deleteAllLogItems);
}
public void deleteLogItems(String groupName) {
AppDatabase.getDatabaseExecutor().execute(() -> _logItemDao.deleteLogItems(groupName));
public void deleteLogItems(String srcCallsign, int hours) {
AppDatabase.getDatabaseExecutor().execute(() -> {
if (srcCallsign == null && hours == -1)
_logItemDao.deleteAllLogItems();
else if (srcCallsign == null)
_logItemDao.deleteLogItemsOlderThanTimestamp(DateTools.currentTimestampMinusHours(hours));
else if (hours == -1)
_logItemDao.deleteLogItemsFromCallsign(srcCallsign);
else
_logItemDao.deleteLogItems(srcCallsign, DateTools.currentTimestampMinusHours(hours));
});
}
}

Wyświetl plik

@ -6,7 +6,7 @@ import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import com.radio.codec2talkie.storage.log.group.LogItemGroup;
import com.radio.codec2talkie.storage.station.StationItem;
import java.util.List;
@ -14,14 +14,11 @@ public class LogItemViewModel extends AndroidViewModel {
private final LogItemRepository _logItemRepository;
private final LiveData<List<LogItem>> _logItemLiveData;
private LiveData<List<LogItem>> _logItemGroupLiveData;
private final LiveData<List<LogItemGroup>> _logItemGroups;
public LogItemViewModel(@NonNull Application application) {
super(application);
_logItemRepository = new LogItemRepository(application);
_logItemLiveData = _logItemRepository.getAllLogItems();
_logItemGroups = _logItemRepository.getGroups();
}
public LiveData<List<LogItem>> getAllData() {
@ -32,11 +29,7 @@ public class LogItemViewModel extends AndroidViewModel {
return _logItemRepository.getLogItems(groupName);
}
public LiveData<List<LogItemGroup>> getGroups() { return _logItemGroups; }
public void deleteAllLogItems() { _logItemRepository.deleteAllLogItems(); }
public void deleteLogItems(String groupName) {
_logItemRepository.deleteLogItems(groupName);
public void deleteLogItems(String srcCallsign, int hours) {
_logItemRepository.deleteLogItems(srcCallsign, hours);
}
}

Wyświetl plik

@ -1,69 +0,0 @@
package com.radio.codec2talkie.storage.log.group;
public class LogItemGroup {
private long timestampEpoch;
private String srcCallsign;
public String dstCallsign;
private String maidenHead;
public double latitude;
public double longitude;
public double altitudeMeters;
public double bearingDegrees;
public double speedMetersPerSecond;
public String status;
public String comment;
public String symbolCode;
public int privacyLevel;
public long getTimestampEpoch() { return timestampEpoch; }
public String getSrcCallsign() { return srcCallsign; }
public String getDstCallsign() { return dstCallsign; }
public double getLatitude() { return latitude; }
public double getLongitude() { return longitude; }
public String getMaidenHead() { return maidenHead; }
public double getAltitudeMeters() { return altitudeMeters; }
public double getBearingDegrees() { return bearingDegrees; }
public double getSpeedMetersPerSecond() { return speedMetersPerSecond; };
public String getStatus() { return status; }
public String getComment() { return comment; };
public String getSymbolCode() { return symbolCode; }
public int getPrivacyLevel() { return privacyLevel; }
public void setTimestampEpoch(long timestampEpoch) { this.timestampEpoch = timestampEpoch; }
public void setSrcCallsign(String srcCallsign) { this.srcCallsign = srcCallsign; }
public void setMaidenHead(String maidenHead) { this.maidenHead = maidenHead; }
public void setDstCallsign(String dstCallsign) { this.dstCallsign = dstCallsign; }
public void setLatitude(double latitude) { this.latitude = latitude; }
public void setLongitude(double longitude) { this.longitude = longitude; }
public void setAltitudeMeters(double altitudeMeters) { this.altitudeMeters = altitudeMeters; }
public void setBearingDegrees(double bearingDegrees) { this.bearingDegrees = bearingDegrees; }
public void setSpeedMetersPerSecond(double speedMetersPerSecond) { this.speedMetersPerSecond = speedMetersPerSecond; }
public void setStatus(String status) { this.status = status; }
public void setComment(String comment) { this.comment = comment; }
public void setSymbolCode(String symbolCode) { this.symbolCode = symbolCode; }
public void setPrivacyLevel(int privacyLevel) { this.privacyLevel = privacyLevel; }
}

Wyświetl plik

@ -1,48 +0,0 @@
package com.radio.codec2talkie.storage.log.group;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.ListAdapter;
public class LogItemGroupAdapter extends ListAdapter<LogItemGroup, LogItemGroupHolder> {
private View.OnClickListener _clickListener;
public LogItemGroupAdapter(@NonNull DiffUtil.ItemCallback<LogItemGroup> diffCallback) {
super(diffCallback);
}
public void setClickListener(View.OnClickListener clickListener) {
_clickListener = clickListener;
}
@NonNull
@Override
public LogItemGroupHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return LogItemGroupHolder.create(parent);
}
@Override
public void onBindViewHolder(LogItemGroupHolder holder, int position) {
LogItemGroup current = getItem(position);
holder.itemView.setOnClickListener(_clickListener);
holder.bind(current);
}
public static class LogItemGroupDiff extends DiffUtil.ItemCallback<LogItemGroup> {
@Override
public boolean areItemsTheSame(@NonNull LogItemGroup oldItem, @NonNull LogItemGroup newItem) {
return oldItem.getSrcCallsign().equals(newItem.getSrcCallsign());
}
@Override
public boolean areContentsTheSame(@NonNull LogItemGroup oldItem, @NonNull LogItemGroup newItem) {
return oldItem.getSrcCallsign().equals(newItem.getSrcCallsign());
}
}
}

Wyświetl plik

@ -1,9 +1,10 @@
package com.radio.codec2talkie.storage.message;
import androidx.room.Entity;
import androidx.room.Index;
import androidx.room.PrimaryKey;
@Entity
@Entity(indices = {@Index(value = {"id", "srcCallsign", "dstCallsign", "ackId"}, unique = true)})
public class MessageItem {
@PrimaryKey(autoGenerate = true)
@ -13,7 +14,9 @@ public class MessageItem {
private String dstCallsign;
private String message;
private boolean needsAck;
private int ackNum;
private boolean isAcknowledged;
private int ackId;
private int retryCnt;
private boolean isTransmit;
public long getId() {
@ -34,7 +37,11 @@ public class MessageItem {
public boolean getNeedsAck() { return needsAck; }
public int getAckNum() { return ackNum; }
public int getAckId() { return ackId; }
public int getRetryCnt() { return this.retryCnt; }
public boolean getIsAcknowledged() { return this.isAcknowledged; }
public boolean getIsTransmit() { return isTransmit; }
@ -42,6 +49,8 @@ public class MessageItem {
this.id = id;
}
public void setRetryCnt(int retryCnt) { this.retryCnt = retryCnt; }
public void setTimestampEpoch(long timestampEpoch) {
this.timestampEpoch = timestampEpoch;
}
@ -56,8 +65,10 @@ public class MessageItem {
public void setNeedsAck(boolean needsAck) { this.needsAck = needsAck; }
public void setAckNum(int ackNum) { this.ackNum = ackNum; }
public void setAckId(int ackId) { this.ackId = ackId; }
public void setIsTransmit(boolean isTransmit) { this.isTransmit = isTransmit; }
public void setIsAcknowledged(boolean isAcknowledged) { this.isAcknowledged = isAcknowledged; }
}

Wyświetl plik

@ -75,6 +75,7 @@ public class MessageItemActivity extends AppCompatActivityWithServiceConnection
TextMessage textMessage = new TextMessage();
textMessage.dst = _groupName;
textMessage.text = messageEdit.getText().toString();
textMessage.ackId = 0;
getService().sendTextMessage(textMessage);
messageEdit.setText("");
}

Wyświetl plik

@ -13,6 +13,8 @@ import com.radio.codec2talkie.R;
import com.radio.codec2talkie.app.AppService;
import com.radio.codec2talkie.protocol.message.TextMessage;
import java.util.Locale;
public class MessageGroupDialogSendTo extends AlertDialog implements View.OnClickListener {
private final AppService _appService;
@ -44,8 +46,9 @@ public class MessageGroupDialogSendTo extends AlertDialog implements View.OnClic
assert targetEdit != null;
assert messageEdit != null;
TextMessage textMessage = new TextMessage();
textMessage.dst = targetEdit.getText().toString();
textMessage.dst = targetEdit.getText().toString().toUpperCase(Locale.ROOT);
textMessage.text = messageEdit.getText().toString();
textMessage.ackId = 0;
_appService.sendTextMessage(textMessage);
dismiss();
} else if (id == R.id.send_message_to_btn_cancel) {

Wyświetl plik

@ -1,17 +1,23 @@
package com.radio.codec2talkie.storage.position;
import androidx.room.Entity;
import androidx.room.Index;
import androidx.room.PrimaryKey;
@Entity
import com.radio.codec2talkie.protocol.position.Position;
@Entity(indices = {@Index(value = {"id", "srcCallsign"}, unique = true)})
public class PositionItem {
private static final double MIN_COORDINATE_CHANGE_DELTA = 0.003;
@PrimaryKey(autoGenerate = true)
private long id;
private long timestampEpoch;
private boolean isTransmit;
public String srcCallsign;
public String dstCallsign;
public String digipath;
public double latitude;
public double longitude;
public String maidenHead;
@ -22,6 +28,8 @@ public class PositionItem {
public String comment;
public String symbolCode;
public int privacyLevel;
public double rangeMiles;
public int directivityDeg;
public long getId() {
return id;
@ -37,6 +45,8 @@ public class PositionItem {
public String getDstCallsign() { return dstCallsign; }
public String getDigipath() { return digipath; }
public double getLatitude() { return latitude; }
public double getLongitude() { return longitude; }
@ -59,6 +69,10 @@ public class PositionItem {
public boolean getIsTransmit() { return isTransmit; }
public int getDirectivityDeg() { return directivityDeg; }
public double getRangeMiles() { return rangeMiles; }
public void setId(long id) {
this.id = id;
}
@ -73,6 +87,8 @@ public class PositionItem {
public void setDstCallsign(String dstCallsign) { this.dstCallsign = dstCallsign; }
public void setDigipath(String digipath) { this.digipath = digipath; }
public void setLatitude(double latitude) { this.latitude = latitude; }
public void setLongitude(double longitude) { this.longitude = longitude; }
@ -92,4 +108,17 @@ public class PositionItem {
public void setSymbolCode(String symbolCode) { this.symbolCode = symbolCode; }
public void setPrivacyLevel(int privacyLevel) { this.privacyLevel = privacyLevel; }
public void setDirectivityDeg(int directivityDeg) { this.directivityDeg = directivityDeg; }
public void setRangeMiles(double rangeMiles) { this.rangeMiles = rangeMiles; }
@Override
public boolean equals(Object o) {
PositionItem positionItem = (PositionItem) o;
return getSrcCallsign().equals(positionItem.getSrcCallsign()) &
getIsTransmit() == positionItem.getIsTransmit() &&
Math.abs(getLongitude() - positionItem.getLongitude()) <= MIN_COORDINATE_CHANGE_DELTA &
Math.abs(getLatitude() - positionItem.getLatitude()) <= MIN_COORDINATE_CHANGE_DELTA;
}
}

Wyświetl plik

@ -5,29 +5,59 @@ import androidx.room.Dao;
import androidx.room.Insert;
import androidx.room.OnConflictStrategy;
import androidx.room.Query;
import androidx.room.Transaction;
import androidx.room.Update;
import com.radio.codec2talkie.storage.message.MessageItem;
import java.util.List;
@Dao
public interface PositionItemDao {
public abstract class PositionItemDao {
@Insert(onConflict = OnConflictStrategy.IGNORE)
void insertPositionItem(PositionItem logItem);
@Insert
public abstract void insertPositionItem(PositionItem positionItem);
@Update
public abstract void updatePositionItem(PositionItem positionItem);
@Transaction
public void upsertPositionItem(PositionItem positionItem) {
PositionItem oldPosition = getLastPositionItem(positionItem.getSrcCallsign());
if (oldPosition != null && oldPosition.equals(positionItem)) {
// update id and coordinates from existing position
positionItem.setId(oldPosition.getId());
positionItem.setLatitude(oldPosition.getLatitude());
positionItem.setLongitude(oldPosition.getLongitude());
//Log.i(TAG, "UPDATE " + positionItem.getSrcCallsign());
updatePositionItem(positionItem);
} else {
//Log.i(TAG, "INSERT " + positionItem.getSrcCallsign());
insertPositionItem(positionItem);
}
}
@Query("SELECT * FROM PositionItem WHERE srcCallsign = :srcCallsign ORDER BY timestampEpoch DESC LIMIT 1")
public abstract PositionItem getLastPositionItem(String srcCallsign);
@Query("SELECT srcCallsign from PositionItem GROUP BY srcCallsign")
LiveData<List<String>> getGroups();
public abstract LiveData<List<String>> getStationNames();
@Query("SELECT * FROM PositionItem ORDER by timestampEpoch DESC")
LiveData<List<PositionItem>> getAllPositionItems();
public abstract LiveData<List<PositionItem>> getAllPositionItems();
@Query("SELECT * FROM PositionItem WHERE srcCallsign = :srcCallsign ORDER BY timestampEpoch DESC")
LiveData<List<PositionItem>> getPositionItems(String srcCallsign);
@Query("SELECT * FROM PositionItem WHERE srcCallsign = :srcCallsign ORDER BY timestampEpoch ASC")
public abstract LiveData<List<PositionItem>> getPositionItems(String srcCallsign);
@Query("DELETE FROM PositionItem WHERE srcCallsign = :srcCallsign")
void deletePositionItems(String srcCallsign);
public abstract void deletePositionItemsFromCallsign(String srcCallsign);
@Query("DELETE FROM PositionItem WHERE timestampEpoch < :timestamp")
public abstract void deletePositionItemsOlderThanTimestamp(long timestamp);
@Query("DELETE FROM PositionItem WHERE timestampEpoch < :timestamp AND srcCallsign = :srcCallsign")
public abstract void deletePositionItems(String srcCallsign, long timestamp);
@Query("DELETE FROM PositionItem")
void deleteAllPositionItems();
public abstract void deleteAllPositionItems();
}

Wyświetl plik

@ -1,14 +1,18 @@
package com.radio.codec2talkie.storage.position;
import android.app.Application;
import android.util.Log;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.Transformations;
import com.radio.codec2talkie.storage.AppDatabase;
import com.radio.codec2talkie.tools.DateTools;
import java.util.List;
public class PositionItemRepository {
private static final String TAG = PositionItemRepository.class.getSimpleName();
private final PositionItemDao _positionItemDao;
@ -17,15 +21,26 @@ public class PositionItemRepository {
_positionItemDao = appDatabase.positionItemDao();
}
public void insertPositionItem(PositionItem positionItem) {
AppDatabase.getDatabaseExecutor().execute(() -> _positionItemDao.insertPositionItem(positionItem));
public void upsertPositionItem(PositionItem positionItem) {
AppDatabase.getDatabaseExecutor().execute(() -> _positionItemDao.upsertPositionItem(positionItem));
}
public void deleteAllPositionItems() {
AppDatabase.getDatabaseExecutor().execute(_positionItemDao::deleteAllPositionItems);
public LiveData<List<PositionItem>> getPositionItems(String srcCallsign) {
return Transformations.distinctUntilChanged(_positionItemDao.getPositionItems(srcCallsign));
}
public void deletePositionItems(String srcCallsign) {
AppDatabase.getDatabaseExecutor().execute(() -> _positionItemDao.deletePositionItems(srcCallsign));
public void deletePositionItems(String srcCallsign, int hours) {
AppDatabase.getDatabaseExecutor().execute(() -> {
AppDatabase.getDatabaseExecutor().execute(() -> {
if (srcCallsign == null && hours == -1)
_positionItemDao.deleteAllPositionItems();
else if (srcCallsign == null)
_positionItemDao.deletePositionItemsOlderThanTimestamp(DateTools.currentTimestampMinusHours(hours));
else if (hours == -1)
_positionItemDao.deletePositionItemsFromCallsign(srcCallsign);
else
_positionItemDao.deletePositionItems(srcCallsign, DateTools.currentTimestampMinusHours(hours));
});
});
}
}

Wyświetl plik

@ -6,6 +6,8 @@ import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import com.radio.codec2talkie.tools.DateTools;
import java.util.List;
public class PositionItemViewModel extends AndroidViewModel {
@ -17,9 +19,11 @@ public class PositionItemViewModel extends AndroidViewModel {
_positionItemRepository = new PositionItemRepository(application);
}
public void deleteAllPositionItems() { _positionItemRepository.deleteAllPositionItems(); }
public LiveData<List<PositionItem>> getPositionItems(String srcCallsign) {
return _positionItemRepository.getPositionItems(srcCallsign);
}
public void deletePositionItems(String srcCallsign) {
_positionItemRepository.deletePositionItems(srcCallsign);
public void deletePositionItems(String srcCallsign, int hours) {
_positionItemRepository.deletePositionItems(srcCallsign, hours);
}
}

Wyświetl plik

@ -0,0 +1,213 @@
package com.radio.codec2talkie.storage.station;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.util.DisplayMetrics;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.room.Entity;
import androidx.room.Index;
import androidx.room.PrimaryKey;
import com.radio.codec2talkie.protocol.aprs.tools.AprsSymbolTable;
import java.util.Objects;
@Entity(indices = {@Index(value = {"srcCallsign"}, unique = true)})
public class StationItem {
@NonNull
@PrimaryKey
private String srcCallsign;
private long timestampEpoch;
public String dstCallsign;
public String digipath;
private String maidenHead;
public double latitude;
public double longitude;
public double altitudeMeters;
public double bearingDegrees;
public double speedMetersPerSecond;
public String status;
public String comment;
public String symbolCode;
public String logLine;
public int privacyLevel;
public double rangeMiles;
public int directivityDeg;
public StationItem(@NonNull String srcCallsign) {
this.srcCallsign = srcCallsign;
}
public long getTimestampEpoch() { return timestampEpoch; }
public String getSrcCallsign() { return srcCallsign; }
public String getDstCallsign() { return dstCallsign; }
public String getDigipath() { return digipath; }
public double getLatitude() { return latitude; }
public double getLongitude() { return longitude; }
public String getMaidenHead() { return maidenHead; }
public double getAltitudeMeters() { return altitudeMeters; }
public double getBearingDegrees() { return bearingDegrees; }
public double getSpeedMetersPerSecond() { return speedMetersPerSecond; }
public String getStatus() { return status; }
public String getComment() { return comment; }
public String getSymbolCode() { return symbolCode; }
public String getLogLine() { return logLine; }
public int getPrivacyLevel() { return privacyLevel; }
public double getRangeMiles() { return rangeMiles; }
public int getDirectivityDeg() { return directivityDeg; }
public void setTimestampEpoch(long timestampEpoch) { this.timestampEpoch = timestampEpoch; }
public void setSrcCallsign(@NonNull String srcCallsign) { this.srcCallsign = srcCallsign; }
public void setDigipath(String digipath) { this.digipath = digipath; }
public void setMaidenHead(String maidenHead) { this.maidenHead = maidenHead; }
public void setDstCallsign(String dstCallsign) { this.dstCallsign = dstCallsign; }
public void setLatitude(double latitude) { this.latitude = latitude; }
public void setLongitude(double longitude) { this.longitude = longitude; }
public void setAltitudeMeters(double altitudeMeters) { this.altitudeMeters = altitudeMeters; }
public void setBearingDegrees(double bearingDegrees) { this.bearingDegrees = bearingDegrees; }
public void setSpeedMetersPerSecond(double speedMetersPerSecond) { this.speedMetersPerSecond = speedMetersPerSecond; }
public void setStatus(String status) { this.status = status; }
public void setComment(String comment) { this.comment = comment; }
public void setSymbolCode(String symbolCode) { this.symbolCode = symbolCode; }
public void setPrivacyLevel(int privacyLevel) { this.privacyLevel = privacyLevel; }
public void setLogLine(String logLine) { this.logLine = logLine; }
public void setRangeMiles(double rangeMiles) { this.rangeMiles = rangeMiles; }
public void setDirectivityDeg(int directivityDeg) { this.directivityDeg = directivityDeg; }
public void updateFrom(StationItem stationItem) {
setTimestampEpoch(stationItem.getTimestampEpoch());
// update position if known
if (stationItem.getMaidenHead() != null) {
setMaidenHead(stationItem.getMaidenHead());
setLatitude(stationItem.getLatitude());
setLongitude(stationItem.getLongitude());
setAltitudeMeters(stationItem.getAltitudeMeters());
setBearingDegrees(stationItem.getBearingDegrees());
setSpeedMetersPerSecond(stationItem.getSpeedMetersPerSecond());
setPrivacyLevel(stationItem.getPrivacyLevel());
setRangeMiles(stationItem.getRangeMiles());
setDirectivityDeg(stationItem.getDirectivityDeg());
}
if (stationItem.getStatus() != null)
setStatus(stationItem.getStatus());
if (stationItem.getComment() != null)
setComment(stationItem.getComment());
if (stationItem.getSymbolCode() != null)
setSymbolCode(stationItem.getSymbolCode());
if (stationItem.getLogLine() != null)
setLogLine(stationItem.getLogLine());
if (stationItem.getDigipath() != null)
setDigipath(stationItem.getDigipath());
if (stationItem.getDstCallsign() != null)
setDstCallsign(stationItem.getDstCallsign());
}
@Override
public boolean equals(Object o) {
StationItem stationItem = (StationItem)o;
return srcCallsign.equals(stationItem.getSrcCallsign()) &&
timestampEpoch == stationItem.getTimestampEpoch() &&
Objects.equals(comment, stationItem.getComment()) &&
Objects.equals(dstCallsign, stationItem.getDstCallsign()) &&
latitude == stationItem.getLatitude() &&
longitude == stationItem.getLongitude();
}
public BitmapDrawable drawLabelWithIcon(Context context, float textSize) {
String callsign = getSrcCallsign();
Bitmap bitmapIcon = AprsSymbolTable.getInstance(context).bitmapFromSymbol(getSymbolCode(), false);
if (bitmapIcon == null) return null;
// construct and calculate bounds
Paint paint = new Paint();
paint.setStyle(Paint.Style.FILL);
paint.setTextSize(textSize);
Rect bounds = new Rect();
paint.getTextBounds(callsign, 0, callsign.length(), bounds);
int width = Math.max(bitmapIcon.getWidth(), bounds.width());
int height = bitmapIcon.getHeight() + bounds.height();
// create overlay bitmap
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
bitmap.setDensity(DisplayMetrics.DENSITY_DEFAULT);
// draw APRS icon
Canvas canvas = new Canvas(bitmap);
float bitmapLeft = width > bitmapIcon.getWidth() ? width / 2.0f - bitmapIcon.getWidth() / 2.0f : 0;
// do not rotate
if (getBearingDegrees() == 0 || !AprsSymbolTable.needsRotation(getSymbolCode())) {
canvas.drawBitmap(bitmapIcon, bitmapLeft, 0, null);
// rotate
} else {
float rotationDeg = (float) (getBearingDegrees() - 90.0f);
Matrix m = new Matrix();
// flip/rotate
if (getBearingDegrees() > 180) {
m.postScale(-1, 1);
m.postTranslate(bitmapIcon.getWidth(), 0);
m.postRotate(rotationDeg - 180, bitmapIcon.getWidth() / 2.0f, bitmapIcon.getHeight() / 2.0f);
// rotate
} else {
m.postRotate(rotationDeg, bitmapIcon.getWidth() / 2.0f, bitmapIcon.getHeight() / 2.0f);
}
m.postTranslate(bitmapLeft, 0);
canvas.drawBitmap(bitmapIcon, m, null);
}
// draw background
paint.setColor(Color.WHITE);
paint.setAlpha(120);
bounds.set(0, bitmapIcon.getHeight(), width, height);
canvas.drawRect(bounds, paint);
// draw text
paint.setColor(Color.BLACK);
paint.setAlpha(255);
paint.setFlags(Paint.ANTI_ALIAS_FLAG);
canvas.drawText(callsign, 0, height, paint);
// add marker
return new BitmapDrawable(context.getResources(), bitmap);
}
}

Wyświetl plik

@ -0,0 +1,53 @@
package com.radio.codec2talkie.storage.station;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.ListAdapter;
import java.util.Objects;
public class StationItemAdapter extends ListAdapter<StationItem, StationItemHolder> {
private View.OnClickListener _clickListener;
public StationItemAdapter(@NonNull DiffUtil.ItemCallback<StationItem> diffCallback) {
super(diffCallback);
}
public void setClickListener(View.OnClickListener clickListener) {
_clickListener = clickListener;
}
@NonNull
@Override
public StationItemHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return StationItemHolder.create(parent);
}
@Override
public void onBindViewHolder(StationItemHolder holder, int position) {
StationItem current = getItem(position);
holder.itemView.setOnClickListener(_clickListener);
holder.bind(current);
}
public static class StationItemDiff extends DiffUtil.ItemCallback<StationItem> {
@Override
public boolean areItemsTheSame(@NonNull StationItem oldItem, @NonNull StationItem newItem) {
return oldItem.getSrcCallsign().equals(newItem.getSrcCallsign());
}
@Override
public boolean areContentsTheSame(@NonNull StationItem oldItem, @NonNull StationItem newItem) {
return oldItem.getSrcCallsign().equals(newItem.getSrcCallsign()) &&
oldItem.getLatitude() == newItem.getLatitude() &&
oldItem.getLongitude() == newItem.getLongitude() &&
Objects.equals(oldItem.getComment(), newItem.getComment());
}
}
}

Wyświetl plik

@ -0,0 +1,62 @@
package com.radio.codec2talkie.storage.station;
import android.database.sqlite.SQLiteConstraintException;
import android.util.Log;
import androidx.lifecycle.LiveData;
import androidx.room.Dao;
import androidx.room.Insert;
import androidx.room.Query;
import androidx.room.Transaction;
import androidx.room.Update;
import java.util.List;
@Dao
public abstract class StationItemDao {
@Insert
public abstract void insertStationItem(StationItem stationItem);
@Update
public abstract void updateStationItem(StationItem stationItem);
@Transaction
public void upsertStationItem(StationItem stationItem) {
StationItem oldStationItem = getStationItem(stationItem.getSrcCallsign());
if (oldStationItem == null) {
try {
insertStationItem(stationItem);
} catch (SQLiteConstraintException ex) {
oldStationItem = getStationItem(stationItem.getSrcCallsign());
}
}
if (oldStationItem != null) {
oldStationItem.updateFrom(stationItem);
updateStationItem(oldStationItem);
}
}
@Query("SELECT * FROM StationItem WHERE srcCallsign = :srcCallsign")
public abstract StationItem getStationItem(String srcCallsign);
@Query("SELECT * FROM StationItem ORDER BY srcCallsign ASC")
public abstract LiveData<List<StationItem>> getAllStationItems();
@Query("SELECT *, (SELECT count(*) FROM PositionItem pos WHERE st.srcCallsign = pos.srcCallsign) AS positionCount " +
"FROM StationItem st " +
"WHERE positionCount > :minCount")
public abstract LiveData<List<StationItem>> getMovingStationItems(int minCount);
@Query("DELETE FROM StationItem WHERE srcCallsign = :srcCallsign")
public abstract void deleteStationItemsFromCallsign(String srcCallsign);
@Query("DELETE FROM StationItem WHERE timestampEpoch < :timestamp")
public abstract void deleteStationItemsOlderThanTimestamp(long timestamp);
@Query("DELETE FROM StationItem WHERE timestampEpoch < :timestamp AND srcCallsign = :srcCallsign")
public abstract void deleteStationItems(String srcCallsign, long timestamp);
@Query("DELETE FROM StationItem")
public abstract void deleteAllStationItems();
}

Wyświetl plik

@ -1,4 +1,4 @@
package com.radio.codec2talkie.storage.log.group;
package com.radio.codec2talkie.storage.station;
import android.annotation.SuppressLint;
import android.content.Context;
@ -21,7 +21,7 @@ import com.radio.codec2talkie.tools.UnitTools;
import java.util.Locale;
public class LogItemGroupHolder extends RecyclerView.ViewHolder {
public class StationItemHolder extends RecyclerView.ViewHolder {
private static final String TAG = RecyclerView.class.getSimpleName();
@ -32,7 +32,7 @@ public class LogItemGroupHolder extends RecyclerView.ViewHolder {
private final LocationManager _locationManager;
private final AprsSymbolTable _symbolTable;
private LogItemGroupHolder(View itemView) {
private StationItemHolder(View itemView) {
super(itemView);
_logItemViewTitle = itemView.findViewById(R.id.log_view_group_item_title);
_logItemViewDistance = itemView.findViewById(R.id.log_view_group_item_distance);
@ -42,7 +42,7 @@ public class LogItemGroupHolder extends RecyclerView.ViewHolder {
_locationManager = (LocationManager) itemView.getContext().getSystemService(Context.LOCATION_SERVICE);
}
public void bind(LogItemGroup group) {
public void bind(StationItem group) {
@SuppressLint("MissingPermission") Location loc = _locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
if (loc == null) {
_logItemViewDistance.setText("");
@ -63,9 +63,7 @@ public class LogItemGroupHolder extends RecyclerView.ViewHolder {
(int)group.getAltitudeMeters(),
group.getStatus(),
group.getComment());
if (group.getMaidenHead() != null) {
_logItemViewMessage.setText(status);
}
_logItemViewMessage.setText(group.getMaidenHead() == null ? group.getLogLine() : status);
String symbol = group.getSymbolCode();
Bitmap iconBitmap = _symbolTable.bitmapFromSymbol(symbol == null ? "/." : symbol, false);
if (iconBitmap == null) {
@ -75,9 +73,9 @@ public class LogItemGroupHolder extends RecyclerView.ViewHolder {
}
}
static LogItemGroupHolder create(ViewGroup parent) {
static StationItemHolder create(ViewGroup parent) {
View view = LayoutInflater.from(parent.getContext()).inflate(
R.layout.activity_log_view_group_item, parent, false);
return new LogItemGroupHolder(view);
return new StationItemHolder(view);
}
}

Wyświetl plik

@ -0,0 +1,55 @@
package com.radio.codec2talkie.storage.station;
import android.app.Application;
import android.database.sqlite.SQLiteConstraintException;
import android.util.Log;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.Transformations;
import com.radio.codec2talkie.storage.AppDatabase;
import com.radio.codec2talkie.tools.DateTools;
import java.util.List;
public class StationItemRepository {
private static final String TAG = StationItemRepository.class.getSimpleName();
private static final int MIN_POSITION_COUNT = 2;
private final StationItemDao _stationItemDao;
private final LiveData<List<StationItem>> _stationItems;
private final LiveData<List<StationItem>> _stationItemsMoving;
public StationItemRepository(Application application) {
AppDatabase appDatabase = AppDatabase.getDatabase(application);
_stationItemDao = appDatabase.stationitemDao();
_stationItems = Transformations.distinctUntilChanged(_stationItemDao.getAllStationItems());
_stationItemsMoving = Transformations.distinctUntilChanged(_stationItemDao.getMovingStationItems(MIN_POSITION_COUNT));
}
public LiveData<List<StationItem>> getAllStationItems(boolean movingOnly) {
return movingOnly ? _stationItemsMoving : _stationItems;
}
public void upsertStationItem(StationItem stationItem) {
AppDatabase.getDatabaseExecutor().execute(() -> {
_stationItemDao.upsertStationItem(stationItem);
});
}
public void deleteStationItems(String srcCallsign, int hours) {
AppDatabase.getDatabaseExecutor().execute(() -> {
AppDatabase.getDatabaseExecutor().execute(() -> {
if (srcCallsign == null && hours == -1)
_stationItemDao.deleteAllStationItems();
else if (srcCallsign == null)
_stationItemDao.deleteStationItemsOlderThanTimestamp(DateTools.currentTimestampMinusHours(hours));
else if (hours == -1)
_stationItemDao.deleteStationItemsFromCallsign(srcCallsign);
else
_stationItemDao.deleteStationItems(srcCallsign, DateTools.currentTimestampMinusHours(hours));
});
});
}
}

Wyświetl plik

@ -0,0 +1,27 @@
package com.radio.codec2talkie.storage.station;
import android.app.Application;
import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import com.radio.codec2talkie.tools.DateTools;
import java.util.List;
public class StationItemViewModel extends AndroidViewModel {
private final StationItemRepository _stationItemRepository;
public StationItemViewModel(@NonNull Application application) {
super(application);
_stationItemRepository = new StationItemRepository(application);
}
public LiveData<List<StationItem>> getAllStationItems(boolean movingOnly) { return _stationItemRepository.getAllStationItems(movingOnly); }
public void deleteStationItems(String srcCallsign, int hours) {
_stationItemRepository.deleteStationItems(srcCallsign, hours);
}
}

Wyświetl plik

@ -1,10 +1,12 @@
package com.radio.codec2talkie.tools;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.graphics.Color;
import androidx.preference.PreferenceManager;
import com.radio.codec2talkie.R;
import com.radio.codec2talkie.app.AppWorker;
import com.radio.codec2talkie.settings.PreferenceKeys;
import com.radio.codec2talkie.settings.SettingsWrapper;
@ -76,14 +78,26 @@ public class AudioTools {
return null;
}
public static String getSpeedStatusText(String codec2ModeName, SharedPreferences sharedPreferences) {
public static String getModulationAsText(SharedPreferences sharedPreferences) {
int modulation = Integer.parseInt(sharedPreferences.getString(PreferenceKeys.KISS_EXTENSIONS_RADIO_MOD, "0"));
return modulation == RadioTools.ModulationTypeLora ? "LoRa" : "FSK";
}
public static String getSpeedStatusText(SharedPreferences sharedPreferences, Resources resources) {
// use freedv mode text instead if it is active
String freedvModeLabel = getFreedvModeAsText(sharedPreferences);
if (freedvModeLabel != null) return freedvModeLabel;
// codec2 speed
String speedModeInfo = "C2: " + AudioTools.extractCodec2Speed(codec2ModeName);
String speedModeInfo;
if (SettingsWrapper.isCodec2Enabled(sharedPreferences)) {
String codec2ModeName = sharedPreferences.getString(PreferenceKeys.CODEC2_MODE, resources.getStringArray(R.array.codec2_modes)[0]);
speedModeInfo = "C2: " + AudioTools.extractCodec2Speed(codec2ModeName);
} else {
int speed = Integer.parseInt(sharedPreferences.getString(PreferenceKeys.OPUS_BIT_RATE, "3200"));
speedModeInfo = "OPUS: " + speed;
}
// radio speed
int radioSpeedBps = RadioTools.getRadioSpeed(sharedPreferences);

Wyświetl plik

@ -0,0 +1,37 @@
package com.radio.codec2talkie.tools;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.util.DisplayMetrics;
public class BitmapTools {
public static BitmapDrawable drawLabel(Context context, String text, float textSize) {
Paint paint = new Paint();
paint.setStyle(Paint.Style.FILL);
paint.setTextSize(textSize);
Rect bounds = new Rect();
paint.getTextBounds(text, 0, text.length(), bounds);
Bitmap bitmap = Bitmap.createBitmap(bounds.width(), bounds.height(), Bitmap.Config.ARGB_8888);
bitmap.setDensity(DisplayMetrics.DENSITY_DEFAULT);
Canvas canvas = new Canvas(bitmap);
paint.setColor(Color.WHITE);
//paint.setAlpha(200);
canvas.drawRect(0, 0, bounds.width(), bounds.height(), paint);
paint.setColor(Color.BLACK);
paint.setAlpha(255);
paint.setFlags(Paint.ANTI_ALIAS_FLAG);
canvas.drawText(text, -bounds.left, bounds.height(), paint);
return new BitmapDrawable(context.getResources(), bitmap);
}
}

Wyświetl plik

@ -12,4 +12,15 @@ public class DateTools {
sdf.setTimeZone(TimeZone.getDefault());
return sdf.format(new Date(timeMilliseconds));
}
public static String epochToIso8601Time(long timeMilliseconds) {
String format = "HH:mm:ss";
SimpleDateFormat sdf = new SimpleDateFormat(format, Locale.getDefault());
sdf.setTimeZone(TimeZone.getDefault());
return sdf.format(new Date(timeMilliseconds));
}
public static long currentTimestampMinusHours(int hours) {
return System.currentTimeMillis() - (hours * 60L * 60L * 1000L);
}
}

Wyświetl plik

@ -7,25 +7,37 @@ import com.radio.codec2talkie.settings.PreferenceKeys;
import com.radio.codec2talkie.settings.SettingsWrapper;
public class RadioTools {
public static final int ModulationTypeLora = 0;
public static final int ModulationTypeFsk = 1;
public static int calculateLoraSpeedBps(int bw, int sf, int cr) {
return (int)(sf * (4.0 / cr) / (Math.pow(2.0, sf) / bw));
}
public static int getRadioSpeed(SharedPreferences sharedPreferences) {
int resultBps = 0;
int maxSpeedBps = 128000;
try {
if (!SettingsWrapper.isSoundModemEnabled(sharedPreferences) && SettingsWrapper.isKissExtensionEnabled(sharedPreferences)) {
int bw = Integer.parseInt(sharedPreferences.getString(PreferenceKeys.KISS_EXTENSIONS_RADIO_BANDWIDTH, "125000"));
int sf = Integer.parseInt(sharedPreferences.getString(PreferenceKeys.KISS_EXTENSIONS_RADIO_SF, "7"));
int cr = Integer.parseInt(sharedPreferences.getString(PreferenceKeys.KISS_EXTENSIONS_RADIO_CR, "5"));
resultBps = RadioTools.calculateLoraSpeedBps(bw, sf, cr);
}
} catch (NumberFormatException|ArithmeticException e) {
e.printStackTrace();
int modulation = Integer.parseInt(sharedPreferences.getString(PreferenceKeys.KISS_EXTENSIONS_RADIO_MOD, "0"));
if (SettingsWrapper.isSoundModemEnabled(sharedPreferences)) {
return SettingsWrapper.getFskSpeed(sharedPreferences);
}
return (resultBps > 0 && resultBps <= maxSpeedBps) ? resultBps : 0;
if (modulation == ModulationTypeLora) {
int resultBps = 0;
int maxSpeedBps = 128000;
try {
if (!SettingsWrapper.isSoundModemEnabled(sharedPreferences) && SettingsWrapper.isKissExtensionEnabled(sharedPreferences)) {
int bw = Integer.parseInt(sharedPreferences.getString(PreferenceKeys.KISS_EXTENSIONS_RADIO_BANDWIDTH, "125000"));
int sf = Integer.parseInt(sharedPreferences.getString(PreferenceKeys.KISS_EXTENSIONS_RADIO_SF, "7"));
int cr = Integer.parseInt(sharedPreferences.getString(PreferenceKeys.KISS_EXTENSIONS_RADIO_CR, "5"));
resultBps = RadioTools.calculateLoraSpeedBps(bw, sf, cr);
}
} catch (NumberFormatException | ArithmeticException e) {
e.printStackTrace();
}
return (resultBps > 0 && resultBps <= maxSpeedBps) ? resultBps : 0;
} else if (modulation == ModulationTypeFsk){
return Integer.parseInt(sharedPreferences.getString(PreferenceKeys.KISS_EXTENSIONS_RADIO_FSK_BIT_RATE, "4.8"));
}
return 0;
}
public static double calculateLoraSensitivity(SharedPreferences sharedPreferences) {

Wyświetl plik

@ -24,18 +24,18 @@ public class ScramblingTools {
private static final String SCRAMBLING_ALGORITHM = "AES/CBC/PKCS5Padding";
private static final String PBE_ALGORITHM = "PBEwithSHA256and128BITAES-CBC-BC";
private static final SecureRandom _randomGenerator = new SecureRandom();
public static ScrambledData scramble(String masterKey, byte[] rawData, int iterations)
throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeySpecException,
InvalidKeyException, BadPaddingException, IllegalBlockSizeException, InvalidAlgorithmParameterException {
SecureRandom rnd = new SecureRandom();
ScrambledData encData = new ScrambledData();
encData.salt = new byte[SALT_BYTES];
encData.iv = new byte[BLOCK_SIZE];
rnd.nextBytes(encData.salt);
rnd.nextBytes(encData.iv);
_randomGenerator.nextBytes(encData.salt);
_randomGenerator.nextBytes(encData.iv);
PBEKeySpec keySpec = new PBEKeySpec(masterKey.toCharArray(), encData.salt, iterations);
SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(PBE_ALGORITHM);

Wyświetl plik

@ -4,6 +4,7 @@ import android.util.Log;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Objects;
public class TextTools {
public static String addZeroWidthSpaces(String text) {
@ -52,4 +53,14 @@ public class TextTools {
}
return result.toString();
}
public static byte[] hexStringToByteArray(String s) {
int len = s.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
+ Character.digit(s.charAt(i+1), 16));
}
return data;
}
}

Wyświetl plik

@ -81,4 +81,8 @@ public class UnitTools {
public static int metersPerSecondToKilometersPerHour(int speedMetersPerSecond) {
return (int) (speedMetersPerSecond * 3.6);
}
public static double milesToKilometers(double rangeMiles) {
return rangeMiles * 1.609344;
}
}

Wyświetl plik

@ -7,7 +7,7 @@ import java.io.IOException;
public class UsbSerial implements Transport {
private static final int RX_TIMEOUT = 100;
private static final int RX_TIMEOUT = 5;
private static final int TX_TIMEOUT = 2000;
private final UsbSerialPort _usbPort;

Wyświetl plik

@ -61,6 +61,17 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textStatus" />
<TextView
android:id="@+id/textTelemetry"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="0dp"
android:layout_marginEnd="0dp"
android:textSize="14sp"
android:textStyle="bold"
app:layout_constraintEnd_toStartOf="@+id/textRssi"
app:layout_constraintTop_toBottomOf="@+id/textStatus" />
<TextView
android:id="@+id/textStatus"
android:layout_width="wrap_content"

Wyświetl plik

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<org.osmdroid.views.MapView android:id="@+id/map"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
</LinearLayout>

Wyświetl plik

@ -23,6 +23,7 @@
<androidx.appcompat.widget.AppCompatEditText
android:id="@+id/messages_edit"
android:layout_width="0dp"
android:maxLength="67"
android:layout_height="wrap_content"
app:layout_constraintEnd_toStartOf="@+id/messages_send"
app:layout_constraintStart_toStartOf="parent"

Wyświetl plik

@ -5,5 +5,23 @@
android:title="@string/log_view_menu_stations" />
<item
android:id="@+id/log_view_menu_clear"
android:title="@string/log_view_menu_clear" />
android:title="@string/log_view_menu_clear_title">
<menu>
<item
android:id="@+id/log_view_menu_clear_1h"
android:title="@string/log_view_menu_clear_1h" />
<item
android:id="@+id/log_view_menu_clear_12h"
android:title="@string/log_view_menu_clear_12h" />
<item
android:id="@+id/log_view_menu_clear_1d"
android:title="@string/log_view_menu_clear_1d" />
<item
android:id="@+id/log_view_menu_clear_7d"
android:title="@string/log_view_menu_clear_7d" />
<item
android:id="@+id/log_view_menu_clear_all"
android:title="@string/log_view_menu_clear_all" />
</menu>
</item>
</menu>

Wyświetl plik

@ -13,6 +13,9 @@
<item
android:id="@+id/aprs_log"
android:title="@string/menu_aprs_log" />
<item
android:id="@+id/aprs_map"
android:title="@string/menu_aprs_map" />
</group>
<group android:id="@+id/group_main">
<item

Wyświetl plik

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/map_menu_move_map"
android:title="@string/map_menu_move_map"
android:checkable="true"
android:checked="false"/>
<item
android:id="@+id/map_menu_rotate_map"
android:title="@string/map_menu_rotate_map"
android:checkable="true"
android:checked="false"/>
<item
android:id="@+id/map_menu_show_range"
android:title="@string/map_menu_show_range"
android:checkable="true"
android:checked="false"/>
<item
android:id="@+id/map_menu_show_moving"
android:title="@string/map_menu_show_moving"
android:checkable="true"
android:checked="false"/>
<item
android:id="@+id/map_menu_clear_cache"
android:title="@string/map_menu_clear_cache_title" />
</menu>

Wyświetl plik

@ -13,6 +13,11 @@
<item>115200</item>
</string-array>
<string-array name="codec_type_modes">
<item>Codec2</item>
<item>OPUS</item>
</string-array>
<string-array name="codec2_modes">
<item>MODE_450=10</item>
<item>MODE_700C=8</item>
@ -24,6 +29,32 @@
<item>MODE_3200=0</item>
</string-array>
<string-array name="opus_frame_size">
<item>2.5</item>
<item>5</item>
<item>10</item>
<item>20</item>
<item>40</item>
<item>60</item>
<item>80</item>
<item>100</item>
<item>120</item>
</string-array>
<string-array name="opus_complexity_value">
<item>0</item>
<item>1</item>
<item>2</item>
<item>3</item>
<item>4</item>
<item>5</item>
<item>6</item>
<item>7</item>
<item>8</item>
<item>9</item>
<item>10</item>
</string-array>
<string-array name="lora_bandwidths">
<item>7800</item>
<item>10400</item>
@ -38,7 +69,6 @@
</string-array>
<string-array name="lora_spreading_factors">
<item>6</item>
<item>7</item>
<item>8</item>
<item>9</item>
@ -275,7 +305,76 @@
<item>/y</item>
</string-array>
<string-array name="radio_mod_entries">
<item>LoRa</item>
<item>FSK</item>
</string-array>
<string-array name="radio_mod_values">
<item>0</item>
<item>1</item>
</string-array>
<string-array name="fsk_rx_bw_entries">
<item>4800 Hz</item>
<item>5800 Hz</item>
<item>7300 Hz</item>
<item>9700 Hz</item>
<item>11700 Hz</item>
<item>14600 Hz</item>
<item>19500 Hz</item>
<item>23400 Hz</item>
<item>29300 Hz</item>
<item>39000 Hz</item>
<item>46900 Hz</item>
<item>58600 Hz</item>
<item>78200 Hz</item>
<item>93800 Hz</item>
<item>117300 Hz</item>
<item>156200 Hz</item>
<item>187200 Hz</item>
<item>234300 Hz</item>
<item>312000 Hz</item>
<item>373000 Hz</item>
<item>467000 Hz</item>
</string-array>
<string-array name="fsk_rx_bw_values">
<item>4800</item>
<item>5800</item>
<item>7300</item>
<item>9700</item>
<item>11700</item>
<item>14600</item>
<item>19500</item>
<item>23400</item>
<item>29300</item>
<item>39000</item>
<item>46900</item>
<item>58600</item>
<item>78200</item>
<item>93800</item>
<item>117300</item>
<item>156200</item>
<item>187200</item>
<item>234300</item>
<item>312000</item>
<item>373000</item>
<item>467000</item>
</string-array>
<string-array name="lora_power_entries">
<item>-9 dBm</item>
<item>-8 dBm</item>
<item>-7 dBm</item>
<item>-6 dBm</item>
<item>-5 dBm</item>
<item>-4 dBm</item>
<item>-3 dBm</item>
<item>-2 dBm</item>
<item>-1 dBm</item>
<item>0 dBm</item>
<item>1 dBm</item>
<item>2 dBm</item>
<item>3 dBm</item>
<item>4 dBm</item>
@ -300,6 +399,17 @@
</string-array>
<string-array name="lora_power_values">
<item>-9</item>
<item>-8</item>
<item>-7</item>
<item>-6</item>
<item>-5</item>
<item>-4</item>
<item>-3</item>
<item>-2</item>
<item>-1</item>
<item>0</item>
<item>1</item>
<item>2</item>
<item>3</item>
<item>4</item>

Wyświetl plik

@ -12,13 +12,24 @@
<string name="usb_bt_client_name_title">Bluetooth device</string>
<string name="usb_bt_client_name_summary">Default Bluetooth device to connect</string>
<string name="codec2_category_title">Codec2 settings</string>
<string name="codec_category_title">Audio Codec Settings</string>
<string name="codec_type_title">Select codec type</string>
<string name="codec_type_settings_title">Codec settings</string>
<string name="codec_type_settings_summary">Change selected codec settings, such as bit rate, frame size</string>
<string name="codec2_category_title">Codec2 Settings</string>
<string name="codec2_mode_title">Mode/Speed</string>
<string name="codec2_test_mode_title">Loopback test mode</string>
<string name="codec2_test_mode_summary">Records and plays recording without transmission</string>
<string name="codec2_recorder_title">Enable recorder</string>
<string name="codec2_recorder_summary">Record incoming and outgoing transmissions for future playback</string>
<string name="opus_category_title">OPUS Settings</string>
<string name="opus_frame_size_title">PCM frame duration [ms]</string>
<string name="opus_bit_rate_title">Bit rate</string>
<string name="opus_bit_rate_summary">Bit rate from 2400 up to 512000bps, set larger frame duration below for smaller bit rates</string>
<string name="opus_complexity_title">Complexity</string>
<string name="main_status_loopback_test">Loopback</string>
<string name="main_status_stop">STOPPED</string>
<string name="main_status_tx">TRANSMITTING</string>
@ -54,11 +65,24 @@
<string name="kiss_extensions_enable_title">Enable extensions</string>
<string name="kiss_extensions_enable_summary">Enable radio control and signal level reports</string>
<string name="kiss_extensions_radio_mod_title">Select radio modulation type</string>
<string name="kiss_extensions_radio_control_title">Set radio parameters</string>
<string name="kiss_extensions_radio_control_summary">Set frequency, bandwidth and other radio parameters</string>
<string name="kiss_extensions_radio_category_lora_title">Set LoRa modulation parameters</string>
<string name="kiss_extensions_radio_category_fsk_title">Set FSK modulation parameters</string>
<string name="kiss_extensions_radio_category_other_title">Modem control</string>
<string name="kiss_extension_radio_split_freq_title">Split frequency operation</string>
<string name="kiss_extension_radio_split_freq_summary">Use separate frequencies for RX and TX</string>
<string name="kiss_extensions_radio_frequency_title">Frequency (Hz)</string>
<string name="kiss_extensions_radio_frequency_summary">Set radio frequency</string>
<string name="kiss_extensions_radio_frequency_summary">Set radio frequency for RX/TX or RX frequency when split operation is enabled</string>
<string name="kiss_extensions_radio_frequency_title_tx">Frequency TX (Hz)</string>
<string name="kiss_extensions_radio_frequency_summary_tx">Set transmit radio frequency</string>
<string name="kiss_extensions_radio_bandwidth_title">Bandwidth (Hz)</string>
<string name="kiss_extensions_radio_bandwidth_summary">Set radio bandwidth</string>
@ -78,10 +102,18 @@
<string name="kiss_extensions_radio_crc_title">Enable CRC check</string>
<string name="kiss_extensions_radio_crc_summary">Enable packet CRC check</string>
<string name="kiss_extension_radio_fsk_bit_rate_title">Bit rate (600–300000 bps)</string>
<string name="kiss_extension_radio_fsk_bit_rate_summary">Set bit rate</string>
<string name="kiss_extension_radio_fsk_freq_dev_title">Set freq deviation (600–200000 Hz)</string>
<string name="kiss_extension_radio_fsk_freq_dev_summary">Set freq deviation</string>
<string name="kiss_extension_radio_fsk_rx_bw_title">Set receive bandwidth (Hz)</string>
<string name="app_volume_ptt_title">Use volume keys for PTT</string>
<string name="app_volume_ptt_summary">Volume up/down keys will be used for PTT</string>
<string name="app_category_title">Application settings</string>
<string name="app_category_title">Application Settings</string>
<string name="app_keep_screen_on_title">Keep screen ON</string>
<string name="app_keep_screen_on_summary">Prevent screen switching off when app is active</string>
@ -148,7 +180,7 @@
<string name="kiss_toast_modem_reboot">Modem reboot requested</string>
<string name="codec2_tx_frame_max_size_title">Maximum super frame size (bytes)</string>
<string name="codec2_tx_frame_max_size_summary">Multiple Codec2 samples are aggregated into super frame not larger than this value</string>
<string name="codec2_tx_frame_max_size_summary">Multiple Codec2 encoded samples are aggregated into super frame not larger than this value</string>
<string name="usb_settings_title">USB settings</string>
<string name="usb_data_bits_title">Serial data bits</string>
@ -161,6 +193,15 @@
<string name="usb_serial_title">USB serial settings</string>
<string name="usb_serial_summary">Set USB serial settings, such as speed, bits, parity, etc.</string>
<string name="tnc_extended_title">Extended TNC settings</string>
<string name="tnc_extended_summary">Additional TNC specific settings</string>
<string name="tnc_extended_uart_title">UART modem prefix</string>
<string name="custom_prefix_enabled_title">Enable UART modem TX prefix</string>
<string name="custom_prefix_enabled_summary">Prefix data with the HEX string, used in fixed transmission mode by some UART modems to specify transmission target</string>
<string name="custom_prefix_title">Packet prefix value as a HEX string</string>
<string name="custom_prefix_summary">Prefix content as a HEX string, e.g. C0FFEE</string>
<string name="app_audio_output_speaker_title">Play audio through the speaker</string>
<string name="app_audio_output_speaker_summary">Output incoming audio through the speaker</string>
<string name="app_audio_input_voice_communication_title">Microphone enhancements</string>
@ -232,6 +273,7 @@
<string name="menu_send_position">Send position</string>
<string name="voax25_label">&#9742;</string>
<string name="text_packets_label">&#128276;</string>
<string name="menu_aprs_log">View log</string>
@ -337,7 +379,9 @@
<string name="aprs_is_settings_title">Internet APRS-IS</string>
<string name="aprs_is_settings_summary">Configure internet APRS-IS server connectivity</string>
<string name="aprs_is_tcpip_server_title">Server</string>
<string name="aprs_is_tcpip_server_summary">APRS-IS TCP server (port 14580) to connect</string>
<string name="aprs_is_tcpip_server_summary">APRS-IS TCP server to connect</string>
<string name="aprs_is_tcpip_server_port_title">Server port</string>
<string name="aprs_is_tcpip_server_port_summary">APRS-IS TCP server port to use</string>
<string name="aprs_is_enable_rx_gate_title">Enable APRS-IS RX gate</string>
<string name="aprs_is_enable_tx_gate_title">Enable APRS-IS TX gate</string>
<string name="aprs_is_enable_rx_gate_summary">Radio packets will be forwarded to APRS-IS</string>
@ -354,4 +398,19 @@
<string name="aprsis_wrong_pass">APRS-IS wrong pass</string>
<string name="aprs_is_enable_self_title">Enable own APRS to APRS-IS</string>
<string name="aprs_is_enable_self_summary">Send own APRS data to APRS-IS in addition to currently selected transport</string>
<string name="menu_aprs_map">Map</string>
<string name="map_menu_clear_cache_title">Clear map tile cache</string>
<string name="log_view_menu_clear_title">Clear log</string>
<string name="log_view_menu_clear_1h">Older than 1 hour</string>
<string name="log_view_menu_clear_12h">Older than 12 hours</string>
<string name="log_view_menu_clear_1d">Older than 1 day</string>
<string name="log_view_menu_clear_7d">Older than 7 days</string>
<string name="log_view_menu_clear_all">Clear all</string>
<string name="log_item_activity_delete_hours_title">This will remove everything older than %d hours. Are you sure?</string>
<string name="map_menu_rotate_map">Rotate map with compass</string>
<string name="map_menu_show_range">Show range circles</string>
<string name="map_menu_show_moving">Show moving stations</string>
<string name="aprs_text_packets_enable_title">Enable text packets</string>
<string name="aprs_text_packets_enable_summary">Send lora aprs compatible text packets (0x3c,0xff,0x01 prefix)</string>
<string name="map_menu_move_map">Move map with own position</string>
</resources>

Wyświetl plik

@ -15,12 +15,11 @@
<!-- 0x067B / 0x2303: Prolific PL2303 -->
<usb-device vendor-id="1659" product-id="8963" />
<!-- 0x1a86 / 0x?523: Qinheng CH34x -->
<!-- 0x1a86 / 0x?523: Qinheng CH34x (Arduino) -->
<usb-device vendor-id="6790" product-id="21795" /> <!-- 0x5523: CH341A -->
<usb-device vendor-id="6790" product-id="29987" /> <!-- 0x7523: CH340 -->
<!-- CDC driver -->
<usb-device vendor-id="9025" /> <!-- 0x2341 / ......: Arduino -->
<usb-device vendor-id="5824" product-id="1155" /> <!-- 0x16C0 / 0x0483: Teensyduino -->
<usb-device vendor-id="1003" product-id="8260" /> <!-- 0x03EB / 0x2044: Atmel Lufa -->
<usb-device vendor-id="7855" product-id="4" /> <!-- 0x1eaf / 0x0004: Leaflabs Maple -->
@ -29,8 +28,13 @@
<!-- spark fun -->
<usb-device vendor-id="6991" /> <!-- 0x1b4f / ......: Spark Fun -->
<!-- arduino -->
<usb-device vendor-id="9025" /> <!-- 0x2341 / ......: Arduino -->
<!-- RIG CAT control -->
<usb-device vendor-id="1155" product-id="22322" /> <!-- 0x0483 / 0x5732: STM, MCHF -->
<usb-device vendor-id="4292" product-id="60000" /> <!-- 0x10c4 / 0xea60: CP2102/2109, iCom -->
<!-- Raspberry PI -->
<usb-device vendor-id="11914" /> <!-- 0x2E8A / ......: Raspberry -->
</resources>

Wyświetl plik

@ -112,28 +112,34 @@
app:fragment="com.radio.codec2talkie.settings.SettingsActivity$SettingsSoundModemFragment">
</Preference>
<Preference
app:key="ports_tnc_extended"
app:title="@string/tnc_extended_title"
app:summary="@string/tnc_extended_summary"
app:fragment="com.radio.codec2talkie.settings.SettingsActivity$SettingsTncExtendedFragment">
</Preference>
</PreferenceCategory>
<PreferenceCategory
app:key="codec2_category"
app:title="@string/codec2_category_title">
app:key="codec_category"
app:title="@string/codec_category_title">
<ListPreference
app:key="codec2_mode"
app:title="@string/codec2_mode_title"
app:entries="@array/codec2_modes"
app:entryValues="@array/codec2_modes"
app:defaultValue="MODE_450=10"
app:key="codec_type"
app:title="@string/codec_type_title"
app:entries="@array/codec_type_modes"
app:entryValues="@array/codec_type_modes"
app:defaultValue="Codec2"
app:summary="%s">
</ListPreference>
<EditTextPreference
app:key="codec2_tx_frame_max_size"
app:title="@string/codec2_tx_frame_max_size_title"
app:summary="@string/codec2_tx_frame_max_size_summary"
app:defaultValue="48">
</EditTextPreference>
<Preference
app:key="codec_type_settings"
app:title="@string/codec_type_settings_title"
app:summary="@string/codec_type_settings_summary"
app:fragment="com.radio.codec2talkie.settings.SettingsActivity$SettingsCodecFragment">
</Preference>
<SwitchPreference
app:key="codec2_recording_enabled"
app:title="@string/codec2_recorder_title"
@ -215,6 +221,14 @@
app:defaultValue="true">
</SwitchPreference>
<SwitchPreference
app:key="aprs_text_packets_enable"
app:title="@string/aprs_text_packets_enable_title"
app:summary="@string/aprs_text_packets_enable_summary"
app:dependency="aprs_enable"
app:defaultValue="false">
</SwitchPreference>
<EditTextPreference
app:key="aprs_callsign"
app:title="@string/aprs_callsign_title"

Wyświetl plik

@ -18,6 +18,13 @@
app:defaultValue="euro.aprs2.net">
</EditTextPreference>
<EditTextPreference
app:key="aprs_is_tcpip_server_port"
app:title="@string/aprs_is_tcpip_server_port_title"
app:summary="@string/aprs_is_tcpip_server_port_summary"
app:defaultValue="14580">
</EditTextPreference>
<SwitchPreference
app:key="aprs_is_enable_rx_gate"
app:title="@string/aprs_is_enable_rx_gate_title"

Wyświetl plik

@ -0,0 +1,58 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<PreferenceCategory
app:key="codec2_category"
app:title="@string/codec2_category_title">
<ListPreference
app:key="codec2_mode"
app:title="@string/codec2_mode_title"
app:entries="@array/codec2_modes"
app:entryValues="@array/codec2_modes"
app:defaultValue="MODE_450=10"
app:summary="%s">
</ListPreference>
<EditTextPreference
app:key="codec2_tx_frame_max_size"
app:title="@string/codec2_tx_frame_max_size_title"
app:summary="@string/codec2_tx_frame_max_size_summary"
app:defaultValue="48">
</EditTextPreference>
</PreferenceCategory>
<PreferenceCategory
app:key="opus_category"
app:title="@string/opus_category_title">
<EditTextPreference
app:key="opus_bit_rate"
app:title="@string/opus_bit_rate_title"
app:summary="@string/opus_bit_rate_summary"
app:defaultValue="3200">
</EditTextPreference>
<ListPreference
app:key="opus_frame_size"
app:title="@string/opus_frame_size_title"
app:entries="@array/opus_frame_size"
app:entryValues="@array/opus_frame_size"
app:defaultValue="40"
app:summary="%s">
</ListPreference>
<ListPreference
app:key="opus_complexity"
app:title="@string/opus_complexity_title"
app:entries="@array/opus_complexity_value"
app:entryValues="@array/opus_complexity_value"
app:defaultValue="5"
app:summary="%s">
</ListPreference>
</PreferenceCategory>
</PreferenceScreen>

Wyświetl plik

@ -13,14 +13,21 @@
app:defaultValue="433775000">
</EditTextPreference>
<ListPreference
app:key="kiss_extension_radio_bandwidth"
app:title="@string/kiss_extensions_radio_bandwidth_title"
app:entries="@array/lora_bandwidths"
app:entryValues="@array/lora_bandwidths"
app:defaultValue="125000"
app:summary="%s">
</ListPreference>
<SwitchPreference
app:key="kiss_extension_radio_split_freq"
app:title="@string/kiss_extension_radio_split_freq_title"
app:summary="@string/kiss_extension_radio_split_freq_summary"
app:defaultValue="false">
</SwitchPreference>
<EditTextPreference
app:key="kiss_extension_radio_frequency_tx"
app:title="@string/kiss_extensions_radio_frequency_title_tx"
app:useSimpleSummaryProvider="true"
app:summary="@string/kiss_extensions_radio_frequency_summary_tx"
app:dependency="kiss_extension_radio_split_freq"
app:defaultValue="433775000">
</EditTextPreference>
<ListPreference
app:key="kiss_extension_radio_power"
@ -32,43 +39,104 @@
</ListPreference>
<ListPreference
app:key="kiss_extension_radio_sf"
app:title="@string/kiss_extensions_radio_sf_title"
app:key="kiss_extension_radio_mod"
app:title="@string/kiss_extensions_radio_mod_title"
app:entries="@array/radio_mod_entries"
app:entryValues="@array/radio_mod_values"
app:summary="%s"
app:entries="@array/lora_spreading_factors"
app:entryValues="@array/lora_spreading_factors"
app:defaultValue="7">
app:defaultValue="0">
</ListPreference>
<ListPreference
app:key="kiss_extension_radio_cr"
app:title="@string/kiss_extensions_radio_cr_title"
app:summary="%s"
app:entries="@array/lora_coding_rates"
app:entryValues="@array/lora_coding_rates"
app:defaultValue="6">
</ListPreference>
<PreferenceCategory
app:key="kiss_extensions_radio_category_lora"
app:title="@string/kiss_extensions_radio_category_lora_title">
<EditTextPreference
app:key="kiss_extension_radio_sync"
app:title="@string/kiss_extensions_radio_sync_title"
app:summary="@string/kiss_extensions_radio_sync_summary"
app:useSimpleSummaryProvider="true"
app:defaultValue="34">
</EditTextPreference>
<ListPreference
app:key="kiss_extension_radio_bandwidth"
app:title="@string/kiss_extensions_radio_bandwidth_title"
app:entries="@array/lora_bandwidths"
app:entryValues="@array/lora_bandwidths"
app:defaultValue="125000"
app:summary="%s">
</ListPreference>
<CheckBoxPreference
app:key="kiss_extension_radio_crc"
app:title="@string/kiss_extensions_radio_crc_title"
app:summary="@string/kiss_extensions_radio_crc_summary"
app:defaultValue="true">
</CheckBoxPreference>
<ListPreference
app:key="kiss_extension_radio_sf"
app:title="@string/kiss_extensions_radio_sf_title"
app:summary="%s"
app:entries="@array/lora_spreading_factors"
app:entryValues="@array/lora_spreading_factors"
app:defaultValue="7">
</ListPreference>
<Preference
app:key="kiss_extension_reboot"
app:title="@string/kiss_extension_reboot_title"
app:summary="@string/kiss_extension_reboot_summary">
</Preference>
<ListPreference
app:key="kiss_extension_radio_cr"
app:title="@string/kiss_extensions_radio_cr_title"
app:summary="%s"
app:entries="@array/lora_coding_rates"
app:entryValues="@array/lora_coding_rates"
app:defaultValue="6">
</ListPreference>
<EditTextPreference
app:key="kiss_extension_radio_sync"
app:title="@string/kiss_extensions_radio_sync_title"
app:summary="@string/kiss_extensions_radio_sync_summary"
app:useSimpleSummaryProvider="true"
app:defaultValue="34">
</EditTextPreference>
<CheckBoxPreference
app:key="kiss_extension_radio_crc"
app:title="@string/kiss_extensions_radio_crc_title"
app:summary="@string/kiss_extensions_radio_crc_summary"
app:defaultValue="true">
</CheckBoxPreference>
</PreferenceCategory>
<PreferenceCategory
app:key="kiss_extensions_radio_category_fsk"
app:title="@string/kiss_extensions_radio_category_fsk_title">
<EditTextPreference
app:key="kiss_extension_radio_fsk_bit_rate"
app:title="@string/kiss_extension_radio_fsk_bit_rate_title"
app:useSimpleSummaryProvider="true"
app:summary="@string/kiss_extension_radio_fsk_bit_rate_summary"
app:defaultValue="4800">
</EditTextPreference>
<EditTextPreference
app:key="kiss_extension_radio_fsk_freq_dev"
app:title="@string/kiss_extension_radio_fsk_freq_dev_title"
app:useSimpleSummaryProvider="true"
app:summary="@string/kiss_extension_radio_fsk_freq_dev_summary"
app:defaultValue="1200">
</EditTextPreference>
<ListPreference
app:key="kiss_extension_radio_fsk_rx_bw"
app:title="@string/kiss_extension_radio_fsk_rx_bw_title"
app:entries="@array/fsk_rx_bw_entries"
app:entryValues="@array/fsk_rx_bw_values"
app:summary="%s"
app:defaultValue="9700">
</ListPreference>
</PreferenceCategory>
<PreferenceCategory
app:key="kiss_extensions_radio_category_other"
app:title="@string/kiss_extensions_radio_category_other_title">
<Preference
app:key="kiss_extension_reboot"
app:title="@string/kiss_extension_reboot_title"
app:summary="@string/kiss_extension_reboot_summary">
</Preference>
</PreferenceCategory>
</PreferenceCategory>
</PreferenceScreen>

Wyświetl plik

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<PreferenceCategory
app:key="tnc_extended_uart"
app:title="@string/tnc_extended_uart_title">
<CheckBoxPreference
app:key="custom_prefix_enabled"
app:title="@string/custom_prefix_enabled_title"
app:summary="@string/custom_prefix_enabled_summary"
app:defaultValue="false">
</CheckBoxPreference>
<EditTextPreference
app:key="custom_prefix"
app:title="@string/custom_prefix_title"
app:summary = "@string/custom_prefix_summary"
app:dependency="custom_prefix_enabled"
app:defaultValue="C0FFEE">
</EditTextPreference>
</PreferenceCategory>
</PreferenceScreen>

Wyświetl plik

@ -54,5 +54,6 @@
app:summary="@string/usb_rts_summary"
app:defaultValue="false">
</CheckBoxPreference>
</PreferenceCategory>
</PreferenceScreen>

BIN
images/modems.png vendored 100644

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 806 KiB

Wyświetl plik

@ -74,12 +74,12 @@ task compileCodec2 {
doLast {
exec {
workingDir "$projectDir/build/codec2_build_linux"
commandLine "/usr/bin/cmake", "$projectDir/src/codec2"
commandLine "cmake", "$projectDir/src/codec2"
}
exec {
workingDir "$projectDir/build/codec2_build_linux"
commandLine "/usr/bin/make"
commandLine "make"
}
for(String abi : rootProject.ext.ABI_FILTERS.split(";")) {
@ -96,7 +96,7 @@ task compileCodec2 {
exec {
workingDir "$projectDir/build/codec2_build_android_" + abi
commandLine "/usr/bin/cmake", "--build", "."
commandLine "cmake", "--build", "."
}
copy {

3
libopus-android/.gitignore vendored 100644
Wyświetl plik

@ -0,0 +1,3 @@
/build
**/.cxx

134
libopus-android/build.gradle vendored 100644
Wyświetl plik

@ -0,0 +1,134 @@
apply plugin: 'digital.wup.android-maven-publish'
apply plugin: 'com.android.library'
buildscript {
repositories {
jcenter()
}
dependencies {
classpath "digital.wup:android-maven-publish:3.6.2"
}
}
android {
compileSdkVersion 30
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
defaultConfig {
minSdkVersion 16
targetSdkVersion 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
externalNativeBuild {
cmake {
cppFlags ""
}
}
ndk {
abiFilters = []
abiFilters.addAll(rootProject.ext.ABI_FILTERS.split(';').collect{it as String})
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
externalNativeBuild {
cmake {
path "src/main/cpp/CMakeLists.txt"
}
}
sourceSets {
main {
jniLibs.srcDirs = ['build/imported-lib']
}
}
}
// As per: https://github.com/googlesamples/android-ndk/blob/master/hello-libs/app/build.gradle
tasks.whenTaskAdded { task ->
if (task.name == 'externalNativeBuildRelease') {
task.dependsOn compileOpus
} else if (task.name == 'externalNativeBuildDebug') {
task.dependsOn compileOpus
}
}
task compileOpus {
doFirst {
project.file("build/opus_build_linux").mkdirs()
for(String abi : rootProject.ext.ABI_FILTERS.split(";")) {
project.file("build/opus_build_android_" + abi).mkdirs()
project.file("build/imported-lib/"+abi).mkdirs()
}
}
doLast {
exec {
workingDir "$projectDir/build/opus_build_linux"
commandLine "cmake", "$projectDir/src/opus"
}
exec {
workingDir "$projectDir/build/opus_build_linux"
commandLine "make"
}
for(String abi : rootProject.ext.ABI_FILTERS.split(";")) {
System.out.println("Handle abi " + abi)
exec {
workingDir "$projectDir/build/opus_build_android_" + abi
commandLine "cmake", "$projectDir/src/opus",
"-DOPUS_BUILD_SHARED_LIBRARY=true",
"-DCMAKE_TOOLCHAIN_FILE=" + android.ndkDirectory + "/build/cmake/android.toolchain.cmake",
"-DANDROID_NATIVE_API_LEVEL=23", "-DANDROID_ABI="+abi,
"-DANDROID_STL=c++_shared"
}
exec {
workingDir "$projectDir/build/opus_build_android_" + abi
commandLine "cmake", "--build", "."
}
copy {
from "$projectDir/build/opus_build_android_" + abi +"/libopus.so"
into "$projectDir/build/imported-lib/"+abi
}
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:support-annotations:28.0.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}
publishing {
publications {
mavenAar(MavenPublication) {
from components.android
groupId rootProject.group
artifactId project.name
version "${rootProject.version}"
}
}
repositories {
//Set this in projectdir/extrasettings.gradle
maven {
url rootProject.file(rootProject.ext.buildConfigProperties['repo.dir']).getAbsolutePath()
}
}
}

Wyświetl plik

@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

Wyświetl plik

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest package="com.radio.opus">
<application>
</application>
</manifest>

Wyświetl plik

@ -0,0 +1,41 @@
# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html
# Sets the minimum version of CMake required to build the native library.
cmake_minimum_required(VERSION 3.4.1)
set(main_DIR ${CMAKE_SOURCE_DIR})
add_library(libopus SHARED IMPORTED)
set_target_properties(libopus PROPERTIES IMPORTED_LOCATION
${main_DIR}/../../../build/imported-lib/${ANDROID_ABI}/libopus.so)
include_directories(${main_DIR}/opus/)
# Searches for a specified prebuilt library and stores the path as a
## variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.
add_library( # Sets the name of the library.
OpusJNI
## Sets the library as a shared library.
SHARED
## Provides a relative path to your source file(s).
OpusJNI.cpp)
find_library( # Sets the name of the path variable.
log-lib
# Specifies the name of the NDK library that
# you want CMake to locate.
log)
# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.
target_link_libraries(
OpusJNI
libopus
${log-lib})

Some files were not shown because too many files have changed in this diff Show More