Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ plugins {

android {
namespace 'com.shiqi.testquickjs'
compileSdk 33
compileSdk 36

defaultConfig {
applicationId "com.shiqi.testquickjs"
minSdk 24
targetSdk 33
targetSdk 36
versionCode 1
versionName "1.0"

Expand All @@ -37,7 +37,7 @@ android {
compose true
}
composeOptions {
kotlinCompilerExtensionVersion '1.4.5'
kotlinCompilerExtensionVersion '1.5.15'
}
packagingOptions {
resources {
Expand All @@ -54,7 +54,7 @@ dependencies {
implementation 'androidx.core:core-ktx:1.9.0'
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1'
implementation 'androidx.activity:activity-compose:1.5.1'
implementation platform('androidx.compose:compose-bom:2022.10.00')
implementation platform('androidx.compose:compose-bom:2024.06.00')
implementation 'androidx.compose.ui:ui'
implementation 'androidx.compose.ui:ui-graphics'
implementation 'androidx.compose.ui:ui-tooling-preview'
Expand Down
54 changes: 54 additions & 0 deletions app/src/main/assets/battery.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/**
* 模擬電池電量 (Battery Level) 的變化
*/

//// 使用 'var' 宣告為全域變數,以便 Java/Kotlin 層可以透過 globalObject 訪問
//var batteryLevel = 100;
//
//// 上次電量變化的時間戳
//var lastBatteryUpdateTime = 0;
//
//// 模擬電量下降
//function decreaseBattery() {
// const currentTime = Date.now(); // 使用內建的 Date.now()
//
// // 每 3 秒 (3000 毫秒) 更新一次電量
// if (currentTime - lastBatteryUpdateTime > 3000) {
// // 隨機減少 1% 或 2%
// batteryLevel -= Math.floor(Math.random() * 2) + 1;
//
// // 確保電量不會低於 20%
// if (batteryLevel < 20) {
// batteryLevel = 100; // 如果電量過低,重新充滿以循環模擬
// }
//
// lastBatteryUpdateTime = currentTime;
// console.log('[battery.js] Battery level was updated to: ' + batteryLevel);
// }
//
// // 即使不更新,也回傳目前的值
// return batteryLevel;
//}
registerGatt("2A19", (function () {

let level = 100;
let last = 0;

function next() {
const now = Clock.millis();
if (now - last >= 5000) { // 每 5 秒掉一點
level -= Math.random() * 0.2;
if (level < 0) level = 100;
last = now;
}
}

function getValue() {
return Math.round(level);
}

return {
next,
getValue
};
})());
17 changes: 17 additions & 0 deletions app/src/main/assets/clock.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
var Clock = (function () {

let now = 0;

function tick(t) {
now = t;
}

function millis() {
return now;
}

return {
tick,
millis
};
})();
30 changes: 30 additions & 0 deletions app/src/main/assets/gatt-core.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// 全域 registry(一開始是空的)
const __gattRegistry = {};

// 提供給各 js module 註冊用
function registerGatt(uuid, impl) {
if (__gattRegistry[uuid]) {
console.log("Gatt already registered:", uuid);
}
__gattRegistry[uuid] = impl;
}

// 統一對外 API(Java 只會用這兩個)
function next(uuid) {
const m = __gattRegistry[uuid];
if (!m || !m.next) {
console.log("next(): no gatt for", uuid);
return;
}
m.next();
}

function getValue(uuid) {
const m = __gattRegistry[uuid];
if (!m || !m.getValue) {
console.log("getValue(): no gatt for", uuid);
return -1;
}
return m.getValue();
}

72 changes: 72 additions & 0 deletions app/src/main/assets/hrm.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
舊的程式碼:每次呼叫 next() 都會更新 bpm
let bpm=72;
let dir=1;

function next(){
bpm+=dir*(Math.random()*2);
if(bpm>85) dir=-1;
if(bpm<65) dir=1;
return Math.round(bpm);
}
*/

// --- 新的程式碼 ---

//// 全域變數,用於儲存心率值、方向和上次更新的時間戳
//var bpm = 72;
//var dir = 1;
//var lastUpdateTime = 0;
//
//// Date.now() 返回自 1970 年 1 月 1 日 00:00:00 UTC 以來的毫秒數
//function now_ms() {
// return Date.now();
//}
//
//// 核心函式,現在包含了計時邏輯
//function next() {
// const currentTime = now_ms();
//
// if (currentTime - lastUpdateTime > 15000) {
// bpm += dir * (Math.random() * 2);
// if (bpm > 85) dir = -1;
// if (bpm < 65) dir = 1;
// lastUpdateTime = currentTime;
//
// console.log('[hrm.js] BPM was updated to: ' + Math.round(bpm));
// }
//
// // 為了保持 evaluate("next()") 能工作,我們仍然回傳 bpm
// return Math.round(bpm);
//}

registerGatt("2A37", (function () {

let bpm = 72;
let dir = 1;
let last = 0;

function next() {
const now = Clock.millis();
if (last === 0) {
last = now;
return;
}

if (now - last >= 1000) {
bpm += dir * (Math.random() * 2);
if (bpm > 85) dir = -1;
if (bpm < 65) dir = 1;
last = now;
}
}

function getValue() {
return Math.round(bpm);
}

return {
next,
getValue
};
})());
65 changes: 65 additions & 0 deletions app/src/main/java/com/shiqi/testquickjs/BatteryGattValue.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package com.shiqi.testquickjs

import android.util.Log
import com.shiqi.quickjs.JSContext
import com.shiqi.quickjs.JSNumber
import kotlin.math.roundToInt

/**
* 一個純粹的業務邏輯類,專注於從一個準備好的 JSContext 中獲取電池電量數據。
*/
class BatteryGattValue(
private val jsContext: JSContext
) : IGattValue {

private val TAG = "BatteryGattValue"

private val level: Int get() {
Log.d(TAG, "[DEBUG] get() called. Using 2-step evaluation for battery.")

//// 步驟 1: 執行 decreaseBattery(),觸發其副作用(更新 batteryLevel 全域變數)
// jsContext.evaluate("decreaseBattery()")
// executePendingJobs() // 確保 console.log 被執行
//
// // 步驟 2: 透過 globalObject 明確地獲取 'batteryLevel' 變數。
// val result = jsContext.globalObject.getProperty("batteryLevel")
//
// if (result is JSNumber) {
// // JS 中的 number 預設是 double,安全地獲取並轉換
// val doubleValue = result.getDouble()
// val intValue = doubleValue.roundToInt()
// Log.d(TAG, "[DEBUG] Successfully get 'batteryLevel' from globalObject. Value: $intValue")
// return intValue
// }
//
// val resultType = result?.javaClass?.simpleName ?: "null"
// Log.e(TAG, "[DEBUG] FAILED! Could not get 'batteryLevel' from globalObject. Result was '$resultType'.")
// return -1 // 回傳錯誤碼
jsContext.evaluate("__temp_result = getValue('2A19');")
val result = jsContext.globalObject.getProperty("__temp_result")

if (result is JSNumber) {
return result.int
}

Log.e(TAG, "Failed to get Battery value via 'getValue(\'2A19\')'. Result was ${result?.javaClass?.simpleName}")
return -1
}

private fun executePendingJobs() {
try {
var hasPendingJob: Boolean
do {
hasPendingJob = jsContext.executePendingJob()
} while (hasPendingJob)
} catch (e: IllegalStateException) {
Log.w(TAG, "[DEBUG] Could not execute pending jobs, context might be closed.", e)
}
}

/**
* 根據藍牙 GATT 規範,Battery Level (0x2A19) 是一個 uint8 的值,代表 0-100 的百分比。
*/
override val gattValue: ByteArray
get() = byteArrayOf(level.toByte())
}
71 changes: 71 additions & 0 deletions app/src/main/java/com/shiqi/testquickjs/HrmGattValue.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package com.shiqi.testquickjs

import android.util.Log
import com.shiqi.quickjs.JSContext
import com.shiqi.quickjs.JSNumber
import kotlin.math.roundToInt

/**
* 一個純粹的業務邏輯類,專注於從一個準備好的 JSContext 中獲取心率數據。
* 它不再持有任何 Android Context。
*/
class HrmGattValue(
private val jsContext: JSContext
) : IGattValue {

private val TAG = "HrmGattValue"

// init 區塊已被移除,因為腳本的加載和執行責任已上移。

private val bpm: Int get() {
Log.d(TAG, "[DEBUG] get() called. Using 2-step evaluation.")

// // 步驟 1: 執行 next(),僅為了觸發其副作用 (更新 bpm 全域變數和 console.log)
// // 我們忽略它的回傳值,因為我們知道它會被 console.log 污染成 null。
// jsContext.evaluate("next()")
// executePendingJobs() // 確保 console.log 被執行
//
// // 步驟 2: 透過 globalObject 明確地獲取 'bpm' 變數。
// // 這是在 Java 層訪問 JS 全域變數的唯一正確方法。
// val result = jsContext.globalObject.getProperty("bpm")
//
// if (result is JSNumber) {
// val doubleValue = result.getDouble()
// val bpmValue = doubleValue.roundToInt()
// Log.d(TAG, "[DEBUG] Successfully get 'bpm' from globalObject. DoubleValue: $doubleValue, RoundedInt: $bpmValue")
// return bpmValue
// }
//
// val resultType = result?.javaClass?.simpleName ?: "null"
// Log.e(TAG, "[DEBUG] FAILED! Could not get 'bpm' from globalObject. Result was '$resultType'.")
// return -1
jsContext.evaluate("__temp_result = getValue('2A37');")

// 步驟 2: 安全地從 globalObject 讀取臨時變數
val result = jsContext.globalObject.getProperty("__temp_result")

if (result is JSNumber) {
return result.int
}

Log.e(TAG, "Failed to get HRM value via 'getValue(\'2A37\')'. Result was ${result?.javaClass?.simpleName}")
return -1 // 如果獲取失敗,回傳 -1
}

private fun executePendingJobs() {
try {
var hasPendingJob: Boolean
do {
hasPendingJob = jsContext.executePendingJob()
} while (hasPendingJob)
} catch (e: IllegalStateException) {
Log.w(TAG, "[DEBUG] Could not execute pending jobs, context might be closed.", e)
}
}

override val gattValue: ByteArray
get() = byteArrayOf(
0x00, // Flags: uint8 HR
bpm.toByte()
)
}
6 changes: 6 additions & 0 deletions app/src/main/java/com/shiqi/testquickjs/IGattValue.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.shiqi.testquickjs

interface IGattValue {
val gattValue: ByteArray
}

Loading