機械にくすぐられたい
どうもこんにちは。
久しぶりの色物記事です。
やはりぱわぱわとは気が合いますね。
もはや非対称性性市場は苛烈を極めているので(中略)
unmannedな性サービス、いやもっと広げて“欲求・心を満たすコト”は発展すると思うんですよ。
壁打ちを極めるというか。
生物系の人、アミューズメント用触手の開発してくださいよ。
さてはて、工学を学ぶ端くれとして想像の補完が効くor実現性を考えることができるということで似たもので機械姦がありますが、やはり前から倒錯はしていたようでpixivのふぁぼ欄は酷いことになっております。
で、中学高校大学と思い立ったタイミングで電子工作をやってるんですが、
あんまり完成したものってなくて、そもそも「いずれ使うだろう」って買ったまま放置してるものもあるんですよね。
そこでおもむろに素材箱漁ってたら天啓を得ました。
ハードの作成
まずは一般の家庭にもあると思うんですが、Arduino mini上位互換のSoC:ATMEGA328pのボードを用意します。
そしてサーボといったらこれ、SG90です。片持ちのホーン無くしちゃいました。
ということでうろ覚えですがブレッドボードに実装しました。
今回は軽負荷なのでサーボVccはボード5Vにつけてます。
3つのタクトスイッチを33kΩのプルダウンでDinにつなげて、
シリアルioはusb変換ボードとつなげて電力源・モニタリング・書き込みに使います。
肝心のくすぐるための羽根は適当に用意しました。
ソフトの作成
VScodeでいままでやったのはNode.js関連(ts、jsx、html)くらいなので、
C++のスーパーセットであるArduinoは慣れてません。
昔ながらのArduino IED IDEも使えるのですが、どうせならgitつかいたいし環境統一したいのでVScodeのpathにArduino CLIを指定します。
C++のコンパイラもたくさんあるんですね。gccしか知りませんでした。
Cのコンパイラのconfigをarduino関連の拡張機能が書き換え続けるせいで面倒なことになってましたが、なんやかんやで解決しました。(いろんなことあってもう覚えてない)
IDEだとデフォで入ってるservoライブラリもcliの素だと入ってないのでコマンド叩かないといけないんですね。AIに聞いたら一発でした。すごい。
ということで色々あって()書いたのですが、めんどくさいしコード全部垂れ流しちゃいますね。
コード
#include <string.h>
#include <Servo.h>
const int DREAD1=2, DREAD2=3, DREAD3=4;//入力用ボタン端子(プルダウン)
const int sv_pin1=6; //サーボピン
const int RAND_ANALOG_PIN=0; //乱数シード用オープン端子
const char* modes[9]={"do","min_ang","max_ang","min_uni","max_uni","min_tim","max_tim","exp_mal","std_mal"};//モード名称
int mode=0;//モード番号
int featuredNum=0;//モード別メイン数値
int ang=90; //サーボ角度
float expM=0,stdM=0;
int angMin=60,angMax=120,angUnitMin=5,angUnitMax=40,delayMin=1,delayMax=10;//各種乱数オプション
int pushed1=0, pushed2=0, pushed3=0; //ボタン長押し判定
int timeCount=0;//次移動までの秒数カウント
int nextDelay=10;//次移動までの秒数
int timeMin=400,timeMax=5000;//次移動までの秒数 最大・最小
bool anyPushed=false;//ボタン押下?→シリアル出力
Servo sv1;
void setup() {
Serial.begin( 9600 );
pinMode(DREAD1,INPUT);//ピンinput
pinMode(DREAD2,INPUT);
pinMode(DREAD3,INPUT);
sv1.attach(sv_pin1,500,2400);//サーボ定義
}
void loop() {
randomSeed(analogRead(RAND_ANALOG_PIN));//ランダムシード
ang=min(ang, 180);
ang=max(ang, 0);
sv1.write(ang);
if(digitalRead(DREAD1)==HIGH){pushed1++;}else{pushed1=0;}
if(digitalRead(DREAD2)==HIGH){pushed2++;}else{pushed2=0;}
if(digitalRead(DREAD3)==HIGH){pushed3++;}else{pushed3=0;}
if(hasShortPushed(pushed1)){mode>=8?mode=0:mode++;anyPushed=true;}
if(hasLongPushed(pushed1)){mode==1?mode=8:(mode<=0?mode=7:mode-=2);anyPushed=true;}
//const char* modes[9]={"do","min_ang","max_ang","min_uni","max_uni","min_tim","max_tim","exp_mal","std_mal"};//モード名称
if(anyPushed==true){
char str[64];
sprintf(str,"%s %d",modes[mode],featuredNum);
Serial.println(str);
anyPushed=false;
}
switch (mode)
{
case 0: //do
if(timeCount>nextDelay){
timeCount=0;
//アンダーフロー対策 ここだけang+100
//if(ang+angUnitMax>angMax){ang-=angUnitMax;}
//if(ang-angUnitMin<angMin){ang+=angUnitMax;}
ang+=100;
int oldAng=ang;
ang=ang+(angUnitMin+(float(random(0,__INT16_MAX__))/__INT16_MAX__)*(angUnitMax-angUnitMin))-((angUnitMax+angUnitMin)/2);
while (ang-100>angMax||ang-100<angMin||abs(ang-oldAng)<angUnitMin)//上下範囲外、最小移動以下だとやり直し
{
ang=ang+(angUnitMin+(float(random(0,__INT16_MAX__))/__INT16_MAX__)*(angUnitMax-angUnitMin))-((angUnitMax+angUnitMin)/2);
}
ang=min(ang, angMax+100);
ang=max(ang, angMin+100);
ang-=100;
nextDelay=makeRandomUES(expM,stdM,timeMin,timeMax);
char str[12];sprintf(str,"next:%d(ms) angle:%d(deg)",nextDelay,ang);Serial.println(str);//動作中、時間と角度表示
}
featuredNum=-1;
break;
case 1: //min_ang
if(hasShortPushed(pushed2)){angMin+=1;anyPushed=true;}
if(hasLongPushed(pushed2)){angMin+=9;anyPushed=true;}
if(hasShortPushed(pushed3)){angMin-=1;anyPushed=true;}
if(hasLongPushed(pushed3)){angMin-=9;anyPushed=true;}
angMin=min(angMin, 180); angMin=max(0,angMin);
ang=angMin;
featuredNum=angMin;
break;
case 2: //max_ang
if(hasShortPushed(pushed2)){angMax+=1;anyPushed=true;}
if(hasLongPushed(pushed2)){angMax+=9;anyPushed=true;}
if(hasShortPushed(pushed3)){angMax-=1;anyPushed=true;}
if(hasLongPushed(pushed3)){angMax-=9;anyPushed=true;}
angMax=min(angMax, 180); angMax=max(angMax, 0);
ang=angMax;
featuredNum=angMax;
break;
case 3: //min_uni
if(hasShortPushed(pushed2)) { angUnitMin += 1; anyPushed=true; }
if(hasLongPushed(pushed2)) { angUnitMin += 9; anyPushed=true; }
if(hasShortPushed(pushed3)) { angUnitMin -= 1; anyPushed=true; }
if(hasLongPushed(pushed3)) { angUnitMin -= 9; anyPushed=true; }
angUnitMin=min(angUnitMin, 180);
angUnitMin=max(angUnitMin, 0);
if(timeCount%1000==0){ang = angUnitMin;}
if(timeCount%1000==500){ang = 0;}
featuredNum=angUnitMin;
break;
case 4: //max_uni
if(hasShortPushed(pushed2)) { angUnitMax += 1; anyPushed=true; }
if(hasLongPushed(pushed2)) { angUnitMax += 9; anyPushed=true; }
if(hasShortPushed(pushed3)) { angUnitMax -= 1; anyPushed=true; }
if(hasLongPushed(pushed3)) { angUnitMax -= 9; anyPushed=true; }
angUnitMax=min(angUnitMax, 180);
angUnitMax=max(angUnitMax, 0);
if(timeCount%1000==0){ang = 0;}
if(timeCount%1000==500){ang = angUnitMax;}
featuredNum=angUnitMax;
break;
case 5: //min_time
if(hasShortPushed(pushed2)) { timeMin += 100; anyPushed=true; }
if(hasLongPushed(pushed2)) { timeMin += 900; anyPushed=true; }
if(hasShortPushed(pushed3)) { timeMin -= 100; anyPushed=true; }
if(hasLongPushed(pushed3)) { timeMin -= 900; anyPushed=true; }
timeMin=min(timeMin, 3000);
timeMin=max(timeMin, 0);
featuredNum=timeMin;
break;
case 6: //max_time
if(hasShortPushed(pushed2)) { timeMax += 100; anyPushed=true; }
if(hasLongPushed(pushed2)) { timeMax += 900; anyPushed=true; }
if(hasShortPushed(pushed3)) { timeMax -= 100; anyPushed=true; }
if(hasLongPushed(pushed3)) { timeMax -= 900; anyPushed=true; }
timeMax=min(timeMax, 30000);
timeMax=max(timeMax, 500);
featuredNum=timeMax;
break;
case 7: //exp_mal
if(hasShortPushed(pushed2)) { expM += 0.10;anyPushed=true; }
if(hasLongPushed(pushed2)) { expM += 0.20; anyPushed=true;}
if(hasShortPushed(pushed3)) { expM -= 0.10;anyPushed=true; }
if(hasLongPushed(pushed3)) { expM -= 0.20; anyPushed=true;}
expM=min(expM, 1.0);
expM=max(expM, 0.0);
featuredNum=expM*100;
break;
case 8: //std_mal
if(hasShortPushed(pushed2)) { stdM += 0.10; anyPushed=true; }
if(hasLongPushed(pushed2)) { stdM += 0.20; anyPushed=true; }
if(hasShortPushed(pushed3)) { stdM -= 0.10; anyPushed=true; }
if(hasLongPushed(pushed3)) { stdM -= 0.20;anyPushed=true; }
stdM=min(stdM, 1.0);
stdM=max(stdM, 0.0);
featuredNum=stdM*100;
break;
default:
break;
}
delay(1);
timeCount++;
}
int readFromSerial_int(){
if (Serial.available() > 0) {
delay(1);
int val = Serial.parseInt(); //文字列データを数値に変換
while (Serial.available() > 0) {//受信バッファクリア
char t = Serial.read();
}
return val;
}
}
bool hasLongPushed(int pushed){//長押し判定
return pushed==300?true:false;
}
bool hasShortPushed(int pushed){//長押し判定
return pushed==10?true:false;
}
char readFromSerial_char(){
char reading_str;
if(Serial.available() > 0) {
reading_str = Serial.read();
if(10 == reading_str) {
reading_str = '¥n';
}
}
return reading_str;
}
/** 間隔乱数生成
* @param expM 指数分布項係数 0-1
* @param stdM 正規分布項係数 0-1
* 1-expM-stdM = 一様分布項係数*/
float makeRandomUES(float expM,float stdM,float t_min,float t_max){
stdM=min(stdM,1);stdM=max(stdM,0);//例外処理
expM=min(expM,1);expM=max(expM,0);
if(expM+stdM>1){stdM=0.5;expM=0.5;}
const float uni=float(random(0,__INT16_MAX__))/__INT16_MAX__; //一様乱数
float exp=-0.2*log(float(random(0,__INT16_MAX__))/__INT16_MAX__) ; //指数乱数
exp=min(exp, 1);exp=max(exp, 0); //上限下限
float std=0; //正規分布
for (size_t i = 0; i < 10; i++) //大数の法則
{std+=float(random(0,__INT16_MAX__))/__INT16_MAX__;}
std=std/10;
std=min(std,1);std=max(std,0); //上限下限
return t_min+(t_max-t_min)* //多項式
(uni*(1.0-expM-stdM)
+exp*expM
+std*stdM);
}
一様乱数Uは組み込み関数より、
正規分布は中心極限定理よりU 10回の平均から、
ポアソン過程の到着時間である指数分布は逆関数法(-1/λ)ln(U)で生成。
ただし上限下限時間指定があるので無限大時間に対して再試行。
これらを係数でブレンドすることにより「いい感じの過程」を生成します。
高級な環境だと組み込まれてるようなボタン長押し判定とか自分で作るの楽しかったです。
はい。なんかいつの間にかアカデミックに熱中してました。
性欲どこ行ったし。
実際動かす
ほとんど実機を動かさずにここまでプログラム組んでしまいました。
果たしてしっかり動くんでしょうか。
OTGケーブルでスマホにつなげてみましょう。
しっかり機敏に動いてますね。デフォルトは一様乱数なのでそれも確認できます。
角度指定もできてます。
“使った”
はい。なんか拍子抜けです。
既知の性感帯である手のひらと脇でやってみたんですが、
機械的な刺激はあるんですが、ランダム性の「不意打ち」感はないんですよね。
もっと熱意があれば開発続けてたんでしょうけど、もう興味は別のことにいっちゃってますね。
え、"開発"する場所違いだろって?
おやすみ!!
この記事が気に入ったらサポートをしてみませんか?