diff --git a/SchwertUndMagieOnPebbleCompanionApp/app/build.gradle.kts b/SchwertUndMagieOnPebbleCompanionApp/app/build.gradle.kts index a0a94ce..7428246 100644 --- a/SchwertUndMagieOnPebbleCompanionApp/app/build.gradle.kts +++ b/SchwertUndMagieOnPebbleCompanionApp/app/build.gradle.kts @@ -42,7 +42,6 @@ dependencies { implementation(libs.androidx.core.ktx) implementation(libs.material) implementation(libs.nanohttpd) - implementation(libs.pebblekit) testImplementation(libs.junit) androidTestImplementation(libs.androidx.espresso.core) androidTestImplementation(libs.androidx.junit) diff --git a/SchwertUndMagieOnPebbleCompanionApp/app/src/main/java/de/ladkau/schwertundmagieonpebblecompanionapp/C64Engine.kt b/SchwertUndMagieOnPebbleCompanionApp/app/src/main/java/de/ladkau/schwertundmagieonpebblecompanionapp/C64Engine.kt deleted file mode 100644 index 9e4af57..0000000 --- a/SchwertUndMagieOnPebbleCompanionApp/app/src/main/java/de/ladkau/schwertundmagieonpebblecompanionapp/C64Engine.kt +++ /dev/null @@ -1,37 +0,0 @@ -package de.ladkau.schwertundmagieonpebblecompanionapp - -import android.graphics.Bitmap -import java.nio.ByteBuffer - -class C64Engine { - // Native hooks representing underlying open-source emulator loops (e.g., Frodo Core) - external fun initEmulator(romPath: String): Boolean - external fun runFrame() - external fun getRawVideoBuffer(buffer: ByteBuffer) // Populates native 320x200 ARGB pixels - external fun injectNativeKey(keyCode: Int, isPressed: Boolean) - - companion object { - init { - System.loadLibrary("frodoc64_core") - } - } - - // Maps standard incoming string commands to traditional C64 matrix keycodes - fun handleWatchInput(command: String) { - val c64KeyCode = when (command) { - "1" -> 0x31 // C64 '1' - "2" -> 0x32 // C64 '2' - "3" -> 0x33 // C64 '3' - "Y" -> 0x59 // C64 'Y' - "N" -> 0x4E // C64 'N' - else -> return - } - - // Simulates a clean hardware keypress cycle - Thread { - injectNativeKey(c64KeyCode, true) - Thread.sleep(100) // Hold down frame duration - injectNativeKey(c64KeyCode, false) - }.start() - } -} \ No newline at end of file diff --git a/SchwertUndMagieOnPebbleCompanionApp/app/src/main/java/de/ladkau/schwertundmagieonpebblecompanionapp/EmulatorService.kt b/SchwertUndMagieOnPebbleCompanionApp/app/src/main/java/de/ladkau/schwertundmagieonpebblecompanionapp/EmulatorService.kt deleted file mode 100644 index a5ba29c..0000000 --- a/SchwertUndMagieOnPebbleCompanionApp/app/src/main/java/de/ladkau/schwertundmagieonpebblecompanionapp/EmulatorService.kt +++ /dev/null @@ -1,74 +0,0 @@ -package de.ladkau.schwertundmagieonpebblecompanionapp - -import android.app.Service -import android.content.Context -import android.content.Intent -import android.os.IBinder -import com.getpebble.android.kit.PebbleKit -import com.getpebble.android.kit.PebbleKit.PebbleDataReceiver -import com.getpebble.android.kit.util.PebbleDictionary -import java.util.UUID - -class EmulatorService : Service() { - private val PEBBLE_APP_UUID = UUID.fromString("6d616769-652d-c64p-ebbl-e57591684000") - private val KEY_COMMAND = 1 - - private lateinit var emulatorEngine: C64Engine - private lateinit var watchStreamer: PebbleStreamer - private var isRunning = false - private var pebbleDataReceiver: PebbleDataReceiver? = null - - override fun onCreate() { - super.onCreate() - emulatorEngine = C64Engine() - watchStreamer = PebbleStreamer(this) - - // Boot standard disk path containing Schwert und Magie Part 1 - val romPath = filesDir.absolutePath + "/schwert_und_magie_1.d64" - emulatorEngine.initEmulator(romPath) - - setupPebbleInputReceiver() - startEmulationLoop() - } - - private fun setupPebbleInputReceiver() { - pebbleDataReceiver = object : PebbleDataReceiver(PEBBLE_APP_UUID) { - override fun receiveData(context: Context?, transactionId: Int, data: PebbleDictionary) { - // Read ASCII string parsed by watch selections - val command = data.getString(KEY_COMMAND) - if (command != null) { - emulatorEngine.handleWatchInput(command) - } - } - } - PebbleKit.registerReceivedDataHandler(this, pebbleDataReceiver) - } - - private fun startEmulationLoop() { - isRunning = true - Thread { - val TargetFrameTimeMs = 33 // Aiming roughly around standard video refresh increments - while (isRunning) { - val startTime = System.currentTimeMillis() - - emulatorEngine.runFrame() - watchStreamer.processAndStreamFrame(emulatorEngine) - - val elapsed = System.currentTimeMillis() - startTime - if (elapsed < TargetFrameTimeMs) { - Thread.sleep(TargetFrameTimeMs - elapsed) - } - } - }.start() - } - - override fun onDestroy() { - isRunning = false - super.onDestroy() - try { - unregisterReceiver(pebbleDataReceiver) - } catch (e: Exception) { /* Context safety cleanup */ } - } - - override fun onBind(intent: Intent?): IBinder? = null -} \ No newline at end of file diff --git a/SchwertUndMagieOnPebbleCompanionApp/app/src/main/java/de/ladkau/schwertundmagieonpebblecompanionapp/PebbleStreamer.kt b/SchwertUndMagieOnPebbleCompanionApp/app/src/main/java/de/ladkau/schwertundmagieonpebblecompanionapp/PebbleStreamer.kt deleted file mode 100644 index 440eaa6..0000000 --- a/SchwertUndMagieOnPebbleCompanionApp/app/src/main/java/de/ladkau/schwertundmagieonpebblecompanionapp/PebbleStreamer.kt +++ /dev/null @@ -1,76 +0,0 @@ -package de.ladkau.schwertundmagieonpebblecompanionapp - -import android.content.Context -import android.graphics.Bitmap -import com.getpebble.android.kit.PebbleKit -import com.getpebble.android.kit.util.PebbleDictionary -import java.nio.ByteBuffer -import java.util.UUID - -class PebbleStreamer(private val context: Context) { - // Matches the unique watch app configuration ID - private val PEBBLE_APP_UUID = UUID.fromString("6d616769-652d-c64p-ebbl-e57591684000") - private val KEY_FRAMEBUFFER = 0 - - private val c64Width = 320 - private val c64Height = 200 - private val targetWidth = 144 - private val targetHeight = 168 - - private val rawVideoByteBuffer = ByteBuffer.allocateDirect(c64Width * c64Height * 4) - private val previousPackedFrame = ByteArray(3024) // Base baseline to identify delta frames - - fun processAndStreamFrame(engine: C64Engine) { - rawVideoByteBuffer.clear() - engine.getRawVideoBuffer(rawVideoByteBuffer) - rawVideoByteBuffer.rewind() - - // 1. Downsample raw output directly to 144x168 target matrix via threshold loop - val packedFrame = ByteArray(3024) // 144 * 168 / 8 bits - var byteIndex = 0 - var bitCounter = 0 - var currentByte = 0 - - // Calculate steps for nearest-neighbor scaling while stripping non-essential margins - val scaleX = c64Width.toFloat() / targetWidth.toFloat() - val scaleY = c64Height.toFloat() / targetHeight.toFloat() - - for (y in 0 until targetHeight) { - val sourceY = (y * scaleY).toInt().coerceIn(0, c64Height - 1) - for (x in 0 until targetWidth) { - val sourceX = (x * scaleX).toInt().coerceIn(0, c64Width - 1) - - // Read Pixel Integer (ARGB) - val pixelIdx = (sourceY * c64Width + sourceX) * 4 - val r = rawVideoByteBuffer.get(pixelIdx + 1).toInt() and 0xFF - val g = rawVideoByteBuffer.get(pixelIdx + 2).toInt() and 0xFF - val b = rawVideoByteBuffer.get(pixelIdx + 3).toInt() and 0xFF - - // Traditional relative luminance formula for 1-bit thresholding - val luminance = (0.299 * r + 0.587 * g + 0.114 * b) - val bit = if (luminance > 128) 1 else 0 // 1 = White background, 0 = Dark Text - - // Pack bits securely into destination byte indices - currentByte = (currentByte shl 1) or bit - bitCounter++ - - if (bitCounter == 8) { - packedFrame[byteIndex++] = currentByte.toByte() - currentByte = 0 - bitCounter = 0 - } - } - } - - // 2. Perform simple Dirty Frame Check to preserve BLE airwaves - if (packedFrame.contentEquals(previousPackedFrame)) { - return // Game screen is currently static; abort transfer - } - System.arraycopy(packedFrame, 0, previousPackedFrame, 0, packedFrame.size) - - // 3. Dispatch the payload out to the connected Pebble Watch - val dict = PebbleDictionary() - dict.addBytes(KEY_FRAMEBUFFER, packedFrame) - PebbleKit.sendDataToPebble(context, PEBBLE_APP_UUID, dict) - } -} \ No newline at end of file diff --git a/SchwertUndMagieOnPebbleCompanionApp/gradle/libs.versions.toml b/SchwertUndMagieOnPebbleCompanionApp/gradle/libs.versions.toml index 9ab976a..f077f8a 100644 --- a/SchwertUndMagieOnPebbleCompanionApp/gradle/libs.versions.toml +++ b/SchwertUndMagieOnPebbleCompanionApp/gradle/libs.versions.toml @@ -8,7 +8,6 @@ appcompat = "1.6.1" material = "1.10.0" activityKtx = "1.8.0" constraintlayout = "2.1.4" -pebblekit = "4.0.1" nanohttpd = "2.3.1" [libraries] @@ -20,7 +19,6 @@ androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version material = { group = "com.google.android.material", name = "material", version.ref = "material" } androidx-activity-ktx = { group = "androidx.activity", name = "activity-ktx", version.ref = "activityKtx" } androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" } -pebblekit = { group = "com.getpebble", name = "pebblekit", version.ref = "pebblekit" } nanohttpd = { group = "org.nanohttpd", name = "nanohttpd", version.ref = "nanohttpd" } [plugins] diff --git a/SchwertUndMagieOnPebbleCompanionApp/settings.gradle.kts b/SchwertUndMagieOnPebbleCompanionApp/settings.gradle.kts index 2b2f564..539f164 100644 --- a/SchwertUndMagieOnPebbleCompanionApp/settings.gradle.kts +++ b/SchwertUndMagieOnPebbleCompanionApp/settings.gradle.kts @@ -19,7 +19,6 @@ dependencyResolutionManagement { repositories { google() mavenCentral() - maven { url = uri("https://oss.sonatype.org/content/repositories/releases/") } } } diff --git a/SchwertUndMagieOnPebbleWatchApp/src/c/SchwertUndMagieOnPebbleFrontend.c b/SchwertUndMagieOnPebbleWatchApp/src/c/SchwertUndMagieOnPebbleFrontend.c index 3b0df2f..d694fef 100644 --- a/SchwertUndMagieOnPebbleWatchApp/src/c/SchwertUndMagieOnPebbleFrontend.c +++ b/SchwertUndMagieOnPebbleWatchApp/src/c/SchwertUndMagieOnPebbleFrontend.c @@ -20,16 +20,9 @@ static void send_key_to_phone(const char *key_str) { } static void inbox_received_callback(DictionaryIterator *iterator, void *context) { - APP_LOG(APP_LOG_LEVEL_DEBUG, "inbox_received_callback fired"); Tuple *time_tuple = dict_find(iterator, KEY_TIME); - if (time_tuple) { - APP_LOG(APP_LOG_LEVEL_DEBUG, "KEY_TIME found, type=%d", (int)time_tuple->type); - } else { - APP_LOG(APP_LOG_LEVEL_WARNING, "KEY_TIME not found in message"); - } if (time_tuple && time_tuple->type == TUPLE_CSTRING) { snprintf(s_time_buffer, sizeof(s_time_buffer), "%s", time_tuple->value->cstring); - APP_LOG(APP_LOG_LEVEL_DEBUG, "Setting time: %s", s_time_buffer); text_layer_set_text(s_time_layer, s_time_buffer); layer_mark_dirty(text_layer_get_layer(s_time_layer)); } diff --git a/SchwertUndMagieOnPebbleWatchApp/src/pkjs/index.js b/SchwertUndMagieOnPebbleWatchApp/src/pkjs/index.js index 2ec8dd9..b8c1b1f 100644 --- a/SchwertUndMagieOnPebbleWatchApp/src/pkjs/index.js +++ b/SchwertUndMagieOnPebbleWatchApp/src/pkjs/index.js @@ -1,17 +1,11 @@ var SERVER = 'http://127.0.0.1:8888'; -// Numeric keys matching the C enum: KEY_TIME=0, KEY_COMMAND=1 var KEY_TIME = 0; var KEY_COMMAND = 1; function httpGet(url) { var xhr = new XMLHttpRequest(); xhr.open('GET', url, true); - xhr.onload = function() { - if (xhr.status !== 200) { - console.log('[SuM] HTTP ' + xhr.status + ' for ' + url); - } - }; xhr.onerror = function() { console.log('[SuM] request failed: ' + url); }; @@ -22,16 +16,14 @@ function sendTimeToWatch() { var xhr = new XMLHttpRequest(); xhr.open('GET', SERVER + '/time', true); xhr.onload = function() { - console.log('[SuM] /time response: ' + xhr.status + ' ' + xhr.responseText); if (xhr.status === 200) { try { var data = JSON.parse(xhr.responseText); var msg = {}; msg[KEY_TIME] = data.time; - console.log('[SuM] sendAppMessage key=' + KEY_TIME + ' value=' + data.time); Pebble.sendAppMessage( msg, - function() { console.log('[SuM] sendAppMessage ok'); }, + function() {}, function(e) { console.log('[SuM] sendAppMessage failed: ' + JSON.stringify(e)); } ); } catch (err) { @@ -46,13 +38,11 @@ function sendTimeToWatch() { } Pebble.addEventListener('ready', function() { - console.log('[SuM] PebbleKit JS ready'); setInterval(sendTimeToWatch, 1000); }); Pebble.addEventListener('appmessage', function(e) { var command = e.payload[KEY_COMMAND]; if (!command) { return; } - console.log('[SuM] key: ' + command); httpGet(SERVER + '/key?cmd=' + encodeURIComponent(command)); });