前段时间在群里整活,说要做一个杯子和振动器,把这些设备接入sillytavern。
但吹出来的牛,肯定要说到做到。于是早上刚把demo做了出来,代码什么的还没来得及整理。
这篇文章将介绍整个开发流程,以及设计思路。
目前是在制作振动器。
硬件设备使用esp32进行开发,sillytavern上使用js做蓝牙驱动。
这里先把单片机和js上的代码发出来。
esp32这块使用arduino的C++开发(后面版本估计要改成纯C)。
#include "NimBLEDevice.h"
#include <string.h>
#include "picojson.h"
#define SERVICE_UUID "b408e1a0-3d8a-11ed-b878-0242ac120002" //服务UUID
#define CONTROL_UUID "de045162-3d97-11ed-b878-0242ac120002" //控制特征UUID
NimBLECharacteristic controlCharacteristic(CONTROL_UUID, NIMBLE_PROPERTY::READ |NIMBLE_PROPERTY::WRITE);
//设置服务器所需要的配置
void setup() {
Serial.begin(115200);
NimBLEDevice::init("Esp32test");
NimBLEServer *pServer = NimBLEDevice::createServer();
NimBLEService *pService = pServer->createService(SERVICE_UUID); //创建设备
// 添加一个带有对象名(官方UUID)的特征,不带对象,这个特征不会改变 //显示特征名
pService->addCharacteristic(&controlCharacteristic); //增加一个控制LED的特性
controlCharacteristic.setValue("\0");
NimBLECharacteristic *pCharacteristic = pService->createCharacteristic(CONTROL_UUID);
Serial.println("what fuck!");
pService->start();
Serial.println("a?");
NimBLEAdvertising *pAdvertising = NimBLEDevice::getAdvertising();
pAdvertising->addServiceUUID(SERVICE_UUID); // advertise the UUID of our service
pAdvertising->setName("why"); // advertise the device name
pAdvertising->start();
Serial.println("Characteristic defined! Now you can read it in your phone!"); //提示消息
}
char test[100];
long int a;
void loop() {
delay(300);
strcpy(test,controlCharacteristic.getValue().c_str());
a++;
Serial.printf("haha:%s %ld %d \n",test,a,(int)test[0]);
dacWrite(25,(int)test[0]);
}
sillytavern插件的js这块的代码如下
// The main script for the extension
// 以下是一些基本扩展功能的示例
// 你可能需要从extensions.js中导入extension_settings、getContext和loadExtensionSettings
import { extension_settings, getContext, loadExtensionSettings } from "../../../extensions.js";
// 您可能需要从主脚本导入一些其他函数
import { saveSettingsDebounced } from "../../../../script.js";
// 跟踪您的扩展所在的位置,名称应与repo名称匹配
const extensionName = "SillyTavern_ConnectPeripherals-main";
const extensionFolderPath = `scripts/extensions/third-party/${extensionName}`;
const extensionSettings = extension_settings[extensionName];
const defaultSettings = {};
// 如果存在扩展设置,则加载它们,否则将它们初始化为默认值。
async function loadSettings() {
// 如果设置不存在,则创建这些设置
extension_settings[extensionName] = extension_settings[extensionName] || {};
if (Object.keys(extension_settings[extensionName]).length === 0) {
Object.assign(extension_settings[extensionName], defaultSettings);
}
// 更新UI中的设置
$("#example_setting").prop("checked", extension_settings[extensionName].example_setting).trigger("input");
}
// 在UI中更改扩展设置时调用此函数
function onExampleInput(event) {
const value = Boolean($(event.target).prop("checked"));
extension_settings[extensionName].example_setting = value;
saveSettingsDebounced();
}
// 正则读字符串标签,这里有问题,日后要改
function extractDeviceContent(str) {
const regex = /<device>([\s\S]*?)<\/device>/g;
const contents = [];
let match;
while ((match = regex.exec(str)) !== null) {
contents.push(match[1]);
}
return contents;
}
// 示例用法
let characteristic;
function wtf1(){
let device = navigator.bluetooth.requestDevice({
filters: [
{ namePrefix: 'why' }
],
optionalServices: [ "b408e1a0-3d8a-11ed-b878-0242ac120002" ]
});
return device;
}
function wtf2(device){
let server= device.gatt.connect();
return server;
}
function wtf3(server){
let service = server.getPrimaryService("b408e1a0-3d8a-11ed-b878-0242ac120002");
return service;
}
function wtf4(service){
let characteristic = service.getCharacteristic("de045162-3d97-11ed-b878-0242ac120002");
return characteristic;
}
function send_value(characteristic,value){
characteristic.writeValue(
new Uint8Array([value])
);
}
async function send_while(characteristic){
// 获取整个消息文本
const context = await getContext();
// 获取最新消息
let inputString = context.chat[context.chat.length-1].mes;
// 通过正则读要传输设备的值
let result = await extractDeviceContent(inputString);
if(result!=''){
console.log(result);
// 发送设备来设置
result=result[result.length-1];
result= await parseInt(result);
console.log(result);
await send_value(characteristic,result);
}
}
// 这个函数在按钮被点击时被调用
async function onButtonClick() {
// 在这里你想做什么就做什么
// 让我们用选中的设置创建一个弹出窗口
// 连接蓝牙
let device=await wtf1();
let server=await wtf2(device);
let service=await wtf3(server);
let characteristic=await wtf4(service);
setInterval(send_while, 1000,characteristic);
// toastr.info(
// `The checkbox is ${extension_settings[extensionName].example_setting ? "checked" : "not checked"}`,
"A popup appeared because you clicked the button!"
// );
}
// 此函数在加载扩展时调用
jQuery(async () => {
// 这是一个从文件加载HTML的示例
const settingsHtml = await $.get(`${extensionFolderPath}/example.html`);
// 将settingsHtml附加到extensions_settings后
// Extension_settings和extensions_settings2是Settings菜单的左列和右列
// 左边应该是处理系统功能的扩展,右边应该是与视觉/UI相关的
$("#extensions_settings").append(settingsHtml);
// 这些都是监听事件的例子
$("#my_button").on("click", onButtonClick);
$("#example_setting").on("input", onExampleInput);
$("#text_test").on("input",onExampleInput);
// 启动时加载设置(如果有的话)
loadSettings();
});
用法就是让AI发送消息带<device>1~255</device>标签,js程序会读取上下文消息中的的标签中的数值。
js程序会连接esp32的蓝牙服务,并通过蓝牙服务向esp32发送振动程度的数值。
esp32程序读取该数值,然后通过dac引脚,设置引脚的输出电压。通过一个8050三极管,驱动振动马达产生振动。
整个过程很简单,代码也没有多少行。
后续还需要重新设计和测试代码,设计电路并打印PCB(以前还真没设计过稳压芯片之类的),以及3D打印出来个外壳。
等这一切都完成之后,再详细的介绍一下整个开发流程和设计细节,帮助AI爱好者们,diy自己的AI玩具。