在 Ubuntu 24.04.1 LTS (WSL) 中使用 openssl 生成 keybox.xml
看到“生成 keybox.xml”,大概率都会联想到 PIF 和 Tricky Store。这里就不多解释它们的用途了。最近在网上看到生成非 AOSP keybox 的教程,在这里做一些补充,并将代码打包成一个 Python 脚本。
参考自:
- Idea 提供者:https://xdaforums.com/t/tee-hacking.4662185/page-21#post-89847987
(如果打不开或者被重定向去另一个网页可能要刷新几遍才能正确打开这个网页),该原始 Idea 需要借助一个密码学工具网站; - RSA 私钥转换:https://stackoverflow.com/questions/17733536/how-to-convert-a-private-key-to-an-rsa-private-key。
做出以下调整:
- 直接使用一站式脚本执行,自动利用 openssl 生成三个 PEM 文件,如果用于预检测的
openssl version命令执行失败,自动尝试通过sudo apt-get install libssl-dev进行安装; - 实现对新版 openssl 生成的 RSA 私钥进行识别,并从 PKCS8 转换为 PKCS1。
直接上 Python 代码,记得以 LF 形式保存换行符,并在 Ubuntu 24.04.1 LTS 中运行。
import os
try:os.chdir(os.path.abspath(os.path.dirname(__file__)))
except:pass
EXIT_SUCCESS = 0
EXIT_FAILURE = 1
EOF = (-1)
keyboxFormatter = """<?xml version="1.0"?>
<AndroidAttestation>
<NumberOfKeyboxes>1</NumberOfKeyboxes>
<Keybox DeviceID="YourDeviceID">
<Key algorithm="ecdsa">
<PrivateKey format="pem">
{0}</PrivateKey>
<CertificateChain>
<NumberOfCertificates>1</NumberOfCertificates>
<Certificate format="pem">
{1}</Certificate>
</CertificateChain>
</Key>
<Key algorithm="rsa">
<PrivateKey format="pem">
{2}</PrivateKey>
</Key>
</Keybox>
</AndroidAttestation>"""def execute(commandline:str) -> int|None:if isinstance(commandline, str):print("$ " + commandline)return os.system(commandline)else:return Nonedef handleOpenSSL(flag:bool = True) -> bool|None:if isinstance(flag, bool):errorLevel = execute("openssl version")if EXIT_SUCCESS == errorLevel:return Trueelif flag: # can try againexecute("sudo apt-get install openssl libssl-dev")return handleOpenSSL(False)else:return Falseelse:return Nonedef pressTheEnterKeyToExit(errorLevel:int|None = None):try:print("Please press the enter key to exit ({0}). ".format(errorLevel) if isinstance(errorLevel, int) else "Please press the enter key to exit. ")input()except:passdef main() -> int:# Parameters #failureCount = 0ecPrivateKeyFilePath = "ecPrivateKey.pem"certificateFilePath = "certificate.pem"rsaPrivateKeyFilePath = "rsaPrivateKey.pem"oldRsaPrivateKeyFilePath = "oldRsaPrivateKey.pem"keyboxFilePath = "keybox.xml"# First-phase Generation #failureCount += execute("openssl ecparam -name prime256v1 -genkey -noout -out \"{0}\"".format(ecPrivateKeyFilePath)) != 0failureCount += execute("openssl req -new -x509 -key \"{0}\" -out {1} -days 3650 -subj \"/CN=Keybox\"".format(ecPrivateKeyFilePath, certificateFilePath)) != 0failureCount += execute("openssl genrsa -out \"{0}\" 2048".format(rsaPrivateKeyFilePath)) != 0if failureCount > 0:print("Cannot generate a sample ``keybox.xml`` file since {0} PEM file{1} not generated successfully. ".format(failureCount, ("s were" if failureCount > 1 else " was")))pressTheEnterKeyToExit(EOF)return EOF# First-phase Reading #try:with open(ecPrivateKeyFilePath, "r", encoding = "utf-8") as f:ecPrivateKey = f.read()with open(certificateFilePath, "r", encoding = "utf-8") as f:certificate = f.read()with open(rsaPrivateKeyFilePath, "r", encoding = "utf-8") as f:rsaPrivateKey = f.read()except BaseException as e:print("Failed to read one or more of the PEM files. Details are as follows. \n{0}".format(e))pressTheEnterKeyToExit(EOF)return EOF# Second-phase Generation #if rsaPrivateKey.startswith("-----BEGIN PRIVATE KEY-----"):print("A newer openssl version is used. The RSA private key in the PKCS8 format will be converted to that in the PKCS1 format soon. ")failureCount += execute("openssl rsa -in \"{0}\" -out \"{1}\" -traditional".format(rsaPrivateKeyFilePath, oldRsaPrivateKeyFilePath))if failureCount > 0:print("Cannot convert the RSA private key in the PKCS8 format to that in the PKCS1 format. ")pressTheEnterKeyToExit(EOF)return EOFelse:print("Finished converting the RSA private key in the PKCS8 format to that in the PKCS1 format. ")try:with open(oldRsaPrivateKeyFilePath, "r", encoding = "utf-8") as f:rsaPrivateKey = f.read()except BaseException as e:print("Failed to update the RSA private key from \"{0}\". Details are as follows. \n{1}".format(oldRsaPrivateKeyFilePath, e))pressTheEnterKeyToExit(EOF)return EOF# Keybox Generation #keybox = keyboxFormatter.format(ecPrivateKey, certificate, rsaPrivateKey)print(keybox)try:with open(keyboxFilePath, "w", encoding = "utf-8") as f:f.write(keybox)print("Successfully wrote the keybox to \"{0}\". ".format(keyboxFilePath))pressTheEnterKeyToExit(EXIT_SUCCESS)return EXIT_SUCCESSexcept BaseException as e:print("Failed to write the keybox to \"{0}\". Details are as follows. \n{1}".format(keyboxFilePath, e))pressTheEnterKeyToExit(EXIT_FAILURE)return EXIT_FAILUREif "__main__" == __name__:exit(main())
替换 /data/adb/tricky_store/keybox.xml 之前,记得先将原来的 keybox.xml(刷入 tricky_store 时自带的那个基于 AOSP 的 keybox.xml)备份为 keybox.xml.bak。

12月14日凌晨做了一些更新:
- 支持粗略检查三个子密钥文件内容,支持 OpenSSL 私钥转 RSA 私钥;
- 如果文件存在,程序会提示是否覆盖;
- 设备ID随机生成。
import os
from random import randint, choice
from base64 import b64decode
try:os.chdir(os.path.abspath(os.path.dirname(__file__)))
except:pass
EXIT_SUCCESS = 0
EXIT_FAILURE = 1
EOF = (-1)
LB = 2 # the lower bound of the length of the device ID
UB = 12 # the upper bound of the length of the device ID
CHARSET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
keyboxFormatter = """<?xml version="1.0"?>
<AndroidAttestation>
<NumberOfKeyboxes>1</NumberOfKeyboxes>
<Keybox DeviceID="{0}">
<Key algorithm="ecdsa">
<PrivateKey format="pem">
{1}</PrivateKey>
<CertificateChain>
<NumberOfCertificates>1</NumberOfCertificates>
<Certificate format="pem">
{2}</Certificate>
</CertificateChain>
</Key>
<Key algorithm="rsa">
<PrivateKey format="pem">
{3}</PrivateKey>
</Key>
</Keybox>
</AndroidAttestation>
"""def canOverwrite(flags:list, idx:int, prompts:str|tuple|list|set) -> bool:if isinstance(flags, list) and isinstance(idx, int) and -len(flags) <= idx < len(flags) and isinstance(prompts, (str, tuple, list, set)):try:if isinstance(prompts, str):print("\"{0}\"".format(prompts))choice = input("The file mentioned above exists. Overwrite or not [aYn]? ")else:print(prompts)choice = input("At least one of the files mentioned above exists. Overwrite or not [aYn]? ")if choice.upper() == "A":for i in range((idx if idx >= 0 else len(flags) + idx), len(flags)): # overwirte the current file and all the following necessary files no matter whether they existflags[i] = Truereturn Trueelif choice.upper() == "N":return Falseelse:flags[idx] = Truereturn Trueexcept BaseException as e:print(e)return Falseelse:input("#")return Falsedef execute(commandline:str) -> int|None:if isinstance(commandline, str):print("$ " + commandline)return os.system(commandline)else:return Nonedef handleOpenSSL(flag:bool = True) -> bool|None:if isinstance(flag, bool):errorLevel = execute("openssl version")if EXIT_SUCCESS == errorLevel:return Trueelif flag: # can try againexecute("sudo apt-get install openssl libssl-dev")return handleOpenSSL(False)else:return Falseelse:return Nonedef pressTheEnterKeyToExit(errorLevel:int|None = None):try:print("Please press the enter key to exit ({0}). ".format(errorLevel) if isinstance(errorLevel, int) else "Please press the enter key to exit. ")input()except:passdef main() -> int:# Parameters #failureCount = 0deviceID = "".join([choice(CHARSET) for _ in range(randint(LB, UB))]) # or specify the device ID manually like "YourDeviceID"ecPrivateKeyFilePath = "ecPrivateKey.pem"certificateFilePath = "certificate.pem"rsaPrivateKeyFilePath = "rsaPrivateKey.pem"keyboxFilePath = "keybox.xml" # None for no files writtenflags = [not (os.path.isfile(ecPrivateKeyFilePath) or os.path.isfile(certificateFilePath)), not os.path.isfile(rsaPrivateKeyFilePath), not os.path.isfile(keyboxFilePath)]# First-phase Generation #if flags[0] or canOverwrite(flags, 0, (ecPrivateKeyFilePath, certificateFilePath)):failureCount += execute("openssl ecparam -name prime256v1 -genkey -noout -out \"{0}\"".format(ecPrivateKeyFilePath)) != 0if flags[0] or not os.path.isfile(certificateFilePath):failureCount += execute("openssl req -new -x509 -key \"{0}\" -out {1} -days 3650 -subj \"/CN=Keybox\"".format(ecPrivateKeyFilePath, certificateFilePath)) != 0if flags[1] or canOverwrite(flags, 1, rsaPrivateKeyFilePath):failureCount += execute("openssl genrsa -out \"{0}\" 2048".format(rsaPrivateKeyFilePath)) != 0if failureCount > 0:print("Cannot generate a sample ``keybox.xml`` file since {0} PEM file{1} not generated successfully. ".format(failureCount, ("s were" if failureCount > 1 else " was")))pressTheEnterKeyToExit(11)return 11# First-phase Reading #try:with open(ecPrivateKeyFilePath, "r", encoding = "utf-8") as f:ecPrivateKey = f.read()with open(certificateFilePath, "r", encoding = "utf-8") as f:certificate = f.read()with open(rsaPrivateKeyFilePath, "r", encoding = "utf-8") as f:rsaPrivateKey = f.read()except BaseException as e:print("Failed to read one or more of the PEM files. Details are as follows. \n{0}".format(e))pressTheEnterKeyToExit(12)return 12# Second-phase Generation #if flags[1]: # only updates the key content when the original key is newly generated or updating is allowedif rsaPrivateKey.startswith("-----BEGIN PRIVATE KEY-----") and rsaPrivateKey.rstrip().endswith("-----END PRIVATE KEY-----"):print("A newer openssl version is used. The RSA private key in the PKCS8 format will be converted to that in the PKCS1 format soon. ")failureCount += execute("openssl rsa -in \"{0}\" -out \"{0}\" -traditional".format(rsaPrivateKeyFilePath))if failureCount > 0:print("Cannot convert the RSA private key in the PKCS8 format to that in the PKCS1 format. ")pressTheEnterKeyToExit(13)return 13else:print("Finished converting the RSA private key in the PKCS8 format to that in the PKCS1 format. ")try:with open(rsaPrivateKeyFilePath, "r", encoding = "utf-8") as f:rsaPrivateKey = f.read()except BaseException as e:print("Failed to update the RSA private key from \"{0}\". Details are as follows. \n{1}".format(rsaPrivateKeyFilePath, e))pressTheEnterKeyToExit(14)return 14elif rsaPrivateKey.startswith("-----BEGIN OPENSSH PRIVATE KEY-----") and rsaPrivateKey.rstrip().endswith("-----END OPENSSH PRIVATE KEY-----"):print("An OpenSSL private key is detected, which will be converted to the RSA private key soon. ")failureCount += execute("ssh-keygen -p -m PEM -f \"{0}\" -N \"\"".format(rsaPrivateKeyFilePath))if failureCount > 0:print("Cannot convert the OpenSSL private key to the RSA private key. ")pressTheEnterKeyToExit(15)return 15else:print("Finished converting the OpenSSL private key to the RSA private key. ")try:with open(rsaPrivateKeyFilePath, "r", encoding = "utf-8") as f: # the ``ssh-keygen`` overwrites the file though no obvious output filepaths specifiedrsaPrivateKey = f.read()except BaseException as e:print("Failed to update the RSA private key from \"{0}\". Details are as follows. \n{1}".format(rsaPrivateKeyFilePath, e))pressTheEnterKeyToExit(16)return 16# Brief Checks #if not (ecPrivateKey.startswith("-----BEGIN EC PRIVATE KEY-----") and ecPrivateKey.rstrip().endswith("-----END EC PRIVATE KEY-----")):print("An invalid EC private key is detected. Please try to use the latest key generation tools to solve this issue. ")pressTheEnterKeyToExit(17)return 17if not (certificate.startswith("-----BEGIN CERTIFICATE-----") and certificate.rstrip().endswith("-----END CERTIFICATE-----")):print("An invalid certificate is detected. Please try to use the latest key generation tools to solve this issue. ")pressTheEnterKeyToExit(18)return 18if not (rsaPrivateKey.startswith("-----BEGIN RSA PRIVATE KEY-----") and rsaPrivateKey.rstrip().endswith("-----END RSA PRIVATE KEY-----")):print("An invalid final RSA private key is detected. Please try to use the latest key generation tools to solve this issue. ")pressTheEnterKeyToExit(19)return 19# Keybox Generation #keybox = keyboxFormatter.format(deviceID, ecPrivateKey, certificate, rsaPrivateKey)print("Generated keybox with a length of {0}: ".format(len(keybox)))print(keybox)if keyboxFilePath is not None and (flags[2] or canOverwrite(flags, 2, keyboxFilePath)):try:with open(keyboxFilePath, "w", encoding = "utf-8") as f:f.write(keybox)print("Successfully wrote the keybox to \"{0}\". ".format(keyboxFilePath))pressTheEnterKeyToExit(EXIT_SUCCESS)return EXIT_SUCCESSexcept BaseException as e:print("Failed to write the keybox to \"{0}\". Details are as follows. \n{1}".format(keyboxFilePath, e))pressTheEnterKeyToExit(20)return 20else:print("The keybox has not been written to any files. Please refer to the text above. ")pressTheEnterKeyToExit(EXIT_FAILURE)return EXIT_FAILUREif "__main__" == __name__:exit(main())
相关文章:
在 Ubuntu 24.04.1 LTS (WSL) 中使用 openssl 生成 keybox.xml
看到“生成 keybox.xml”,大概率都会联想到 PIF 和 Tricky Store。这里就不多解释它们的用途了。最近在网上看到生成非 AOSP keybox 的教程,在这里做一些补充,并将代码打包成一个 Python 脚本。 参考自: Idea 提供者:…...
【JavaSE基础】第十六章:IO流
一、理解 1.简单而言:流就是内存与存储设备之间传输数据的通道、管道。 2.流的分类: (1) 按方向 ( 以 JVM 虚拟机为参照物 ) 【重点】 输入流:将< 存储设备 > 中的内容读入到 < 内存 > 中。 输…...
常见漏洞—SSRF_FastCGI
FastCGI协议 简介 Fast CGI源自旧版本的CGI 路由/结构图 # 访问url --> 浏览器生成HTTP请求报文 --> web server解析请求(例如nginx) web server 是内容的分发者 当访问静态页面时,web server 会直接返回资源,例如index.htm…...
LeetCode 283.移动零(超简单讲解)
283.移动零 题目示例示例1示例2 解题思路快慢指针实现设计 详细代码 题目 给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。 请注意 ,必须在不复制数组的情况下原地对数组进行操作。 示例 示例1 …...
GIS原理及应用、地理坐标系与投影坐标系
文章目录 一、GIS定义1.1 地理信息系统1.2 建模1.3 相关教程1.4 GIS前沿方向 二、GIS数据格式2.1 矢量2.2 栅格2.3 矢量与栅格的区别 三、GIS数据组织3.1 抽象3.2 分层3.3 栅格与切片 四、坐标系4.1 坐标系简介4.2 大地坐标系GCS4.3 投影坐标系PCS4.4 投影变换 五、空间数据库与…...
用github镜像加速, --recursive还是去github站怎么处理?
小伙伴们大多碰到过github抽风的情况,时通时断,时快时慢,非常考验心情。 以前碰到连不上的时候,我大多就是在gitee和gitcode网站找一下镜像,找到后直接git clone 新地址即可。但是碰到 --recursive的时候就不行了&…...
ctfshow-web 151-170-文件上传
151. 我们首先想到就是上传一句话木马。但是看源代码限制了png。 (1)改前端代码。 这里是前端限制了上传文件类型,那我们就改一下就好了嘛,改成php。 这里直接修改不行,给大家推荐一篇简短文章,大家就会了(…...
【电源专题】开关转换器使能(EN)管脚的几种不同方式
我们的文章说到了很多与使能有关的电源案例和原理,如下所示: 【电源专题】案例:芯片规格书使能定义高电平最小阈值1.4V,那真的是到1.4V时才开始输出?_芯片的电流阀值-CSDN博客...
5G学习笔记之SNPN系列之ID和广播消息
目录 1. 概述 2. SNPN ID 3. SNPN广播消息 1. 概述 SNPN:Stand-alone Non-Public Network,独立的非公共网络,由NPN独立运营,不依赖与PLMN网络。 SNPN不支持的5GS特性: 与EPS交互 emergency services when the UE acce…...
Qt-Advanced-Docking-System配置及使用、心得
Qt-Advanced-Docking-System 1. Qt-Advanced-Docking-System描述2. 功能特点2.1. 灵活的停靠方式2.2. 嵌套停靠2.3. 自定义布局保存与恢复2.4. 外观和行为定制 3. 与Qt原生停靠系统的比较4. 使用场景4.1. 集成开发环境(IDE)4.2. 图形设计软件4.3. 数据分…...
【Bolt.new + PromptCoder】三分钟还原油管主页
【Bolt.new PromptCoder】三分钟还原油管主页 PromptCoder官网:PromptCoder Bolt官网:https://bolt.new/ Bolt 是什么? Bolt.new 是一个提供创建全栈网络应用服务的平台。它允许用户通过提示(Prompt)、运行&#x…...
影像组学+病理组学+深度学习人工智能应用
影像组学 基础学习内容: 特征提取:使用pyradiomics进行形状、纹理、小波变换等特征提取。特征筛选:应用ICC、相关系数、mRMR、Lasso等方法。建模:使用LR、SVM、RF、XGBoost、LightGBM等机器学习算法。模型评估:通过A…...
RK3568平台(基础篇)io命令支持
一.什么是io命令 “io” 命令通常用于显示 Linux 系统中的 I/O 统计信息。它提供了有关磁盘读写操作的详细信息,包括每个块设备的读写次数、读写扇区数、读写延迟等。io命令可以直接操作某个寄存器,用于查看设置某个GPIO 引脚配置了什么iomux。 二.io命令支持 RK平台要支持…...
Yolov8源码分析
1、目录介绍 主要目录ultralitics(重点) 1、assets目录 这个文件保存了YOLO历史上可以说是最经典的两张图片,供大家测试程序来使用的。 2、cfg 这个文件下面保存了我们的模型配置文件,cfg目录是项目配置的集中地,其…...
Python中的装饰器`@functools.lru_cache`:用法、来源与应用 (中英双语)
今天看到一段源码 https://github.com/google-research/google-research/blob/master/instruction_following_eval/instructions_util.py 如下,对其中使用的装饰器函数感到好奇,所以产生了这篇博客。 functools.lru_cache(maxsizeNone) def _get_sentenc…...
思维图(GoT):解锁大模型解决复杂问题的能力
今天分享的是苏黎世联邦理工学院、华沙理工大学和Cledar联合发表的一篇文章:思维图:用大语言模型解决复杂问题 论文题目:Graph of Thoughts: Solving Elaborate Problems with Large Language Models 论文链接:https://arxiv.or…...
使用winscp从windows访问Ubuntu进行文件传输
Ubuntu 系统上的准备工作 • 安装 SSH 服务器: 确保 Ubuntu 系统上已经安装了 SSH 服务器。如果没有安装,可以使用以下命令安装: sudo apt update sudo apt install openssh-server • 启动 SSH 服务: 确保 SSH 服务正在运行&a…...
Java全栈项目:实验室预约管理系统的设计与实现
一、项目介绍 实验室预约管理系统是一个基于Java全栈技术开发的Web应用系统,旨在提供便捷的实验室预约、管理和使用体验。本系统主要面向高校师生,实现实验室资源的智能化、信息化管理。 二、技术栈 前端技术 Vue.jsElement UIAxiosVue RouterVuex …...
使用 esrally race 测试 Elasticsearch 性能及 Kibana 可视化分析指南
前言: 在对 Elasticsearch 集群进行性能测试与调优的过程中,esrally 是官方推荐的测试工具。通过 esrally race 命令,我们可以模拟各种查询与索引负载,对集群进行基准测试。然而,仅看 esrally 的终端输出并不直观&…...
OpenAI 第七日 推出了一项新功能——ChatGPT的“Projects”
每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗?订阅我们的简报,深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同,从行业内部的深度分析和实用指南中受益。不要错过这个机会,成为AI领…...
定时器任务——若依源码分析
分析util包下面的工具类schedule utils: ScheduleUtils 是若依中用于与 Quartz 框架交互的工具类,封装了定时任务的 创建、更新、暂停、删除等核心逻辑。 createScheduleJob createScheduleJob 用于将任务注册到 Quartz,先构建任务的 JobD…...
论文解读:交大港大上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(一)
宇树机器人多姿态起立控制强化学习框架论文解析 论文解读:交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(一) 论文解读:交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化…...
用docker来安装部署freeswitch记录
今天刚才测试一个callcenter的项目,所以尝试安装freeswitch 1、使用轩辕镜像 - 中国开发者首选的专业 Docker 镜像加速服务平台 编辑下面/etc/docker/daemon.json文件为 {"registry-mirrors": ["https://docker.xuanyuan.me"] }同时可以进入轩…...
10-Oracle 23 ai Vector Search 概述和参数
一、Oracle AI Vector Search 概述 企业和个人都在尝试各种AI,使用客户端或是内部自己搭建集成大模型的终端,加速与大型语言模型(LLM)的结合,同时使用检索增强生成(Retrieval Augmented Generation &#…...
CSS设置元素的宽度根据其内容自动调整
width: fit-content 是 CSS 中的一个属性值,用于设置元素的宽度根据其内容自动调整,确保宽度刚好容纳内容而不会超出。 效果对比 默认情况(width: auto): 块级元素(如 <div>)会占满父容器…...
面向无人机海岸带生态系统监测的语义分割基准数据集
描述:海岸带生态系统的监测是维护生态平衡和可持续发展的重要任务。语义分割技术在遥感影像中的应用为海岸带生态系统的精准监测提供了有效手段。然而,目前该领域仍面临一个挑战,即缺乏公开的专门面向海岸带生态系统的语义分割基准数据集。受…...
GitHub 趋势日报 (2025年06月06日)
📊 由 TrendForge 系统生成 | 🌐 https://trendforge.devlive.org/ 🌐 本日报中的项目描述已自动翻译为中文 📈 今日获星趋势图 今日获星趋势图 590 cognee 551 onlook 399 project-based-learning 348 build-your-own-x 320 ne…...
解读《网络安全法》最新修订,把握网络安全新趋势
《网络安全法》自2017年施行以来,在维护网络空间安全方面发挥了重要作用。但随着网络环境的日益复杂,网络攻击、数据泄露等事件频发,现行法律已难以完全适应新的风险挑战。 2025年3月28日,国家网信办会同相关部门起草了《网络安全…...
无需布线的革命:电力载波技术赋能楼宇自控系统-亚川科技
无需布线的革命:电力载波技术赋能楼宇自控系统 在楼宇自动化领域,传统控制系统依赖复杂的专用通信线路,不仅施工成本高昂,后期维护和扩展也极为不便。电力载波技术(PLC)的突破性应用,彻底改变了…...
ABAP设计模式之---“Tell, Don’t Ask原则”
“Tell, Don’t Ask”是一种重要的面向对象编程设计原则,它强调的是对象之间如何有效地交流和协作。 1. 什么是 Tell, Don’t Ask 原则? 这个原则的核心思想是: “告诉一个对象该做什么,而不是询问一个对象的状态再对它作出决策。…...
