2026年6月27日土曜日

dot_cleanしてくれる常駐アプリを作りました

 最近MacbookNeoをつかいまくってて、

SDカードとか書き込んでて気がついた

そうだった。.ds_storeとか_ファイル名とか、Macを使ってると隠しファイルができてしまうのだ。


しかし!

それらを消すコマンドがある!


ターミナルを開いて、以下のコマンドを入れる

dot_clean -m /Volumes/NO\ NAME 

マウントされている名前はNO NAMEなんだけど、スペースが入ってるからバックスラッシュ・スペースでつながっている

これを毎回やるのは嫌なので、

取り出しをするときにフックして、dot_cleanを実行するアプリを作ってみた。

名前はdcleanにした

xcodeを立ち上げて、アカウント入れたりなんだりしてから、contentViewやItemを消して、dcleanAppだけ残して以下のように書き換え

import SwiftUI

import AppKit

import Combine // 🌟 これを追加!


// MARK: - 1. ログのデータ構造と管理クラス


// 重複したログでも区別できるように Identifiable にする

struct LogItem: Identifiable {

    let id = UUID()

    let text: String

}


// ログを管理し、UI(メニューバー)を更新する専用のクラス

class LogStore: ObservableObject {

    static let shared = LogStore()

    

    @Published var logs: [LogItem] = []

    

    // UIの更新は必ずメインスレッドで行う

    func addLog(_ message: String) {

        DispatchQueue.main.async {

            let formatter = DateFormatter()

            formatter.dateFormat = "HH:mm:ss" // 実行時間も表示

            let timeString = formatter.string(from: Date())

            

            let newLog = LogItem(text: "[\(timeString)] \(message)")

            

            // 最新のログが一番上に来るように追加

            self.logs.insert(newLog, at: 0)

            

            // メニューが長くなりすぎないよう最新10件に制限

            if self.logs.count > 10 {

                self.logs.removeLast()

            }

        }

    }

    

    func clear() {

        DispatchQueue.main.async {

            self.logs.removeAll()

        }

    }

}


// MARK: - 2. メインのApp定義


@main

struct dcleanApp: App {

    // LogStoreの変更を監視する

    @StateObject private var logStore = LogStore.shared

    

    init() {

        // アプリ起動時にディスク監視を開始

        DiskObserver.shared.start()

    }

    

    var body: some Scene {

        MenuBarExtra("dclean", systemImage: "wand.and.stars") {

            Text("外付けドライブの取り外しを監視中...")

                .font(.caption)

                .disabled(true)

            

            // ログが存在する場合のみ履歴メニューを表示

            if !logStore.logs.isEmpty {

                Divider()

                Text("最近の動作履歴")

                    .font(.caption)

                    .disabled(true)

                

                ForEach(logStore.logs) { log in

                    Text(log.text)

                        .font(.system(size: 11)) // ログは少し小さめの文字で表示

                }

                

                Divider()

                Button("履歴をクリア") {

                    logStore.clear()

                }

            }

            

            Divider()

            Button("アプリを終了") {

                NSApplication.shared.terminate(nil)

            }

        }

        .menuBarExtraStyle(.menu)

    }

}


// MARK: - 3. ディスク監視クラス


class DiskObserver: NSObject {

    static let shared = DiskObserver()

    

    private override init() {

        super.init()

    }

    

    func start() {

        NSWorkspace.shared.notificationCenter.addObserver(

            self,

            selector: #selector(handleVolumeWillUnmount(_:)),

            name: NSWorkspace.willUnmountNotification,

            object: nil

        )

    }

    

    @objc func handleVolumeWillUnmount(_ notification: Notification) {

        guard let volumeURL = notification.userInfo?[NSWorkspace.volumeURLUserInfoKey] as? URL else { return }

        let path = volumeURL.path

        

        guard path.hasPrefix("/Volumes/") else { return }

        

        // "/Volumes/USB_Drive" から "USB_Drive" の部分だけを抽出して見やすくする

        let driveName = volumeURL.lastPathComponent

        

        print("取り外しを検知: \(path) - dot_cleanを開始します。")

        LogStore.shared.addLog("⏳ \(driveName) をクリーン中...")

        

        runDotClean(for: path, driveName: driveName)

    }

    

    func runDotClean(for path: String, driveName: String) {

        let task = Process()

        task.launchPath = "/usr/sbin/dot_clean"

        task.arguments = ["-m", path]

        

        do {

            try task.run()

            task.waitUntilExit()

            

            print("dot_clean完了: \(path) - 安全に取り外します。")

            LogStore.shared.addLog("✅ \(driveName) のクリーン完了")

        } catch {

            print("dot_clean実行エラー: \(error.localizedDescription)")

            LogStore.shared.addLog("❌ \(driveName) でエラー発生。\(error.localizedDescription)")

        }

    }

}

三角を押して動作確認したら、ビルド対象をAny Mac(arm64, x86_64)にして、メニューのプロダクトからアーカイブを選んでディストリビュートし、CopyAppを選んで適当なところに配置、XCODEでを終わらせて、できたAppをダブルクリックしたら動くはず

これで、いちいち、dot_cleanする必要はなくなった

欲しい方はこちらからどうぞ

https://drive.google.com/file/d/1SG18pzazkZzb-8_BR4v4Jonl2tqZUpFc/view?usp=drive_link

起動するとこのようなアイコンが出ます。

終了させるときは「アプリを終了」です。



実はMacだとZIPして他所様に渡すときにもこれらのゴミが入ることがあります。

ZIPするときにすでにフォルダ紛れているのです。

フォルダごとGoogleDriveに置くときにも入ってしまいます。

これについては自分で気をつけるしかなく、

そうは言っても限界がありますので、クイックアクションを作ります。

ショートカットアプリを開き、クイックアクションを選択し、右の+をクリック

以下のように構成します。

シェルスクリプトは以下のようにしました。
# フォルダが渡されているかチェック
if [ -n "$1" ] && [ -d "$1" ]; then
# 対象フォルダ内の .DS_Store, ._* ファイル, __MACOSX フォルダを一括削除
find "$1" \( -name ".DS_Store" -o -name "._*" \) -type f -delete
find "$1" -name "__MACOSX" -type d -exec rm -rf {} +
fi

これで、Finderからフォルダを選んでクイックアクションから「きれいにする」を選ぶことでゴミを消せます。


しかしいちいち、ZIPするときに気を使っていられないので、
最終兵器Kekaというアプリも導入します。
Kekaは¥1000で買ってあげるのがいいとは思いますが、
実は公式サイトでダウンロードを押すと無料でダウンロードできます。
Macのリソースフォークを含めないというチェックがデフォルトでチェックされていますので、このアプリを使って圧縮を行うと、ゴミは入らなくなります。




2026年6月21日日曜日

自動開閉ゴミ箱の回路を差し替えた



以前買ったそこそこ高い自動開閉ゴミ箱

なぜ最近、閉まらなくなってしまって

開く時と閉まる時でトルクが全然違う

ドライバーがいかれたのだろうか?

直すのも面倒なので回路ごと差し替えることにしました。




使ったのはPIC16F1825とDRV8835、GP2Y0E03、他に部品は、LEDとプルアップ抵抗4.7k2本、あとはユニバーサル基盤

開発環境はMacbookNeoで、MPLab X IDE 6.3 と snap、あとは、リボンケーブルやデュポンコネクタ、ジュンフロン線、半田、コテくらいですかね

開く時と閉まる時の時間設定をすると、動き始めと動き終わりがゆっくりになるように調整しています。
開いている時間は、家族から45秒くらい欲しいと言われたので、その設定にしました。
一応スイッチを付ければ、距離センサーで反応する高さを設定することもできるようにしてあるのですが、今のところ25cm固定で問題なさそうなので、スイッチは付けてません。そのうちやるかも知れませんが、今のところ必要性がないです。
他に考えてるのは、snap用のコネクタが剥き出しなので、TPUで部品作って、覆うようにしようかなとかですかね



snap用のコネクタも付けました。
今回結構多くのデュポンコネクタを作りました。
2.54mmピッチで、AWG28あたりの線を使うと、
デュポン一択ですね

2026年6月11日木曜日

2026年6月10日水曜日

ノウハウの蓄積と継承問題

 農業や漁業も若者に魅力のない職業らしいが、高齢化という点では、建設業もやばい、そして実はソフトウェアもやばい


というか本気でソフトウェアはマヂやばい


建設業に似てると思う


どちらの業種もノウハウが山のようにある


それなのに大工の研修数年やった程度の人に家を建ててもらうことになるのはマヂ勘弁してほしい


プログラマもそうだろ?


半年プログラミング講義して、現場に投入よ?


今はAI使ってそれらしい答えが得られるんだけど、、、

それ、正しいわけがないので、ちゃんと読んで治せる人がいないと全然ダメ

そのうちAIが変わるかもしれないけど、今時点ではどれもこれもまだダメ

いやー本当に、プログラムなんて組まないでいい時代になってほしいよ

そうしたらスッキリやめるのになー


まぁ、多分仕事は無くならない気がする

建てた家が十年後大丈夫か見極められる人、書いたプログラムがなんでそういう設計なのかメンテコストなど見越して設計できる人、そういう立場の連中は残るだろう


そこが居なくなったら大変よ



2026年6月9日火曜日

自動開閉ゴミ箱の調子が悪い

 開くときにはトルクがあるのだけど、閉まるときに全然トルクがない

多分Hブリッジイカれた


仕方ないので自前回路で置き換える

まずは部品選定(単一電池2本で動くようにしないといけない)

PIC16F1825

モータードライバはDRV8835

赤外線距離センサーはGP2Y0E03

んで次がプログラム


あとは書き込んで配線して試すだけ・・・
ちょっと今日はできないのでまた時間のあるときにやる

2026年6月4日木曜日

フィラメントケースを再考する


https://www.onlyspoolz.com/portfolio/

こちらを見ると1kgサイズのスプールは厚みが65mm程度

だからわたしが以前採用したこちらのケースでは4つのスプールが収まります。

https://amzn.to/3ROjlrF


3100円なんですけど、業務用というものもあって、

https://amzn.to/4odPO6W

サイズもほぼ一緒でこちらは2209円


わたしは底面から15cmの中心に、

Φ20のパイプを33cm長に切って、

3Dプリンタで作った部品で固定しています。


それを作ったのが2018年の年末で、

そこからずっと使ってますが、

一度もジャムったことがありません

よくある底面で支えるタイプも試したことはあるんですが、どうもゴロゴロして安定しません

フィラメントスプールは中心に穴が空いてるんだから、そこにΦ20のパイプを通して保持する方が断然安定しますし、この容器の中にシリカゲルを仕込んだ方が扱いも簡単です。

ですが、残念なのが一点

パイプ一本なので、真ん中のスプールだけ変えたい時に全スプールをら持ち上げなくてはなりません。

まぁ、4キロくらいどうってことはないんですけど、フィラメントがPTFEパイプに通っているとちょっとやりづらさはあります。

できれば、パイプを4本に切って、スプールホルダを四つ中に仕込みたい。

2ミリ幅くらいで仕込めたらいいんだけど、パイプ保持には両側から2cmくらいは欲しい

まぁそんな感じでホルダーのデザインをしてますがなかなかうまくいかないですね

薄さと強度の両立ってなかなか大変です。

多分これなら行けるかも?

幅70mm以下のスプールに対応

ホルダーの幅自体は74mmになり、4つ分を横に並べると、74 x 4 = 296mm

先程の容器の下部は小さくすぼんでいるため、

容器の最小サイズは下部となり、30 x 20 x 23くらい

30cm なら296mmでぎり入ります。


百均の園芸支柱11mmを70mm長に切って4箇所取り付け、最上部はφ20mmのパイプを同じく70mmに切って上からはめる

はめるときは真ん中を少し広げないといけないので、

都度ホルダーごと上に抜き取る必要はありますが、

フィラメント4つを引き抜くよりは楽です。

ケースもこれ以上幅広のものを用意したくは無いので、これで決まりですかね・・・

あ、いやだめだ

スプールの真ん中がφ20くらいのパイプならOKなんだけど、

それの支え部分がφ40

全然だめだ・・・

もうちょい単純に考えてこれでいい気がしてきました

スプール中心にφ20のパイプを使うのもやめます。

支柱部品も3Dプリントで作ってしまいます。


強度とか使い勝手に関しては使ってみないとわかりません

追記(2026/06/06)

やはり使ってみて改善点が出てきました
最終的にこの形になりそうです





2026年6月3日水曜日

Duet6HC+SBCでの3.6.3


3日悩んだのですが、事の発端は、ファームのアップデートでした。

アップデート後SPIコネクションがresetされる現象が以下の様に発生

2026/6/1 16:42:29 Warning: SPI connection has been reset
2026/6/1 16:42:29 Connection to Duet established
2026/6/1 16:42:29 Warning: Lost connection to Duet (Transfer timeout while waiting for TfrRdy pin)
2026/6/1 16:25:59 File 0:/gcodes/Dish - PoleMiddle_PETG_6h0m.gcode selected for printing
2026/6/1 16:13:54 Finished printing file 0:/gcodes/Dish - PoleBottom_PETG_1h41m.gcode, print time was 2h 7m
2026/6/1 15:41:46 接続確立


まずいことに、わたしはリボンケーブルにちょっと自身がなかったのです。

自前で40ピン、28ピンのケーブルを作ったので、もしや、失敗したのかと思い、FC40ピンソケットを購入し、つけ直しました。

しかし、また再現、仕方なく、もう一度つけ直しました。

それでも再現・・・

そこで、公式を見てみたのですが、

22ピンであるところ、TfrRdy pinは正常に動いていることを確認しました。

次に、疑ったのはファームです。

SBC(RaspberryPi4)には、source.listにunstableの設定が書かれていました。

stableにして、ダウングレード手順に従ってダウングレードしました。

そしたら、嘘のように安定しました。

いやぁ・・・新しいからいいわけではないですね


 

2026年5月27日水曜日

GoogleFitがなくなるというので

 仕方なく作っているレシピアプリ(というか、献立記録アプリ)を作り直している

材料を選んで献立を作り、

食事の記録画面では献立から朝昼晩間食を記録できる

記録先はログインしたGoogleアカウントのドライブ上にJSONで記録する




2026年5月9日土曜日

GoogleFitへ食べたものを登録できるアプリを作っている

 今のところこんな感じ

食成分データベース八訂版を使って食べたものを登録(レシピ作成)

卵かけご飯のような普通の飯も登録しておいてカレンダーに登録する

GoogleFitと同期できる

言語はGo

フレームワークはFiber


iPhoneのGoogleFitアプリでの表示はこんな感じ