2024-11-22 00:00:00
安装gRPC库
pip install grpcio
安装gRPC工具
pip install grpcio-tools
下载官方例程
git clone -b v1.66.0 --depth 1 --shallow-submodules https://github.com/grpc/grpc
演示用例在这里
grpc/examples/python/helloworld
先启动服务端
python greeter_server.py
可以看到已经在监听了
再启动客户端
python greeter_client.py
正常连接到了服务端
服务端
from concurrent import futures
import logging
import grpc
import helloworld_pb2
import helloworld_pb2_grpc
# 继承自helloworld_pb2_grpc.GreeterServicer,重写了sayhello的函数
class Greeter(helloworld_pb2_grpc.GreeterServicer):
def SayHello(self, request, context):
# 对应返回 hello 和访问者的名字
return helloworld_pb2.HelloReply(message="Hello, %s!" % request.name)
def serve():
# 启动还是比较简单的,设置好端口
port = "50051"
# 调用helloworld_pb2_grpc就完成了
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
helloworld_pb2_grpc.add_GreeterServicer_to_server(Greeter(), server)
server.add_insecure_port("[::]:" + port)
server.start()
print("Server started, listening on " + port)
server.wait_for_termination()
if __name__ == "__main__":
logging.basicConfig()
serve()
客户端
from __future__ import print_function
import logging
import grpc
import helloworld_pb2
import helloworld_pb2_grpc
def run():
# NOTE(gRPC Python Team): .close() is possible on a channel and should be
# used in circumstances in which the with statement does not fit the needs
# of the code.
print("Will try to greet world ...")
# 设置本地 端口
with grpc.insecure_channel("localhost:50051") as channel:
stub = helloworld_pb2_grpc.GreeterStub(channel)
# 发送信息 并等待结果
response = stub.SayHello(helloworld_pb2.HelloRequest(name="you"))
print("Greeter client received: " + response.message)
if __name__ == "__main__":
logging.basicConfig()
run()
helloworld_pb2_grpc.py
# 继承的原型函数在这里
class GreeterServicer(object):
"""The greeting service definition.
"""
def SayHello(self, request, context):
"""Sends a greeting
"""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
def SayHelloStreamReply(self, request, context):
"""Missing associated documentation comment in .proto file."""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
def SayHelloBidiStream(self, request_iterator, context):
"""Missing associated documentation comment in .proto file."""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
# 主要是这个函数,把函数的返回绑定到一起
def add_GreeterServicer_to_server(servicer, server):
rpc_method_handlers = {
'SayHello': grpc.unary_unary_rpc_method_handler(
servicer.SayHello,
request_deserializer=helloworld__pb2.HelloRequest.FromString,
response_serializer=helloworld__pb2.HelloReply.SerializeToString,
),
'SayHelloStreamReply': grpc.unary_stream_rpc_method_handler(
servicer.SayHelloStreamReply,
request_deserializer=helloworld__pb2.HelloRequest.FromString,
response_serializer=helloworld__pb2.HelloReply.SerializeToString,
),
'SayHelloBidiStream': grpc.stream_stream_rpc_method_handler(
servicer.SayHelloBidiStream,
request_deserializer=helloworld__pb2.HelloRequest.FromString,
response_serializer=helloworld__pb2.HelloReply.SerializeToString,
),
}
# 创建服务名称和通用句柄
generic_handler = grpc.method_handlers_generic_handler(
'helloworld.Greeter', rpc_method_handlers)
# server添加通用的句柄
server.add_generic_rpc_handlers((generic_handler,))
# 将处理方法注册给server
server.add_registered_method_handlers('helloworld.Greeter', rpc_method_handlers)
helloworld_pb2.py
"""Generated protocol buffer code."""
from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import runtime_version as _runtime_version
from google.protobuf import symbol_database as _symbol_database
from google.protobuf.internal import builder as _builder
_runtime_version.ValidateProtobufRuntimeVersion(
_runtime_version.Domain.PUBLIC,
5,
27,
2,
'',
'helloworld.proto'
)
# @@protoc_insertion_point(imports)
_sym_db = _symbol_database.Default()
# 这里直接用代码的形式写了一个helloworld的描述符
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x10helloworld.proto\x12\nhelloworld\"\x1c\n\x0cHelloRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\"\x1d\n\nHelloReply\x12\x0f\n\x07message\x18\x01 \x01(\t2\xe4\x01\n\x07Greeter\x12>\n\x08SayHello\x12\x18.helloworld.HelloRequest\x1a\x16.helloworld.HelloReply\"\x00\x12K\n\x13SayHelloStreamReply\x12\x18.helloworld.HelloRequest\x1a\x16.helloworld.HelloReply\"\x00\x30\x01\x12L\n\x12SayHelloBidiStream\x12\x18.helloworld.HelloRequest\x1a\x16.helloworld.HelloReply\"\x00(\x01\x30\x01\x42\x36\n\x1bio.grpc.examples.helloworldB\x0fHelloWorldProtoP\x01\xa2\x02\x03HLWb\x06proto3')
_globals = globals()
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'helloworld_pb2', _globals)
if not _descriptor._USE_C_DESCRIPTORS:
_globals['DESCRIPTOR']._loaded_options = None
_globals['DESCRIPTOR']._serialized_options = b'\n\033io.grpc.examples.helloworldB\017HelloWorldProtoP\001\242\002\003HLW'
_globals['_HELLOREQUEST']._serialized_start=32
_globals['_HELLOREQUEST']._serialized_end=60
_globals['_HELLOREPLY']._serialized_start=62
_globals['_HELLOREPLY']._serialized_end=91
_globals['_GREETER']._serialized_start=94
_globals['_GREETER']._serialized_end=322
# @@protoc_insertion_point(module_scope)
实际这里使用的proto文件,是如下定义的
// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
// The request message containing the user's name.
message HelloRequest {
string name = 1;
}
// The response message containing the greetings
message HelloReply {
string message = 1;
}
实际使用的proto文件是在examples/protos/helloworld.proto
中的,这里添加一个新的函数
syntax = "proto3";
option java_multiple_files = true;
option java_package = "io.grpc.examples.helloworld";
option java_outer_classname = "HelloWorldProto";
option objc_class_prefix = "HLW";
package helloworld;
// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {}
rpc SayHello2 (HelloRequest) returns (HelloReply) {}
rpc SayHelloStreamReply (HelloRequest) returns (stream HelloReply) {}
rpc SayHelloBidiStream (stream HelloRequest) returns (stream HelloReply) {}
}
// The request message containing the user's name.
message HelloRequest {
string name = 1;
}
// The response message containing the greetings
message HelloReply {
string message = 1;
}
需要重新生成对应的代码
python -m grpc_tools.protoc -I../../protos --python_out=. --pyi_out=. --grpc_python_out=. ../../protos/helloworld.proto
这里就会重新生成了
def add_GreeterServicer_to_server(servicer, server):
rpc_method_handlers = {
'SayHello': grpc.unary_unary_rpc_method_handler(
servicer.SayHello,
request_deserializer=helloworld__pb2.HelloRequest.FromString,
response_serializer=helloworld__pb2.HelloReply.SerializeToString,
),
'SayHello2': grpc.unary_unary_rpc_method_handler(
servicer.SayHello2,
request_deserializer=helloworld__pb2.HelloRequest.FromString,
response_serializer=helloworld__pb2.HelloReply.SerializeToString,
),
server和client都增加hello2以后,再次运行就能看到已经给过来正确的反应了
到这里最简单的gRPC就完成了
核心就三步:
上面演示的例子都是C/S架构的,也是gRPC常用的模式,一方请求,一方应答,这是普通的RPC。服务方是不能主动发起请求的,只有客户方主动。还有其他3种方式。
还是之前的例子中,有对应的流式实现
NUMBER_OF_REPLY = 10
class Greeter(MultiGreeterServicer):
async def sayHello(
self, request: HelloRequest, context: grpc.aio.ServicerContext
) -> HelloReply:
logging.info("Serving sayHello request %s", request)
for i in range(NUMBER_OF_REPLY):
yield HelloReply(message=f"Hello number {i}, {request.name}!")
对于服务端的响应,这里可以看到返回了10此请求,并且这个函数是异步的
async def run() -> None:
async with grpc.aio.insecure_channel("localhost:50051") as channel:
stub = hellostreamingworld_pb2_grpc.MultiGreeterStub(channel)
# Read from an async generator
async for response in stub.sayHello(
hellostreamingworld_pb2.HelloRequest(name="you")
):
print(
"Greeter client received from async generator: "
+ response.message
)
# Direct read from the stub
hello_stream = stub.sayHello(
hellostreamingworld_pb2.HelloRequest(name="you")
)
while True:
response = await hello_stream.read()
if response == grpc.aio.EOF:
break
print(
"Greeter client received from direct read: " + response.message
)
客户端这边,前面是异步流式获取,后面是正常流式获取获取
可能看例子,这里流式传输的意义不是很明显,除了能多次发送请求或者多次响应,还有啥用。
grpc的双向流式可以类比成WebSocket,客户端和服务器都可以互相发送信息
// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {}
rpc SayHello2 (HelloRequest) returns (HelloReply) {}
rpc SayHelloStreamReply (HelloRequest) returns (stream HelloReply) {}
rpc SayHelloBidiStream (stream HelloRequest) returns (stream HelloReply) {}
}
在流式传输的例子中proto文件的定义使用了一个特殊关键词,stream
凡是被stream修饰的参数,那么传输时就会采用流式。
如果修饰到返回值,那就是服务器流式,如果修饰参数,那就是客户端流式,如果同时有那就是双向流式传输
总体来说gRPC就是这样,流式上感觉似乎没有WebSocket简单,特别是如果是用来做双向交互的时候WebSocket似乎更简单,更好做一些
https://grpc.io/docs/languages/python/quickstart/
https://blog.yuanpei.me/posts/grpc-streaming-transmission-minimalist-guide/
https://hamhire.tech/posts/coding/grpc-03.stream-demo.html
2024-11-15 00:00:00
给MCU移植Crypto、SSL、SSH、SFTP等库,真的找不到一个例子,目前看到的库大部分都是商用的。
比如wolfssh、CycloneSSH、libssh2、TinySSH、microSSH、Dropbear,这些库可能linux使用比较多,但是那边安装移植也方便多了,降到MCU一库难求,更别说详细的移植文档了,基本没有
https://www.wolfssl.com/products/wolfssh/
wolfssh的库整个集成到了CubeMX中,简单的几个操作就可以把ssh集成进去
系统时钟不要用systick,留给FreeRTOS用
随便加一个input IO作为SD卡的输入检测
再随便加一个串口作为SSH的端口
以太网选择RMII模式,地址改到0x24开头
SD卡改到4线模式
FATFS选择SD卡
打开RNG,SSL需要随机数生成
LWIP开启,Driver_PHY选择LAN8742,后面也是
接着wolfSSL和wolfSSH都需要下载,开启
SSH开启以后,IO选择LWIP,SFTP打开,SCP可以关闭
SSL这里需要支持FreeRTOS
关闭wolfCrypt test,这个编译会带进来很多多语言的内容,keil编译会出错
剩下设置就随便改改,就可以生成代码了,生成以后依然编译不了,还需要修改一些点
首先在wolfSSL.I-CUBE-wolfSSL_conf.h
里需要定义板子,如果不是wolf测试的板子,需要在下面这里定义具体用的是哪种类型的,具体硬件库啊、加密方式啊、库的版本、使用的串口是哪个需要明确一下
//#warning Please define a hardware platform!
/* This means there is not a pre-defined platform for your board/CPU */
/* You need to define a CPU type, HW crypto and debug UART */
/* CPU Type: WOLFSSL_STM32F1, WOLFSSL_STM32F2, WOLFSSL_STM32F4,
WOLFSSL_STM32F7, WOLFSSL_STM32H7, WOLFSSL_STM32L4, WOLFSSL_STM32L5,
WOLFSSL_STM32G0, WOLFSSL_STM32WB and WOLFSSL_STM32U5 */
#define WOLFSSL_STM32H7
/* Debug UART used for printf */
/* The UART interface number varies for each board/CPU */
/* Typically this is the UART attached to the ST-Link USB CDC UART port */
#define HAL_CONSOLE_UART huart1
/* Hardware Crypto - uncomment as available on hardware */
//#define WOLFSSL_STM32_PKA
//#define NO_STM32_RNG
//#undef NO_STM32_HASH
//#undef NO_STM32_CRYPTO
//#define WOLFSSL_GENSEED_FORTEST /* if no HW RNG is available use test seed */
#define STM32_HAL_V2
移除所有test相关代码,这个部分用gcc编译更容易过一些
..\include\stdio.h(372): error: #2746: argument for attribute "nonnull" is larger than number of parameters
const char * __restrict /*format*/, ...) __attribute__((__nonnull__(1,2)));
这个错暂时解不掉,得用CubeIDE编译才行,Keil这里过不去
https://www.oryx-embedded.com/products/CycloneSSH.html
CycloneSSH是oryx-embedded下的一个库,他们包含很多相关组件
https://www.st.com.cn/zh/partner-products-and-services/cyclonessh.html
ST官方也有他们的合作页面,但是CubeMX里不支持生成
虽然Github也开源了,放出来了源码,但是那个源码一个demo都没有,任何文档也没有,接口也没说,只能干看着。
要查看Demo需要下载官方的代码,然后在demo里面有各个公司各个板子的demo工厂,这里以stm32h743i_eval的板子为例,测试了一下SFTP的demo
先修改代码同意协议,否则无法生成
/*
* CycloneTCP Open is licensed under GPL version 2. In particular:
*
* - If you link your program to CycloneTCP Open, the result is a derivative
* work that can only be distributed under the same GPL license terms.
*
* - If additions or changes to CycloneTCP Open are made, the result is a
* derivative work that can only be distributed under the same license terms.
*
* - The GPL license requires that you make the source code available to
* whoever you make the binary available to.
*
* - If you sell or distribute a hardware product that runs CycloneTCP Open,
* the GPL license requires you to provide public and full access to all
* source code on a nondiscriminatory basis.
*
* If you fully understand and accept the terms of the GPL license, then edit
* the os_port_config.h header and add the following directive:
*
* #define GPL_LICENSE_TERMS_ACCEPTED
*/
#ifndef GPL_LICENSE_TERMS_ACCEPTED
#error Before compiling CycloneTCP Open, you must accept the terms of the GPL license
#endif
如果不定义GPL_LICENSE_TERMS_ACCEPTED
会导致这里报错,编译不下去
CycloneSSH的问题在于虽然他用demo可以直接编译,但是他底层调用的是他自己的TCPIP接口,也就是CycloneTCP,而不是Lwip,这就导致如果你要移植,必须还得搞懂TCP这里的接口具体是什么的,怎么往下对接,Lwip的底层你也得熟悉才能完成这个事情
带了安全的协议真的麻烦
https://www.stmcu.org.cn/module/forum/forum.php?mod=viewthread&tid=616445
2024-11-15 00:00:00
cws一直有问题,而且只支持access token,经常要换很麻烦。之前有关注到dairoot的mirror,这次刚好试一下,发现体验还行
https://github.com/dairoot/ChatGPT-Mirror
项目很简单
https://chatgpt.dairoot.cn/
官方体验站,也可以使用免费账号测试,基本都差不多
脚本内是docker,所以机器需要提前安好docker
git clone https://github.com/dairoot/ChatGPT-Mirror.git
cd ChatGPT-Mirror/
# 修改管理后台账号密码
cp .env.example .env && vi .env
chmod +x ./deploy.sh
# 可能的话需要改一下 把启动脚本的docker compose改成docker-compose
vi ./deploy.sh
# 启动
./deploy.sh
caddy 反代一下
gpt.你的域名.com {
reverse_proxy 127.0.0.1:50002
}
http://你的ip:50002
可以正常访问了
管理员账号先进去添加使用者账号和ChatGPT账号
账号支持使用session token,可以维持30天,还是比较好的
可以把一些账号绑定给某一个号池,这样方便管理
访问起来和GPT差不多,还可以
团队内部使用足够了,后续把套餐再升级到team那就更方便了
类似的镜像站还有始皇的,之前看好像也可以通过权限分享
new.oaifree.com
https://dairoot.cn/2024/07/02/install-chatgpt-mirror/
2024-11-12 00:00:00
GitLab联动Jenkins完成CI\CD流程
Jenkins自动升级,完成以后Jenkins就无法启动了,查log可以看到提示说明java版本太老了
Jenkins升级后不再支持java11的,需要安装java17或者21,我这里直接选择21
进入Jenkins目录下,修改jenkins.xml文件,将其中的jdk修改为新安装的jdk,然后重启一点电脑,重启jenkins服务即可
首先Jenkins需要安装GitLab插件,否则收不到GitLab的请求
然后在Credentials中添加GitLab的账号
用户名和密码的形式即可
还有一种方式创建GitLab API token,这种是有使用年限的
系统设置中的GitLab,然后添加Credentials,选择GitLab API token
这个token可以从GitLab用户设置中创建
然后把token复制到jenkins那边,通过右侧的Test Connection
就能测试出来token是否可以使用
正常的话就显示Success
这种方式创建的token,在配置CI的时候,可以不选择credentials,Jenkins会自动帮你匹配对应的GitLab库的token
创建一个空配置,前面的GitLab相关选项都不选择,直接选Git 仓库中填入对应地址,选择刚才创建的GitLab账号
分支选好
选择当推送时进行build,记录下旁边写的webhook的url,有些时候端口可以不要(如果端口是对外使用的),展开下面的高级选项
点击生成token,记录下这个token值,剩下执行脚本或者命令部分就正常填写,没有大差别了,弄好以后保存
回到GitLab,由于是内网机器,所以需要开启内网连接的请求
管理员权限,进入设置-网络管理
出站请求中允许本地网络,必要的话可以把本地的域名或者是ip直接给进去,防止被拦截
回到工程中,设置-Webhooks,新建一个,URL使用刚才记录的URL,Secret令牌使用刚才的token,保存
测试,点击推送事件
可以看到正常返回200
Jenkins侧也有正确的响应
到这里整个webhook的触发方式就可以正常使用了
按道理说GitLab和Jenkins还有另外一个集成的地方,但是前面几次测试都提示缺少Token,导致并不能触发。
按理来说集成方式应该是最简单的,这里重新参考了官方的配置指南,并且重试了一遍流程以后发现可以走通
Webhook中的GitLab API token的配置操作需要提前做完,然后才能接着操作
Jenkins中新建一个配置,这次选择GitLab连接和仓库名称,千问不要写错了
下面的配置里勾上tag和merge触发,其实不勾也行。
注意高级中,一定要确保下面的secret token是空白的,如果不是使用clear清空他
最后构建选择Publish build status to GitLab,就可以把状态返回回来了
回到GitLab的仓库中,在集成-Jenkins中启用集成,并且输入Jenkins的URL,然后输入Jenkins的配置名称
接着输入Jenkins的账号和密码,保存,测试设置,就能看到正常工作了
还得是官方文档,全网大部分都是一通乱操作,最后都走到了webhooks的方式中去了,而集成是最简单的
https://blog.csdn.net/weixin_63294004/article/details/143671722
https://blog.csdn.net/weixin_43546282/article/details/129130533
https://www.cnblogs.com/ygbh/p/17483811.html#_label3_1_3_2
https://docs.GitLab.com/ee/integration/jenkins.html#grant-jenkins-access-to-the-GitLab-project
2024-11-11 00:00:00
记录一下接入米家失败的几个案例
遥控倒是挺好拆的,经过测试接入按钮是物理的,成功的概率应该很大了
接下来就翻车了,反复测试了好几次发现,直接把按钮接入地或者电源,都不能正常触发按键,被控对象有反应,但是不能正常工作。
仔细查了一下主控芯片:TLSR8366ET24
它实际可以当作GPIO的引脚其实不多,但是结合遥控器,要控的除了13个按键,其实还有5个led灯,不可能接18个GPIO去做独立按键控制的,所以他其实这里用了矩阵键盘,逆向了电路以后,发现他的按键两端IO确实都是接入了主控芯片,而不是地
实际遇到的情况:
由于使用了矩阵键盘,这里又想保留原本的键盘、又想接入米家就无法做到了,除非米家模块支持Bypass模式,在输入的时候可以进行闭合/断开操作,平常保持断开/闭合,让扫描信号可以正常工作,从而可以模拟按键,否则无论怎么设计都没办法直接接入到米家中
所以遇到这种情况就没办法接入了,不过测试了一下米家模块,用2节7号电池,活不过一周就耗尽了,这个还是得配合电源一起使用。
https://www.denvel.com
顺手查了一下遥控的生产厂家,迪富电子,专门做遥控的
别问为什么不买接入米家的香氛机,就是爱折腾(眼瞎买错了,而且还是两次)
这个按键是电容的,所以用物理的方式肯定无法直接触发,而且这个还要做防水,实际无损拆开也不容易。
经过一番搜索 ,还是看到了可以控制电容屏的触摸按钮,但是店家也不能保证可以控制这种类型的按钮,只能说买来试试,不行就88会员免费退了
设计的还是挺简单的,看起来是通过PWM引起电容变化,进而被检测的,用的耳机接头,一个小板子可以控4个,支持typec供电,也给预留了电池供电口
经过测试,果然不行。如果用手拿着电容头,确实可以触发,但是如果按键触发电容头变化,就不行。
店家也挺好的,让我直接整个给他退回去,他再研究一下,看能否处理。经过他测试,试用了他们新的高频电容头,发现不行,只好给我退回来了。
这个电容触发模块本身的价格+香氛机比直接买接入米家的香氛还贵,成品还是更香啊
垃圾马桶盖太烂了,智能太差了,既不能接入米家,也不能自动冲水,妥妥的废物。
之前看了一些智能冲水的外部产品,虽然不能进米家但是也不错
触发逻辑很简单,被遮挡6s以上,释放时会自动进行冲水,同时还有一个触摸按键可以直接触发冲水
安装需要注意排水阀直径需要在60-80mm直接,否则这个环套不上去,同时水箱盖按钮长度有36mm,否则可能盖不上。
安装完以后,放好感应器基本完美了。
理想马桶盖+冲水的智能逻辑:
小的,检测到来人自动开盖(顶盖+座圈),离开后自动关盖,离开后6s自动冲水,触发香氛
大的,检测到来人自动开盖(顶盖),冲洗,冲洗完成后,自动关盖,离开后6s自动冲水,触发香氛
现实马桶盖+冲水的智能逻辑:
小的,检测到来人自动开盖(顶盖),手动按遥控器开座圈,离开后6s自动冲水
大的,检测到来人自动开盖(顶盖),冲洗,冲洗完成后,手动按遥控关盖,离开后6s自动冲水
正常感应应该是安装在水箱上的,但是这个马桶盖很容易自动开合,那就会出现自动冲水,为了减少误触只好安装在侧面
这个不要买就行了,触发灵敏度不能调节,触发条件其实还是有点苛刻的,要求压感片被很明显的压变形才能触发,只有坐姿比较容易,其他方式,比如躺卧等比较难触发。
这个是拿电阻式薄膜压力带+门窗触发器改造来的,实际挺简单的
咸鱼上还有一种压力片的感应点更多一些,这种稍微好一些。
看了一下主要触发的方式,EL357N是一个光电耦合器模块,理论上是用来隔离输入和输出的,但是这里直接把输出给到了这个软的吸盘,而且这个吸盘竟然还导电
主控是STC的8G1K08A,简单一个单片机,主要用来输出PWM给光电模块
给的PWM脉冲大概是3Hz,占空比是25%,然后就能触发屏幕的点击了
其他的方式触发点击,本质上是一样的方案,所以没有尝试
还有纯机械的方式,直接模拟人手了,这种有点太弱智了,和拿个舵机直接控没啥区别了,不能接受
电容按键一般后面都有芯片,其实可以看对应芯片是以什么方式输出结果的,如果I2C,类似之前升降桌的芯片,那种就很难从中间介入。但是如果输出结果也是简单的0/1信号,那也可以直接从芯片侧接入,来接入智能,同时还能保留原本的按键
还是得尽量选择天生带接入的,后期改造很难做到理想
https://item.taobao.com/item.htm?id=771817542208