diff --git a/app/src/main/java/de/rumpold/androiddsky/DSKY.java b/app/src/main/java/de/rumpold/androiddsky/DSKY.java index 3cc62c8..ed5e4e1 100755 --- a/app/src/main/java/de/rumpold/androiddsky/DSKY.java +++ b/app/src/main/java/de/rumpold/androiddsky/DSKY.java @@ -11,6 +11,11 @@ import java.net.SocketAddress; import java.util.Arrays; import java.util.HashMap; import java.util.Map; +import java.util.Timer; +import java.util.TimerTask; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; import de.rumpold.androiddsky.network.YaAgcClient; import de.rumpold.androiddsky.ui.DSKYActivity; @@ -19,9 +24,18 @@ import de.rumpold.androiddsky.ui.DSKYActivity; * Created by Adriano on 17.05.2016. */ public class DSKY { - private static final String TAG = "TAG"; + public interface ConnectionListener { + void onConnect(); + + void onDisconnect(); + } + + private static final String TAG = "DSKY"; + + private final Lock connectLock = new ReentrantLock(); private final DSKYActivity activity; + private ConnectionListener listener; private final YaAgcClient client; private String prog; @@ -36,19 +50,19 @@ public class DSKY { private char[] register2 = new char[5]; private char[] register3 = new char[5]; - private boolean compActy; - private boolean uplinkActy; - private boolean noAtt; - private boolean gimbalLock; - private boolean tracker; - private boolean temp; - private boolean vel; - private boolean alt; - private boolean progError; - private boolean keyRel; - private boolean oprErr; - private boolean stby; - private boolean restart; + private volatile boolean compActy; + private volatile boolean uplinkActy; + private volatile boolean noAtt; + private volatile boolean gimbalLock; + private volatile boolean tracker; + private volatile boolean temp; + private volatile boolean vel; + private volatile boolean alt; + private volatile boolean progError; + private volatile boolean keyRel; + private volatile boolean oprErr; + private volatile boolean stby; + private volatile boolean restart; private final Map keycodeMap = new HashMap<>(); @@ -74,20 +88,10 @@ public class DSKY { keycodeMap.put("PRO", 1 << 13); } - public DSKY(DSKYActivity activity) { + public DSKY(DSKYActivity activity, ConnectionListener listener) { this.activity = activity; - - final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(activity.getApplicationContext()); - final String agcHost = preferences.getString("agc_host", null); - final int agcPort = Integer.parseInt(preferences.getString("agc_port", "-1")); - - if (agcHost == null || agcPort <= 0) { - this.client = null; - return; - } else { - final SocketAddress agcAddress = new InetSocketAddress(agcHost, agcPort); - this.client = new YaAgcClient(this, agcAddress); - } + this.listener = listener; + this.client = new YaAgcClient(this); Arrays.fill(register1, ' '); Arrays.fill(register2, ' '); @@ -103,14 +107,53 @@ public class DSKY { @Override public void run() { try { - if (client != null) { - client.connect(); + Log.d(TAG, "Acquiring connect lock... "); + connectLock.lock(); + Log.d(TAG, "Done."); + + final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(activity.getApplicationContext()); + final String agcHost = preferences.getString("agc_host", null); + final int agcPort = Integer.parseInt(preferences.getString("agc_port", "-1")); + final SocketAddress agcAddress = new InetSocketAddress(agcHost, agcPort); + final boolean success = client.connect(agcAddress); + + if (success) { + Log.i(TAG, "Successfully connected"); + listener.onConnect(); + } else { + Log.w(TAG, "Connection failed"); } } catch (IOException e) { // throw new RuntimeException("Cannot connect to yaAGC", e); + } finally { + connectLock.unlock(); } } }); + + new Timer().schedule(new TimerTask() { + private final AtomicBoolean _keyRel = new AtomicBoolean(false); + private final AtomicBoolean _oprErr = new AtomicBoolean(false); + + @Override + public void run() { + Log.v("BLINK", String.format("keyRel=%b/%b, oprErr=%b/%b", + keyRel, _keyRel.get(), oprErr, _oprErr.get())); + handle(R.id.lbl_keyRel, _keyRel, keyRel); + handle(R.id.lbl_oprErr, _oprErr, oprErr); + } + + private void handle(int indicator, AtomicBoolean internalState, boolean state) { + if (state) { + internalState.set(!internalState.get()); + activity.updateIndicator(indicator, internalState.get()); + + } else { + internalState.set(false); + activity.updateIndicator(indicator, false); + } + } + }, 667, 667); } public void refreshDisplay() { @@ -119,9 +162,9 @@ public class DSKY { activity.updateIndicator(R.id.lbl_uplinkActy, uplinkActy); activity.updateIndicator(R.id.lbl_prog, progError); activity.updateIndicator(R.id.lbl_tracker, tracker); - activity.updateIndicator(R.id.lbl_oprErr, oprErr); +// activity.updateIndicator(R.id.lbl_oprErr, oprErr); activity.updateIndicator(R.id.lbl_stby, stby); - activity.updateIndicator(R.id.lbl_keyRel, keyRel); +// activity.updateIndicator(R.id.lbl_keyRel, keyRel); activity.updateIndicator(R.id.lbl_restart, restart); activity.updateIndicator(R.id.lbl_compActy, compActy); @@ -155,7 +198,9 @@ public class DSKY { client.disconnect(); } } catch (IOException e) { - throw new RuntimeException("Cannot connect to yaAGC", e); + throw new RuntimeException("Cannot disconnect from yaAGC", e); + } finally { + listener.onDisconnect(); } } }); diff --git a/app/src/main/java/de/rumpold/androiddsky/network/YaAgcClient.java b/app/src/main/java/de/rumpold/androiddsky/network/YaAgcClient.java index 4f736b6..526ca94 100755 --- a/app/src/main/java/de/rumpold/androiddsky/network/YaAgcClient.java +++ b/app/src/main/java/de/rumpold/androiddsky/network/YaAgcClient.java @@ -8,7 +8,7 @@ import android.widget.Toast; import java.io.IOException; import java.net.SocketAddress; import java.nio.ByteBuffer; -import java.nio.channels.ClosedByInterruptException; +import java.nio.channels.AsynchronousCloseException; import java.nio.channels.SocketChannel; import de.rumpold.androiddsky.DSKY; @@ -23,7 +23,6 @@ public class YaAgcClient { private static final int KEYBOARD_CHANNEL = 015; private final DSKY dsky; - private final SocketAddress agcAddress; private SocketChannel agcChannel; private Thread handler; @@ -56,7 +55,7 @@ public class YaAgcClient { buf.position(buf.position() + 4); } - } catch (ClosedByInterruptException | InterruptedException ignored) { + } catch (AsynchronousCloseException | InterruptedException ignored) { } catch (IOException e) { Log.w(TAG, "Connection error", e); new Handler(Looper.getMainLooper()).post(new Runnable() { @@ -72,12 +71,11 @@ public class YaAgcClient { } } - public YaAgcClient(DSKY dsky, SocketAddress agcAddress) { + public YaAgcClient(DSKY dsky) { this.dsky = dsky; - this.agcAddress = agcAddress; } - public void connect() throws IOException { + public boolean connect(SocketAddress agcAddress) throws IOException { Log.i(TAG, "Connecting to yaAGC at " + agcAddress); agcChannel = SocketChannel.open(); boolean success = agcChannel.connect(agcAddress); @@ -87,6 +85,8 @@ public class YaAgcClient { handler = new Thread(new DSKYHandler(), "DSKY I/O Handler"); handler.start(); } + + return success; } public void disconnect() throws IOException { @@ -103,7 +103,7 @@ public class YaAgcClient { } public boolean isConnected() { - return agcChannel.isConnected(); + return agcChannel != null && agcChannel.isConnected(); } public void sendKeyCode(int keyCode) throws IOException { @@ -178,6 +178,10 @@ public class YaAgcClient { handleChannel11(packet); break; + case 013: + handleChannel13(packet); + break; + default: return; } @@ -190,6 +194,18 @@ public class YaAgcClient { dsky.refreshDisplay(); } + /** + * Handle changes of additional indicators, transmitted on I/O channel 13 + * + * @param packet + */ + private void handleChannel13(AgcPacket packet) { + final boolean stby = (packet.getDataCode() & 0b0100_0000_0000) != 0; + dsky.setStby(stby); + + Log.d(TAG, String.format("[DSKY indicators] stby = %b", stby)); + } + /** * Handle changes of the indicator lights, transmitted on I/O channel 11 * @@ -199,13 +215,17 @@ public class YaAgcClient { final boolean compActy = (packet.getDataCode() & 0b0000_0010) != 0; final boolean uplinkActy = (packet.getDataCode() & 0b0000_0100) != 0; final boolean temp = (packet.getDataCode() & 0b0000_1000) != 0; + final boolean keyRel = (packet.getDataCode() & 0b0001_0000) != 0; + final boolean oprErr = (packet.getDataCode() & 0b0100_0000) != 0; dsky.setCompActy(compActy); dsky.setUplinkActy(uplinkActy); dsky.setTemp(temp); + dsky.setKeyRel(keyRel); + dsky.setOprErr(oprErr); - Log.d(TAG, String.format("[DSKY indicators] compActy = %s, uplinkActy = %s, temp = %s", - compActy, uplinkActy, temp)); + Log.d(TAG, String.format("[DSKY indicators] compActy = %b, uplinkActy = %b, temp = %b, keyRel = %b", + compActy, uplinkActy, temp, keyRel)); } /** diff --git a/app/src/main/java/de/rumpold/androiddsky/ui/DSKYActivity.java b/app/src/main/java/de/rumpold/androiddsky/ui/DSKYActivity.java index 082e378..e2188ac 100755 --- a/app/src/main/java/de/rumpold/androiddsky/ui/DSKYActivity.java +++ b/app/src/main/java/de/rumpold/androiddsky/ui/DSKYActivity.java @@ -6,13 +6,16 @@ import android.os.Handler; import android.preference.PreferenceManager; import android.support.annotation.IdRes; import android.support.v7.app.AppCompatActivity; +import android.util.Log; import android.view.HapticFeedbackConstants; import android.view.Menu; import android.view.MenuItem; -import android.view.View; import android.widget.Button; import android.widget.TextView; +import butterknife.BindColor; +import butterknife.ButterKnife; +import butterknife.OnClick; import de.rumpold.androiddsky.DSKY; import de.rumpold.androiddsky.R; @@ -21,30 +24,33 @@ import de.rumpold.androiddsky.R; * status bar and navigation/system bar) with user interaction. */ public class DSKYActivity extends AppCompatActivity { - /** - * Some older devices needs a small delay between UI widget updates - * and a change of the status and navigation bar. - */ - private static final int UI_ANIMATION_DELAY = 300; + private static final String TAG = "DSKYActivity"; private final Handler mViewHandler = new Handler(); + private MenuItem disconnectButton; + private MenuItem connectButton; private DSKY dsky; - private int rightActiveColor; - private int activeColor; - private int passiveColor; - private int compActyColor; - public void updateIndicator(@IdRes final int id, final boolean state) { + @BindColor(R.color.indicatorRightActive) + int rightActiveColor; + @BindColor(R.color.indicatorActive) + int activeColor; + @BindColor(R.color.indicatorPassive) + int passiveColor; + @BindColor(R.color.compActy) + int compActyColor; + + public void updateIndicator(@IdRes final int id, final boolean active) { mViewHandler.post(new Runnable() { @Override public void run() { - TextView indicator = (TextView) findViewById(id); + final TextView indicator = findViewById(id); if (indicator == null) { return; } - if (state) { + if (active) { final int color; if ("right".equals(indicator.getTag())) { color = rightActiveColor; @@ -65,7 +71,7 @@ public class DSKYActivity extends AppCompatActivity { mViewHandler.post(new Runnable() { @Override public void run() { - TextView display = (TextView) findViewById(id); + final TextView display = findViewById(id); display.setText(value); } }); @@ -74,29 +80,53 @@ public class DSKYActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setContentView(R.layout.activity_dsky); + ButterKnife.bind(this); - dsky = new DSKY(this); + dsky = new DSKY(this, new DSKY.ConnectionListener() { + @Override + public void onConnect() { + Log.i(TAG, "Connected to AGC"); + mViewHandler.post(new Runnable() { + @Override + public void run() { + if (connectButton != null) { + connectButton.setVisible(false); + } + if (disconnectButton != null) { + disconnectButton.setVisible(true); + } + } + }); + } - // Colors - rightActiveColor = getResources().getColor(R.color.indicatorRightActive); - activeColor = getResources().getColor(R.color.indicatorActive); - passiveColor = getResources().getColor(R.color.indicatorPassive); - compActyColor = getResources().getColor(R.color.compActy); - - createButtonListeners(); + @Override + public void onDisconnect() { + Log.i(TAG, "Disconnected from AGC"); + mViewHandler.post(new Runnable() { + @Override + public void run() { + if (connectButton != null) { + connectButton.setVisible(true); + } + if (disconnectButton != null) { + disconnectButton.setVisible(false); + } + } + }); + } + }); } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.action_connect: - if (!dsky.isConnected()) { - dsky.connect(); - } else { - dsky.disconnect(); - } + dsky.connect(); + return true; + + case R.id.action_disconnect: + dsky.disconnect(); return true; case R.id.action_settings: @@ -112,39 +142,22 @@ public class DSKYActivity extends AppCompatActivity { @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.dsky_menu, menu); + connectButton = menu.findItem(R.id.action_connect); + disconnectButton = menu.findItem(R.id.action_disconnect); + return super.onCreateOptionsMenu(menu); } - private void createButtonListeners() { - final View.OnClickListener buttonListener = new View.OnClickListener() { - @Override - public void onClick(View view) { - final Button button = (Button) view; - button.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY, - HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING); - dsky.buttonPressed(button.getText().toString()); - } - }; - - findViewById(R.id.btn_verb).setOnClickListener(buttonListener); - findViewById(R.id.btn_noun).setOnClickListener(buttonListener); - findViewById(R.id.btn_plus).setOnClickListener(buttonListener); - findViewById(R.id.btn_minus).setOnClickListener(buttonListener); - findViewById(R.id.btn_0).setOnClickListener(buttonListener); - findViewById(R.id.btn_1).setOnClickListener(buttonListener); - findViewById(R.id.btn_2).setOnClickListener(buttonListener); - findViewById(R.id.btn_3).setOnClickListener(buttonListener); - findViewById(R.id.btn_4).setOnClickListener(buttonListener); - findViewById(R.id.btn_5).setOnClickListener(buttonListener); - findViewById(R.id.btn_6).setOnClickListener(buttonListener); - findViewById(R.id.btn_7).setOnClickListener(buttonListener); - findViewById(R.id.btn_8).setOnClickListener(buttonListener); - findViewById(R.id.btn_9).setOnClickListener(buttonListener); - findViewById(R.id.btn_clr).setOnClickListener(buttonListener); - findViewById(R.id.btn_pro).setOnClickListener(buttonListener); - findViewById(R.id.btn_key_rel).setOnClickListener(buttonListener); - findViewById(R.id.btn_entr).setOnClickListener(buttonListener); - findViewById(R.id.btn_rset).setOnClickListener(buttonListener); + @OnClick({R.id.btn_verb, R.id.btn_noun, R.id.btn_plus, R.id.btn_minus, + R.id.btn_0, R.id.btn_1, R.id.btn_2, + R.id.btn_3, R.id.btn_4, R.id.btn_5, + R.id.btn_6, R.id.btn_7, R.id.btn_8, R.id.btn_9, + R.id.btn_clr, R.id.btn_pro, R.id.btn_key_rel, + R.id.btn_entr, R.id.btn_rset}) + public void dskyButtonCallback(Button button) { + button.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY, + HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING); + dsky.buttonPressed(button.getText().toString()); } @Override diff --git a/app/src/main/res/menu/dsky_menu.xml b/app/src/main/res/menu/dsky_menu.xml index b5bf09f..3cbc019 100644 --- a/app/src/main/res/menu/dsky_menu.xml +++ b/app/src/main/res/menu/dsky_menu.xml @@ -1,12 +1,16 @@ + xmlns:app="http://schemas.android.com/apk/res-auto"> + + app:showAsAction="ifRoom" /> + app:showAsAction="never" /> \ No newline at end of file