Author SHA1 Message Date
mazengfei 11e6123cc9 隐藏温湿度 2026-03-10 18:06:23 +08:00
lvmeng 14d8064b13 优化 2026-02-05 14:50:33 +08:00
mazengfei 8006b0d077 优化 2026-01-29 18:53:16 +08:00
mazengfei dbb7ccf8c7 优化 2026-01-15 11:37:37 +08:00
lvmeng 3ff58641aa 优化 2026-01-13 09:29:30 +08:00
lvmeng 5d58cdc4cf 增加定时任务更新货柜数据过期状态 2026-01-13 09:27:41 +08:00
马增飞 9a222037ea 优化 2026-01-12 18:03:41 +08:00
lvmeng 247a332ff3 优化 2026-01-12 14:42:46 +08:00
mazengfei 2fe0381987 jks 2025-12-30 17:23:56 +08:00
lvmeng 7e32268a4c 优化 2025-12-30 16:59:56 +08:00
马增飞 5b0b98c8b8 优化 2025-12-30 16:28:35 +08:00
马增飞 aff3d5cc69 优化 2025-12-03 09:44:37 +08:00
马增飞 6d1b997145 优化 2025-11-26 16:00:19 +08:00
马增飞 ad669e421b 优化 2025-11-20 10:11:28 +08:00
lvmeng 119a69bbbc 优化 2025-11-19 08:31:40 +08:00
马增飞 096499cd65 优化并增加日志 2025-11-18 18:49:46 +08:00
lvmeng 79d14ca300 增加终端反馈 2025-11-18 16:29:38 +08:00
马增飞 f8eeb83c6f 优化 2025-11-17 16:27:00 +08:00
马增飞 99001fff2f 优化 2025-11-13 18:10:34 +08:00
马增飞 10059bf4f6 联调优化 2025-11-12 20:14:37 +08:00
马增飞 32ea2f1379 设备联调优化 2025-11-11 18:26:07 +08:00
lvmeng c20ce471cb 优化 2025-11-07 08:58:21 +08:00
马增飞 3366fc02e5 Merge remote-tracking branch 'origin/main'
# Conflicts:
#	app/src/main/java/com/shuwei/intelligent/shelves/InitActivity.kt
#	app/src/main/java/com/shuwei/intelligent/shelves/net/ApiService.kt
#	app/src/main/java/com/shuwei/intelligent/shelves/net/NetViewModel.kt
2025-11-06 19:32:30 +08:00
马增飞 647ec0dcb7 设备联调优化 2025-11-06 19:29:18 +08:00
58 changed files with 3106 additions and 935 deletions
+1
View File
@@ -73,6 +73,7 @@ dependencies {
implementation("com.geyifeng.immersionbar:immersionbar-ktx:3.2.2")
implementation("com.squareup.okhttp3:okhttp:4.9.1")
implementation("com.squareup.okhttp3:logging-interceptor:4.9.1")
implementation("com.google.code.gson:gson:2.10.1")
implementation("com.squareup.retrofit2:retrofit:2.9.0")
implementation("com.squareup.retrofit2:converter-gson:2.9.0")
+5 -3
View File
@@ -26,7 +26,7 @@
android:theme="@style/Theme.IntelligentShelves"
android:usesCleartextTraffic="true">
<activity
android:name=".InitActivity"
android:name=".activity.InitActivity"
android:exported="true"
android:screenOrientation="portrait"
tools:ignore="DiscouragedApi,LockedOrientationActivity">
@@ -37,12 +37,14 @@
</intent-filter>
</activity>
<activity
android:name=".HomeActivity"
android:name=".activity.HomeActivity"
android:screenOrientation="portrait"/>
<activity
android:name=".ShelfActivity"
android:name=".activity.ShelfActivity"
android:screenOrientation="portrait"
tools:ignore="DiscouragedApi,LockedOrientationActivity" />
<activity android:name="com.shuwei.intelligent.shelves.activity.SettingActivity" />
<activity android:name="com.shuwei.intelligent.shelves.activity.LogActivity" />
<receiver
android:name=".utils.BootReceiver"
@@ -6,6 +6,7 @@ import android.content.Intent
import android.content.IntentFilter
import android.content.SharedPreferences
import com.shuwei.intelligent.shelves.utils.BootReceiver
import com.shuwei.intelligent.shelves.utils.CrashHandler
/**
* @author: star
@@ -17,6 +18,7 @@ class App : Application() {
override fun onCreate() {
super.onCreate()
app = this
CrashHandler.init(this)
val filter = IntentFilter(Intent.ACTION_BOOT_COMPLETED)
registerReceiver(BootReceiver(), filter)
}
@@ -1,506 +0,0 @@
package com.shuwei.intelligent.shelves
import android.annotation.SuppressLint
import android.content.Intent
import android.graphics.Color
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.provider.Settings
import android.util.Log
import android.util.SparseArray
import androidx.activity.viewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.GridLayoutManager
import com.chad.library.adapter4.util.setOnDebouncedItemClick
import com.shuwei.intelligent.shelves.adapter.ShelfAdapter
import com.shuwei.intelligent.shelves.base.BaseActivity
import com.shuwei.intelligent.shelves.databinding.ActivityHomeBinding
import com.shuwei.intelligent.shelves.model.SendWeightEvent
import com.shuwei.intelligent.shelves.model.ShelfModel
import com.shuwei.intelligent.shelves.model.ShelfResult
import com.shuwei.intelligent.shelves.model.WeightEvent
import com.shuwei.intelligent.shelves.net.Loading
import com.shuwei.intelligent.shelves.net.NetViewModel
import com.shuwei.intelligent.shelves.net.RespData
import com.shuwei.intelligent.shelves.net.UiState
import com.shuwei.intelligent.shelves.serial.SerialPortManager
import com.shuwei.intelligent.shelves.task.TaskManager
import com.shuwei.intelligent.shelves.utils.binaryToHex
import com.shuwei.intelligent.shelves.utils.ext.gone
import com.shuwei.intelligent.shelves.utils.ext.toast
import com.shuwei.intelligent.shelves.utils.ext.visible
import com.shuwei.intelligent.shelves.utils.f2C
import com.shuwei.intelligent.shelves.utils.hexToBinary
import com.shuwei.intelligent.shelves.utils.hexToDec
import kotlinx.coroutines.launch
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
class HomeActivity : BaseActivity() {
companion object {
private const val TAG = "HomeActivity"
private const val MM_DD_EEEE__HH_MM_SS = "MM月dd日 EEEE HH:mm:ss"
//协议头
const val HEADER = "D6DADEDB"
//协议尾
const val FOOTER = "FFFF"
const val ACTIVE_CMD =
"${HEADER}000281C5D70A7D428A4FA67B50F7DACA241938938B4B9CBD73673E9C8E7F2E15062D${FOOTER}"
const val LEFT_SHELF_OPEN_CMD =
"${HEADER}020101000000000000000000000000000000000000${FOOTER}"
const val RIGHT_SHELF_OPEN_CMD =
"${HEADER}020100010000000000000000000000000000000000${FOOTER}"
//const val TEMPERATURE_CMD = "${HEADER}1001${FOOTER}"
const val DEVICE_INFO_CMD = "${HEADER}0301000000000000000000000000${FOOTER}"
val list: MutableList<ShelfModel> = mutableListOf()
val weightArray = SparseArray<Double?>()
var shelfIndex = 0
// public fun getRealWeight():Double = weightArray[shelfIndex]?:0.0
// 温度
var showTemperatureC = "0"
// 湿度
var showHumidity = "0"
}
private lateinit var binding: ActivityHomeBinding
private val viewModel: NetViewModel by viewModels()
@SuppressLint("NotifyDataSetChanged", "HardwareIds")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityHomeBinding.inflate(layoutInflater)
EventBus.getDefault().register(this)
setBackground()
setContentView(binding.root)
updateLeftStatus("1-1冷藏柜 -°C / -%")
initRecyclerView()
lifecycleScope.launch {
val openState = SerialPortManager.open()
Log.d(TAG, "onCreate: openState=$openState")
if (openState) {
SerialPortManager.startReceive { data ->
runOnUiThread {
// 更新UI显示接收数据
receiveSerialPortData(data)
}
}
SerialPortManager.send(ACTIVE_CMD)
//SerialPortManager.send(DEVICE_INFO_CMD)
}
repeatOnLifecycle(Lifecycle.State.CREATED) {
launch {
viewModel.getAccessTokenUiState.collect { state ->
when (state) {
is UiState.Loading -> {}
is UiState.Success<*> -> getTokenSuccess(state.data)
is UiState.Error -> toast(state.msg)
else -> {}
}
}
}
launch {
viewModel.getShelfListUiState.collect { state ->
when (state) {
is UiState.Loading -> showProgress()
is UiState.Success<*> -> updateUI(state.data)
is UiState.Error -> showError(state.msg)
else -> {}
}
}
}
}
}
TaskManager.startTask()
// var androidId = Settings.Secure.getString(contentResolver, Settings.Secure.ANDROID_ID)
//// androidId = "d0e7a2d5f25c775c"
// Log.d(TAG, "onCreate: androidId=${androidId}")
// if (androidId == "65e1012638f78260" || androidId == "40f89e91ee40611d") {
// androidId = "d0e7a2d5f25c775c"
// }
// App.deviceId = androidId
// //"53babf69-0d2f-3875-a649-f2e12f80ad4c1"
//
// val swDpValue = resources?.configuration?.smallestScreenWidthDp
// Log.d(TAG, "onCreate: swDpValue=$swDpValue")
viewModel.getAccessToken(App.deviceId)
// repeat(100) {
// lifecycleScope.launch {
// val data = "D6DADEDB01010C6102000000000000010000000500008000000000060000000500000004000080060000000100000010190080701800800000000000000000000000000000000000000000000000005037414645333633333334333731373056322E313069FFFF"
// receiveSerialPortData(data)
// delay(500)
// }
// }
// lifecycleScope.launch {
// repeat(100) {
// val sb = StringBuilder().apply {
//// 610200000000000001000000050000800000000006000000050000000400008006000000010000001019008070180080
// repeat(12) {
// val num = (0..99).random()
// val num2 = (0..99).random()
// val num3 = (0..99).random()
// val num4 = (0..1).random()
// val numStr = if (num <= 9) "0${num}" else "" + num
// val numStr2 = if (num2 <= 9) "0${num2}" else "" + num2
// val numStr3 = if (num3 <= 9) "0${num3}" else "" + num3
// val numStr4 = if (num4 == 1) "80" else "00"
// append(numStr).append(numStr2).append(numStr3).append(numStr4)
// }
// }
// val data =
// "${HEADER}0302000000000C${sb}5037414645333633333334333731373056322E313069${FOOTER}"
// receiveSerialPortData(data)
// delay(500)
// }
// }
}
private fun getTokenSuccess(data: RespData<*>) {
Log.d(TAG, "getTokenSuccess: $data")
data.result?.let {
App.accessToken = it.toString()
//UIUtils.toast(it.toString())
viewModel.getShelfList(deviceId = App.deviceId)
}
}
private fun showProgress() {
Loading.show(this)
}
@SuppressLint("NotifyDataSetChanged")
private fun updateUI(data: RespData<*>) {
binding.include?.root?.gone()
window?.decorView?.postDelayed({Loading.dismiss()},500)
if (data.result !is ShelfResult) {
loadEmptyView()
return
}
App.canteenId = data.result.canteenId
val tempList = data.result.containerGoodsList
if (tempList.isNullOrEmpty()) {
loadEmptyView()
return
}
if (tempList.size < 10) {
toast("数据错误")
return
}
list.clear()
val subList01 = tempList.subList(0, 5)
val subList02 = tempList.subList(5, 10)
repeat(5) { index ->
list.add(subList01[index].also { it.deviceId = App.deviceId })
list.add(subList02[index].also { it.deviceId = App.deviceId })
}
shelfAdapter.notifyDataSetChanged()
}
private fun showError(message: String) {
Loading.dismiss()
toast(message)
loadEmptyView()
}
private val serialReader by lazy { StringBuilder() }
private fun receiveSerialPortData(srcData: String) {
Log.d(TAG, "receiveSerialPortData: $srcData")
var data = ""
if (srcData.startsWith(HEADER) && srcData.contains(FOOTER)) {
serialReader.clear()
val endIndex = srcData.indexOf(FOOTER) + FOOTER.length
serialReader.append(srcData.substring(0, endIndex))
data = serialReader.toString()
} else if (srcData.startsWith(HEADER) && srcData.contains(FOOTER).not()) {
serialReader.clear()
serialReader.append(srcData).append(",")
return
} else if (srcData.startsWith(HEADER).not() && srcData.contains(FOOTER).not()) {
serialReader.append(srcData).append(",")
return
} else {
val endIndex = srcData.indexOf(FOOTER) + FOOTER.length
serialReader.append(srcData.substring(0, endIndex))
Log.d(TAG, "receiveSerialPortData:serialReader: $serialReader")
data = serialReader.toString().replace(",", "")
}
val cmd = data.substring(HEADER.length, HEADER.length + 4)
when (cmd) {
"0101" -> {
//心跳-01
Log.d(TAG, "receiveSerialPortData:心跳01: $data")
heartBeat(data)
}
// "8101" -> {
// //心跳-81
// Log.d(TAG, "receiveSerialPortData:心跳08: $data")
// heartBeat(data)
// }
"0202" -> {
//开关锁反馈
lockFeedback(data)
}
"0302" -> {
//获取设备状态
getDeviceInfo(data)
}
else -> {}
}
}
private var fixedDataBody = ""
private fun lockFeedback(data: String) {
runCatching {
val fixedDataBodyEndIndex = data.length - FOOTER.length
val fixedDataBodyStartIndex = HEADER.length + "0202".length + 42
fixedDataBody = data.substring(fixedDataBodyStartIndex, fixedDataBodyEndIndex)
val lockNo = if (list[shelfIndex].deviceNo in 1..5) "01" else "02"
val lockOneState = data.substring(HEADER.length + 4, HEADER.length + 6)
if (lockOneState == "00") {
//锁1关
lifecycleScope.launch {
SerialPortManager.send("${HEADER}0202${lockNo}${FOOTER}")
}
}
val lockTwoState = data.substring(HEADER.length + 6, HEADER.length + 8)
if (lockTwoState == "00") {
//锁2关
lifecycleScope.launch {
SerialPortManager.send("${HEADER}0202${lockNo}${FOOTER}")
}
}
}.onFailure {
it.printStackTrace()
}
}
private fun heartBeat(data: String) {
runCatching {
if (data.length < 200) {
return
}
val weightStartIndex = 14
val weightEndIndex = weightStartIndex + 12 * 8
getWeightInfo(
start = weightStartIndex,
end = weightEndIndex,
data = data
)
val temperatureStartIndex = 158
val temperatureEndIndex = temperatureStartIndex + 2
val temperatureHex = data.substring(temperatureStartIndex, temperatureEndIndex)
val temperature = hexToDec(temperatureHex)
val tempC = f2C(temperature.toDouble())
showTemperatureC = "%.1f".format(tempC)
val percent = 10 * list.count { it.goodsName.isNullOrBlank().not() }
updateLeftStatus("1-1冷藏柜 ${showTemperatureC}°C / ${percent}%")
}.onFailure {
it.printStackTrace()
}
}
private fun getWeightInfo(start: Int, end: Int, data: String) {
var index = 1
var count = start
while (count < end) {
val weightInfo = data.substring(count, count + 8)
var weightHex = ""
weightInfo.run {
weightHex =
substring(6, 8) + substring(4, 6) + substring(2, 4) + substring(0, 2)
}
var realWeight = 0
var firstByteBinary = hexToBinary(weightHex.substring(0, 2))
if (firstByteBinary.startsWith("0")) {
//正数
realWeight = weightHex.toInt()
} else {
//负数
firstByteBinary = "0${firstByteBinary.substring(1)}"
val firstByteHex = binaryToHex(firstByteBinary)
weightHex = firstByteHex + weightHex.substring(2)
realWeight = -1 * weightHex.toInt()
}
val realWeightDouble = realWeight / 1000.0
weightArray.put(index, realWeightDouble)
if (index <= 10) {
list.firstOrNull { it.deviceNo == index }?.let {
it.weight = realWeightDouble
val pos = list.indexOf(it)
shelfAdapter.notifyItemChanged(pos)
Log.d(TAG, "getWeightInfo: deviceNo=${it.deviceNo},realWeight=${realWeight}")
EventBus.getDefault().post(SendWeightEvent(it.deviceNo, realWeight))
}
}
count += 8
index++
}
// ShelfActivity.updateWeight(getRealWeight())
// if (list.isNotEmpty()) {
// val intent = Intent(ShelfActivity.RECEIVER_DEVICE_INFO + list[shelfIndex].deviceNo)
// intent.putExtra(ShelfActivity.SHELF_WEIGHT, weightArray[shelfIndex])
// LocalBroadcastManager.getInstance(this).sendBroadcast(intent)
// }
}
private fun getDeviceInfo(data: String) {
Log.d(TAG, "getDeviceInfo: data=$data")
val weightStartIndex = HEADER.length + 14
val temperatureStartIndex = weightStartIndex + 12 * 8
val temperatureEndIndex = temperatureStartIndex + 2
val temperatureHex = data.substring(temperatureStartIndex, temperatureEndIndex)
val temperature = hexToDec(temperatureHex)
val tempC = f2C(temperature.toDouble())
showTemperatureC = "%.1f".format(tempC)
val percent = 10 * list.count { it.goodsName.isNullOrBlank().not() }
updateLeftStatus("1-1冷藏柜 ${showTemperatureC}°C / ${percent}%")
getWeightInfo(
start = weightStartIndex,
end = temperatureStartIndex,
data = data
)
}
override fun onDestroy() {
lifecycleScope.launch {
SerialPortManager.close()
}
TaskManager.cancelTask()
EventBus.getDefault().unregister(this)
super.onDestroy()
}
@Suppress("DEPRECATION")
private val shelfAdapter by lazy {
ShelfAdapter(list).apply {
setOnDebouncedItemClick { adapter, view, position ->
shelfIndex = position
lifecycleScope.launch {
val openCmd = if (list[position].deviceNo in 1..5)
LEFT_SHELF_OPEN_CMD
else
RIGHT_SHELF_OPEN_CMD
val openState = SerialPortManager.send(openCmd)
if (openState.not()) {
toast("${list[position].goodsName}柜门开启失败")
return@launch
}
launch(Intent(this@HomeActivity, ShelfActivity::class.java).also {
it.putExtra(ShelfActivity.SHELF_MODEL, list[position])
}) {
it?.run {
val model =
getSerializableExtra(ShelfActivity.SHELF_MODEL) as ShelfModel
list[position].apply {
goodsId = model.goodsId
goodsName = model.goodsName
weight = model.weight
if (model.createTime.isNullOrBlank().not()) {
createTime = model.createTime
}
}
notifyItemChanged(position)
}
}
}
}
}
}
private fun initRecyclerView() {
binding.rvShelf.run {
layoutManager =
GridLayoutManager(this@HomeActivity, 2, GridLayoutManager.VERTICAL, false)
adapter = shelfAdapter
itemAnimator.let {
if (it is DefaultItemAnimator) {
it.supportsChangeAnimations = false
}
}
}
}
private fun updateDateTime() {
val sdf = SimpleDateFormat(MM_DD_EEEE__HH_MM_SS, Locale.CHINA)
val dateTime = sdf.format(Date())
updateRightStatus(dateTime)
}
private val handler = Handler(Looper.getMainLooper())
private val updateTask = object : Runnable {
override fun run() {
updateDateTime()
handler.postDelayed(this, 1000)
}
}
override fun onResume() {
super.onResume()
hideStatusBar()
handler.post(updateTask)
}
override fun onPause() {
super.onPause()
handler.removeCallbacks(updateTask)
}
@SuppressLint("NotifyDataSetChanged")
private fun loadEmptyView() {
list.clear()
shelfAdapter.notifyDataSetChanged()
binding.include?.let {
it.root.visible()
it.root.setOnClickListener {
viewModel.getAccessToken(App.deviceId)
}
it.ivEmptyIcon.setImageResource(R.drawable.ic_empty_white)
it.tvEmptyContent.setTextColor(Color.WHITE)
}
}
@Subscribe(threadMode = ThreadMode.MAIN)
public fun weightEvent(event: WeightEvent) {
}
}
@@ -0,0 +1,811 @@
package com.shuwei.intelligent.shelves.activity
import android.annotation.SuppressLint
import android.content.Intent
import android.graphics.Color
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.util.Log
import android.util.SparseIntArray
import androidx.activity.viewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.GridLayoutManager
import com.shuwei.intelligent.shelves.App
import com.shuwei.intelligent.shelves.R
import com.shuwei.intelligent.shelves.adapter.ShelfAdapter
import com.shuwei.intelligent.shelves.base.BaseActivity
import com.shuwei.intelligent.shelves.databinding.ActivityHomeBinding
import com.shuwei.intelligent.shelves.dialog.CommonDialog
import com.shuwei.intelligent.shelves.model.ClearShelfEvent
import com.shuwei.intelligent.shelves.model.SendWeightEvent
import com.shuwei.intelligent.shelves.model.ShelfBody
import com.shuwei.intelligent.shelves.model.ShelfModel
import com.shuwei.intelligent.shelves.model.ShelfResult
import com.shuwei.intelligent.shelves.model.WeightEvent
import com.shuwei.intelligent.shelves.net.Loading
import com.shuwei.intelligent.shelves.net.NetViewModel
import com.shuwei.intelligent.shelves.net.RespData
import com.shuwei.intelligent.shelves.net.UiState
import com.shuwei.intelligent.shelves.net.apiService
import com.shuwei.intelligent.shelves.serial.SerialPortManager
import com.shuwei.intelligent.shelves.utils.HexUtils
import com.shuwei.intelligent.shelves.utils.IntervalExecutor
import com.shuwei.intelligent.shelves.utils.binaryToHex
import com.shuwei.intelligent.shelves.utils.ext.copyTextToClipboard
import com.shuwei.intelligent.shelves.utils.ext.gone
import com.shuwei.intelligent.shelves.utils.ext.toJsonString
import com.shuwei.intelligent.shelves.utils.ext.toast
import com.shuwei.intelligent.shelves.utils.ext.visible
import com.shuwei.intelligent.shelves.utils.hexToBinary
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
class HomeActivity : BaseActivity() {
companion object {
private const val TAG = "HomeActivity"
private const val LOG_PREFIX = "receiveSerialPortData:收到"
private const val MM_DD_EEEE__HH_MM_SS = "MM月dd日 EEEE HH:mm:ss"
//协议头
const val HEADER = "D6DADEDB"
//协议尾
const val FOOTER = "FFFF"
// const val ACTIVE_CMD =
// "${HEADER}000281C5D70A7D428A4FA67B50F7DACA241938938B4B9CBD73673E9C8E7F2E15062D${FOOTER}"
const val LEFT_SHELF_OPEN_CMD =
"${HEADER}020101000000000000000000000000000000000000${FOOTER}"
const val RIGHT_SHELF_OPEN_CMD =
"${HEADER}020100010000000000000000000000000000000000${FOOTER}"
//const val TEMPERATURE_CMD = "${HEADER}1001${FOOTER}"
const val DEVICE_INFO_CMD = "${HEADER}0301000000000000000000000000${FOOTER}"
const val START_TEMP_CTRL_CMD = "${HEADER}0F01360000${FOOTER}"
//const val START_CMD= "${HEADER}0F01050020${FOOTER}"
// const val ACTIVE_CMD = "${HEADER}000281C5D70A7D428948AD7254FADBB1516738938B4B9CBD73673E9D8E7F2E15062D$FOOTER"
// 410激活码
// const val ACTIVE_CMD = "${HEADER}000281C5D70A7D42894EA17650F5D0BB516838E7863C9ABD766C38958AFF0F3C062D${FOOTER}"
//1楼餐厅激活码
const val ACTIVE_CMD =
"${HEADER}000281C5D70A7D428B44A57454F5DACA241938938B4B9CBD73673E9C8E7FC0C4992D${FOOTER}"
//华氏温度单位
const val F_TEMP_CMD = "${HEADER}0F01390000${FOOTER}"
//摄氏温度单位
const val C_TEMP_CMD = "${HEADER}0F01390001${FOOTER}"
//开灯(开 继电器1
const val LIGHT_CLOSE = "${HEADER}020102020102020000000000000000000000000000${FOOTER}"
const val LIGHT_OPEN = "${HEADER}020102020002020000000000000000000000000000${FOOTER}"
const val TEMPERATURE_CTRL = "${HEADER}0F01040000${FOOTER}"
val list: MutableList<ShelfModel> = mutableListOf()
val weightArray = SparseIntArray()
var shelfIndex = 0
// public fun getRealWeight():Double = weightArray[shelfIndex]?:0.0
// 温度
var showTemperatureC = "0"
// 湿度
var showHumidity = "0"
}
//401-false1楼餐厅-true
private var isNeedOpenLock = true
private lateinit var binding: ActivityHomeBinding
private val viewModel: NetViewModel by viewModels()
@SuppressLint("NotifyDataSetChanged", "HardwareIds")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityHomeBinding.inflate(layoutInflater)
EventBus.getDefault().register(this)
setBackground()
setContentView(binding.root)
// updateLeftStatus("-°C / -%")
updateLeftStatus("")
initRecyclerView()
lifecycleScope.launch {
val openState = SerialPortManager.open()
log("onCreate: openState=$openState")
if (openState) {
SerialPortManager.startReceive { data ->
// 更新UI显示接收数据
lifecycleScope.launch {
try {
receiveSerialPortData(data)
} catch (e: Exception) {
e.printStackTrace()
}
}
}
//SerialPortManager.send(DEVICE_INFO_CMD)
}
repeatOnLifecycle(Lifecycle.State.CREATED) {
launch {
viewModel.getAccessTokenUiState.collect { state ->
when (state) {
is UiState.Loading -> {}
is UiState.Success<*> -> getTokenSuccess(state.data)
is UiState.Error -> toast(state.msg)
else -> {}
}
}
}
launch {
viewModel.getShelfListUiState.collect { state ->
when (state) {
is UiState.Loading -> showProgress()
is UiState.Success<*> -> updateUI(state.data)
is UiState.Error -> showError(state.msg)
else -> {}
}
}
}
}
}
//sendCmd(ACTIVE_CMD)
binding.root.postDelayed({
sendCmd(START_TEMP_CTRL_CMD)
sendCmd(C_TEMP_CMD)
}, 5000)
// TaskManager.startTask()
saveGoodsTask()
overdueTask()
// var androidId = Settings.Secure.getString(contentResolver, Settings.Secure.ANDROID_ID)
//// androidId = "d0e7a2d5f25c775c"
// log("onCreate: androidId=${androidId}")
// if (androidId == "65e1012638f78260" || androidId == "40f89e91ee40611d") {
// androidId = "d0e7a2d5f25c775c"
// }
// App.deviceId = androidId
// //"53babf69-0d2f-3875-a649-f2e12f80ad4c1"
//
// val swDpValue = resources?.configuration?.smallestScreenWidthDp
// log("onCreate: swDpValue=$swDpValue")
// viewModel.getAccessToken(App.deviceId)
viewModel.getShelfList(deviceId = App.deviceId)
// binding.root.postDelayed({
// loadTestData()
// }, 30*1000)
}
private fun loadTestData() {
list.add(ShelfModel(deviceNo = 1, goodsName = "鸡蛋", deviceId = App.deviceId))
list.add(ShelfModel(deviceNo = 6, goodsName = "", deviceId = App.deviceId))
list.add(ShelfModel(deviceNo = 2, goodsName = "", deviceId = App.deviceId))
list.add(ShelfModel(deviceNo = 7, goodsName = "生姜", deviceId = App.deviceId))
list.add(ShelfModel(deviceNo = 3, goodsName = "黄瓜", deviceId = App.deviceId))
list.add(ShelfModel(deviceNo = 8, goodsName = "", deviceId = App.deviceId))
list.add(ShelfModel(deviceNo = 4, goodsName = "", deviceId = App.deviceId))
list.add(ShelfModel(deviceNo = 9, goodsName = "", deviceId = App.deviceId))
list.add(ShelfModel(deviceNo = 5, goodsName = "", deviceId = App.deviceId))
list.add(ShelfModel(deviceNo = 10, goodsName = "", deviceId = App.deviceId))
shelfAdapter.notifyDataSetChanged()
}
private fun getTokenSuccess(data: RespData<*>) {
log("getTokenSuccess: $data")
data.data?.let {
App.accessToken = it.toString()
//UIUtils.toast(it.toString())
viewModel.getShelfList(deviceId = App.deviceId)
}
}
private fun showProgress() {
Loading.show(this)
}
@SuppressLint("NotifyDataSetChanged")
private fun updateUI(data: RespData<*>) {
binding.include?.root?.gone()
window?.decorView?.postDelayed({ Loading.dismiss() }, 500)
if (data.data !is ShelfResult) {
loadEmptyView()
return
}
deviceName = data.data.deviceName
// updateLeftStatus("$deviceName -°C / -%")
updateLeftStatus("$deviceName")
App.canteenId = data.data.placeId
val tempList = data.data.containerGoodsList
if (tempList.isNullOrEmpty()) {
loadEmptyView()
return
}
// if (tempList.size < 10) {
// toast("数据错误")
// return
// }
list.clear()
val subList01 = tempList.subList(0, tempList.size / 2)
val subList02 = tempList.subList(tempList.size / 2, tempList.size)
repeat(tempList.size / 2) { index ->
list.add(subList01[index].also { it.deviceId = App.deviceId })
list.add(subList02[index].also { it.deviceId = App.deviceId })
}
shelfAdapter.notifyDataSetChanged()
}
private fun showError(message: String) {
Loading.dismiss()
toast(message)
loadEmptyView()
}
/**
* 终端id
*/
private var terminalId: String = ""
/**
* 固件版本
*/
private var terminalVersion: String = ""
private val serialReader by lazy { StringBuilder() }
private fun receiveSerialPortData(srcData: String) {
log("receiveSerialPortData: $srcData")
var data = ""
if (srcData.contains(HEADER) && srcData.contains(FOOTER)) {
serialReader.clear()
val startIndex = srcData.lastIndexOf(HEADER)
var endIndex = srcData.indexOf(FOOTER) + FOOTER.length
if (endIndex < startIndex) {
//类似这种:31314FFFFFD6DADEDB01010CFOOTER在HEADER前
data = srcData.substring(startIndex)
if (data.contains(FOOTER)) {
endIndex = srcData.indexOf(FOOTER) + FOOTER.length
data = data.substring(0, endIndex)
handleReceivedData(data)
serialReader.clear()
return
}
serialReader.clear()
serialReader.append(data).append(",")
return
}
serialReader.append(srcData.substring(startIndex, endIndex))
data = serialReader.toString()
handleReceivedData(data)
serialReader.clear()
return
}
if (srcData.contains(HEADER) && srcData.contains(FOOTER).not()) {
serialReader.clear()
val startIndex = srcData.lastIndexOf(HEADER)
serialReader.append(srcData.substring(startIndex)).append(",")
return
}
if (srcData.contains(HEADER).not() && srcData.contains(FOOTER).not()) {
serialReader.append(srcData).append(",")
return
}
//if (srcData.contains(HEADER).not() && srcData.contains(FOOTER))
val endIndex = srcData.indexOf(FOOTER) + FOOTER.length
serialReader.append(srcData.substring(0, endIndex))
log("receiveSerialPortData:serialReader: $serialReader")
data = serialReader.toString().replace(",", "")
handleReceivedData(data)
}
private fun handleReceivedData(data: String) {
if (data.isEmpty()) {
return
}
val cmd = data.substring(HEADER.length, HEADER.length + 4)
val cmdFlag = cmd.substring(0, 2)
when (cmd) {
"0001" -> {
//终端发起激活指令,拿到固定数据体(终端ID-15字节、固件版本-5字节、命令次数-1字节)
val reqBody =
data.substring(HEADER.length + cmd.length, data.length - FOOTER.length)
if (reqBody.length == 44) {
terminalId = reqBody.substring(2, 32)
terminalVersion = reqBody.substring(32, 42)
log("${LOG_PREFIX}[激活]${cmdFlag}指令:${data},终端id:${terminalId},固件版本:${terminalVersion},发送激活命令:$ACTIVE_CMD")
sendCmd(ACTIVE_CMD)
}
}
"0101", "8101" -> {
//心跳0102${p1}${p2}${version}
//心跳81(终端发起,当终端发送此命令时需要服务端存储串口屏显示信息)
val respCmd = "${HEADER}01020506${terminalVersion}$FOOTER"
log("${LOG_PREFIX}[心跳]${cmdFlag}指令:${data},发送应答指令:${respCmd}")
sendCmd(respCmd)
heartBeat(data)
}
"0202" -> {
//开关锁反馈020200${deviceId}${version}
val respCmd = "${HEADER}020200${terminalId}${terminalVersion}$FOOTER"
log("${LOG_PREFIX}[开锁]${cmdFlag}指令:${data},发送应答指令:${respCmd}")
sendCmd(respCmd)
//lockFeedback(data)
}
"0302" -> {
//获取设备状态
log("${LOG_PREFIX}[获取设备状态]${cmdFlag}指令:${data}")
getDeviceInfo(data)
}
"0501" -> {
//卡号上发
val respCmd = "${HEADER}050203$FOOTER"
log("${LOG_PREFIX}[卡号上发]${cmdFlag}指令:${data},发送应答指令:${respCmd}")
sendCmd(respCmd)
}
"0B01" -> {
//操作记录上传
val respCmd = "${HEADER}0B0203$FOOTER"
log("${LOG_PREFIX}[操作记录上传]${cmdFlag}指令:${data},发送应答指令:${respCmd}")
sendCmd(respCmd)
}
"0C01" -> {
//对时
val timeLong = System.currentTimeMillis() / 1000 + (8 * 3600)
val timeStr = "0000000" + String.format("%X", timeLong)
val time =
timeStr.substring(timeStr.length - 8).chunked(2).reversed().joinToString("")
val respCmd = "${HEADER}0B02${time}$FOOTER"
log("${LOG_PREFIX}[对时]${cmdFlag}指令:${data},发送应答指令:${respCmd}")
sendCmd(respCmd)
}
"0E01" -> {
//终端位置上报
val respCmd = "${HEADER}0E0200$FOOTER"
log("${LOG_PREFIX}[终端位置上报]${cmdFlag}指令:${data},发送应答指令:${respCmd}")
sendCmd(respCmd)
}
"1001" -> {
//温控信息上报
val respCmd = "${HEADER}100200$FOOTER"
val logMsg =
"${LOG_PREFIX}[温控信息上报]${cmdFlag}指令:${data},发送应答指令:${respCmd}"
log(logMsg)
sendCmd(respCmd)
//val tempC = HexUtils.signedHexToDec(data.take(4)) * 0.1
// updateLeftStatus("$deviceName ${"%.1f".format(tempC)}°C / -%")
// updateLeftStatus("$deviceName -°C / -%")
updateLeftStatus("$deviceName")
//D6DADEDB1001015D020000000027001E00020000001E001C00B40000000000010000000100040000001900440003000F00000000000FFFE6000200010002FFD80044000300010005001E0001FFD80056001400280000000000000000008300090000003C00D2000000000000007F003700000000A100000031323136334238423134303233303456322E313100FFFF
runOnUiThread {
if (tipDialog == null) {
CommonDialog(this).apply {
dialogTitle = "温馨提示"
// dialogContent = logMsg
confirmBlock = {
logMsg?.copyTextToClipboard(this@HomeActivity)
}
}
}
if (tipDialog?.isShowing == false) {
tipDialog?.dialogContent = logMsg
tipDialog?.show()
}
}
}
"1002" -> {
log("${LOG_PREFIX}[温控信息应答]1002数据:${data}")
}
"0F02" -> {
log("${LOG_PREFIX}[温控信息应答]0F02数据:${data}")
//D6DADEDB0F020331323136334238423134303233303456322E31313EFFFF00000055010000000000002707000001000000220100000000000091040000120100000100000095140080611400800000000000000000000000000000000000000000010100005031323136334238423134303233303456322E31313EFFFF
runOnUiThread { }
}
else -> {}
}
}
private var tipDialog: CommonDialog? = null
private var fixedDataBody = ""
// private fun lockFeedback(data: String) {
// runCatching {
// val fixedDataBodyEndIndex = data.length - FOOTER.length
// val fixedDataBodyStartIndex = HEADER.length + "0202".length + 42
// fixedDataBody = data.substring(fixedDataBodyStartIndex, fixedDataBodyEndIndex)
//
// val lockNo = if (list[shelfIndex].deviceNo in 1..5) "01" else "02"
// val lockOneState = data.substring(HEADER.length + 4, HEADER.length + 6)
// if (lockOneState == "00") {
// //锁1关
// SerialPortManager.send("${HEADER}0202${lockNo}${FOOTER}")
// }
// val lockTwoState = data.substring(HEADER.length + 6, HEADER.length + 8)
// if (lockTwoState == "00") {
// //锁2关
// SerialPortManager.send("${HEADER}0202${lockNo}${FOOTER}")
// }
// }.onFailure {
// it.printStackTrace()
// }
// }
private fun heartBeat(data: String) {
runCatching {
if (data.length < 200) {
return
}
val weightStartIndex = 14
val weightEndIndex = weightStartIndex + 12 * 8
getWeightInfo(
start = weightStartIndex,
end = weightEndIndex,
data = data
)
val temperatureStartIndex = 158
val temperatureEndIndex = temperatureStartIndex + 2
// val temperatureHex = data.substring(temperatureStartIndex, temperatureEndIndex)
// val temperature = hexToDec(temperatureHex)
// val tempC = f2C(temperature.toDouble())
// showTemperatureC = "%.1f".format(tempC)
//val percent = 10 * list.count { it.goodsName.isNullOrBlank().not() }
// updateLeftStatus("${deviceName} [$temperatureHex] ${showTemperatureC}°C / -%")
getFixDataBody(data, temperatureEndIndex)
}.onFailure {
it.printStackTrace()
}
}
private var deviceName = ""
private fun getWeightInfo(start: Int, end: Int, data: String) {
var index = 1
var count = start
while (count < end) {
val weightInfo = data.substring(count, count + 8)
var weightHex = ""
weightInfo.run {
weightHex =
substring(6, 8) + substring(4, 6) + substring(2, 4) + substring(0, 2)
}
var realWeight = 0
var firstByteBinary = hexToBinary(weightHex.substring(0, 2))
if (firstByteBinary.startsWith("0")) {
//正数
realWeight = weightHex.toInt()
} else {
//负数
firstByteBinary = "0${firstByteBinary.substring(1)}"
val firstByteHex = binaryToHex(firstByteBinary)
weightHex = firstByteHex + weightHex.substring(2)
realWeight = -1 * weightHex.toInt()
}
weightArray.put(index, realWeight)
if (index in 1..10 && list.size == 10) {
list.firstOrNull { it.deviceNo == index }?.let {
it.weight = realWeight.toDouble()
val pos = list.indexOf(it)
shelfAdapter.notifyItemChanged(pos)
log("getWeightInfo: deviceNo=${it.deviceNo},realWeight=${realWeight}")
EventBus.getDefault().post(SendWeightEvent(it.deviceNo, realWeight))
}
// val pos = index - 1
// val item = list[pos]
// item.weight = realWeight.toDouble()
// shelfAdapter.notifyItemChanged(pos)
// log("getWeightInfo: deviceNo=${item.deviceNo},realWeight=${realWeight}")
// EventBus.getDefault().post(SendWeightEvent(item.deviceNo, realWeight))
}
count += 8
index++
}
// ShelfActivity.updateWeight(getRealWeight())
// if (list.isNotEmpty()) {
// val intent = Intent(ShelfActivity.RECEIVER_DEVICE_INFO + list[shelfIndex].deviceNo)
// intent.putExtra(ShelfActivity.SHELF_WEIGHT, weightArray[shelfIndex])
// LocalBroadcastManager.getInstance(this).sendBroadcast(intent)
// }
}
private fun getDeviceInfo(data: String) {
log("getDeviceInfo: data=$data")
val weightStartIndex = HEADER.length + 14
val temperatureStartIndex = weightStartIndex + 12 * 8
val temperatureEndIndex = temperatureStartIndex + 2
val temperatureHex = try {
data.substring(temperatureStartIndex, temperatureEndIndex)
} catch (e: Exception) {
e.printStackTrace()
return
}
// val temperature = hexToDec(temperatureHex)
// val tempC = f2C(temperature.toDouble())
// showTemperatureC = "%.1f".format(tempC)
//val percent = 10 * list.count { it.goodsName.isNullOrBlank().not() }
// updateLeftStatus("${deviceName} ${showTemperatureC}°C / -%")
// val tempC = HexUtils.signedHexToDec(temperatureHex) * 0.1
// updateLeftStatus("$deviceName -°C / -%")
updateLeftStatus("$deviceName")
getWeightInfo(
start = weightStartIndex,
end = temperatureStartIndex,
data = data
)
getFixDataBody(data, temperatureEndIndex)
}
fun getFixDataBody(data: String, temperatureEndIndex: Int) {
try {
if (terminalId.isNotBlank() && terminalVersion.isNotBlank()) {
return
}
val fixDataBodyStartIndex = temperatureEndIndex + 1
val fixDataBody = data.substring(fixDataBodyStartIndex, data.length - FOOTER.length)
if (fixDataBody.length == 42) {
terminalId = fixDataBody.substring(0, 30)
terminalVersion = fixDataBody.substring(30, 40)
log("receiveSerialPortData:从设备状态获取数据,终端id:${terminalId},固件版本:${terminalVersion}")
}
} catch (e: Exception) {
e.printStackTrace()
}
}
override fun onDestroy() {
lifecycleScope.launch {
SerialPortManager.close()
}
// TaskManager.cancelTask()
saveTaskJob?.cancel()
overdueTaskJob?.cancel()
EventBus.getDefault().unregister(this)
super.onDestroy()
}
@Suppress("DEPRECATION")
private val shelfAdapter by lazy {
ShelfAdapter(list).apply {
setOnItemClickListener { _, _, position ->
shelfIndex = position
lifecycleScope.launch {
if (isNeedOpenLock) {
val openCmd = if (list[position].deviceNo in 1..5)
LEFT_SHELF_OPEN_CMD
else
RIGHT_SHELF_OPEN_CMD
val openState = SerialPortManager.send(openCmd)
}
// if (openState.not()) {
// toast("${list[position].goodsName}柜门开启失败")
// return@launch
// }
//SerialPortManager.send(START_CMD)
launch(Intent(this@HomeActivity, ShelfActivity::class.java).also {
it.putExtra(ShelfActivity.SHELF_MODEL, list[position])
}) {
it?.run {
val model =
getSerializableExtra(ShelfActivity.SHELF_MODEL) as ShelfModel
list[position].apply {
goodsId = model.goodsId
goodsName = model.goodsName
weight = model.weight
if (model.putCabinetDay.isNullOrBlank().not()) {
putCabinetDay = model.putCabinetDay
}
}
notifyItemChanged(position)
try {
val submitList = list.sortedBy { item -> item.deviceNo }
val body = ShelfBody().also { body ->
body.deviceId = App.deviceId ?: ""
body.canteenId = App.canteenId
body.temperature = showTemperatureC
body.humidity = showHumidity
body.goodsList = submitList
}
Log.d("mzf", "performSync: body:${body.toJsonString()}")
lifecycleScope.launch {
val resp = apiService.saveShelfGoodsList(body = body)
Log.d("mzf", "performSync: resp:${resp.toJsonString()}")
}
} catch (e: Exception) {
e.printStackTrace()
}
}
}
}
}
}
}
private fun initRecyclerView() {
binding.rvShelf.run {
layoutManager =
GridLayoutManager(this@HomeActivity, 2, GridLayoutManager.VERTICAL, false)
adapter = shelfAdapter
itemAnimator.let {
if (it is DefaultItemAnimator) {
it.supportsChangeAnimations = false
}
}
}
}
private fun updateDateTime() {
val sdf = SimpleDateFormat(MM_DD_EEEE__HH_MM_SS, Locale.CHINA)
val dateTime = sdf.format(Date())
updateRightStatus(dateTime)
}
private val handler = Handler(Looper.getMainLooper())
private val updateTask = object : Runnable {
override fun run() {
updateDateTime()
handler.postDelayed(this, 1000)
}
}
override fun onResume() {
super.onResume()
hideStatusBar()
handler.post(updateTask)
// D6DADEDB00010031323136334238423134303233303456322E313100FFFF
// sendCmd("${HEADER}00010031323136334238423134303233303456322E313152${FOOTER}")
}
override fun onPause() {
super.onPause()
handler.removeCallbacks(updateTask)
}
@SuppressLint("NotifyDataSetChanged")
private fun loadEmptyView() {
list.clear()
shelfAdapter.notifyDataSetChanged()
binding.include?.let {
it.root.visible()
it.root.setOnClickListener {
//viewModel.getAccessToken(App.deviceId)
viewModel.getShelfList(deviceId = App.deviceId)
}
it.ivEmptyIcon.setImageResource(R.drawable.ic_empty_white)
it.tvEmptyContent.setTextColor(Color.WHITE)
}
}
@Subscribe(threadMode = ThreadMode.MAIN)
public fun weightEvent(event: WeightEvent) {
}
@Subscribe(threadMode = ThreadMode.MAIN)
public fun clearEmptyShelf(event: ClearShelfEvent) {
val item = list.firstOrNull { it.deviceNo == event.shelfNo }
item?.apply {
goodsId = ""
goodsName = null
weight = 0.0
weightBak = if (event.weight <= 0.0) 0.0 else -1 * event.weight.toDouble()
createTime = null
overdueDay = null
}
shelfAdapter.notifyItemChanged(list.indexOf(item))
}
private val taskExecutor by lazy { IntervalExecutor() }
private var saveTaskJob: Job?=null
/**
* 保存定时任务
*/
private fun saveGoodsTask() {
saveTaskJob = taskExecutor.startIntervalTaskWithInitialDelay(1 * 60 * 1000L, 5 * 60 * 1000L) {
saveShelfGoodsList()
}
}
private var overdueTaskJob: Job?=null
/**
* 过期时间定时任务
*/
private fun overdueTask() {
overdueTaskJob = taskExecutor.startIntervalTaskWithInitialDelay(5 * 1000L, 5 * 60* 1000L) {
updateOverdueState()
}
}
private fun updateOverdueState() {
viewModel.updateOverdueState { state, items ->
if (items.isNullOrEmpty()) return@updateOverdueState
list.forEachIndexed { index, model ->
//接口过期状态变化后更新状态
if (items[index].overdue != model.overdue) {
model.overdue = items[index].overdue
shelfAdapter.notifyItemChanged(index)
log("更新编号为${model.deviceNo}物品:${model.goodsName}的过期状态:${model.overdue}")
}
}
}
}
private fun saveShelfGoodsList() {
log("performSync: list:${list.toJsonString()}")
val submitList = list.sortedBy { it.deviceNo }
submitList.forEach {
if (it.goodsId.isNullOrBlank()) {
it.weight = it.weightBak
}
}
// submitList.forEach {
// it.temperature = HomeActivity.showTemperatureC
// it.humidity = HomeActivity.showHumidity
// }
val body = ShelfBody().also {
it.deviceId = App.deviceId
it.canteenId = App.canteenId
it.temperature = showTemperatureC
it.humidity = showHumidity
it.goodsList = submitList
}
log("performSync: body:${body.toJsonString()}")
viewModel.saveShelfGoodsList(body)
}
}
@@ -1,14 +1,13 @@
package com.shuwei.intelligent.shelves
package com.shuwei.intelligent.shelves.activity
import android.annotation.SuppressLint
import android.os.Bundle
import android.provider.Settings
import android.util.Log
import androidx.activity.viewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.shuwei.intelligent.shelves.App
import com.shuwei.intelligent.shelves.base.BaseActivity
import com.shuwei.intelligent.shelves.databinding.ActivityInitBinding
import com.shuwei.intelligent.shelves.model.DeviceConfigInfo
@@ -24,17 +23,13 @@ import com.shuwei.intelligent.shelves.utils.ext.startActivity
import com.shuwei.intelligent.shelves.utils.ext.toJsonString
import com.shuwei.intelligent.shelves.utils.ext.toObject
import com.shuwei.intelligent.shelves.utils.ext.toast
import com.shuwei.intelligent.shelves.utils.ext.visible
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.FlowCollector
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.launch
import kotlin.collections.forEach
import kotlin.getValue
import kotlin.jvm.java
import kotlin.ranges.downTo
import kotlin.text.isBlank
import kotlin.to
class InitActivity : BaseActivity() {
companion object {
@@ -50,32 +45,31 @@ class InitActivity : BaseActivity() {
super.onCreate(savedInstanceState)
binding = ActivityInitBinding.inflate(layoutInflater)
setContentView(binding.root)
hideStatusBar()
// // 获取 ANDROID_ID
// val androidId =
// Settings.Secure.getString(contentResolver, Settings.Secure.ANDROID_ID)
// App.deviceId = androidId
// SpTool.put(SpTool.DEVICE_ID, androidId)
// App.configUrl = UrlConfig.BASE_URL
// App.canteenId = "0"
var deviceId = AppUtil.getUDID(this)
Log.d(TAG, "onCreate: deviceId=$deviceId")
// deviceId = "39a7abdd06b3c7ab"
// deviceId = "3ea47dc0-3cf0-3c2f-909c-265a9a65572e"
deviceId = "6ce7cd59-b875-38b2-b73d-88a570be3212"
App.deviceId = deviceId
SpTool.put(SpTool.DEVICE_ID, deviceId)
val deviceConfigCache = SpTool.getString(SpTool.DEVICE_CONFIG_CACHE)
val checkResult = checkConfigData(deviceConfigCache)
if (checkResult.not()) {
binding.ivQrCode.visible()
binding.btnInit.visible()
//进行初始化操作
initConfig()
return
}
App.deviceId = AppUtil.getUDID( this)
// App.deviceId = "4787e213-90ab-3e32-88e0-ac271a937751"
Log.d(TAG, "onCreate: deviceId = ${App.deviceId}")
SpTool.put(SpTool.DEVICE_ID, App.deviceId)
App.configUrl = UrlConfig.BASE_URL
App.canteenId = "0"
// var deviceId = AppUtil.getUDID(this)
// Log.d(TAG, "onCreate: deviceId=$deviceId")
//// deviceId = "39a7abdd06b3c7ab"
//// deviceId = "3ea47dc0-3cf0-3c2f-909c-265a9a65572e"
// App.deviceId = deviceId
//
// SpTool.put(SpTool.DEVICE_ID, deviceId)
// val deviceConfigCache = SpTool.getString(SpTool.DEVICE_CONFIG_CACHE)
// val checkResult = checkConfigData(deviceConfigCache)
// if (checkResult.not()) {
// binding.ivQrCode.visible()
// binding.btnInit.visible()
// //进行初始化操作
// initConfig()
// return
// }
binding.ivQrCode.invisible()
binding.btnInit.invisible()
@@ -116,7 +110,7 @@ class InitActivity : BaseActivity() {
when (state) {
is UiState.Loading -> {}
is UiState.Success<*> -> {
state.data.result?.let { deviceToken ->
state.data.data?.let { deviceToken ->
Log.d(TAG, "initConfig: $deviceToken")
viewModel.getDeviceConfig(
deviceId = App.deviceId,
@@ -134,7 +128,7 @@ class InitActivity : BaseActivity() {
when (state) {
is UiState.Loading -> {}
is UiState.Success<*> -> {
state.data.result?.let {
state.data.data?.let {
if (it is DeviceConfigInfo) {
SpTool.put(SpTool.DEVICE_CONFIG_CACHE, it.toJsonString())
App.configUrl = it.appPackageUrl?:""
@@ -0,0 +1,93 @@
package com.shuwei.intelligent.shelves.activity
import android.os.Bundle
import androidx.recyclerview.widget.LinearLayoutManager
import com.shuwei.intelligent.shelves.adapter.LogAdapter
import com.shuwei.intelligent.shelves.base.BaseActivity
import com.shuwei.intelligent.shelves.databinding.ActivityLogListBinding
import com.shuwei.intelligent.shelves.dialog.CommonDialog
import com.shuwei.intelligent.shelves.utils.ext.copyTextToClipboard
import com.shuwei.intelligent.shelves.utils.ext.gone
import com.shuwei.intelligent.shelves.utils.ext.startActivity
import com.shuwei.intelligent.shelves.utils.ext.toast
import com.shuwei.intelligent.shelves.utils.ext.visible
class LogActivity : BaseActivity() {
companion object {
const val IS_LOG_DIR = "isLogDir"
const val LOG_DATE = "logDate"
}
private lateinit var binding:ActivityLogListBinding
private var isLogDir = false
private var logDate = ""
private val logList = mutableListOf<String>()
private val logAdapter by lazy {
LogAdapter(logList).apply {
setOnItemClickListener { _, _, position ->
if (isLogDir.not()) {
showConfirmDialog(logList[position])
return@setOnItemClickListener
}
startActivity<LogActivity> {
putExtra(IS_LOG_DIR, false)
putExtra(LOG_DATE, logList[position])
}
}
}
}
private fun showConfirmDialog(content:String) {
CommonDialog(this).apply {
dialogTitle = "温馨提示"
dialogContent = content
confirmBlock = {
content?.copyTextToClipboard(this@LogActivity)
}
}.show()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityLogListBinding.inflate(layoutInflater)
setBackground()
setContentView(binding.root)
isLogDir = intent.getBooleanExtra(IS_LOG_DIR, false)
logDate = intent.getStringExtra(LOG_DATE) ?: ""
binding.ivBack.setOnClickListener { finish() }
binding.tvTitle.text = if (isLogDir) "全部日志" else logDate
binding.tvTitle.setOnClickListener {
logList.reverse()
logAdapter.notifyDataSetChanged()
}
if (isLogDir) {
logList.addAll(fileLogger.getLogFiles())
} else {
logList.addAll(fileLogger.readLogsByName(logDate))
}
binding.rvLogList.let {
it.layoutManager = LinearLayoutManager(this)
it.adapter = logAdapter
}
binding.btnFind.run {
if (isLogDir) gone() else visible()
}
binding.btnFind.setOnClickListener {
if (isLogDir) {
return@setOnClickListener
}
val list = logList.filter { it.contains("${HomeActivity.HEADER}1001") }
if (list.isEmpty()) {
toast("未找到到1001指令")
return@setOnClickListener
}
logList.clear()
logList.addAll(list)
logAdapter.notifyDataSetChanged()
}
}
}
@@ -0,0 +1,66 @@
package com.shuwei.intelligent.shelves.activity
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import com.shuwei.intelligent.shelves.base.BaseActivity
import com.shuwei.intelligent.shelves.databinding.ActivitySettingBinding
import com.shuwei.intelligent.shelves.utils.KeyboardUtil
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
class SettingActivity: BaseActivity() {
private lateinit var binding: ActivitySettingBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivitySettingBinding.inflate(layoutInflater)
setBackground()
setContentView(binding.root)
binding.btnSendCmd.setOnClickListener{
val cmd = binding.etInputCmd.text.toString().trim()
if (cmd.isBlank()) {
return@setOnClickListener
}
var record = binding.tvCmdRecord.text.toString().trim()
record = cmd+"\n"+record
binding.tvCmdRecord.text = record
val realCmd = "${HomeActivity.HEADER}0F01${cmd}${HomeActivity.FOOTER}"
sendCmd(realCmd)
}
binding.ivBack.setOnClickListener { finish() }
binding.root.setOnClickListener { v ->
KeyboardUtil.hideKeyboard(v)
}
}
private fun updateDateTime() {
val sdf = SimpleDateFormat(ShelfActivity.YYYY_MM_DD__EEEE_HH_MM_SS, Locale.CHINA)
val dateTime = sdf.format(Date())
val arr = dateTime.split("***")
updateLeftStatus(arr[0])
updateRightStatus(arr[1])
}
private val handler = Handler(Looper.getMainLooper())
private val updateTask = object : Runnable {
override fun run() {
updateDateTime()
handler.postDelayed(this, 1000)
}
}
override fun onResume() {
super.onResume()
hideStatusBar()
handler.post(updateTask)
}
override fun onPause() {
super.onPause()
handler.removeCallbacks(updateTask)
}
}
@@ -1,4 +1,4 @@
package com.shuwei.intelligent.shelves
package com.shuwei.intelligent.shelves.activity
import android.annotation.SuppressLint
import android.content.Intent
@@ -16,20 +16,20 @@ import androidx.lifecycle.repeatOnLifecycle
import androidx.recyclerview.widget.GridLayoutManager
import com.chad.library.adapter4.util.setOnDebouncedItemClick
import com.scwang.smart.refresh.layout.constant.RefreshState
import com.shuwei.intelligent.shelves.HomeActivity.Companion.DEVICE_INFO_CMD
import com.shuwei.intelligent.shelves.App
import com.shuwei.intelligent.shelves.R
import com.shuwei.intelligent.shelves.activity.HomeActivity.Companion.DEVICE_INFO_CMD
import com.shuwei.intelligent.shelves.adapter.SearchAdapter
import com.shuwei.intelligent.shelves.base.BaseActivity
import com.shuwei.intelligent.shelves.databinding.ActivityShelfBinding
import com.shuwei.intelligent.shelves.model.ClearShelfEvent
import com.shuwei.intelligent.shelves.model.GoodsModel
import com.shuwei.intelligent.shelves.model.GoodsRecord
import com.shuwei.intelligent.shelves.model.SendWeightEvent
import com.shuwei.intelligent.shelves.model.ShelfModel
import com.shuwei.intelligent.shelves.net.Loading
import com.shuwei.intelligent.shelves.net.NetViewModel
import com.shuwei.intelligent.shelves.net.RespData
import com.shuwei.intelligent.shelves.net.UiState
import com.shuwei.intelligent.shelves.serial.SerialPortManager
import com.shuwei.intelligent.shelves.task.TaskManager
import com.shuwei.intelligent.shelves.utils.DateTimeUtil
import com.shuwei.intelligent.shelves.utils.KeyboardUtil
import com.shuwei.intelligent.shelves.utils.ext.addOnActionSearchListener
@@ -53,7 +53,7 @@ class ShelfActivity : BaseActivity() {
companion object {
private const val TAG = "ShelfActivity"
private const val YYYY_MM_DD__EEEE_HH_MM_SS = "yyyy年MM月dd日 EEEE***HH:mm:ss"
const val YYYY_MM_DD__EEEE_HH_MM_SS = "yyyy年MM月dd日 EEEE***HH:mm:ss"
const val SHELF_MODEL = "shelfModel"
const val RECEIVER_DEVICE_INFO = "receiverDeviceInfo"
const val SHELF_WEIGHT = "shelfWeight"
@@ -89,9 +89,9 @@ class ShelfActivity : BaseActivity() {
shelfModel?.let {
binding.tvShelfName.text = it.shelfName
binding.tvFoodName.text = if (it.goodsName.isNullOrBlank()) "-" else it.goodsName
realWeight = ((it.weight ?: 0.0) * 1000).toInt()
// realWeight = (HomeActivity.getRealWeight() * 1000).toInt()
binding.tvFoodWeight.text = "${realWeight}"
realWeight = it.weight?.toInt()?:0
binding.tvFoodWeight.text = if(realWeight < 1000) "${realWeight}" else "%.3f千克".format(realWeight/1000.0)
startTime = System.currentTimeMillis()
}
initRecyclerView()
@@ -124,10 +124,10 @@ class ShelfActivity : BaseActivity() {
shelfName = shelfModel?.shelfName,
goodsId = currentGoodsId,
goodsName = list[clickIndex].goodsName,
weight = realWeight / 1000.0,
weight = realWeight.toDouble(),
overdueDay = list[clickIndex].overdueDay,
// createTime = if (shelfModel?.goodsId == currentGoodsId) null else DateTimeUtil.formatDateTime(dateTime = Date())
createTime = DateTimeUtil.formatDateTime(dateTime = Date())
putCabinetDay = DateTimeUtil.formatDateTime(dateTime = Date())
)
val intent = Intent().apply {
putExtra(SHELF_MODEL, data)
@@ -136,30 +136,20 @@ class ShelfActivity : BaseActivity() {
finish()
}
binding.root.setOnClickListener { v ->
KeyboardUtil.hideKeyboard(v.context, v)
KeyboardUtil.hideKeyboard(v)
}
binding.btnClearZero.setOnClickListener { v ->
binding.btnClearZero.setOnClickListener {
clearZero()
}
binding.btnClearEmpty.setOnClickListener {
val tempWeight = shelfModel?.weight?.toInt()?:0
clearZero()
binding.tvFoodName.text = "-"
//binding.tvFoodWeight.text = "0克"
EventBus.getDefault().post(ClearShelfEvent(shelfModel!!.deviceNo, tempWeight))
}
sendCmd(DEVICE_INFO_CMD)
lifecycleScope.launch {
val shelfNo = shelfModel?.deviceNo ?: 0
val hex = shelfNo.toString(16).padStart(2, '0').uppercase()
Log.d(TAG, "onCreate: hex=$hex")
val zeroClearingCmd = "${HomeActivity.HEADER}0401${hex}${HomeActivity.FOOTER}"
Log.d(TAG, "onCreate: zeroClearingCmd=$zeroClearingCmd")
SerialPortManager.send(zeroClearingCmd)
//清零
realWeight = 0
// binding.tvFoodWeight.text = "${realWeight}克"
SerialPortManager.send(DEVICE_INFO_CMD)
Loading.show(this@ShelfActivity)
v.postDelayed({ Loading.dismiss() }, 5000)
}
KeyboardUtil.hideKeyboard(v.context, v)
}
lifecycleScope.launch {
SerialPortManager.send(DEVICE_INFO_CMD)
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.getGoodsListUiState.collect { state ->
when (state) {
@@ -185,7 +175,7 @@ class ShelfActivity : BaseActivity() {
}
getGoodsList()
TaskManager.startDeviceTask()
// TaskManager.startDeviceTask()
}
private fun finishRefresh() {
@@ -220,45 +210,44 @@ class ShelfActivity : BaseActivity() {
finishRefresh()
binding.root.postDelayed({ Loading.dismiss() }, 200)
Log.d(TAG, "updateUI: ${data.toJsonString()}")
if (data.result !is GoodsRecord) {
val goodsList = data.data as MutableList<GoodsModel>
if (data.data.isNullOrEmpty()) {
if (pageNo == 1) {
loadEmptyView()
}
return
}
if (data.result.records.isNullOrEmpty()) {
if(pageNo == 1){
loadEmptyView()
}
return
}
var firstOne:GoodsModel?=null
if (pageNo == 1) {
if (shelfModel?.goodsId.isNullOrBlank().not()) {
firstOne = list.firstOrNull { it.goodsId == shelfModel?.goodsId }
if (firstOne == null) {
firstOne = GoodsModel(
goodsId = shelfModel?.goodsId,
goodsName = shelfModel?.goodsName,
isClicked = true
)
}
}
list.clear()
}
val records = data.result.records
if (pageNo == 1) {
firstOne?.let { one ->
val filterResult = records.firstOrNull{one.goodsId == it.goodsId }
if (filterResult != null) {
records.remove(filterResult)
}
clickIndex = 0
list.add(one)
}
}
list.addAll(records)
val enableLoadMore = records.size >= PAGE_SIZE
// var firstOne:GoodsModel?=null
// if (pageNo == 1) {
// if (shelfModel?.goodsId.isNullOrBlank().not()) {
// firstOne = list.firstOrNull { it.goodsId == shelfModel?.goodsId }
// if (firstOne == null) {
// firstOne = GoodsModel(
// goodsId = shelfModel?.goodsId,
// goodsName = shelfModel?.goodsName,
// isClicked = true
// )
// }
// }
// list.clear()
// }
// val records = data.data.records
// if (pageNo == 1) {
// firstOne?.let { one ->
// val filterResult = records.firstOrNull{one.goodsId == it.goodsId }
// if (filterResult != null) {
// records.remove(filterResult)
// }
// clickIndex = 0
// list.add(one)
// }
// }
list.addAll(goodsList)
val enableLoadMore = goodsList.size >= PAGE_SIZE
binding.refreshLayout.setEnableLoadMore(enableLoadMore)
if (enableLoadMore) {
pageNo++
@@ -291,7 +280,7 @@ class ShelfActivity : BaseActivity() {
override fun onDestroy() {
// unregisterReceiver(receiver)
EventBus.getDefault().unregister(this)
TaskManager.cancelDeviceTask()
// TaskManager.cancelDeviceTask()
super.onDestroy()
}
@@ -368,8 +357,36 @@ class ShelfActivity : BaseActivity() {
@Subscribe(threadMode = ThreadMode.MAIN)
public fun receiveWeightEvent(event: SendWeightEvent) {
if (event.shelfNo == (shelfModel?.deviceNo ?: 0)) {
binding.tvFoodWeight.text = "${event.weight}"
val intervalTime = System.currentTimeMillis() - startTime
val weight = event.weight
log("编号${event.shelfNo}货架获取重量成功:${weight}克,数据获取间隔时间为:${intervalTime}")
binding.tvFoodWeight.text = if(weight < 1000) "${weight}" else "%.3f千克".format(weight/1000.0)
startTime = System.currentTimeMillis()
window.decorView.postDelayed({ Loading.dismiss() }, 1000)
}
}
private fun clearZero() {
lifecycleScope.launch {
val shelfNo = shelfModel?.deviceNo ?: 0
val hex = shelfNo.toString(16).padStart(2, '0').uppercase()
Log.d(TAG, "onCreate: hex=$hex")
val zeroClearingCmd = "${HomeActivity.HEADER}0401${hex}${HomeActivity.FOOTER}"
log("编号${shelfNo}货架执行清零指令:${zeroClearingCmd}")
sendCmd(zeroClearingCmd)
sendCmd(
if (shelfNo in 1..5) HomeActivity.LEFT_SHELF_OPEN_CMD else HomeActivity.RIGHT_SHELF_OPEN_CMD
)
//清零
realWeight = 0
// binding.tvFoodWeight.text = "${realWeight}克"
sendCmd(DEVICE_INFO_CMD)
startTime = System.currentTimeMillis()
Loading.show(this@ShelfActivity)
// window.decorView.postDelayed({ Loading.dismiss() }, 5000)
}
KeyboardUtil.hideKeyboard(window.decorView)
}
private var startTime = 0L
}
@@ -0,0 +1,25 @@
package com.shuwei.intelligent.shelves.adapter
import android.content.Context
import android.view.LayoutInflater
import android.view.ViewGroup
import com.chad.library.adapter4.BaseQuickAdapter
import com.chad.library.adapter4.viewholder.QuickViewHolder
import com.shuwei.intelligent.shelves.databinding.ListItemLogBinding
class LogAdapter(list: MutableList<String>) :
BaseQuickAdapter<String, LogAdapter.VH>(list) {
inner class VH(var binding: ListItemLogBinding) : QuickViewHolder(binding.root)
override fun onCreateViewHolder(context: Context, parent: ViewGroup, viewType: Int): VH {
val inflater = LayoutInflater.from(context)
val binding = ListItemLogBinding.inflate(inflater, parent, false)
return VH(binding)
}
override fun onBindViewHolder(holder: VH, position: Int, item: String?) {
holder.binding.tvLogName.text = item
}
}
@@ -14,6 +14,7 @@ import com.shuwei.intelligent.shelves.utils.DateTimeUtil
import com.shuwei.intelligent.shelves.utils.ext.gone
import com.shuwei.intelligent.shelves.utils.ext.visible
import java.util.Date
import kotlin.math.abs
class ShelfAdapter(list: MutableList<ShelfModel>) :
BaseQuickAdapter<ShelfModel, ShelfAdapter.VH>(list) {
@@ -35,25 +36,29 @@ class ShelfAdapter(list: MutableList<ShelfModel>) :
override fun onBindViewHolder(holder: VH, position: Int, item: ShelfModel?) {
val shelfNo = item!!.deviceNo
item.shelfName = "货架-${if (shelfNo < 10) "0${shelfNo}" else "$shelfNo"}"
val weight = item.weight ?: 0.toDouble()
//货架-
item.shelfName = "${if (shelfNo < 10) "0${shelfNo}" else "$shelfNo"}"
val weight = item.weight ?: 0.0
val binding = holder.binding
if (item.overdueDay.isNullOrBlank()) {
binding.ivStaleFood.gone()
} else {
runCatching {
val shelfDate = stringToDate(item.createTime)
if (shelfDate == null) {
binding.ivStaleFood.gone()
} else {
val days = item.overdueDay!!.toInt()
val resultDate = DateTimeUtil.addDays(shelfDate, days)
if (resultDate.before(Date())) {
binding.ivStaleFood.visible()
} else {
binding.ivStaleFood.gone()
}
// val shelfDate = stringToDate(item.putCabinetDay)
binding.ivStaleFood.run {
if (item.overdue == true) visible() else gone()
}
// if (shelfDate == null) {
// binding.ivStaleFood.gone()
// } else {
// val days = item.overdueDay!!.toInt()
// val resultDate = DateTimeUtil.addDays(shelfDate, days)
// if (resultDate.before(Date())) {
// binding.ivStaleFood.visible()
// } else {
// binding.ivStaleFood.gone()
// }
// }
}.onFailure {
binding.ivStaleFood.gone()
}
@@ -61,8 +66,9 @@ class ShelfAdapter(list: MutableList<ShelfModel>) :
//?.setImageResource()
binding.tvFoodWeight.run {
setTextColor(getColor(R.color.food_weight_orange))
val showWeight = if (weight == 0.toDouble()) "0" else "%.3f".format(weight)
text = "${showWeight}千克"
//val showWeight = if (weight == 0.toDouble()) "0克" else "%.3f千克".format(weight)
text = if(abs(weight) < 1000) "${weight.toInt()}" else "%.3f千克".format(weight/1000.0)
//"${(weight*1000).roundToInt()}克"
}
//val isBlankShelf = weight <= 0
val isBlankShelf = item.goodsName.isNullOrBlank()
@@ -102,7 +108,7 @@ class ShelfAdapter(list: MutableList<ShelfModel>) :
// }
binding.tvStoreDate.run {
setTextColor(getColor(R.color.black999))
text = item.createTime
text = item.putCabinetDay
}
}
@@ -1,24 +1,34 @@
package com.shuwei.intelligent.shelves.base
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.util.Log
import android.view.View
import android.view.WindowInsets
import android.view.WindowInsetsController
import android.widget.TextView
import androidx.activity.enableEdgeToEdge
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.lifecycle.lifecycleScope
import com.gyf.immersionbar.BarHide
import com.gyf.immersionbar.ImmersionBar
import com.shuwei.intelligent.shelves.activity.HomeActivity
import com.shuwei.intelligent.shelves.activity.HomeActivity.Companion.LIGHT_CLOSE
import com.shuwei.intelligent.shelves.activity.HomeActivity.Companion.LIGHT_OPEN
import com.shuwei.intelligent.shelves.R
import com.shuwei.intelligent.shelves.activity.LogActivity
import com.shuwei.intelligent.shelves.activity.SettingActivity
import com.shuwei.intelligent.shelves.databinding.ActivityBaseBinding
import com.shuwei.intelligent.shelves.serial.SerialPortManager
import com.shuwei.intelligent.shelves.utils.FileLogger
import com.shuwei.intelligent.shelves.utils.ext.clickWithDebounce
import com.shuwei.intelligent.shelves.utils.ext.startActivity
import kotlinx.coroutines.launch
@Suppress("DEPRECATION")
open class BaseActivity : AppCompatActivity() {
val TAG = this.javaClass.simpleName
private lateinit var binding: ActivityBaseBinding
private var launcher: ActivityResultLauncher<Intent>? = null
@@ -55,6 +65,7 @@ open class BaseActivity : AppCompatActivity() {
// }
// }
lightSwitchClick()
}
fun hideStatusBar() {
@@ -95,5 +106,79 @@ open class BaseActivity : AppCompatActivity() {
binding.tvRightStatus.text = data
}
// 5分钟
private val TIME_OUT: Long = (5 * 60 * 1000).toLong()
private val mHandler by lazy { Handler(Looper.getMainLooper()) }
private val mTimeoutRunnable = Runnable {
if (this is HomeActivity) {
this.switchLight(false)
}
}
override fun onUserInteraction() {
super.onUserInteraction()
//开灯
if (System.currentTimeMillis() - lastTouchTime >= TIME_OUT) {
this.switchLight(true)
}
// 重置定时器
mHandler.removeCallbacks(mTimeoutRunnable)
mHandler.postDelayed(mTimeoutRunnable, TIME_OUT)
lastTouchTime = System.currentTimeMillis()
}
private var lastTouchTime = 0L
override fun onResume() {
super.onResume()
// 启动定时器
mHandler.postDelayed(mTimeoutRunnable, TIME_OUT)
}
override fun onPause() {
super.onPause()
// 取消定时器
mHandler.removeCallbacks(mTimeoutRunnable)
}
fun switchLight(isOpen: Boolean) {
val tag = binding.tvLeftStatus.tag?.toString()?.toBoolean() ?: true
if (tag == isOpen) {
return
}
sendCmd(if (isOpen) LIGHT_OPEN else LIGHT_CLOSE)
binding.tvLeftStatus.tag = isOpen
}
private fun lightSwitchClick() {
switchLight(true)
binding.tvLeftStatus.clickWithDebounce {
val tag = binding.tvLeftStatus.tag?.toString()?.toBoolean() ?: true
switchLight(tag.not())
}
binding.tvRightStatus.clickWithDebounce {
if (this is HomeActivity) {
startActivity<LogActivity> {
putExtra(LogActivity.IS_LOG_DIR, true)
}
return@clickWithDebounce
}
startActivity<SettingActivity> { }
}
}
fun sendCmd(cmd:String) {
lifecycleScope.launch {
val result = SerialPortManager.send(cmd)
log("发送命令${cmd}结果:${result}")
}
}
val fileLogger by lazy { FileLogger(this) }
fun log(message:String) {
Log.d(TAG, message)
fileLogger.log(message)
}
}
@@ -0,0 +1,38 @@
package com.shuwei.intelligent.shelves.base
import android.app.Dialog
import android.content.Context
import android.graphics.drawable.ColorDrawable
import android.os.Bundle
import android.view.Gravity
import android.view.View
import com.shuwei.intelligent.shelves.R
import com.shuwei.intelligent.shelves.utils.ext.dp
import com.shuwei.intelligent.shelves.utils.ext.hideKeyboard
abstract class BaseDialog(
context: Context,
var defWidth: Int = 400.dp,
var defHeight: Int = 300.dp
) : Dialog(context, R.style.DialogTheme) {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val rootView = getRootView()
setContentView(rootView)
setCancelable(false)
setCanceledOnTouchOutside(false)
window?.apply {
setLayout(defWidth, defHeight)
setBackgroundDrawable(ColorDrawable())
setGravity(Gravity.CENTER)
}
rootView.setOnClickListener { it.hideKeyboard() }
initView()
}
abstract fun getRootView(): View
abstract fun initView()
}
@@ -0,0 +1,33 @@
package com.shuwei.intelligent.shelves.dialog
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import com.shuwei.intelligent.shelves.base.BaseDialog
import com.shuwei.intelligent.shelves.databinding.DialogLayoutBinding
class CommonDialog(context: Context):BaseDialog(context) {
private lateinit var binding:DialogLayoutBinding
override fun getRootView(): View {
binding = DialogLayoutBinding.inflate(LayoutInflater.from(context))
return binding.root
}
var dialogTitle:String?=null
var dialogContent:String?=null
var confirmBlock:(()->Unit)?=null
override fun initView() {
binding.btnConfirm.setOnClickListener {
confirmBlock?.invoke()
dismiss()
}
}
override fun show() {
super.show()
binding.tvDialogTitle.text = dialogTitle
binding.tvDialogContent.text = dialogContent
}
}
@@ -6,3 +6,5 @@ data class SendWeightEvent(
)
data class WeightEvent(var weight: Int)
data class ClearShelfEvent(var shelfNo: Int, var weight: Int)
@@ -6,12 +6,16 @@ data class ShelfModel(
var id: String? = null,
var deviceNo: Int = 0,
var shelfName: String? = null,
var goodsId: String = "",
var goodsId: String? = "",
var goodsName: String? = null,
var deviceId: String? = null,
var weight: Double? = null,
var weight: Double = 0.0,
var weightBak: Double = 0.0,
var createTime: String? = null,
var putCabinetDay: String? = null,
var updateTime: String? = null,
var overdueDay: String? = null,
var temperature: String? = null,
var humidity: String = "0",
var overdue:Boolean ?= false
) : Serializable
@@ -1,7 +1,8 @@
package com.shuwei.intelligent.shelves.model
data class ShelfResult(
val canteenId: String = "",
val placeId: String = "",
val canteenName: String = "",
val deviceName: String = "",
val containerGoodsList: List<ShelfModel>? = null
)
@@ -2,7 +2,7 @@ package com.shuwei.intelligent.shelves.net
import com.shuwei.intelligent.shelves.App
import com.shuwei.intelligent.shelves.model.DeviceConfigInfo
import com.shuwei.intelligent.shelves.model.GoodsRecord
import com.shuwei.intelligent.shelves.model.GoodsModel
import com.shuwei.intelligent.shelves.model.ShelfBody
import com.shuwei.intelligent.shelves.model.ShelfResult
import retrofit2.http.Body
@@ -28,22 +28,19 @@ interface ApiService {
@GET("/equipment/stEquipment/queryByEquipmentCode")
suspend fun getDeviceInfo(
@Query("equipmentCode") equipmentCode: String,
@Query("appVersion") appVersion: String = App.appVersion,
@Header("X-Access-Token") token: String
@Query("appVersion") appVersion: String = App.appVersion
): RespData<DeviceConfigInfo>
@GET
suspend fun getAccessToken(
@Url url: String = UrlConfig.GET_ACCESS_TOKEN,
@Query("qrcodeId") qrcodeId: String,
@Header("x-access-token") token: String = App.accessToken
@Query("qrcodeId") qrcodeId: String
): RespData<String?>
@GET
suspend fun getShelfList(
@Url url: String = UrlConfig.GET_SHELF_LIST,
@Query("deviceId") deviceId: String,
@Header("x-access-token") token: String = App.accessToken
@Query("deviceId") deviceId: String
): RespData<ShelfResult>
@GET
@@ -52,15 +49,13 @@ interface ApiService {
@Query("canteenId") canteenId: String = "0",
@Query("goodsName") goodsName: String? = null,
@Query("pageNo") pageNo: Int = 1,
@Query("pageSize") pageSize: Int = 50,
@Header("x-access-token") token: String = App.accessToken
): RespData<GoodsRecord>
@Query("pageSize") pageSize: Int = 50
): RespData<MutableList<GoodsModel>?>
@POST
suspend fun saveShelfGoodsList(
@Url url: String = UrlConfig.SAVE_SHELF_GOODS_LIST,
@Body body: ShelfBody,
@Header("x-access-token") token: String = App.accessToken
@Body body: ShelfBody
): RespData<Any?>
}
@@ -6,6 +6,7 @@ import com.shuwei.intelligent.shelves.App
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.Response
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import java.util.concurrent.TimeUnit
@@ -32,13 +33,24 @@ class HttpManager private constructor() {
writeTimeout(15, TimeUnit.SECONDS)
sslSocketFactory(createSSLSocketFactory(), TrustAllCerts())
hostnameVerifier { _, _ -> true }
addInterceptor(LoggingInterceptor())
// addInterceptor(LoggingInterceptor())
addNetworkInterceptor(HttpLoggingInterceptor(logger = {
Log.d("HttpManager","okhttp logger ==>${it}")
}).apply {
level =
// if (MyApp.DEBUG) {
HttpLoggingInterceptor.Level.BODY
// } else {
// HttpLoggingInterceptor.Level.NONE
// }
})
// addInterceptor { chain ->
// val request = chain.request().newBuilder()
// .header("x-access-token", App.accessToken)
// .build()
// chain.proceed(request)
// }
addInterceptor(RequestInterceptor())
}
.build()
}
@@ -49,19 +61,18 @@ class HttpManager private constructor() {
// fun newCall(request: Request): Call = client.newCall(request)
inner class LoggingInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
// 打印请求日志
Log.d("OkHttp", "--> ${request.method},${request.url}")
val response = chain.proceed(request)
// 打印响应日志
Log.d("OkHttp", "<-- ${response.code},${response.request.url}")
return response
}
}
// inner class LoggingInterceptor : Interceptor {
// override fun intercept(chain: Interceptor.Chain): Response {
// val request = chain.request()
// // 打印请求日志
// Log.d("HttpManager", "--> ${request.method},${request.url},${request.body}")
//
// val response = chain.proceed(request)
// // 打印响应日志
// Log.d("HttpManager", "<-- ${response.body}")
// return response
// }
// }
// 信任所有证书的TrustManager实现
@SuppressLint("CustomX509TrustManager")
@@ -1,5 +1,6 @@
package com.shuwei.intelligent.shelves.net
import android.app.Activity
import android.app.Dialog
import android.content.Context
import android.graphics.Color
@@ -15,24 +16,34 @@ object Loading {
private var dialog: LoadingDialog? = null
fun show(context: Context) {
fun show(activity: Activity) {
try {
if (activity.isFinishing || activity.isDestroyed) {
return
}
if (dialog?.isShowing == true) {
dialog?.dismiss()
}
if (dialog == null) {
dialog = LoadingDialog(context)
dialog = LoadingDialog(activity)
}
dialog?.show()
} catch (e: Exception) {
e.printStackTrace()
}
}
fun dismiss() {
try {
dialog?.let {
if (it.isShowing) {
it.dismiss()
}
}
dialog = null
} catch (e: Exception) {
e.printStackTrace()
}
}
}
@@ -47,12 +58,13 @@ class LoadingDialog(
binding.tvMessage.text = message
setContentView(binding.root)
setCancelable(true)
setCanceledOnTouchOutside(true)
window?.run {
setBackgroundDrawable(Color.TRANSPARENT.toDrawable())
setFlags(
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
);
// setFlags(
// WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
// WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
// )
}
setOnDismissListener {
onDismiss()
@@ -1,9 +1,13 @@
package com.shuwei.intelligent.shelves.net
import android.util.Log
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.shuwei.intelligent.shelves.App
import com.shuwei.intelligent.shelves.model.ShelfBody
import com.shuwei.intelligent.shelves.model.ShelfModel
import com.shuwei.intelligent.shelves.net.HttpManager.Companion.instance
import com.shuwei.intelligent.shelves.utils.ext.toJsonString
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
@@ -44,10 +48,10 @@ class NetViewModel : ViewModel() {
_getDeviceTokenUiState.value = UiState.Loading
runCatching {
val response = apiService.getDeviceToken(qrcodeId = deviceId)
if (response.success) {
if (response.isSuccess()) {
_getDeviceTokenUiState.value = UiState.Success(response)
} else {
_getDeviceTokenUiState.value = UiState.Error(response.message ?: "请求失败")
_getDeviceTokenUiState.value = UiState.Error(response.msg ?: "请求失败")
}
}.onFailure {
_getDeviceTokenUiState.value = UiState.Error(it.message ?: "请求异常")
@@ -57,16 +61,17 @@ class NetViewModel : ViewModel() {
fun getDeviceConfig(deviceId: String, deviceToken: String) {
viewModelScope.launch {
_getDeviceConfigUiState.value = UiState.Loading
_getDeviceTokenUiState.value = UiState.Loading
runCatching {
val response = apiService.getDeviceInfo(equipmentCode = deviceId, token = deviceToken)
if (response.success) {
_getDeviceConfigUiState.value = UiState.Success(response)
val response =
apiService.getDeviceInfo(equipmentCode = deviceId)
if (response.isSuccess()) {
_getDeviceTokenUiState.value = UiState.Success(response)
} else {
_getDeviceConfigUiState.value = UiState.Error(response.message ?: "请求失败")
_getDeviceTokenUiState.value = UiState.Error(response.msg ?: "请求失败")
}
}.onFailure {
_getDeviceConfigUiState.value = UiState.Error(it.message ?: "请求异常")
_getDeviceTokenUiState.value = UiState.Error(it.message ?: "请求异常")
}
}
}
@@ -76,10 +81,10 @@ class NetViewModel : ViewModel() {
_getAccessTokenUiState.value = UiState.Loading
runCatching {
val response = apiService.getAccessToken(qrcodeId = deviceId)
if (response.success) {
if (response.isSuccess()) {
_getAccessTokenUiState.value = UiState.Success(response)
} else {
_getAccessTokenUiState.value = UiState.Error(response.message ?: "请求失败")
_getAccessTokenUiState.value = UiState.Error(response.msg ?: "请求失败")
}
}.onFailure {
_getAccessTokenUiState.value = UiState.Error(it.message ?: "请求异常")
@@ -92,10 +97,10 @@ class NetViewModel : ViewModel() {
_getShelfListUiState.value = UiState.Loading
runCatching {
val response = apiService.getShelfList(deviceId = deviceId)
if (response.success) {
if (response.isSuccess()) {
_getShelfListUiState.value = UiState.Success(response)
} else {
_getShelfListUiState.value = UiState.Error(response.message ?: "请求失败")
_getShelfListUiState.value = UiState.Error(response.msg ?: "请求失败")
}
}.onFailure {
_getShelfListUiState.value = UiState.Error(it.message ?: "请求异常")
@@ -103,6 +108,21 @@ class NetViewModel : ViewModel() {
}
}
fun updateOverdueState(block:(Boolean, List<ShelfModel>?)-> Unit) {
viewModelScope.launch {
runCatching {
val response = apiService.getShelfList(deviceId = App.deviceId)
if (response.isSuccess()) {
block(true, response.data?.containerGoodsList)
} else {
block(false, emptyList())
}
}.onFailure {
block(false, null)
}
}
}
// fun saveShelfGoodsList(body: ShelfBody) {
// viewModelScope.launch {
// _saveShelfGoodsListUiState.value = UiState.Loading
@@ -128,16 +148,19 @@ class NetViewModel : ViewModel() {
viewModelScope.launch {
_getGoodsListUiState.value = UiState.Loading
runCatching {
if (App.configUrl.isNullOrBlank()) {
App.configUrl = UrlConfig.BASE_URL
}
val response = apiService.getGoodsList(
canteenId = canteenId,
goodsName = goodsName,
pageNo = pageNo,
pageSize = pageSize
)
if (response.success) {
if (response.isSuccess()) {
_getGoodsListUiState.value = UiState.Success(response)
} else {
_getGoodsListUiState.value = UiState.Error(response.message ?: "请求失败")
_getGoodsListUiState.value = UiState.Error(response.msg ?: "请求失败")
}
}.onFailure {
_getGoodsListUiState.value = UiState.Error(it.message ?: "请求异常")
@@ -145,4 +168,17 @@ class NetViewModel : ViewModel() {
}
}
fun saveShelfGoodsList(body: ShelfBody) {
viewModelScope.launch {
runCatching {
val resp = apiService.saveShelfGoodsList(body = body)
Log.d("NetViewModel", "saveShelfGoodsList,performSync: resp:${resp.toJsonString()}")
}.onFailure {
it.printStackTrace()
Log.d("NetViewModel", "saveShelfGoodsList异常:${it.message}")
}
}
}
}
@@ -0,0 +1,35 @@
package com.shuwei.intelligent.shelves.net
import android.text.TextUtils
import com.shuwei.intelligent.shelves.App
import okhttp3.Interceptor
import okhttp3.Request
import okhttp3.Response
/**
* 请求拦截器
*/
class RequestInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val originalRequest = chain.request()
val requestBuilder = originalRequest.newBuilder()
.header("Content-Type", "application/json")
.header("Accept", "application/json")
.header("X-Access-Token", "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJjYW50ZWVuSWQiOiJiZTE1NDgzMS0zNDY2LTNiYTItYTJlYS01NzY1MmM5MTlmZWQiLCJ0eXBlIjoiNCIsInVzZXJJZCI6IjAwMDAwMDAwMDAwMDAwMDAwMDEifQ.sN40cOC-O5WQFrF4IDUs8fFlkNdUKLbJt_rHyTsgYYM")
.header("X-DEVICE-CODE", App.deviceId)
.header("authorization", "57ee87183f2a4fa59683ec9ef41c8f5d")
val newRequest = requestBuilder.build()
return chain.proceed(newRequest)
}
// private fun getToken(originRequest: Request): String {
// val tokenParam = originRequest.header("X-Access-Token")
// if (!TextUtils.isEmpty(tokenParam)) return tokenParam!!
// // 从本地获取token的逻辑
// val spUtil = SPUtil.getInstance(context = App.getContext())
// return spUtil.get(GlobalKey.KEY_TOKEN, "") as String
//// return "eyJhbGciOiJIUzUxMiJ9.eyJpZCI6MTQ2LCJ1c2VyTmFtZSI6IjEzNjgxNDQ4ODU2IiwibmFtZSI6IuW-kOejiiIsInBhc3N3b3JkIjoiOTllOTQ1ZmVjZmZjNWIzNDI4MmUwNDRlODYyMzdjM2UxZjU5OWY5OCIsInNhbHQiOiI0NmEzMzUzYWU4OTA0MDYxYjMzODU5ZWNlYTBlMGE2NyIsInBob25lIjoiMTM2ODE0NDg4NTYiLCJzdGF0dXMiOjEsInVzZXJUeXBlIjoyLCJjcmVhdGVVc2VyTm8iOiIxNDEiLCJjcmVhdGVUaW1lIjoxNjI2OTQzNDE2MDAwLCJ1cGRhdGVVc2VyTm8iOiIxNDEiLCJ1cGRhdGVUaW1lIjoxNjI2OTQzNDE2MDAwLCJpc0RlbCI6ZmFsc2UsImVhSWQiOjk5LCJlYUlkTGlzdCI6Ijk5IiwiaXNTaG9wTWFuYWdlciI6dHJ1ZSwidXNlck5vIjoiMWY5Nzk5ZWMtODlkYi00MWYyLTk1YTEtY2UzNTA3Y2QyMTU2In0.f7wImPgBOYMV0AqRchnXGPkUWZN9dFJ9gLPsaB8uNldd21IfXLjJl8y-FiWVuVUvlwUvGpgqGDFR1JKj5H7amw"
// }
}
@@ -1,8 +1,11 @@
package com.shuwei.intelligent.shelves.net
data class RespData<T>(
val success: Boolean = false,
val message: String? = null,
val code: Int = 0,
val result: T? = null
)
// val success: Boolean = false,
val msg: String? = null,
val code: String? = null,
val data: T? = null,
// val total: Int
) {
fun isSuccess() = code == "00000"
}
@@ -3,7 +3,7 @@ package com.shuwei.intelligent.shelves.net
import com.shuwei.intelligent.shelves.App
object UrlConfig {
const val DEVICE_BASE_URL = "http://device.shuziweidao.com:8889"
// const val DEVICE_BASE_URL = "http://device.shuziweidao.com:8889"
// /**
// * device获取token
// */
@@ -13,12 +13,18 @@ object UrlConfig {
// */
// const val DEVICE_CONFIG = "${DEVICE_BASE_URL}/equipment/stEquipment/queryByEquipmentCode"
// const val BASE_URL = "http://192.168.1.3:9002"
const val BASE_URL = "https://yyjk.shuziweidao.com/gateway"
const val BASE_URL = "https://dev.yixiong-tech.com:8081"
// const val BASE_URL = "https://yyjk.shuziweidao.com/gateway"
// const val BASE_URL = "http://192.168.1.201:14801"
const val DEVICE_BASE_URL = BASE_URL
var GET_ACCESS_TOKEN = "${App.configUrl}/restaurant/equipment/stEquipment/getEquipmentToken"
var GET_SHELF_LIST = "${App.configUrl}/inventory/smartShelves/app/getShelvesListByDeviceId"
var GET_GOODS_LIST = "${App.configUrl}/inventory/smartShelves/app/getGoodsPageList"
var SAVE_SHELF_GOODS_LIST = "${App.configUrl}/inventory/smartShelves/app/saveShelvesGoodsList"
var GET_SHELF_LIST =
"${App.configUrl}/terminal/neglect/smartShelves/app/smartShelves/app/getShelvesListByDeviceId"
var GET_GOODS_LIST =
"${App.configUrl}/terminal/neglect/smartShelves/app/smartShelves/app/getGoodsPageList"
var SAVE_SHELF_GOODS_LIST =
"${App.configUrl}/terminal/neglect/smartShelves/app/smartShelves/app/saveShelvesGoodsList"
}
@@ -1,12 +1,14 @@
package com.shuwei.intelligent.shelves.serial
import android.annotation.SuppressLint
import androidx.lifecycle.lifecycleScope
import com.shuwei.intelligent.shelves.utils.hexToBytes
import com.shuwei.intelligent.shelves.utils.toHexString2
import io.github.jeadyx.jserialport.AndroidSerialPort
import io.github.jeadyx.jserialport.SerialPort
import io.github.jeadyx.jserialport.SerialPortFactory
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.collectLatest
import java.nio.charset.Charset
import java.util.Locale
@@ -14,6 +16,7 @@ object SerialPortManager {
private const val portName: String = "/dev/ttyS1"
private const val baudRate: Int = 115200
private var serialPort: AndroidSerialPort? = null
// private var serialPort: SerialPort? = null
private val scope = CoroutineScope(Dispatchers.IO)
@@ -53,10 +56,15 @@ object SerialPortManager {
fun startReceive(callback: (String) -> Unit) {
scope.launch {
while (isActive) {
serialPort?.read()?.collect { buffer ->
try {
val flow = serialPort?.read()
flow?.collect { buffer ->
val data = buffer.toHexString2().toUpperCase()
callback(data)
}
} catch (e: Exception) {
e.printStackTrace()
}
delay(50)
}
}
@@ -1,32 +1,32 @@
package com.shuwei.intelligent.shelves.task
import android.content.Context
import android.util.Log
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import com.shuwei.intelligent.shelves.HomeActivity
import com.shuwei.intelligent.shelves.serial.SerialPortManager
class SyncDeviceTask(appContext: Context, workerParams: WorkerParameters) :
CoroutineWorker(appContext, workerParams) {
companion object {
private const val TAG = "SyncDeviceTask"
}
override suspend fun doWork(): Result {
return try {
// 执行后台任务逻辑
Log.d(TAG, "doWork: ----------SyncDeviceTask----------")
SerialPortManager.send(HomeActivity.DEVICE_INFO_CMD)
TaskManager.startDeviceTask()
Result.success()
} catch (e: Exception) {
e.printStackTrace()
Result.retry()
}
}
}
//package com.shuwei.intelligent.shelves.task
//
//import android.content.Context
//import android.util.Log
//import androidx.work.CoroutineWorker
//import androidx.work.WorkerParameters
//import com.shuwei.intelligent.shelves.activity.HomeActivity
//import com.shuwei.intelligent.shelves.serial.SerialPortManager
//
//class SyncDeviceTask(appContext: Context, workerParams: WorkerParameters) :
// CoroutineWorker(appContext, workerParams) {
//
// companion object {
// private const val TAG = "SyncDeviceTask"
// }
//
// override suspend fun doWork(): Result {
// return try {
// // 执行后台任务逻辑
// Log.d(TAG, "doWork: ----------SyncDeviceTask----------")
// SerialPortManager.send(HomeActivity.DEVICE_INFO_CMD)
//
// TaskManager.startDeviceTask()
//
// Result.success()
// } catch (e: Exception) {
// e.printStackTrace()
// Result.retry()
// }
// }
//
//}
@@ -1,58 +1,67 @@
package com.shuwei.intelligent.shelves.task
import android.content.Context
import android.util.Log
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import com.shuwei.intelligent.shelves.App
import com.shuwei.intelligent.shelves.HomeActivity
import com.shuwei.intelligent.shelves.model.ShelfBody
import com.shuwei.intelligent.shelves.net.apiService
import com.shuwei.intelligent.shelves.utils.ext.toJsonString
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
class SyncTask(appContext: Context, workerParams: WorkerParameters) :
CoroutineWorker(appContext, workerParams) {
companion object {
private const val TAG = "SyncTask"
}
override suspend fun doWork(): Result {
return try {
// 执行后台任务逻辑
performSync()
TaskManager.startTask()
Result.success()
} catch (e: Exception) {
e.printStackTrace()
Result.retry()
}
}
private suspend fun performSync() {
withContext(Dispatchers.IO) {
val list = HomeActivity.list
Log.d(TAG, "performSync: list:${list.toJsonString()}")
val submitList = list.sortedBy { it.deviceNo }
submitList.forEach {
it.temperature = HomeActivity.showTemperatureC
it.humidity = HomeActivity.showHumidity
}
val body = ShelfBody().also {
it.deviceId = App.deviceId ?: ""
it.canteenId = App.canteenId
it.temperature = HomeActivity.showTemperatureC
it.humidity = HomeActivity.showHumidity
it.goodsList = submitList
}
val resp = apiService.saveShelfGoodsList(body = body)
Log.d(TAG, "performSync: body:${body.toJsonString()}")
Log.d(TAG, "performSync: resp:${resp.toJsonString()}")
}
}
}
//package com.shuwei.intelligent.shelves.task
//
//import android.content.Context
//import android.util.Log
//import androidx.work.CoroutineWorker
//import androidx.work.WorkerParameters
//import com.shuwei.intelligent.shelves.App
//import com.shuwei.intelligent.shelves.activity.HomeActivity
//import com.shuwei.intelligent.shelves.model.ShelfBody
//import com.shuwei.intelligent.shelves.net.apiService
//import com.shuwei.intelligent.shelves.utils.ext.toJsonString
//import kotlinx.coroutines.Dispatchers
//import kotlinx.coroutines.withContext
//
//class SyncTask(appContext: Context, workerParams: WorkerParameters) :
// CoroutineWorker(appContext, workerParams) {
//
// companion object {
// private const val TAG = "SyncTask"
// }
//
// override suspend fun doWork(): Result {
// return try {
// // 执行后台任务逻辑
// performSync()
//
// TaskManager.startTask()
//
// Result.success()
// } catch (e: Exception) {
// e.printStackTrace()
// Result.retry()
// }
// }
//
// private suspend fun performSync() {
// withContext(Dispatchers.IO) {
// val list = HomeActivity.list
// Log.d(TAG, "performSync: list:${list.toJsonString()}")
// val submitList = list.sortedBy { it.deviceNo }
// submitList.forEach {
// if (it.goodsId.isNullOrBlank()) {
// it.weight = it.weightBak
// }
// }
//// submitList.forEach {
//// it.temperature = HomeActivity.showTemperatureC
//// it.humidity = HomeActivity.showHumidity
//// }
// val body = ShelfBody().also {
// it.deviceId = App.deviceId
// it.canteenId = App.canteenId
// it.temperature = HomeActivity.showTemperatureC
// it.humidity = HomeActivity.showHumidity
// it.goodsList = submitList
// }
// Log.d(TAG, "performSync: body:${body.toJsonString()}")
// try {
// val resp = apiService.saveShelfGoodsList(body = body)
// Log.d(TAG, "performSync: resp:${resp.toJsonString()}")
// } catch (e: Exception) {
// e.printStackTrace()
// }
// }
// }
//
//}
@@ -1,58 +1,59 @@
package com.shuwei.intelligent.shelves.task
import androidx.work.ExistingWorkPolicy
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager
import com.shuwei.intelligent.shelves.App
import java.util.concurrent.TimeUnit
object TaskManager {
const val TASK_NAME = "IntelligentShelvesSyncTask"
const val DEVICE_TASK_NAME = "IntelligentShelvesDeviceTask"
fun startTask() {
val nextRequest = OneTimeWorkRequestBuilder<SyncTask>()
.setInitialDelay(3, TimeUnit.MINUTES)
// .setInitialDelay(30, TimeUnit.SECONDS)
// .setInputData(inputData)
.build()
WorkManager.getInstance(App.getInstance())
.enqueueUniqueWork(TASK_NAME, ExistingWorkPolicy.REPLACE, nextRequest)
// val constraints = Constraints.Builder()
// .setRequiredNetworkType(NetworkType.CONNECTED)
// .setRequiresBatteryNotLow(true)
//package com.shuwei.intelligent.shelves.task
//
//import androidx.work.ExistingWorkPolicy
//import androidx.work.OneTimeWorkRequestBuilder
//import androidx.work.WorkManager
//import com.shuwei.intelligent.shelves.App
//import java.util.concurrent.TimeUnit
//
//object TaskManager {
// const val TASK_NAME = "IntelligentShelvesSyncTask"
// const val DEVICE_TASK_NAME = "IntelligentShelvesDeviceTask"
//
// fun startTask() {
// val nextRequest = OneTimeWorkRequestBuilder<SyncTask>()
// .setInitialDelay(2, TimeUnit.MINUTES)
//// .setInitialDelay(30, TimeUnit.SECONDS)
//// .setInitialDelay(10, TimeUnit.MINUTES)
//// .setInputData(inputData)
// .build()
// val syncRequest = PeriodicWorkRequestBuilder<SyncTask>(
// 3L, TimeUnit.MINUTES, // 最小间隔15分钟
// 3L, TimeUnit.MINUTES // 弹性时间窗口
// )
// .setConstraints(constraints)
// WorkManager.getInstance(App.getInstance())
// .enqueueUniqueWork(TASK_NAME, ExistingWorkPolicy.REPLACE, nextRequest)
//
//// val constraints = Constraints.Builder()
//// .setRequiredNetworkType(NetworkType.CONNECTED)
//// .setRequiresBatteryNotLow(true)
//// .build()
//// val syncRequest = PeriodicWorkRequestBuilder<SyncTask>(
//// 3L, TimeUnit.MINUTES, // 最小间隔15分钟
//// 3L, TimeUnit.MINUTES // 弹性时间窗口
//// )
//// .setConstraints(constraints)
//// .build()
//// WorkManager.getInstance(App.get())
//// .enqueueUniquePeriodicWork(
//// TASK_NAME,
//// ExistingPeriodicWorkPolicy.KEEP,
//// syncRequest
//// )
// }
//
// fun cancelTask() {
// WorkManager.getInstance(App.getInstance())
// .cancelUniqueWork(TASK_NAME)
// }
//
// fun startDeviceTask() {
// val nextRequest = OneTimeWorkRequestBuilder<SyncDeviceTask>()
// .setInitialDelay(2, TimeUnit.MINUTES)
// .build()
// WorkManager.getInstance(App.get())
// .enqueueUniquePeriodicWork(
// TASK_NAME,
// ExistingPeriodicWorkPolicy.KEEP,
// syncRequest
// )
}
fun cancelTask() {
WorkManager.getInstance(App.getInstance())
.cancelUniqueWork(TASK_NAME)
}
fun startDeviceTask() {
val nextRequest = OneTimeWorkRequestBuilder<SyncDeviceTask>()
.setInitialDelay(3, TimeUnit.SECONDS)
.build()
WorkManager.getInstance(App.getInstance())
.enqueueUniqueWork(DEVICE_TASK_NAME, ExistingWorkPolicy.REPLACE, nextRequest)
}
fun cancelDeviceTask() {
WorkManager.getInstance(App.getInstance())
.cancelUniqueWork(DEVICE_TASK_NAME)
}
}
// WorkManager.getInstance(App.getInstance())
// .enqueueUniqueWork(DEVICE_TASK_NAME, ExistingWorkPolicy.REPLACE, nextRequest)
// }
//
// fun cancelDeviceTask() {
// WorkManager.getInstance(App.getInstance())
// .cancelUniqueWork(DEVICE_TASK_NAME)
// }
//
//}
@@ -406,9 +406,8 @@ public class AppUtil {
*/
@SuppressLint("MissingPermission")
public static String getUDID(Context context) {
// String androidID = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID);
// L.e("androidID===" + androidID);
// return androidID;
// return "SWZC-HG-001";
//// //"test111";
String androidID = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID);
if (!androidID.equals("")) {
try {
@@ -3,7 +3,7 @@ package com.shuwei.intelligent.shelves.utils
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import com.shuwei.intelligent.shelves.InitActivity
import com.shuwei.intelligent.shelves.activity.InitActivity
import kotlin.jvm.java
class BootReceiver : BroadcastReceiver() {
@@ -0,0 +1,251 @@
package com.shuwei.intelligent.shelves.utils
import android.app.ActivityManager
import android.app.AlarmManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.Handler
import android.os.Looper
import android.os.Process
import android.util.Log
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.io.PrintWriter
import java.io.StringWriter
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import kotlin.system.exitProcess
/**
* 崩溃处理
*/
class CrashHandler private constructor(private val context: Context) :
Thread.UncaughtExceptionHandler {
companion object {
private const val TAG = "CrashHandler"
const val CRASH_REPORTS_DIR = "crash_reports"
private const val LOG_LINES = 500 // 收集最近500行日志
@Volatile
private var instance: CrashHandler? = null
fun init(context: Context) {
if (instance == null) {
synchronized(CrashHandler::class.java) {
if (instance == null) {
instance = CrashHandler(context.applicationContext)
}
}
}
}
//fun getCrashReportFiles(context: Context): Array<File> {
// val crashDir = getCrashDir()
// return if (crashDir.exists() && crashDir.isDirectory) {
// crashDir.listFiles { _, name -> name.endsWith(".log") } ?: emptyArray()
// } else {
// emptyArray()
// }
//}
//
//fun clearCrashReports(context: Context) {
// getCrashReportFiles(context).forEach { it.delete() }
//}
}
private val defaultHandler = Thread.getDefaultUncaughtExceptionHandler()
init {
Thread.setDefaultUncaughtExceptionHandler(this)
}
override fun uncaughtException(thread: Thread, ex: Throwable) {
handleException(thread, ex)
try {
Thread.sleep(3000)
} catch (e: InterruptedException) {
e.printStackTrace()
}
// 如果系统提供了默认的异常处理器,则交给系统去结束程序
// 否则自己结束程序
defaultHandler?.uncaughtException(thread, ex) ?: run {
Process.killProcess(Process.myPid())
exitProcess(1)
}
}
/**
* 自动重启app
*/
// private fun restartApp() {
// // 延迟1秒后重启应用
// Handler(Looper.getMainLooper()).postDelayed({
// val intent = Intent(context, MainActivity::class.java).apply {
// addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
// }
//
// val pendingIntent = PendingIntent.getActivity(
// context, 0, intent,
// PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE
// )
//
// val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
// alarmManager.set(AlarmManager.RTC, System.currentTimeMillis() + 100, pendingIntent)
//
// Process.killProcess(Process.myPid())
// exitProcess(1)
// }, 1000)
// }
private fun handleException(thread: Thread, ex: Throwable) {
// 收集设备信息和异常信息
val crashInfo = collectCrashInfo(thread, ex)
// 保存日志文件
saveCrashInfoToFile(crashInfo)
// 这里可以添加其他处理逻辑,比如上传到服务器等
}
private fun collectCrashInfo(thread: Thread, ex: Throwable): String {
return buildString {
// 收集设备信息
collectDeviceInfo(this)
// 收集应用日志
append("\n\n").append(collectLogs())
// 收集线程和异常信息
append("\n\n========== Thread & Exception Info ==========\n")
append("Thread: ${thread.name}\n")
append("Stack Trace:\n")
val sw = StringWriter()
val pw = PrintWriter(sw)
ex.printStackTrace(pw)
var cause: Throwable? = ex.cause
while (cause != null) {
cause.printStackTrace(pw)
cause = cause.cause
}
pw.close()
append(sw.toString())
}
}
private fun collectDeviceInfo(sb: StringBuilder) {
sb.append("========== Device Info ==========\n")
try {
// 应用信息
val pm = context.packageManager
val pi = pm.getPackageInfo(context.packageName, 0)
sb.append("App Version: ${pi.versionName}_${pi.versionCode}\n")
// Android 版本信息
sb.append("OS Version: ${Build.VERSION.RELEASE}_${Build.VERSION.SDK_INT}\n")
// 设备信息
sb.append("Vendor: ${Build.MANUFACTURER}\n")
sb.append("Model: ${Build.MODEL}\n")
sb.append("CPU ABI: ${Build.SUPPORTED_ABIS[0]}\n")
// 其他信息
sb.append("Locale: ${Locale.getDefault()}\n")
sb.append(
"Current Time: ${
SimpleDateFormat(
"yyyy-MM-dd HH:mm:ss",
Locale.getDefault()
).format(Date())
}\n"
)
// 内存信息
val memoryInfo = ActivityManager.MemoryInfo()
val activityManager =
context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
activityManager.getMemoryInfo(memoryInfo)
sb.append("Available Memory: ${memoryInfo.availMem / (1024 * 1024)}MB\n")
sb.append("Total Memory: ${memoryInfo.totalMem / (1024 * 1024)}MB\n")
sb.append("Low Memory: ${memoryInfo.lowMemory}\n")
} catch (e: Exception) {
Log.e("","Error while collecting device info")
sb.append("Error while collecting device info: ${e.message}\n")
}
}
private fun collectLogs(): String {
return buildString {
append("========== Application Logs ==========\n")
try {
val process = Runtime.getRuntime().exec("logcat -d -v threadtime")
val reader = process.inputStream.bufferedReader()
val logLines = reader.readLines()
val start = maxOf(0, logLines.size - LOG_LINES)
logLines.subList(start, logLines.size).forEach {
append(it).append("\n")
}
} catch (e: IOException) {
Log.e(TAG, "Error collecting logs")
append("Error collecting logs: ${e.message}\n")
}
}
}
private fun saveCrashInfoToFile(crashInfo: String) {
try {
val time = SimpleDateFormat("yyyy-MM-dd_HH-mm-ss", Locale.getDefault()).format(Date())
val fileName = "crash_$time.log"
val crashDir = getCrashDir()
val crashFile = File(crashDir, fileName)
FileOutputStream(crashFile).use { it.write(crashInfo.toByteArray()) }
Log.d(TAG,"Crash info saved to: ${crashFile.absolutePath}")
} catch (e: Exception) {
Log.e(TAG, "Error saving crash info to file")
}
}
/**
* 清理旧的崩溃日志
*/
fun cleanupOldCrashReports(maxAgeDays: Int = 7) {
val crashDir = getCrashDir()
if (!crashDir.exists() || !crashDir.isDirectory) return
val now = System.currentTimeMillis()
val maxAgeMillis = maxAgeDays * 24 * 60 * 60 * 1000L
crashDir.listFiles()?.forEach { file ->
if (file.lastModified() < now - maxAgeMillis) {
file.delete()
}
}
}
private fun getCrashDir():File {
//val crashDir = File(context.getExternalFilesDir(null), CRASH_REPORTS_DIR)
var crashDir = File(context.filesDir, CRASH_REPORTS_DIR)
if (!crashDir.exists()) {
crashDir.mkdirs()
}
if (!(crashDir.exists())) {
crashDir = File(context.cacheDir, CRASH_REPORTS_DIR)
}
return crashDir
}
}
@@ -0,0 +1,131 @@
package com.shuwei.intelligent.shelves.utils
import android.content.Context
import com.shuwei.intelligent.shelves.utils.CrashHandler.Companion.CRASH_REPORTS_DIR
import java.io.*
import java.text.SimpleDateFormat
import java.util.*
/**
* 文件日志记录器
* 功能:保存日志到本地文件,每条数据一行,每天生成新文件
*/
class FileLogger(private val context: Context) {
companion object {
private const val LOG_DIR = "logs"
private const val FILE_PREFIX = "log_"
private const val FILE_EXTENSION = ".txt"
private val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
private val timeFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault())
}
/**
* 记录日志
* @param message 日志消息
* @param level 日志级别
*/
fun log(message: String, level: LogLevel = LogLevel.INFO) {
try {
val logEntry = formatLogEntry(message, level)
writeToFile(logEntry)
} catch (e: IOException) {
e.printStackTrace()
}
}
/**
* 格式化日志条目
*/
private fun formatLogEntry(message: String, level: LogLevel): String {
val timestamp = timeFormat.format(Date())
return "[$timestamp] [$level] $message\n"
}
/**
* 写入文件
*/
private fun writeToFile(logEntry: String) {
// val path = File(context.filesDir, LOG_DIR)
// if (path.exists().not()) {
// path.mkdirs()
// }
val fileName = getCurrentDateFileName()
// val file = File(path, fileName)
// if (file.exists().not()) {
// file.createNewFile()
// }
val output = context.openFileOutput(fileName, Context.MODE_APPEND)
val writer = BufferedWriter(OutputStreamWriter(output))
writer.use {
it.write(logEntry)
}
}
/**
* 获取当前日期对应的文件名
*/
private fun getCurrentDateFileName(): String {
val date = dateFormat.format(Date())
return "${FILE_PREFIX}${date}${FILE_EXTENSION}"
}
/**
* 读取指定日期的日志文件
* @param date 日期字符串,格式:yyyy-MM-dd
*/
fun readLogsByDate(date: String): List<String> {
val fileName = "${FILE_PREFIX}${date}${FILE_EXTENSION}"
return readLogsByName(fileName)
}
fun readLogsByName(fileName: String): List<String> {
try {
val input = context.openFileInput(fileName)
val reader = BufferedReader(InputStreamReader(input))
val list: MutableList<String> = reader.useLines { lines ->
lines.toList().toMutableList()
}
// val input2 = FileInputStream(getCrashDir(context))
// val reader2 = BufferedReader(InputStreamReader(input2))
// val list2: MutableList<String> = reader2.useLines {lines ->
// lines.toList().toMutableList()
// }
// list.addAll(list2)
return list
} catch (e: FileNotFoundException) {
return emptyList()
}
}
public fun getCrashDir(context: Context):File {
//val crashDir = File(context.getExternalFilesDir(null), CRASH_REPORTS_DIR)
var crashDir = File(context.filesDir, CRASH_REPORTS_DIR)
if (!crashDir.exists()) {
crashDir.mkdirs()
}
if (!(crashDir.exists())) {
crashDir = File(context.cacheDir, CRASH_REPORTS_DIR)
}
return crashDir
}
/**
* 获取所有日志文件列表
*/
fun getLogFiles(): List<String> {
return context.fileList()
.filter { it.startsWith(FILE_PREFIX) && it.endsWith(FILE_EXTENSION) }
//.map { it.replace(FILE_EXTENSION, "") }
}
/**
* 日志级别枚举
*/
enum class LogLevel {
DEBUG, INFO, WARN, ERROR
}
}
@@ -0,0 +1,64 @@
package com.shuwei.intelligent.shelves.utils;
public class HexUtils {
/**
* 将有符号十六进制字符串转换为十进制整数
* @param hex 十六进制字符串(支持负数补码表示)
* @return 对应的十进制整数
*/
public static int signedHexToDec(String hex) {
// 验证输入格式
if (hex == null || hex.isEmpty()) {
throw new IllegalArgumentException("输入不能为空");
}
if (!hex.matches("[0-9A-Fa-f]+")) {
throw new IllegalArgumentException("无效的十六进制格式: " + hex);
}
// 转换为无符号长整型
long unsignedValue = Long.parseLong(hex, 16);
// 判断是否为负数(最高位为1
int bitLength = hex.length() * 4; // 每个十六进制字符对应4位
long signBitMask = 1L << (bitLength - 1);
if ((unsignedValue & signBitMask) != 0) {
// 负数:计算补码
long maxValue = (1L << bitLength) - 1;
return (int) (unsignedValue - (maxValue + 1));
} else {
// 正数:直接转换
return (int) unsignedValue;
}
}
/**
* 测试方法
*/
public static void main(String[] args) {
// 测试用例
String[] testCases = {
"FF", // -1 (8位)
"FFFE", // -2 (16位)
"FFFFFFFE", // -2 (32位)
"7F", // 127
"80", // -128
"7FFF", // 32767
"8000" // -32768
};
System.out.println("有符号十六进制转换测试:");
for (String hex : testCases) {
try {
int result = signedHexToDec(hex);
System.out.println(hex + " -> " + result);
} catch (Exception e) {
System.out.println(hex + " -> 错误: " + e.getMessage());
}
}
}
}
@@ -0,0 +1,64 @@
package com.shuwei.intelligent.shelves.utils
import kotlinx.coroutines.*
class IntervalExecutor {
/**
* 启动定时任务
* @param delayMillis 延迟时间(毫秒)
* @param action 要执行的方法
* @return Job 可用于取消任务
*/
fun startIntervalTask(delayMillis: Long, action: suspend () -> Unit): Job {
return CoroutineScope(Dispatchers.Default).launch {
while (isActive) {
action()
delay(delayMillis)
}
}
}
/**
* 启动定时任务(带初始延迟)
* @param initialDelay 初始延迟时间(毫秒)
* @param delayMillis 后续执行间隔(毫秒)
* @param action 要执行的方法
* @return Job 可用于取消任务
*/
fun startIntervalTaskWithInitialDelay(
initialDelay: Long,
delayMillis: Long,
action: suspend () -> Unit
): Job {
return CoroutineScope(Dispatchers.Default).launch {
delay(initialDelay)
while (isActive) {
action()
delay(delayMillis)
}
}
}
}
// 使用示例
fun main() = runBlocking {
val executor = IntervalExecutor()
// 示例1:每隔10秒执行一次
val job1 = executor.startIntervalTask(10000) {
println("定时任务执行: ${System.currentTimeMillis()}")
// 这里可以执行你的业务逻辑
}
// 示例2:先延迟5秒,然后每隔3秒执行一次
val job2 = executor.startIntervalTaskWithInitialDelay(5000, 3000) {
println("带初始延迟的定时任务: ${System.currentTimeMillis()}")
}
// 运行30秒后取消任务
delay(30000)
job1.cancel()
job2.cancel()
println("所有定时任务已取消")
}
@@ -13,5 +13,9 @@ object KeyboardUtil {
view.clearFocus() // 清除焦点避免键盘再次弹出
}
fun hideKeyboard(view: View) {
val imm = view.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.hideSoftInputFromWindow(view.windowToken, 0)
view.clearFocus() // 清除焦点避免键盘再次弹出
}
}
@@ -0,0 +1,36 @@
package com.shuwei.intelligent.shelves.utils
import android.util.SparseArray
import androidx.core.util.containsKey
import com.shuwei.intelligent.shelves.model.ShelfModel
object StaleFoodRule {
private val shelfMap = SparseArray<ShelfModel>()
// fun load(list: MutableList<ShelfModel>) {
// if (list.isEmpty()) {
// return
// }
// list.forEach {
// shelfMap.put(it.deviceNo, it)
// }
// }
fun judge(item: ShelfModel, block:(Boolean)-> Unit = {}) {
val deviceNo = item.deviceNo
if (!shelfMap.containsKey(deviceNo)) {
//首次设置数据
shelfMap.put(deviceNo, item)
return
}
val lastItem = shelfMap.get(deviceNo)
if (lastItem.goodsId != item.goodsId) {
//柜子商品更换
shelfMap.put(deviceNo, item)
return
}
//同一产品
}
}
@@ -0,0 +1,282 @@
package com.shuwei.intelligent.shelves.utils;
//import cn.hutool.core.util.HexUtil;
//import cn.hutool.core.util.StrUtil;
//import com.chengyi.framework.utils.HexUtils;
//import lombok.Data;
//import lombok.extern.slf4j.Slf4j;
/**
* @Author: NThink
* @Date: 2021/12/10
* @Description: 鸿科HK70温控器信息消息解包
*/
public class TemperatureInfoHK70MsgBodyUnpack {
// 柜温探头温度
private Double gwttTemp;
// 蒸发探头温度
private Double zfttTemp;
// 冷凝探头温度
private Double lnttTemp;
// 停机温度
private Double tjTemp;
// 开机温度回差
private Double kjTempRange;
// 压缩机启动延时
private Integer ysjStartDelay;
// 首次通电压缩机启动延时
private Integer ysjStartDelayFirst;
// 柜温探头补偿
private Double gwttbcTemp;
// 停机温度可设置的最小值
private Double tjTempMin;
// 停机温度可设置的最大值
private Double tjTempMax;
// 压缩机最大待机时间
private Integer ysjMaxWait;
// 压缩机最短运行时间
private Integer ysjMinWork;
// 蒸发器探头状态
private Integer zfttStatus;
// 自动除霜周期
private Integer zdcszq;
// 除霜过程显示
private Integer csWorkShow;
// 除霜最长时间
private Integer cszcsj;
// 除霜中止温度
private Double cszzTemp;
// 除霜类型
private Integer csType;
// 风机运行模式
private Integer fanWorkType;
// 风机首次启动延时
private Integer fanFirstStartDelay;
// 滴水后风机启动延时
private Integer fanStartDelayAfterDrip;
// 开关机状态
private String powerStatus;
// 制冷状态
private String coolStatus;
// 除霜状态
private String csStatus;
// 滴水状态
private String dsStatus;
// 柜温探头报警
private String gwttAlarm;
// 除霜探头报警
private String csttAlarm;
// 冷凝探头报警
private String lnttAlarm;
// 终端ID
private String imei;
// 固件版本
private String version;
// 命令次数
private Integer cmdCount;
public TemperatureInfoHK70MsgBodyUnpack(String body) {
// ===================== 寄存器内容:110字节 =====================
String jcqContent = body.substring(0, 220);
// 柜温探头温度 [0, 4]
double gwttTemp = Double.parseDouble(String.format("%.1f", HexUtils.signedHexToDec(jcqContent.substring(0, 4)) * 0.1));
// setGwttTemp(gwttTemp);
// // 蒸发探头温度 [4, 8]
// double zfttTemp = Double.parseDouble(String.format("%.1f", HexUtils.signedHexToDec(jcqContent.substring(4, 8)) * 0.1));
// setZfttTemp(zfttTemp);
// // 冷凝探头温度 [8, 12]
// double lnttTemp = Double.parseDouble(String.format("%.1f", HexUtils.signedHexToDec(jcqContent.substring(8, 12)) * 0.1));
// setLnttTemp(lnttTemp);
// // 停机温度 [12, 16]
// double tjTemp = Double.parseDouble(String.format("%.1f", HexUtils.signedHexToDec(jcqContent.substring(12, 16)) * 1.0));
// setTjTemp(tjTemp);
// // 开机温度回差 [16, 20]
// double kjwdhc = Double.parseDouble(String.format("%.1f", HexUtils.signedHexToDec(jcqContent.substring(16, 20)) * 0.1));
// setKjTempRange(kjwdhc);
// // 压缩机启动延时 [20, 24]
// int ysjqdys = Integer.parseInt(jcqContent.substring(20, 24), 16);
// setYsjStartDelay(ysjqdys);
// // 首次通电压缩机启动延时 [24, 28]
// int ysjqdysFirst = Integer.parseInt(jcqContent.substring(24, 28), 16);
// setYsjStartDelayFirst(ysjqdysFirst);
// // 柜温探头补偿 [28, 32]
// double gwttbc = Double.parseDouble(String.format("%.1f", HexUtils.signedHexToDec(jcqContent.substring(28, 32)) * 0.1));
// setGwttbcTemp(gwttbc);
// // 停机温度可设置的最小值 [32, 36]
// double tjTempMin = Double.parseDouble(String.format("%.1f", HexUtils.signedHexToDec(jcqContent.substring(32, 36)) * 1.0));
// setTjTempMin(tjTempMin);
// // 停机温度可设置的最大值 [36, 40]
// double tjTempMax = Double.parseDouble(String.format("%.1f", HexUtils.signedHexToDec(jcqContent.substring(36, 40)) * 1.0));
// setTjTempMin(tjTempMax);
// // 压缩机最大待机时间 [40, 44]
// int ysjzddjsj = Integer.parseInt(jcqContent.substring(40, 44), 16);
// setYsjMaxWait(ysjzddjsj);
// // 压缩机最短运行时间 [44, 48]
// int ysjzdyxsj = Integer.parseInt(jcqContent.substring(44, 48), 16);
// setYsjMinWork(ysjzdyxsj);
// // 蒸发器探头选择 [48, 52]
// int zfqtt = Integer.parseInt(jcqContent.substring(48, 52), 16);
// setZfttStatus(zfqtt);
// // TODO 蒸发器探头补偿 [52, 56]
// // TODO 自动除霜周期计算方式 [56, 60]
// // 自动除霜周期 [60, 64]
// int zdcszq = Integer.parseInt(jcqContent.substring(60, 64), 16);
// setZdcszq(zdcszq);
// // 除霜过程显示选择 [64, 68]
// int csgcxx = Integer.parseInt(jcqContent.substring(64, 68), 16);
// setCsWorkShow(csgcxx);
// // 除霜最长时间 [68, 72]
// int cszcsj = Integer.parseInt(jcqContent.substring(68, 72), 16);
// setCszcsj(cszcsj);
// // 除霜终止温度 [72, 76]
// double cszzwd = Double.parseDouble(String.format("%.1f", HexUtils.signedHexToDec(jcqContent.substring(72, 76)) * 1.0));
// setCszzTemp(cszzwd);
// // TODO 除霜滴水时间 [76, 80]
// // TODO 除霜后柜温显示延时 [80, 84]
// // TODO 除霜启动延迟 [84, 88]
// // 除霜类型选择 [88, 92]
// int cslx = Integer.parseInt(jcqContent.substring(88, 92), 16);
// setCsType(cslx);
// // TODO 辅热提前启动时间 [92, 96]
// // TODO 强制除霜温度 [96, 100]
// // 风机运行模式 [100, 104]
// int fjyxms = Integer.parseInt(jcqContent.substring(100, 104), 16);
// setFanWorkType(fjyxms);
// // 风机首次启动延时 [104, 108]
// int fjscqdys = Integer.parseInt(jcqContent.substring(104, 108), 16);
// setFanFirstStartDelay(fjscqdys);
// // 滴水后风机启动延时 [108, 112]
// int dshfjqdys = Integer.parseInt(jcqContent.substring(108, 112), 16);
// setFanStartDelayAfterDrip(dshfjqdys);
// // TODO 风机工作最低温度 [112, 116]
// // TODO 风机工作最高温度 [116, 120]
// // TODO 风机循环开启时间 [120, 124]
// // TODO 风机循环关闭时间 [124, 128]
// // TODO 比例制冷压缩机停止时间 [128, 132]
// // TODO 比例制冷压缩机运行时间 [132, 136]
// // TODO 蜂鸣音报警 [136, 140]
// // TODO 柜温低温报警(绝对温度) [140, 144]
// // TODO 柜温高温报警(绝对温度) [144, 148]
// // TODO 柜温报警延时 [148, 152]
// // TODO 上电首次柜温报警延时 [152, 156]
// // TODO 辅助继电器功能选择 [156, 160]
// // TODO 门开关控制输出选择 [160, 164]
// // TODO 开门时蜂鸣器响应延时 [164, 168]
// // TODO 第三探头功能选择 [168, 172]
// // TODO 冷凝器高温报警启动值 [172, 176]
// // TODO 冷凝器高温报警下回差 [176, 180]
// // TODO 冷凝器高温保护启动值 [180, 184]
// // TODO 实时时钟功能开关 [不上报]
// // TODO 非营业模式开启时间 [不上报]
// // TODO 非营业模式关闭时间 [不上报]
// // TODO 非营业模式温度增量 [不上报]
// // TODO 非营业模式灯光开启时间 [不上报]
// // TODO 节假日设置 [不上报]
// // TODO 节假日非营业模式开启时间 [不上报]
// // TODO 除雾自动控制模式 [184, 188]
// // TODO 除雾自动开启时间 [188, 192]
// // TODO 除雾自动关闭时间 [192, 196]
// // TODO 开关机自动控制模式 [不上报]
// // TODO 自动开机时间 [不上报]
// // TODO 自动关机时间 [不上报]
// // TODO 单位选择 [196, 200]
// // TODO 柜温每升高 1℃显示延时 [204, 208]
// // TODO 显示分辨率选择 [208, 212]
// // TODO 机器地址 [212, 216]
// // TODO 管理员密码设定 [216, 220]
//
// // ===================== 状态位解析:4字节 =====================
// // 1.状态信息
// String statusHexStr = body.substring(220, 224);
// statusHexStr = HexUtils.reverse(statusHexStr);
// String statusBitStr = HexUtils.hexString2binaryString(statusHexStr);
// statusBitStr = StrUtil.reverse(statusBitStr);
// // 机组运行状态 0:停止 1:运行 [0, 1]
// String powerStatus = "0".equals(statusBitStr.substring(0, 1)) ? "close" : "open";
// setPowerStatus(powerStatus);
// // TODO 自检状态 0:停止 1:运行 [1, 2]
// // 压缩机运行状态 0:停止 1:运行 [2, 3]
// String coolStatus = "0".equals(statusBitStr.substring(3, 4)) ? "close" : "open";
// setCoolStatus(coolStatus);
// // 除霜状态 0:停止 1:运行 [3, 4]
// String csStatus = "0".equals(statusBitStr.substring(10, 11)) ? "close" : "open";
// setCsStatus(csStatus);
// // 滴水状态 0:关闭 1:运行中 [4, 5]
// String dsStatus = "0".equals(statusBitStr.substring(12, 13)) ? "close" : "open";
// setDsStatus(dsStatus);
// // TODO 风机状态 0:停止 1:运行 [5, 6]
// // TODO 除露状态 0:停止 1:运行 [6, 7]
// // TODO 灯光状态 0:停止 1:运行 [7, 8]
// // TODO 除霜加热或电磁阀状态 0:停止 1:运行 [8, 9]
// // TODO 辅助加热状态 0:停止 1:运行 [9, 10]
// // TODO 报警状态 0:停止 1:运行 [10, 11]
// // TODO 开门状态 0:停止 1:运行 [11, 12]
// // TODO 非营业(节能)状态 0:停止 1:运行 [12, 13]
// // TODO 节假日状态 0:停止 1:运行 [13, 14]
// // TODO 保留 0:停止 1:运行 [14, 15]
// // TODO 保留 0:停止 1:运行 [15, 16]
//
// // 2.报警信息
// String alarmHexStr = body.substring(224, 228);
// String alarmBitStr = HexUtils.hexString2binaryString(alarmHexStr);
// alarmBitStr = StrUtil.reverse(alarmBitStr);
// // 柜温探头故障 0:无报警 1:报警
// String gwttAlarm = "0".equals(alarmBitStr.substring(0, 1)) ? "normal" : "alarm";
// setGwttAlarm(gwttAlarm);
// // 除霜探头故障 0:无报警 1:报警
// String csAlarm = "0".equals(alarmBitStr.substring(1, 2)) ? "normal" : "alarm";
// setCsttAlarm(csAlarm);
// // 冷凝探头故障 0:无报警 1:报警
// String lnttAlarm = "0".equals(alarmBitStr.substring(2, 3)) ? "normal" : "alarm";
// setLnttAlarm(lnttAlarm);
// // TODO 柜温高温报警 0:无报警 1:报警
// // TODO 柜温低温报警 0:无报警 1:报警
// // TODO 冷凝高温报警 0:无报警 1:报警
// // TODO 冷凝保护报警 0:无报警 1:报警
// // TODO 门打开报警 0:无报警 1:报警
// // TODO 时钟故障报警 0:无报警 1:报警
//
// // 终端ID 15字节
// setImei(HexUtil.decodeHexStr(body.substring(228, 258)));
// // 固件版本 5字节
// setVersion(HexUtil.decodeHexStr(body.substring(258, 268)));
// // 命令次数
// setCmdCount(Integer.parseInt(body.substring(268, 270), 16));
}
public static void main(String[] args) {
// String reverse = HexUtils.reverse("8000");
// System.out.println(reverse);
}
}
@@ -10,6 +10,7 @@ import android.os.Bundle
import android.util.TypedValue
import android.view.View
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputMethodManager
import android.widget.EditText
import android.widget.Toast
import androidx.core.app.ActivityOptionsCompat
@@ -22,6 +23,8 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import android.content.ClipData
import android.content.ClipboardManager
fun Context.toast(
message: String?,
@@ -151,3 +154,24 @@ fun View.clickWithDebounce(delay: Long = 500, action: () -> Unit) {
}
}
}
fun View.hideKeyboard() {
val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.hideSoftInputFromWindow(this.windowToken, 0)
this.clearFocus() // 清除焦点避免键盘再次弹出
}
fun String.copyTextToClipboard(context: Context) {
// 获取系统服务中的ClipboardManager
val clipboard: ClipboardManager = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
// 创建一个ClipData对象,包含要复制的文本
val clip = ClipData.newPlainText("label", this)
// 将ClipData对象设置到剪贴板
clipboard.setPrimaryClip(clip)
// 显示一个简单的Toast提示用户文本已复制
Toast.makeText(context, "文本已复制到剪贴板", Toast.LENGTH_SHORT).show()
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

@@ -0,0 +1,7 @@
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="?android:attr/colorControlHighlight">
<item android:id="@android:id/mask">
<color android:color="@android:color/white" />
</item>
</ripple>
@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:gravity="center_vertical">
<ImageView
android:id="@+id/ivBack"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_gravity="top|start"
android:layout_marginStart="12dp"
android:layout_marginTop="12dp"
android:src="@drawable/ic_back_512"
tools:ignore="ContentDescription" />
<TextView
android:id="@+id/tvTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
tools:text="设置"
android:textColor="@color/black"
android:textSize="28sp" />
<Button
android:id="@+id/btnFind"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="15dp"
android:textSize="18sp"
android:layout_gravity="end|center_vertical"
android:text="查找1001指令"/>
</FrameLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvLogList"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
@@ -35,10 +35,10 @@
android:layout_width="wrap_content"
android:layout_height="60dp"
android:layout_marginTop="24dp"
android:fontFamily="sans-serif-medium"
android:gravity="center"
android:textColor="@color/bg_page"
android:textSize="30sp"
android:fontFamily="sans-serif-medium"
tools:text="货架 - 03" />
<Space
@@ -89,11 +89,11 @@
android:layout_marginEnd="24dp"
android:backgroundTint="@color/bg_page"
android:ellipsize="end"
android:fontFamily="sans-serif-medium"
android:insetTop="0dp"
android:insetBottom="0dp"
android:maxLines="1"
android:text="清零"
android:fontFamily="sans-serif-medium"
android:textColor="@color/white"
android:textSize="30sp"
app:cornerRadius="12dp"
@@ -105,11 +105,10 @@
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_gravity="top|start"
android:layout_marginTop="24dp"
android:layout_marginStart="24dp"
android:layout_marginTop="24dp"
android:src="@drawable/ic_back_512"
tools:ignore="ContentDescription" />
</FrameLayout>
</com.google.android.material.card.MaterialCardView>
@@ -153,16 +152,16 @@
android:layout_height="60dp"
android:layout_marginEnd="24dp"
android:background="@null"
android:fontFamily="sans-serif-medium"
android:hint="输入食材名称"
android:maxLines="1"
android:inputType="text"
android:text=""
android:imeOptions="actionSearch"
android:textColor="@color/food_name_black"
android:inputType="text"
android:maxLines="1"
android:paddingStart="1dp"
android:paddingEnd="1dp"
android:text=""
android:textColor="@color/food_name_black"
android:textColorHint="#B4BEC8"
android:fontFamily="sans-serif-medium"
android:textSize="30sp"
tools:ignore="Autofill,HardcodedText,TextFields" />
</LinearLayout>
@@ -170,8 +169,8 @@
<FrameLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:layout_marginTop="24dp">
android:layout_marginTop="24dp"
android:layout_weight="1">
<com.scwang.smart.refresh.layout.SmartRefreshLayout
android:id="@+id/refreshLayout"
@@ -186,16 +185,16 @@
android:id="@+id/rvSearch"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingStart="36dp"
android:paddingBottom="12dp"
android:paddingTop="12dp"
android:paddingEnd="36dp"
android:clipToPadding="false"
android:overScrollMode="never"
tools:listitem="@layout/list_item_search"
android:paddingStart="36dp"
android:paddingTop="12dp"
android:paddingEnd="36dp"
android:paddingBottom="12dp"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
app:spanCount="2"
tools:itemCount="10"/>
tools:itemCount="10"
tools:listitem="@layout/list_item_search" />
<com.scwang.smart.refresh.footer.ClassicsFooter
android:layout_width="match_parent"
@@ -205,36 +204,66 @@
<include
android:id="@+id/include"
layout="@layout/layout_empty_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
layout="@layout/layout_empty_view"
android:visibility="gone" />
</FrameLayout>
<!-- android:layout_marginTop="48dp"-->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/btnClearEmpty"
android:layout_width="0dp"
android:layout_height="80dp"
android:layout_marginStart="48dp"
android:layout_marginTop="24dp"
android:layout_marginEnd="24dp"
android:layout_marginBottom="24dp"
android:layout_weight="1"
android:backgroundTint="@color/bg_page"
android:ellipsize="end"
android:insetTop="0dp"
android:insetBottom="0dp"
android:maxLines="1"
android:text="清空"
android:textColor="@color/white"
android:textSize="30sp"
android:textStyle="bold"
app:cornerRadius="12dp"
app:elevation="0dp"
tools:ignore="HardcodedText" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnConfirm"
android:layout_width="match_parent"
android:layout_width="0dp"
android:layout_height="80dp"
android:layout_marginStart="24dp"
android:layout_marginTop="24dp"
android:layout_marginEnd="48dp"
android:layout_marginStart="48dp"
android:layout_marginBottom="24dp"
android:layout_weight="1"
android:backgroundTint="@color/bg_page"
android:ellipsize="end"
android:insetTop="0dp"
android:insetBottom="0dp"
android:maxLines="1"
android:text="确定"
android:textStyle="bold"
android:textColor="@color/white"
android:textSize="30sp"
android:textStyle="bold"
app:cornerRadius="12dp"
app:elevation="0dp"
tools:ignore="HardcodedText" />
</LinearLayout>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</LinearLayout>
@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="400dp"
android:layout_height="300dp"
app:cardCornerRadius="10dp"
app:cardElevation="0dp"
xmlns:tools="http://schemas.android.com/tools"
app:cardBackgroundColor="@color/white">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginVertical="30dp"
android:orientation="vertical">
<TextView
android:id="@+id/tvDialogTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:textColor="@color/black"
android:textSize="22sp"
tools:text="标题"/>
<TextView
android:id="@+id/tvDialogContent"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:layout_gravity="center_horizontal"
android:textColor="@color/black"
android:gravity="center"
android:layout_marginHorizontal="50dp"
android:textSize="16sp"
tools:text="内容"/>
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btnConfirm"
android:layout_width="120dp"
android:layout_height="50dp"
android:text="确定"
android:background="@color/bg_card_blue"
android:textColor="@color/white"
android:textSize="20sp"
android:layout_gravity="center_horizontal"/>
</LinearLayout>
</androidx.cardview.widget.CardView>
@@ -10,7 +10,7 @@
<ImageView
android:id="@+id/ivEmptyIcon"
android:layout_width="270dp"
android:layout_width="250dp"
android:layout_height="wrap_content"
android:src="@drawable/ic_empty_gray"
android:adjustViewBounds="true"
@@ -21,8 +21,8 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/black999"
android:layout_marginTop="60dp"
android:textSize="60sp"
android:layout_marginTop="50dp"
android:textSize="50sp"
android:text="暂无数据"
tools:ignore="HardcodedText" />
</LinearLayout>
@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="60dp">
<TextView
android:id="@+id/tvLogName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="30dp"
android:ellipsize="end"
android:maxLines="1"
android:textColor="@color/white"
android:textSize="20sp"
tools:text="2025-11-18" />
<com.google.android.material.divider.MaterialDivider
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_gravity="bottom"
android:layout_marginHorizontal="30dp"
app:dividerColor="@color/gray_edit" />
</FrameLayout>
@@ -8,7 +8,7 @@
android:layout_margin="12dp"
app:cardBackgroundColor="@color/bg_card_blue"
app:cardCornerRadius="12dp"
app:cardElevation="5dp"
app:cardElevation="0dp"
app:rippleColor="#B0BEC5">
<LinearLayout
+10 -6
View File
@@ -11,29 +11,33 @@
<FrameLayout
android:id="@+id/flStatusBar"
android:layout_width="match_parent"
android:layout_height="30dp"
android:paddingStart="12dp"
android:paddingEnd="12dp"
android:paddingTop="9dp">
android:layout_height="30dp">
<!-- android:paddingTop="9dp"-->
<TextView
android:id="@+id/tv_left_status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:textColor="@color/white"
android:layout_gravity="start"
android:textSize="12sp"
android:paddingStart="12dp"
android:paddingEnd="0dp"
android:fontFamily="sans-serif-medium"
tools:text="1-1冷藏柜 -2°C / 87%" />
<TextView
android:id="@+id/tv_right_status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:textColor="@color/white"
android:layout_gravity="end"
android:fontFamily="sans-serif-medium"
android:textSize="12sp"
android:paddingStart="0dp"
android:paddingEnd="12dp"
tools:text="6月22日 星期日 15:26:33" />
</FrameLayout>
@@ -25,4 +25,11 @@
layout="@layout/layout_empty_view"
android:visibility="gone"/>
<!-- <include-->
<!-- android:id="@+id/includeLight"-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="wrap_content"-->
<!-- layout="@layout/layout_light_switch"-->
<!-- android:visibility="gone"/>-->
</FrameLayout>
@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:gravity="center_vertical">
<ImageView
android:id="@+id/ivBack"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_gravity="top|start"
android:layout_marginStart="12dp"
android:layout_marginTop="12dp"
android:src="@drawable/ic_back_512"
tools:ignore="ContentDescription" />
<TextView
android:id="@+id/tvTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
tools:text="设置"
android:textColor="@color/black"
android:textSize="28sp" />
<Button
android:id="@+id/btnFind"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="10dp"
android:textSize="12sp"
android:layout_gravity="end|center_vertical"
android:text="查找1001指令"/>
</FrameLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvLogList"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
@@ -0,0 +1,111 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:background="@color/bg_page">
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="260dp"
android:layout_marginStart="12dp"
android:layout_marginTop="6dp"
android:layout_marginEnd="12dp"
app:cardBackgroundColor="@color/white"
app:cardCornerRadius="6dp"
app:cardElevation="0dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="10dp">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:gravity="center_vertical">
<ImageView
android:id="@+id/ivBack"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_gravity="top|start"
android:layout_marginTop="12dp"
android:layout_marginStart="12dp"
android:src="@drawable/ic_back_512"
tools:ignore="ContentDescription" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="28sp"
android:layout_gravity="center"
android:text="设置"
android:textColor="@color/black"/>
</FrameLayout>
<!-- <EditText-->
<!-- android:id="@+id/etInputCmdPrefix"-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="60dp"-->
<!-- android:hint="请输入命令"-->
<!-- android:maxLines="1"-->
<!-- android:text=""-->
<!-- android:textSize="22sp" />-->
<EditText
android:id="@+id/etInputCmd"
android:layout_width="match_parent"
android:layout_height="60dp"
android:hint="请输入命令内容"
android:maxLines="1"
android:textSize="22sp" />
<Button
android:id="@+id/btnSendCmd"
android:layout_width="180dp"
android:layout_height="60dp"
android:textSize="22sp"
android:layout_gravity="center"
android:layout_marginTop="30dp"
android:text="发送命令" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:layout_marginStart="12dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="12dp"
android:layout_marginBottom="12dp"
app:cardBackgroundColor="@color/white"
app:cardCornerRadius="6dp"
app:cardElevation="0dp">
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="10dp">
<TextView
android:id="@+id/tvCmdRecord"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:textSize="20sp"
android:text=""/>
</LinearLayout>
</ScrollView>
</com.google.android.material.card.MaterialCardView>
</LinearLayout>
+33 -3
View File
@@ -211,13 +211,42 @@
android:visibility="gone" />
</FrameLayout>
<!-- android:layout_marginTop="48dp"-->
<com.google.android.material.button.MaterialButton
android:id="@+id/btnConfirm"
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical">
<com.google.android.material.button.MaterialButton
android:id="@+id/btnClearEmpty"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="40dp"
android:layout_marginStart="24dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="12dp"
android:layout_marginBottom="12dp"
android:backgroundTint="@color/bg_page"
android:ellipsize="end"
android:insetTop="0dp"
android:insetBottom="0dp"
android:maxLines="1"
android:text="清空"
android:textColor="@color/white"
android:textSize="15sp"
android:textStyle="bold"
app:cornerRadius="6dp"
app:elevation="0dp"
tools:ignore="HardcodedText" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnConfirm"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="40dp"
android:layout_marginStart="12dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="24dp"
android:layout_marginBottom="12dp"
android:backgroundTint="@color/bg_page"
@@ -232,6 +261,7 @@
app:cornerRadius="6dp"
app:elevation="0dp"
tools:ignore="HardcodedText" />
</LinearLayout>
</LinearLayout>
@@ -0,0 +1,126 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#DDFFFFFF">
<ImageView
android:id="@+id/ivLightHide"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_marginVertical="10dp"
android:layout_marginEnd="10dp"
android:layout_gravity="end|center_vertical"
android:src="@drawable/close"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="80dp"
android:orientation="horizontal"
android:gravity="center_vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="20sp"
android:text="继电器1"
android:layout_marginStart="50dp"
android:textColor="@color/black"/>
<TextView
android:id="@+id/btnLightOpen"
android:layout_width="80dp"
android:layout_height="wrap_content"
android:textSize="20sp"
android:text="开"
android:gravity="center"
android:background="@drawable/bg_init_button"
android:layout_marginStart="50dp"
android:textColor="@color/black"/>
<TextView
android:id="@+id/btnLightClose"
android:layout_width="80dp"
android:layout_height="wrap_content"
android:textSize="20sp"
android:text="关"
android:gravity="center"
android:background="@drawable/bg_init_button"
android:layout_marginStart="50dp"
android:textColor="@color/black"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="80dp"
android:orientation="horizontal"
android:gravity="center_vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="20sp"
android:text="继电器2"
android:layout_marginStart="50dp"
android:textColor="@color/black"/>
<TextView
android:id="@+id/btnLightOpen2"
android:layout_width="80dp"
android:layout_height="wrap_content"
android:textSize="20sp"
android:text="开"
android:gravity="center"
android:background="@drawable/bg_init_button"
android:layout_marginStart="50dp"
android:textColor="@color/black"/>
<TextView
android:id="@+id/btnLightClose2"
android:layout_width="80dp"
android:layout_height="wrap_content"
android:textSize="20sp"
android:text="关"
android:gravity="center"
android:background="@drawable/bg_init_button"
android:layout_marginStart="50dp"
android:textColor="@color/black"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="80dp"
android:orientation="horizontal"
android:gravity="center_vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="20sp"
android:text="继电器3"
android:layout_marginStart="50dp"
android:textColor="@color/black"/>
<TextView
android:id="@+id/btnLightOpen3"
android:layout_width="80dp"
android:layout_height="wrap_content"
android:textSize="20sp"
android:text="开"
android:gravity="center"
android:background="@drawable/bg_init_button"
android:layout_marginStart="50dp"
android:textColor="@color/black"/>
<TextView
android:id="@+id/btnLightClose3"
android:layout_width="80dp"
android:layout_height="wrap_content"
android:textSize="20sp"
android:text="关"
android:gravity="center"
android:background="@drawable/bg_init_button"
android:layout_marginStart="50dp"
android:textColor="@color/black"/>
</LinearLayout>
</LinearLayout>
+26
View File
@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="60dp">
<TextView
android:id="@+id/tvLogName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="30dp"
android:ellipsize="end"
android:maxLines="1"
android:textColor="@color/white"
android:textSize="20sp"
tools:text="2025-11-18" />
<com.google.android.material.divider.MaterialDivider
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_gravity="bottom"
android:layout_marginHorizontal="30dp"
app:dividerColor="@color/gray_edit" />
</FrameLayout>
+20
View File
@@ -19,4 +19,24 @@
<item name="android:windowIsFloating">true</item>
<item name="android:windowBackground">@android:color/transparent</item>
</style>
<style name="DialogTheme" parent="@android:style/Theme.Dialog">
<!-- 边框 -->
<item name="android:windowFrame">@null</item>
<!-- 是否浮现在activity之上 -->
<item name="android:windowIsFloating">true</item>
<!-- 半透明 -->
<item name="android:windowIsTranslucent">true</item>
<!-- 无标题 -->
<item name="android:windowNoTitle">true</item>
<item name="android:background">@android:color/transparent</item>
<!-- 背景透明 -->
<item name="android:windowBackground">@android:color/transparent</item>
<!-- 模糊 -->
<item name="android:backgroundDimEnabled">true</item>
<!-- 遮罩层 -->
<item name="android:backgroundDimAmount">0.5</item>
</style>
</resources>
BIN
View File
Binary file not shown.
+2 -2
View File
@@ -1,7 +1,7 @@
#Thu Jul 17 15:32:57 CST 2025
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
#distributionUrl=https\://mirrors.cloud.tencent.com/gradle/gradle-8.13-bin.zip
#distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
distributionUrl=https\://mirrors.cloud.tencent.com/gradle/gradle-8.13-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
+3 -5
View File
@@ -1,10 +1,8 @@
## This file is automatically generated by Android Studio.
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
#
# This file should *NOT* be checked into Version Control Systems,
## This file must *NOT* be checked into Version Control Systems,
# as it contains information specific to your local configuration.
#
# Location of the SDK. This is only used by Gradle.
# For customization when using a Version Control System, please read the
# header note.
sdk.dir=D\:\\Android\\sdk
#Tue Jan 13 10:38:32 CST 2026
sdk.dir=C\:\\Users\\HUAWEI\\AppData\\Local\\Android\\Sdk