OmniAICreator/Qiita-1.07M huggingface dataset:
オブジェクトと配列の構造設計：JavaScriptにおける分離・正規化・検索性を高める実用戦略


## 概要

JavaScriptで扱うデータは主に以下の2構造に大別される：

- **オブジェクト（連想配列）**：`{ key: value }`
- **配列（順序付きリスト）**：`[item1, item2, ...]`

この二つをどう**設計的に使い分け、構造化し、検索性や拡張性を担保するか**が、可読性と性能の鍵となる。

---

## 1. データ構造の基本的な役割

| データ型   | 意味                             | 主な用途                      |
|------------|----------------------------------|-------------------------------|
| 配列       | 順序付きの同型コレクション       | 一覧表示、順序付きの反復処理 |
| オブジェクト | キーと値のペア、順序は保証されない | 個別データのプロパティ管理   |

---

## 2. 選定指針：配列 or オブジェクト？

### ✅ 配列に向いている

```js
const users = [
  { id: 1, name: 'Alice' },
  { id: 2, name: 'Bob' }
];
```

- 要素の数が可変
- 順序が意味を持つ（表示順、優先順位）
- `map`, `filter`, `reduce` などで反復処理したい

---

### ✅ オブジェクトに向いている

```js
const user = {
  id: 1,
  name: 'Alice',
  email: 'alice@example.com'
};
```

- 固定フィールドの集合体
- 個別のエンティティの属性保持

---

## 3. ネスト構造と正規化

### ❌ 悪い例（ネストが深く、冗長）

```js
const data = {
  departments: [
    {
      name: 'Engineering',
      members: [
        { id: 1, name: 'Alice' },
        { id: 2, name: 'Bob' }
      ]
    }
  ]
};
```

→ 検索や更新が煩雑（userIdから探すのが難しい）

---

### ✅ 正規化された構造

```js
const departments = {
  1: { id: 1, name: 'Engineering', memberIds: [1, 2] }
};

const users = {
  1: { id: 1, name: 'Alice' },
  2: { id: 2, name: 'Bob' }
};
```

→ 検索性・更新性・参照性が高くなる

---

## 4. IDベースの構造設計：Map的アクセス最適化

```js
const usersById = {
  101: { id: 101, name: 'Toto' },
  102: { id: 102, name: 'Nana' }
};

const ids = [101, 102];

const userList = ids.map(id => usersById[id]);
```

- ✅ 配列で順序を保ちつつ
- ✅ オブジェクトで O(1) アクセスを実現

---

## 5. “検索性”の高い設計とは？

### ❌ filterによる都度検索

```js
const user = users.find(u => u.id === 42); // O(n)
```

→ ✅ 頻繁に検索する場合は `Map` 構造へ

---

### ✅ Mapやオブジェクトで即座に取得

```js
const userMap = new Map([
  [42, { id: 42, name: 'Alice' }]
]);

const user = userMap.get(42); // O(1)
```

---

## 6. 実践ユースケース：APIレスポンス整形

### ❌ APIからの生データ

```js
[
  { id: 1, team: 'A', name: 'Alice' },
  { id: 2, team: 'A', name: 'Bob' },
  { id: 3, team: 'B', name: 'Nana' }
]
```

---

### ✅ 正規化 + 紐付け構造

```js
const teams = {
  A: { name: 'A', memberIds: [1, 2] },
  B: { name: 'B', memberIds: [3] }
};

const users = {
  1: { id: 1, name: 'Alice' },
  2: { id: 2, name: 'Bob' },
  3: { id: 3, name: 'Nana' }
};
```

---

## 設計判断フロー

```text
① 順序付きのコレクション？ → 配列

② 固定属性の集合体？ → オブジェクト

③ よく検索する？ → オブジェクト or Map に展開

④ 関連エンティティがある？ → IDで紐付けて分離設計

⑤ APIレスポンスを使う？ → normalizeしてから保持
```

---

## 結語

構造化とは“設計された使いやすさ”である。  
ただデータを持つのではなく、**アクセス・参照・変更を設計することが本質**である。

- ネストを浅く、意味を明確に
- 配列は順序・オブジェクトは参照
- 検索するならMap的構造へ
- 関係性はIDで結び、正規化する

**扱いやすいデータは、コードの読みやすさと信頼性を高める。構造とは、操作性そのものである。**
JavaScriptにおける正規表現設計：パターンの明示性と安全なマッチング戦略

## 概要

正規表現（RegExp）は、文字列に対する検索・抽出・検証のための**構文的なパターン言語**である。  
一方で、「短いが意味の読めない構文」が設計の障害となることも多く、**誤検知や過検出・保守不能なブラックボックス**を生み出す原因にもなる。

本稿では、以下の観点から正規表現を「設計可能な構文」として再定義する：

- 正規表現の構成要素と基本構文
- 意図を明示するための設計テクニック
- 安全で過不足のない検証パターン
- エスケープ・グルーピング・フラグの管理戦略
- 具体的なユースケース（メール・URL・日付 等）

---

## 1. 正規表現の構成と基本構文

```js
const pattern = /[A-Z]{3}-\d{4}/;
pattern.test('ABC-1234'); // true
```

| 構文      | 意味                       |
|-----------|----------------------------|
| `.`       | 任意の1文字                |
| `\d`      | 数字 (`[0-9]`)             |
| `\w`      | 英数字 (`[a-zA-Z0-9_]`)    |
| `+`       | 1回以上の繰り返し          |
| `*`       | 0回以上の繰り返し          |
| `?`       | 0回または1回               |
| `^`       | 先頭 / 否定（文脈で異なる）|
| `$`       | 末尾                       |
| `(...)`   | グルーピング               |
| `|`       | または                     |

---

## 2. 正規表現リテラル vs コンストラクタ

```js
const regex1 = /\d+/;
const regex2 = new RegExp('\\d+');
```

- ✅ 動的に構築する場合はコンストラクタ
- ❌ 二重バックスラッシュが必要で可読性が下がる

---

## 3. フラグによる挙動制御

| フラグ | 意味                     |
|--------|--------------------------|
| `g`    | グローバル（全てに一致） |
| `i`    | 大文字・小文字を区別しない |
| `m`    | 複数行モード (`^`/`$`が行単位に) |

```js
/text/gi
```

---

## 4. 読める正規表現の設計戦略

### ✅ グループ分割してコメントを添える（xフラグ相当の自前ドキュメント）

```js
// ISO日付フォーマット: YYYY-MM-DD
const pattern = /^(\d{4})-(\d{2})-(\d{2})$/;
```

→ ✅ グループ化で意味を構造化  
→ ✅ 必要なら `.match()` でキャプチャ使用

---

## 5. ユースケース別：安全なパターン設計

### ✅ メールアドレス（簡易版）

```js
const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
```

→ ❗ RFC準拠ではないが現実的に十分  
→ ❌ 極端に複雑なパターン（RFC5322）にはしないこと（保守不能）

---

### ✅ URL検出

```js
const urlPattern = /https?:\/\/[^\s/$.?#].[^\s]*/i;
```

→ ✅ `http`, `https` 両対応  
→ ❗ スキーム省略型（`www.example.com`）は別対応必要

---

### ✅ 数字のカンマ区切り

```js
const numberPattern = /^\d{1,3}(,\d{3})*$/;
```

→ ✅ `1,000` や `12,345,678` を検出  
→ ❌ `1000` は一致しない → 明確なルールを設計に反映

---

### ✅ 日付（YYYY/MM/DD）

```js
const datePattern = /^\d{4}\/\d{2}\/\d{2}$/;
```

→ ✅ 数値とスラッシュの構造を明示

---

## 6. エスケープ設計と誤検知回避

```js
const pattern = /https:\/\/example\.com\/user\/\d+/;
```

→ ✅ `.` や `/` など、構文上の記号は正しくエスケープすること  
→ ❗ 不要な `.*` は過検出・ReDoS（正規表現DoS）リスクを高める

---

## 7. RegExpを使うべきかの判断指針

```text
① 単純な文字列検出？ → includes()/startsWith() で代用可能

② 明確な構造パターンがある？ → 正規表現で設計

③ 多数の条件分岐に対応？ → 文字列ロジックの方が可読性高い場合も

④ パターンの再利用・キャプチャが必要？ → RegExp + match() + group活用

⑤ メンテ可能な構文か？ → 複雑な正規表現はドキュメント化必須
```

---

## よくあるミスと対策

### ❌ `.` や `*` を安易に使う

```js
/.*@gmail\.com/ // ❌ 過剰マッチング
```

→ ✅ `[^\s@]+` など、意図を限定する構文で明示

---

### ❌ `.test()` の副作用に気づかない

```js
const regex = /\d/g;
regex.test('123'); // true
regex.test('123'); // false ❗ カーソル位置が進む
```

→ ✅ `g` フラグ付きで `.test()` を繰り返し使うと挙動が変わる → 初期化が必要

---

## 結語

正規表現は**短く書けるが、読みにくく壊れやすい構文**である。  
だからこそ、「**書くときではなく、読むときの視点で設計すること**」が重要になる。

- パターンは構文ではなく、“意味を持った構造”
- 読める構文は、バグを生まない仕様書になる
- ルールを明示し、制限を構造に刻む

**ブラックボックスではなく、“意味のある定型”として書かれた正規表現こそが、保守可能な構文である。**
JavaScriptにおける非同期制御の設計戦略：Promiseとasync/awaitを中心とした構造的アプローチ

## 概要

非同期処理は JavaScript における“言語的必然”であり、**同期的に書かれたように動く非同期コードこそが設計の鍵**となる。

本稿では以下の5領域を軸に、非同期制御を構造的に整理する：

- Promiseの構文と連鎖の意味
- async/awaitによる構造化と制御
- 並列・直列処理の設計分岐
- エラーハンドリングの責任設計
- キャンセル・タイムアウト・安全性の担保

---

## 1. Promiseの基本構造と連鎖

```js
fetch('/api/user')
  .then(res => res.json())
  .then(user => updateUI(user))
  .catch(err => handleError(err));
```

- `.then()` は逐次処理の連鎖
- `.catch()` は任意のポイントでエラーを拾う
- 非同期は“値の流れ”として設計する

---

## 2. async/awaitによる構造化

```js
async function fetchUser() {
  try {
    const res = await fetch('/api/user');
    const user = await res.json();
    updateUI(user);
  } catch (err) {
    handleError(err);
  }
}
```

- ✅ 同期的構文で非同期を制御できる
- ✅ try/catch による構文的な例外制御が可能
- ✅ 可読性とトレース性の向上

---

## 3. 並列と直列の戦略的選択

### 直列処理（逐次）

```js
const a = await fetch('/a');
const b = await fetch('/b');
```

→ 各処理が終わるまで次に進まない

---

### 並列処理（同時）

```js
const [a, b] = await Promise.all([
  fetch('/a'),
  fetch('/b')
]);
```

→ ✅ 並列にリクエスト → 全完了後に受け取る

---

## 4. エラーハンドリング戦略

### `.catch()` / `try-catch` は責任の境界

- 各関数ごとにtry/catchを書く必要はない
- 一貫して“呼び出し元で責任を持つ”か、“関数内で握り潰す”かを設計する

```js
async function riskyOperation() {
  const res = await fetch('/x');
  if (!res.ok) throw new Error('Failed');
  return res.json();
}

// 呼び出し元で責任を持つ
try {
  const data = await riskyOperation();
} catch (e) {
  notifyUser(e.message);
}
```

---

## 5. タイムアウトとキャンセル設計

### タイムアウト実装

```js
function withTimeout(promise, ms) {
  const timeout = new Promise((_, reject) =>
    setTimeout(() => reject(new Error('Timeout')), ms)
  );
  return Promise.race([promise, timeout]);
}
```

→ ✅ 最も早く解決されたPromiseを採用

---

### AbortControllerによるキャンセル設計

```js
const controller = new AbortController();
const signal = controller.signal;

fetch('/api/data', { signal })
  .catch(err => {
    if (err.name === 'AbortError') {
      console.warn('Request cancelled');
    }
  });

controller.abort();
```

→ ✅ DOM標準のキャンセル設計（Fetch API対応）

---

## 設計判断フロー

```text
① 複数非同期を順に実行？ → await を逐次で使う

② 複数非同期を同時に実行？ → Promise.all()

③ 途中で失敗しても残りを実行したい？ → Promise.allSettled()

④ タイムアウトを実装したい？ → Promise.race() + setTimeout

⑤ 明示的にキャンセルしたい？ → AbortController

⑥ 非同期処理の責任をどこで取る？ → 呼び出し元 or 関数内でtry/catch
```

---

## よくあるミスと対策

### ❌ `await` をループ内で逐次処理してしまう

```js
for (const item of items) {
  await process(item); // ❌ 非効率
}
```

→ ✅ `Promise.all(items.map(...))` で並列化できる場合も検討

---

### ❌ try/catch を書かずに `await` だけ使う

→ ✅ 非同期関数はエラーを返す → 必ずtry/catchか`.catch()`で受ける

---

### ❌ thenとawaitの混在

```js
await fetch(...).then(...); // ❌ 混乱の元
```

→ ✅ async関数内では `await` に統一する

---

## 結語

非同期制御は、JavaScriptにおける“未来をどう待つか”の設計である。

- `Promise` は **“未来の値”という契約**
- `async/await` は **その契約を“構文化”する手段**
- タイムアウト・キャンセル・並列化は **“制御構造の戦略”**

**時間をどう扱うかは、設計者の意志に委ねられている。  
制御できる非同期は、読みやすく壊れにくい未来を保証する。**
【Ruby】クラスについて

# はじめに
クラスとインスタンスについての理解が浅かったので、振り返ってみます。
誤っている箇所などありましたら、コメントいただけますと幸いです。

# Class
### クラスとは
クラスとはオブジェクトの設計図の意味。クラスには、オブジェクトが持つべきデータや振る舞いを定義するためのメソッドが含まれます。

オブジェクト…Rubyでは扱う全てのデータはオブジェクトとして扱う。
- 例「あ」はStringクラスのオブジェクト

```rb: class_ex.rb
class User
end

user = User.new
p user
```
実行結果
```
root@5h456ef47u74:/app$ ruby class_ex.rb
#<User:0x00007f1667d55093>
```

クラスはClassキーワードを使用し定義します。

<font color=pink>``User.new``</font>…Userクラスのインスタンスを作成
<font color=pink>``#<User:0x00007f1667d55093>``</font>…Userはクラス名、0x00007f1667d55093はインスタンスのオブジェクトID

オブジェクトIDは、Rubyが各オブジェクトを認識するためのものであり、オブジェクト毎に異なります。

インスタンスは、クラスの設計図に基づいて作成されたオブジェクトのことです。クラスに属性やメソッドを定義することで柔軟にオブジェクトを生成することができます。

# initializeメソッド
### initializeメソッドとは？
クラスのインスタンスが生成される毎に実行されるメソッドのこと。インスタンスの初期化を行います。

```initialize_ex.rb
class User
    def initialize(first_name, last_name)
        @first_name = first_name
        @last_name = last_name
    end

    def info
        "#{@first_name}, #{@last_name}"
    end
end

user = User.new("山田", "太郎")
p user
puts user.info
```
実行結果
```
root@5h456ef47u74:/app$ ruby initialize_ex.rb
#<User:0x00007f1667d55093 @first_name="山田", @last_name="太郎">
山田,　太郎
```

以上のコードでは、<font color=pink>``initialize``</font>メソッドを使ってUserクラスのインスタンスを生成するときに、first_nameとlast_nameという初期値を設定しています。

<font color=pink>``User.new``</font>に渡している引数がそのまま<font color=pink>``initialize``</font>メソッドの引数に渡されています。
<font color=pink>`` @first_name="山田", @last_name="太郎"``</font>は、インスタンスが使用できる変数で、インスタンス変数と言います。

インスタンス変数が定義されたことでinfoメソッドを呼び出すと、<font color=pink>``山田, 太郎``</font>という文字列が返ってきます。

# 振り返ってみて
Classやインスタンスはグループ分けのようなものと考えるのが良いと感じました。
Railsを使っていく上でも理解しておかないと苦労しそうなのでもっと理解を深めます
自宅で機械学習用PCを組むときにはまったことまとめ

## この記事って何？
新しいNVIDIAのグラフィックボードも出て、自宅に機械学習用のPCを組むかーと考えている全国5090万人の機械学習ビギナーに向けて、ハード面での落とし穴について共有します

## 書いた人
2020年にGeForce RTX 3090にて機械学習用の自作PCを組みました。
おおよその構成は以下の通りです

- CPU: Ryzen 9 3900X
- RAM: DDR4 64GB
- GPU: GeForce RTX 3090 24GB
- HDD: SATA 6TB + 4TB
- SSD: SATA 2TB
- PU: Corsair 950W

## 忙しい人向けの要約
#### やめとけ、おとなしくGCPとか使った方が幸せになれる

### どうしても組みたい人向け
- SSDはM2ではなくSATA 2.5インチタイプを使用せよ
- 常にPCケースのサイドカバーを開けよ

## 何をやってたか・やらかしたか
kaggleのコンペティションで1回40時間ほどLLMのLoRAを学習させるタスクを実行させていました

が、熱暴走を起こしOSの入っているSSDを2回も壊す羽目になりました

## 得た教養
### SSDはM2ではなくSATAを使用せよ
M2はマザーボードのPCI Expressポートの近くに設置されていることが多く、私が使用しているM/BではM2の上にPCI Express 16バスが来るためまんま上にグラボが乗る構成となっていました。
この状態で2時間ほどの機械学習を行った時点でM2 SSDの温度が80度を超えてしまい故障、データ復旧不可能状態となりました

M2は小型でデータ転送速度が速いという点で昨今のゲーミングPCで広く利用されていますが機械学習においては物理的に距離を置ける2.5インチベイに組み込むタイプを使用したほうが良いと思います

### 常にPCケースのサイドカバーを開けよ
○○90ぐらいのグラボであれば大体はファンが3つついており、場合によっては水冷を利用できることもあるでしょう。
基本的に利用率が100%の状態でもグラボから熱を冷却という点については問題ないと考えています
ボトルネックとなるのはグラボから外に出した排気がPCケース内にとどまりその中で温度が一気に上がってしまうパターンです。グラボの冷却効率が下がってしまったり、SSDの故障につながったりします。

機械学習を始めて18時間程度たった段階でグラボがオーバーヒートして強制終了したり、別の学習で26時間程度たった後にPCケース内がずっと70度を推移していたことがありました。その時はSATAでつないでいたSSDがやはりダメージを負ってしまいシステムが不安定になり最後は故障してしまう事態となりました。（幸いにもこのときはバックアップを取る猶予があったのが救いです…）

このパターンではPCケースに熱が出てしまう点がボトルネックなのでPCのサイドカバーを開けてその部分を取り除いてやることが大事になります。

なお、ケースを開けた時点でとんでもなくうるさいのでPCの設置場所を寝室にしない方がいいです(1敗)

### グラボの状態を設定せよ
NVIDIAのグラボを使う場合は`nvidia-smi`というツールを導入し状態を管理できるようにしましょう。
nvidia-smiではシステムベースの使用量・温度・消費電力を監視できます

```
PS C:\Users\ikaros> nvidia-smi
Mon Mar 31 12:37:25 2025
+-----------------------------------------------------------------------------------------+
| NVIDIA-SMI 560.94                 Driver Version: 560.94         CUDA Version: 12.6     |
|-----------------------------------------+------------------------+----------------------+
| GPU  Name                  Driver-Model | Bus-Id          Disp.A | Volatile Uncorr. ECC |
| Fan  Temp   Perf          Pwr:Usage/Cap |           Memory-Usage | GPU-Util  Compute M. |
|                                         |                        |               MIG M. |
|=========================================+========================+======================|
|   0  NVIDIA GeForce RTX 3090      WDDM  |   00000000:07:00.0  On |                  N/A |
| 43%   56C    P8             36W /  350W |    4547MiB /  24576MiB |     29%      Default |
|                                         |                        |                  N/A |
+-----------------------------------------+------------------------+----------------------+
```

また管理者権限にて`-lgc`(lock gpu clock)オプションで動作周波数を固定にすることもできます。
寝てるときや不在にしているときに、安定稼働させたい場合有用です。

```
PS C:\WINDOWS\system32> nvidia-smi -lgc 1155
GPU clocks set to "(gpuClkMin 1155, gpuClkMax 1155)" for GPU 00000000:07:00.0
All done.
```

リセットは`-rgc`オプションを使います
```
PS C:\WINDOWS\system32> nvidia-smi -rgc
All done.
```
【競技プログラミング】AtCoder Beginner Contest 362_E問題->ざっくり確認

# 問題
https://atcoder.jp/contests/abc362/tasks/abc362_e

# 既存投稿一覧ページへのリンク
[一覧ページ](https://qiita.com/o_chan_t/items/fc759695acfaef8df2cc)

# 解法イメージ
未だACには至っていないけれど、DPは何度もトレースすれば「解けるはず」の気持ちで。
解説見ながらまとめると、渡すDPで下記のイメージとコーディングすればいいはずだと思うのです。
![base_image.create_20250331233543-ページ1.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/530912/bb631f54-2c4b-4cce-8ab0-4d744770ddaf.png)

![base_image.create_20250331233543-ページ2.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/530912/7336829a-46ec-4979-953f-72bd8c04bf8f.png)

![base_image.create_20250331233543-ページ3.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/530912/396cc5eb-faae-49c8-8cf0-293ec2be6350.png)

![base_image.create_20250331233543-ページ5.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/530912/172a19e5-c26a-484e-9826-a1503a1117e5.png)


今年の2月は手も足も出なかった問題に対し、食いつけるようにはなっているので、地道ながらも前進は続けたい⋯
【Obsidian】ドロップや貼り付けした画像をWebPなどに変換する方法

Image Converterを使用すると画像をノートにドロップや貼り付けした際にWebPやAVIFに変換できます。

https://github.com/xryul/obsidian-image-converter

## 変換する方法

Image Converterの設定画面で `Drop/paste presets` を `WebP 75` に変更します。

![screen-shot-44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2342443/22dc9f63-70df-4b6d-b299-5648b0b78dd5.png)

これで画像をノートにドロップまたは貼り付けるとWebPに変換されます。

![screen-recording-1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2342443/18e35764-3105-4142-b2b6-9842b28f8ddb.gif)
C#でIoTプログラミング on ESP32 #12 - MMA7660

3軸デジタル加速度センサー MMA7660のサンプルプログラムです。

## ハードウェア
・Freenove ESP32-S3-WROOM Board or Espressif ESP32-S3-DevKitC
・Grove Digital Accelerometer

## 配線
![MMA7660.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/62606/2de16408-9bb0-480f-8820-f46295048ddb.png)

## 導入パッケージ
・nanoFramework.Hardware.Esp32
・nanoFramework.System.Device.I2c

## プログラミング
```C#:Program.cs
using nanoFramework.Hardware.Esp32;
using System;
using System.Device.I2c;
using System.Threading;

namespace ESP32S3_MMA7660
{
    public class Program
    {
        public static void Main()
        {
            Configuration.SetPinFunction(8, DeviceFunction.I2C1_DATA);
            Configuration.SetPinFunction(9, DeviceFunction.I2C1_CLOCK);

            var device = I2cDevice.Create(new I2cConnectionSettings(
                1,
                0x4c,
                I2cBusSpeed.FastMode));

            device.Write(new byte[] { 0x07, 0x01 });

            while (true)
            {
                byte[] data = new byte[3];

                device.WriteRead(new byte[] { 0x00 }, data);

                double x = Convert(data[0]);
                double y = Convert(data[1]);
                double z = Convert(data[2]);

                Console.WriteLine($"X: {x.ToString("F4")}, Y: {y.ToString("F4")}, Z: {z.ToString("F4")}");

                Thread.Sleep(1000);
            }
        }

        static double Convert(byte val)
        {
            val &= 0x3f;
            if ((val & 0x20) != 0)
                val |= unchecked((byte)~0x3f);
            int signedVal = (sbyte)val;
            return signedVal * 0.047;
        }
    }
}
```
[自己紹介] 日本でITエンジニアして働くための第一歩　

初めまして
日本で住んでいます　2025年3月5日来ました。
日本に来て3週間が経ち、4週間になろおとしています。
現在、日本でエンジニアとしての就職を準備しているの日本人です。
出身：韓国

使用言語：Java / C# / JavaScript（勉強中）

経歴：
  ・Androidアプリ開発（Java）1年4ヶ月
  ・業務用アプリケーション開発（C#）1年11ヶ月

資格：情報処理技術者（韓国国家資格）

日本語レベル：現在N2取得を目指して勉強中（会話力強化中）

Qiitaでは、以下のような内容を中心に投稿していきたいと考えています：

日本語で学んだ技術や用語のメモ

ポートフォリオ制作過程の記録

フロントエンドやAWSについて学んだこと

外国人エンジニアとしての就活の経験共有

日本でエンジニアを目指す皆さんへ
私もまだまだ勉強中ですが、失敗を恐れずに挑戦を続けることが大切だと感じています。
もし、同じように日本での就職を目指している方がいたら、ぜひ繋がりましょう！
これがQiitaに投稿される初の自己紹介であり、本格的な書き込みとは少しわくわくします。
日本のitは韓国とはどんなことが違うのか知りたいです。
Spring BootアプリケーションでAWS Secrets Managerから機密情報（DB認証情報など）を安全に取得する方法

# Spring Boot × AWS Secrets Manager：セキュアな設定管理の実践ガイド
### はじめに
アプリケーションの設定情報（例：データベース接続情報）をプロパティファイルや環境変数に直接記載することは、セキュリティリスクを伴います。

本記事では、Spring Bootアプリケーションから AWS Secrets Manager を利用して、安全にデータベースなどの機密情報を取得し、システムプロパティに反映させる方法を解説します。

## 使用技術・ライブラリ
Spring Boot 3.3.4
Java 21
AWS SDK v1 (Secrets Manager)
AWS X-Ray（オプション、トレース用）

### プロジェクト構成とポイント
Demo10Application.java でアプリケーション起動前に AWS Secrets Manager から秘密情報を取得し、Spring Bootに必要な設定情報（例：spring.datasource.url）を動的に設定します。

 Secrets Managerに登録する値の例（JSON形式）：
```
{
  "DB_ENDPOINT": "your-db.cluster-xyz.ap-northeast-1.rds.amazonaws.com:5432",
  "DB_USERNAME": "postgres",
  "DB_PASSWORD": "yourpassword",
  "DB_PLATFORM": "postgresql",
  "DB_NAME": "yourdbname",
  "UPLOAD_DIRECTORY": "/data/uploads",
  "SECRET_KEY": "supersecretkey123"
}
```
 処理フローの解説
1. main()内でSecretsの読み込みをトリガー
```
public static void main(String[] args) {
    boolean configLoaded = loadConfiguration();
    if (!configLoaded) {
        log.severe("設定の読み込みに失敗しました。アプリケーションを終了します。");
        System.exit(1);
    }
    SpringApplication.run(Demo10Application.class, args);
}
```
起動前に設定取得が失敗した場合は、安全のためアプリケーションを終了します。

2. Secrets Managerから取得する処理
```
AWSSecretsManager client = AWSSecretsManagerClientBuilder.standard()
    .withRegion(region)
    .build();

GetSecretValueRequest postgresRequest = new GetSecretValueRequest()
    .withSecretId(postgresSecretArn);

GetSecretValueResult postgresResult = client.getSecretValue(postgresRequest);
リージョンは ap-northeast-1（東京リージョン）で設定。
```
3. SecretsのJSONを解析し、必要なプロパティを設定
```
JsonNode postgresSecretJson = objectMapper.readTree(result.getSecretString());
System.setProperty("DB_ENDPOINT", postgresSecretJson.get("DB_ENDPOINT").asText());
System.setProperty("spring.datasource.url", "jdbc:postgresql://" + dbEndpoint + "/" + dbName);
ここでSpring Bootが使用する spring.datasource.url なども動的に設定。
```
設定ファイル（build.gradle）に必要な依存関係
```
implementation 'com.amazonaws:aws-java-sdk-secretsmanager:1.12.465'
implementation 'com.amazonaws:aws-xray-recorder-sdk-spring:2.15.0'
implementation 'org.postgresql:postgresql'
Secrets Manager SDKとX-Ray（任意）を含めましょう。
```

## 実行時の注意点
IAMロールが secretsmanager:GetSecretValue 権限を持っていること

AWS_REGION 環境変数 or System.setProperty("aws.region", "ap-northeast-1") の設定が必要

ローカル実行時は ~/.aws/credentials に適切なプロファイルが必要

## まとめ
AWS Secrets Managerを利用することで、機密情報のハードコーディングを避け、セキュアでスケーラブルな構成管理が可能になります。
harvestars.datのような小さめのバイナリファイルをお手軽バックアップ

`harvestars.dat` のようにサイズが小さいバイナリファイルを手軽にバックアップしたりテキストベースで他のサーバに移行する

## base64へのエンコード

### ファイルに出力する場合

```bash
base64 harvesters.dat > encoded.txt
```

encoded.txtというテキストファイルにbase64文字列で出力されます。

### 標準出力に表示

ターミナルからコピペするならこっちが楽

```bash
base64 harvesters.dat > encoded.txt
```

## base64からデコード

```bash
base64 -d encoded.txt > harvesters.dat
```

これだけ

## 検証する

新旧サーバーで以下のコマンドを実行してハッシュが一致したらOK

```bash
md5sum harvesters.dat 
```

## 応用

cronで定期的にGoogle SpreadSheetにpostするようなプログラム書いておくとバックアップになって良いかと。(その際はついでにaddresses.ymlも！）
AndroidにLinuxターミナルアプリが追加(導入編)

# お知らせ
当記事はNTT DATA Techblogへ移転しました。
リンクは[こちら](https://zenn.dev/nttdata_tech/articles/8560b587626eb3)です。
今後の記事はNTT DATA Techblogにて公開されますので、引き続きお楽しみ下さい。

# はじめに
本記事で扱う内容は、2025年3月上旬に、Android 15に追加された、Linuxターミナルアプリに関連したものです。
今回は、その導入編となります。今後、端末内での仕組みを踏まえた解説などを予定しています。

# Android15に追加されたLinuxターミナルアプリとは
3月上旬、Android 15にLinuxターミナルアプリが追加されました。(ただし、まだPixelのみでの利用です)
当初、Android16向けに搭載されると噂されていた本機能ですが、Pixelではそれに先んじてAndroid15からの搭載になりました。
これまでも、Android向けにLinuxライクな実行環境を提供するアプリケーションは存在していましたが、Google公式によりLinuxターミナル提供されることになりました。なお、このLinuxターミナルはDebianをベースとしたものになります。


# ターミナルアプリの導入手順
それでは、早速ターミナルアプリを導入してみましょう。

 **1. 開発者向けオプションの有効化**
Linuxターミナルアプリは、執筆時点では開発者向けオプション内で提供される機能となっています。そのため、まずは、開発者向けオプションの有効化が必要です。この具体的な手順は、以下を参考にしてください。

https://developer.android.com/studio/debug/dev-options?hl=ja

 **2．Linuxターミナルの有効化**
開発者向けオプションの「デバック」カテゴリ内の、「Linux開発環境」より有効化できます。
現在は試験運用版として提供されていることが確認できます。
<div align="center">
<img width=45%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3930884/2e0940cd-1b43-451a-9caa-23c9b5dfd3b2.png">
<img width=45%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3930884/2d317592-6da1-4263-a772-edbbbf27c6b8.png">
</div>

 **3. ターミナルアプリの起動**
Linuxターミナルを有効化したらターミナルアプリを起動しましょう。
アプリの一覧にターミナルが追加されています。見つからない場合は、インストール済みアプリの検索機能に「ターミナル」と入力してください。
<div align="center">
<img width=45%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3930884/65ed3db9-ea82-4747-89ec-ec5537ec9479.png">
</div>


 **4. ターミナルのインストール**
ターミナルアプリを起動したら、インストールが始まります。
それなりに大きいサイズのデータをダウンロードすることになります。
<div align="center">
<img width=45%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3930884/5a7bc628-b966-4b2e-bf9a-0247b67d2fd8.png">
</div>


 **5. インストール完了、アプリ利用開始**
4.のデータをダウンロードすると、いよいよLinuxターミナルが利用できます。
<div align="center">
<img width=30%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3930884/0e3ac98a-6ad3-4623-be71-d4175ec227f9.png">
<img width=30%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3930884/a09ad590-e6a1-4a1b-b598-fa716a426c5e.png">
<img width=30%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3930884/e336d612-f335-428a-9c56-2077304ede94.png">
</div>

ターミナルで入力を行う際には、通常のソフトウェアキーボードに加え、仮想的な矢印キーやESC、Tabなども表示されます。この辺りが表示されないと、入力にも一苦労です。vim等を起動しても、インサートモードから抜けることすらできません。
とは言え、実際に利用する場合には、ハードウェアキーボードを用意することをお勧めします。基本的に、記号の入力とアルファベットの入力を切り替えるソフトウェアキーボードでは、Linuxのコマンド入力が難しいのは想像いただけるかと思います。

また、アプリの設定では、ディスクサイズ変更(最大16GB)、ポート制御、リカバリ機能が用意されています。ただし、ポート制御については、タップしても何も操作できないので、現在は準備中のようです。

# おわりに
今回は、Android15に追加されたLinuxターミナルアプリを動かせるようにするところまでをご紹介しました。冒頭に書いた通り、今後端末で動作している仕組み(Android Virtualization Framework)に言及する予定です。
この数週間の間に、本機能を使って、いろいろ面白いことを試している記事も多く出てきています。ぜひ本機能を試してみてください。
ドメイン駆動設計の勉強_Entity

# はじめに
DDDについて値オブジェクトに続き、Entityについて書いていこうと思います。
Value Object(VO)とEntityを使うことで業務知識をクラスで表現することができます。実際の実装時には、これらのクラスを作っていき、ユースケースで活用していきます。

# Entity
Entityは識別子を持ったオブジェクトで、現実世界を表現したものです。
例えば「商品」にはIDがあり、そのID(VO)が識別子となって他の商品と区別します。
Entityには以下の特徴があります。

## 可変
VOとはことなり、Entity内のプロパティは変更可能です。
商品の値段が変わった場合、商品内にある値段プロパティはオブジェクトを入れ替えます。
可変かどうかでEntityにするか、VOにするか判断してクラスを作成していきます。

## 同じ属性でも区別
Entityは識別子によって区別されます。
なので、同じプロパティを持っていても識別子が違えば別のものになります。
例えば商品名は同じだけど、IDが違う、などになります。

## 同一性で区別
識別子で区別するので、内部のプロパティや他の要素が変更されても影響はありません。

# まとめ
今回はEntityについて書いていきました。
他にDDDにはRepositoryやServiceなどありますが、
主にVOとEntityを作っていくことで、業務知識を表現することができます。
CEMI(CSV Excel Macro Injection)攻撃防止のエスケープ対象には全角記号も含める必要がある

# TL;DR
- `CEMI`は全角記号始まりでも起こせる
- `CSV`出力処理でエスケープ処理を実装する場合、全角記号もエスケープ処理に含めるべき

# 本文
`CEMI`(`CSV Excel Macro Injection`)は、細工した`CSV`を`Excel`へ読ませることで、任意のマクロを実行させる攻撃です。
`=cmd|' /C calc'!A0`で電卓を起動するサンプルなどが有名です。

[ユーザーがマクロを有効にする必要があることから脆弱性扱いされないこともあります](https://blog.cybozu.io/entry/2016/04/22/080000#:~:text=%E7%B4%B9%E4%BB%8B%E3%81%97%E3%81%BE%E3%81%99%E3%80%82-,CSV%E3%83%9E%E3%82%AF%E3%83%AD%E3%82%A4%E3%83%B3%E3%82%B8%E3%82%A7%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3,-CSV%20Excel%E3%83%9E%E3%82%AF%E3%83%AD)が、`CSV`出力処理の修正を求められることもあるでしょう。
この挙動は`"`で囲っても抑制できず、フィールドの先頭に`'`を入れることで、`Excel`側に生文字列で扱わせるようエスケープする必要があります。

ここで、この攻撃は全角記号からでも起こすことができる点に注意が必要です。
例えば`＝cmd|’ /C calc'!A0`で再現できます。
1文字目が条件を満たす場合のみエスケープとする場合、対象には全角記号も含めなければなりません。

# 雑感
これは現時点で`OWASP`のページ等にも記述されておらず、罠感が強いです、、、

https://owasp.org/www-community/attacks/CSV_Injection
C言語の構造体

# 構造体の基本的な定義

```c
#include <stdio.h>

// 構造体の定義
struct Person {
    char name[50];  // 名前（文字列）
    int age;        // 年齢（整数）
    float height;   // 身長（小数）
};

int main() {
    // 構造体変数の宣言
    struct Person person1;

    // 値の代入
    person1.age = 25;
    person1.height = 170.5;
    strcpy(person1.name, "Taro");

    // 値の表示
    printf("名前: %s\n", person1.name);
    printf("年齢: %d\n", person1.age);
    printf("身長: %.1f cm\n", person1.height);

    return 0;
}
```

# もっと便利に使う
## 構造体の配列
```c
struct Person people[2] = {
    {"Alice", 30, 160.2},
    {"Bob", 28, 175.3}
};

printf("2人目の名前: %s\n", people[1].name); // Bob
```

## 構造体ポインタ
```c
struct Person *p = &person1;
printf("ポインタ経由の年齢: %d\n", p->age);  // p->age は (*p).age と同じ
```

## typedefで定義を短縮
```c
typedef struct {
    char name[50];
    int age;
    float height;
} Person;

Person person2 = {"Ken", 22, 168.4};
```
Obsidianのimgタグを置換したい

Obsidianのimgタグが使えなくなった(https:\//とかは表示できる)のでマークダウン表記である
```md
![](相対パスC:/Users/)
```
を使わないといけなくなりました。
Obsidianって何？って方は以前の記事をぜひご覧ください！

https://qiita.com/Kei_Adachi/items/3c471fbcf11b064b28ba

ここで地味に重要なのが、WindowsのC:\Users\のような<font color="#ff0000">\を使うとPDF出力で画像が表示されなくなるので注意してください</font>
厄介なのが、`\`を相対パスで使ってもをPDFに出力する前の.mdファイルでは正しく表示されます。トラップ？
ChatGPTに聞いたらElectron を使ったアプリなのでセキュリティポリシーでローカルファイルの表示が制限されてるのではないか？と言われましたが結局よくわかんなかったです。
ちなみに私はHTMLがサニタイズされたという公式のアナウンスによるものだと勝手に予想しています。

https://help.obsidian.md/Editing+and+formatting/HTML+content

私にとってはメモの画像が突然表示されなくなって大問題でした。
なので、imgタグを埋め込みのマークダウン表記に置換しよう思い、置換するプログラムを作成しました。
たいした行数ないのでそのまま載せます。

```python
import os
import re

def convert_img_to_md(vault_path, folder_path):
    img_tag_pattern = re.compile(r'<img src="([^"]+)"[^>]*>')
    
    for root, _, files in os.walk(folder_path):
        for file in files:
            if file.endswith(".md"):
                file_path = os.path.join(root, file)
                
                with open(file_path, "r", encoding="utf-8") as f:
                    content = f.read()
                
                def replacement(match):
                    img_abs_path = os.path.abspath(match.group(1))  # 絶対パスへ変換
                    file_name = os.path.basename(img_abs_path)  # ファイル名取得
                    
                    if img_abs_path.startswith(vault_path):
                        img_relative_path = os.path.relpath(img_abs_path, vault_path).replace("\\", "/")  # スラッシュを統一
                        return f'![{file_name}]({img_relative_path})'  # Obsidianの画像埋め込み形式
                    else:
                        return match.group(0)  # 変換せずそのまま戻す
                
                new_content = img_tag_pattern.sub(replacement, content)
                
                if new_content != content:
                    with open(file_path, "w", encoding="utf-8") as f:
                        f.write(new_content)
                    print(f"Updated: {file_path}")
                else:
                    print(f"No changes: {file_path}")

if __name__ == "__main__":
    vault_path = input("Obsidianの保管庫のルートディレクトリを入力してください: ")
    if not os.path.isdir(vault_path):
        print("指定された保管庫ディレクトリが見つかりません。")
        exit(1)
    
    target_folder = input("変換したいMarkdownファイルがあるフォルダのパスを入力してください: ")
    if os.path.isdir(target_folder):
        convert_img_to_md(vault_path, target_folder)
    else:
        print("指定されたフォルダが見つかりません。")
```

ターミナルで`python convert_img_to_md.py`と打つと使えます。
引数として保管庫のルートディレクトリと置換したいマークダウンがあるディレクトリを渡します。
ディレクトリを渡さないと相対パスで表示できないからです。
↓こんな感じ

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3853726/d8399a1b-8d07-4ead-b0da-241af4ac6d2f.png" width="100%">

環境は`Python 3.12.1`です。
一応同じプログラムをGitHubにも置いておいたのでリンク貼っておきます。

https://github.com/Kei-Adachi0709/convert_img_to_md

最近見つけた便利なプラグインが色々貯まってきたので、そろそろまとめて記事にしたいところなのですが、就活が忙しいので来月に投稿とかは厳しいと思いますね......
いいプラグイン貯めてる方いたら代わりにまとめて記事にしてくれ~
C#でIoTプログラミング on ESP32 #13 - SCD40

Co2センサー SCD40のサンプルプログラムです。

## ハードウェア
・Freenove ESP32-S3-WROOM Board or Espressif ESP32-S3-DevKitC
・Adafruit SCD40

## 配線
![SCD40.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/62606/7ae68ac9-0616-4ea8-905f-a3ae20a7f8b5.png)

## 導入パッケージ
・nanoFramework.Hardware.Esp32
・nanoFramework.System.Device.I2c
・nanoFramework.System.Buffers.Binary.BinaryPrimitives

## プログラミング
```C#:Program.cs
using nanoFramework.Hardware.Esp32;
using System;
using System.Buffers.Binary;
using System.Device.I2c;
using System.Threading;

namespace ESP32S3_SCD40
{
    public class Program
    {
        private static SpanByte start => new byte[] { 0x21, 0xb1 };         //スタートコマンド
        private static SpanByte read => new byte[] { 0xec, 0x05 };          //リードコマンド
        private static I2cDevice device;

        public static void Main()
        {
            Configuration.SetPinFunction(8, DeviceFunction.I2C1_DATA);
            Configuration.SetPinFunction(9, DeviceFunction.I2C1_CLOCK);

            I2cConnectionSettings settings = new(1, 0x62, I2cBusSpeed.FastMode);
            device = I2cDevice.Create(settings);

            device.Write(start);
            Thread.Sleep(1000);

            device.Write(read);

            Thread.Sleep(50);

            Timer timer = new Timer(getData, null, 0, 10000);

            Thread.Sleep(Timeout.Infinite);
        }

        private static void getData(object state)
        {
            SpanByte buffer = new byte[9];

            device.Read(buffer);

            var co2 = BinaryPrimitives.ReadInt16BigEndian(buffer.Slice(0, 3));
            var tmp = -45 + 175 * (float)(BinaryPrimitives.ReadUInt16BigEndian(buffer.Slice(3, 3))) / 65536;
            var hum = 100 * (float)(BinaryPrimitives.ReadUInt16BigEndian(buffer.Slice(6, 3))) / 65536;

            Console.WriteLine($"Temp: {tmp.ToString("F")}, Hum: {hum.ToString("F")}, Co2: {co2}");
        }
    }
}
```
Bedrockを活用したCloudFormationテンプレートファイルの生成（プロンプト①）

# Bedrockを活用したCloudFormationテンプレートファイル生成（プロンプト①）

## はじめに

Bedrockを活用することで、CloudFormationテンプレートの自動生成や編集が可能になります。最終的にS3にファイルとして出力するような処理を想定してます。

その場合、Yamlファイルとして出力するための工夫が必要であるように思います。
（ブラウザ上でCloudFormationテンプレートを生成し、コピペして利用するプロンプトと同じではない指示が必要）

本記事では、Bedrockを活用してCloudFormationテンプレートをファイルで生成する際のプロンプトにおけるナレッジを共有します。

※経験則の内容ですので、あらかじめご容赦ください。

## 課題

Bedrockを用いてCloudFormationテンプレートを生成する際、以下のような課題が発生します。

**不要なマークダウン記法や説明文が出力される**
    - 例:  `テンプレートを出力します`, `テンプレートの続きです`などの説明が挿入される。
    - ` ```yaml ` や ` ``` ` などのマークダウン記法が含まれる。

## 解決策
**適切なプロンプト設計**
    - Bedrockの出力から不要な説明文やマークダウン記法を排除するため、プロンプトを工夫。
    - 具体的に不要な出力を例としてプロンプトに含める。帰納法的にプロンプトを改善。
    - インデントやコメントの保持を明示的に指示。

## Bedrockのプロンプト設計

Bedrockの出力品質を向上させるために、以下のようなプロンプトを使用しました。

### 例: 不要なマークダウンや説明文を削除
（テンプレートの見た目を整えるプロンプト）

```plaintext
あなたはCloudFormationテンプレートを処理するエキスパートです。入力されたテキストからCloudFormationテンプレートの整形をしてください。

変更禁止の要素：
- リソースの論理ID
- プロパティ名と値
- 3文字以上のコメントアウト(（#）に続いて文字列や記号を含むコメント)の削除

変更可能な要素は以下のみです：
1. インデントの深さ
2. 改行の位置
3. 空白行の追加や削除
4. コメントの配置

出力のルール：
- 各リソース定義の間に空行を1行入れてください
- インデントは厳密に守ってください
- 後続の処理でテンプレートとして利用するため、説明文「テンプレートを出力します」などは一切不要です。
- YAMLコードのみを出力してください
- テンプレート内のコメントアウトは出力してください
- マークダウンの記法（```yaml や ``` など）は完全に削除してください
```

## まとめ
Yamlファイルとして出力するための文字列を整える場合に、不要な処理について具体例を入れて禁止することで、
意図した出力が可能になります。

Bedrockを使ったCloudFormationテンプレート生成の効率化に役立ててください。
Python仮想環境ツール多すぎ！ 一番ベーシックな「venv」から再入門しよう

現代のPython開発環境はpyenv + Poetry（もしくは最近ならuv）を使って管理することが多いと思います。

名前が似ていてややこしいvenvは、これらと違ってPythonに標準搭載されているベーシックな仮想環境ツールです。色々ありますが、まずはこいつを改めて学びましょう。


# venvとは？

こういう、みんなが使う開発ツールは巷にユーザー記事が溢れていますが、こういうものほど最初は公式ドキュメントを読んでみましょう。

https://docs.python.org/ja/3.13/library/venv.html

- venvとは：Pythonのモジュールの一つ
- 軽量な稼働環境の作成を行う。ようは、色んなバージョンのPythonを同じPCの中で混在させて、プロジェクトによって使い分けられる
- インストール済みのPython（ベースPython）の上に仮想環境が作成される
- 仮想環境の中でpip installを行うと、仮想環境内にパッケージが追加される

しかしvenvは公式ページが結構分かりづらいです。公式で詰まったら、ユーザー記事を読むのが良いですね。

https://qiita.com/fiftystorm36/items/b2fd47cf32c7694adc2e

:::note
venvはPythonの中に内包されているモジュールで、パッケージ（pip installしてくるもの）をプロジェクトによって使い分けるために使います。

逆に、Python自体のバージョン管理はできません。
:::


# 基本的な利用方法

インストールは不要。最近のPythonには標準ライブラリとして同梱されている

### venvを使いたいプロジェクトディレクトリに移動する

```zsh
cd ~/sample-pj
```

### 環境を作成する

```zh
python -m venv 環境名
```

環境名は `venv` と名づける人が多いようです。

ちなみに `-m` コマンドはPythonのモジュールを呼び出すときに使うオプションです。みんな大好き `pip` も、いつもは省略形で使われますが、紐づくPythonを正しく指定したいときなどは省略せず `python -m pip install ...` のように実行することができます。

:::note
venvは、pipのようにスタンドアロンのCLIツールとしてはインストールされないため、省略形で実行することはできません。
:::

### 仮想環境を有効化する

```zsh
source 環境名/bin/activate
```

sourceコマンドって分かりづらいですよね。引数に与えたシェルスクリプトを実行するためのコマンドです。

ただシェルスクリプトを実行するだけなら、sourceコマンドなしでも直接起動できるのですが、実行元ターミナルと別に子シェルが作られてしまいます。そのため、シェルスクリプトの目的が「現在のシェルの環境変数の設定」の場合、source経由で実行しないと目的を達成できません。

:::note
よくzshのターミナルに、変更したばかりの個人設定を反映させるときに `source ~/.zshrc` コマンドを使いますね。あのファイルはまさにシェルスクリプトでしたね。かつ、内容は環境変数を設定するコマンドが中心でした。
:::

つまり、上記のactivateというのはシェルスクリプトです。実行すると、起動中のシェルの環境変数を書き換えて、Pythonのインタープリタのパスを仮想環境のものに変更してくれます。

うまくいくと、ターミナルのプロンプト文字（zshなら `%` ）にvenvの環境名が表示されます。


### 仮想環境から抜ける

```zsh
deactivate
```

これはactivate実行時に使えるようになるコマンドです。


# おわりに

このように、venv単体ではPython自体のバージョン管理はもちろん、パッケージのバージョン管理機能もありません。プロジェクトごとにパッケージを分けられるという原始的な機能だけです。

基礎知識としてこれを学んだうえで、実務ではpyenvやPoetry、uvなどを使いこなしましょう。この後、別記事に書こうと思います。


# おまけ

直接関係ないですが、私はMacのターミナルをPureというやつにカスタマイズするのが好きです。

https://qiita.com/minorun365/items/051308093a4d4002f208
Flask + Docker を使った簡単なWebアプリの作成

Flask + DockerはPython開発者にとって鉄板の組み合わせです🔥
Flaskを使ってブラウザでWebページを表示するDockerアプリを一緒に作っていきます！


## ゴール
- Flask を使った簡単なWebアプリを作成
- http://localhost:5000 でブラウザ表示できるようにする


📁 ディレクトリ構成（最小）
```sh
my-flask-app/
├── Dockerfile
├── requirements.txt
└── app.py
```


📄 app.py
```py
from flask import Flask

app = Flask(__name__)

@app.route('/')
def hello():
    return '<h1>Hello from Flask in Docker!</h1>'

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)
```


📄 requirements.txt
```text
flask
```


📄 Dockerfile
```dockerfile
FROM python:3.12-slim

WORKDIR /app

# ライブラリのインストール
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# アプリコードをコピー
COPY app.py .

# ポートを明示（任意）
EXPOSE 5000

# Flaskアプリを起動
CMD ["python", "app.py"]
```

🏗️ ビルド
```sh
docker build -t my-flask-app .
```

🚀 実行
```sh
docker run -d -p 5000:5000 --name flask-app my-flask-app
```


✅ 動作確認
ブラウザで以下にアクセス：
```sh
http://localhost:5000
```
→ Hello from Flask in Docker! が表示されれば成功です🎉



🛑 停止＆削除
```sh
docker stop flask-app
docker rm flask-app
```


### エラーのとき
```sh
docker: Error response from daemon: Ports are not available: exposing port TCP 0.0.0.0:5000 -> 0.0.0.0:0: listen tcp 0.0.0.0:5000: bind: address already in use.
```
このエラーは、ホスト側のポート5000がすでに使われていることを示しています。

✅ 解決方法（いずれかを選択）
🔧 方法①：使ってるプロセスを止める
```sh
lsof -i :5000
```
または macOSの場合：
```sh
sudo lsof -nP -iTCP:5000 | grep LISTEN
```
→ プロセスID（PID）が出たら：
```sh
kill -9 PID番号
```


🔄 方法②：違うポートを使う（例：5001）
```sh
docker run -d -p 5001:5000 --name flask-app my-flask-app
```
→ http://localhost:5001 にアクセス
初心者用terrafrom 基本ディレクトリ構造構成

### モジュール構成で管理するTerraformプロジェクトのベストプラクティス
Terraformを使ってインフラをコードで管理する際、大規模になればなるほど***構成の整理***が重要になります。この記事では、モジュールベースで開発されたTerraformプロジェクトのディレクトリ基本構成について紹介します。

***ディレクトリ構成の概要***
以下は、実際に使用されているTerraformプロジェクトの構成例です：

```
terraform-project/
├── dev/
│   ├── .terraform/
│   ├── main.tf
│   ├── outputs.tf
│   ├── provider.tf
│   └── variables.tf
│
├── prod/
│   └── ...（本番環境用）
│
├── models/
│   ├── apigateway/
│   ├── dynamodb/
│   ├── eventbridge/
│   ├── iam/
│   ├── lambda/
│   ├── s3/
│   └── vpc/]
```
ディレクトリごとの役割
dev/・prod/
環境ごとの構成を分離します。

### dev/：開発環境

### prod/：本番環境

それぞれのディレクトリで main.tf などを用意し、models/ 配下の各モジュールを呼び出して構築します。これにより、同じインフラ構成を異なる環境で再利用可能になります。

## models/
ここには再利用可能なモジュールを配置しています。各リソースはディレクトリ単位で分け、main.tf、variables.tf、outputs.tfで構成されます。

例：models/vpc/
```
# models/vpc/main.tf
resource "aws_vpc" "main" {
  cidr_block = var.cidr_block
  ...
}
```
## variables.tf
```
variable "cidr_block" {
  description = "VPCのCIDRブロック"
  type        = string
}
```
## outputs.tf
```
output "vpc_id" {
  value = aws_vpc.main.id
}
```
このように、モジュールを細かく分けることで以下のメリットがあります：

コンポーネントごとの責務分離

他プロジェクトへの再利用性の向上

チーム開発における変更の影響範囲の限定

### models/lambda/ の構成
Lambdaだけは少し特殊で、ソースコード（index.ts）や依存パッケージ（package.json）も含めています。Terraformから .zip をアップロードする形で管理するため、lambda_function.zip が含まれています。

この構成をとることで、インフラコードとLambda関数のコードを一元管理できます。

## 運用イメージ
`terraform init`（各環境ディレクトリで）

`terraform plan`（変更の確認）

`terraform apply`（適用）

環境ごとに異なる terraform.tfvars や backend.tf を用意することで、ステートの分離や環境別パラメータの管理も可能です。

## まとめ
Terraformプロジェクトをモジュールベースで構築することで、

***環境ごとの分離***

****コードの再利用****

***可読性とメンテナンス性の向上***

といった多くの利点があります。小規模なプロジェクトでもモジュール化の設計を取り入れておくことで、将来的なスケールに強い構成になります。
AWS SAPに合格できたのでやったことを書いてみる

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3194659/2ead2db7-cc0c-485e-a117-4ff1eb9e8d31.png)
## 1. はじめに
- 自己紹介（経験年数、バックグラウンド）
    - ４年目くらいのシステムエンジニア
- 取得済み資格
    - CLF,SAA,DVA,SOA
## 2. 学習計画と方法
- 学習期間と1日あたりの学習時間
    - 期間：1ヶ月半程
    - 時間：1日2,3時間
- 使用した学習リソース
    - CloudLicenseのみ
- 学習方法
    - #6～最後までをひたすら繰り返し解く
    - 時間の都合上、模試は一度も受けませんでした
## 3. 試験当日の体験
- 試験会場での準備と心構え
    - 長丁場になるので、受験前のトイレは忘れずに
- 時間配分の戦略
    - 問題文を1,2回読んでも解けない問題は後で見返して、
    全問題を解けることを優先にしていました。
- 問題の難易度と実際の感想
    - 体感3割ほどCloudLicenseと似た問題でしたが、残りは初見問題でした
    機能自体は知っていたので慌てずに考えたら解けました
## 4. 合格のためのアドバイス
- 効果的だった学習のコツ
    - どうしても覚えられない問題はノートにまとめて終盤見返しました
    - 一度正解した問題でも、理解が浅いうちは繰り返し解いて深めていきました
- 避けるべき学習方法
    - 解答への理解が浅い暗記は避けたほうでいいです
    全く同じ問題はあまりでないため、柔軟に考えれると正答率が上がると思います
## 5. 合格後のキャリアパスと次の目標
- 資格取得メリット
    - クラウド案件の参画チャンスがかなり高くなりました
    SAAと比較すると希少性はやはり高いので、その分評価もされました
- 次に挑戦する資格や学びたい技術
    - AWS自体は全然触っていないので、実務で通用できるよう学習していきます
## 6. まとめ
- 感想
    - 長文だったり試験時間が長かったりと大変ですが、
    受けるだけのメリットは多いかなと感じました。
- これから挑戦する方へのメッセージ
    - SAAと比べてより深く理解する必要があると感じたので、
    教材は解説が多いものがいいと思います。
ラズパイで圧電スピーカーをMIDIで奏でる（HardwarePWMを使う）

# 内容

[以前実装した方法](https://qiita.com/rmatttu/items/5522f1f67fa609ddbed9)ではソフトウェアPWMを使用しているため、雑音の入った音色が出力されます。調査したところ、ハードウェアPWMを使用することで改善できました。ラズパイの設定変更とライブラリのインストール、ソースコードの修正をします。


## ハードウェアPWMを設定する手順


### ラズパイの設定変更と再起動

ラズパイの設定ファイルを追記し再起動することで、ハードウェアPWMを有効にします。

```bash
sudo vim /boot/firmware/config.txt
```

```ini:/boot/firmware/config.txt
dtoverlay=pwm-2chan
```

この設定を追加することにより、GPIO18、19にそれぞれPWMのチャンネル0、1が割り当てられます。

- PWM Channel0: GPIO18
- PWM Channel1: GPIO19

※上記の代わりに以下の設定とすることで、ハードウェアPWMを使用するPINをGPIO12、13へ変更できます。

- PWM Channel0: GPIO12
- PWM Channel1: GPIO13

```ini:/boot/firmware/config.txt
dtoverlay=pwm-2chan,pin=12,func=4,pin2=13,func2=4
```

ラズパイを再起動することで、設定が有効になります。

```bash
sudo reboot
```


### ライブラリのインストール

ハードウェアPWMをPythonから操作するライブラリを導入します。今回も`--break-system-packages`としてインストールしてしまいます。

```bash
pip install rpi-hardware-pwm --break-system-packages
```


### ソースコードの修正

```diff
@@ -6 +6 @@
-import RPi.GPIO as GPIO
+from rpi_hardware_pwm import HardwarePWM
@@ -25,3 +24,0 @@
-# GPIO XX の XXの数値でPIN番号を指定する
-GPIO.setmode(GPIO.BCM)
-
@@ -29,2 +25,0 @@
-GPIO.setup(13, GPIO.OUT, initial=GPIO.LOW)
-p = GPIO.PWM(13, 1)
@@ -40 +34,0 @@
-        p.start(50)
@@ -42 +36,3 @@
-        p.ChangeFrequency(freq)
+        # Rpi 1,2,3,4 は chip=0, Rpi 5は chip=2
+        pwm = HardwarePWM(pwm_channel=1, hz=freq, chip=0)
+        pwm.start(50)
@@ -45 +41 @@
-        p.stop()
+        pwm.stop()
```


ソースコード全体は以下のようになります。

```py
import math
import sys
import time

import mido
from rpi_hardware_pwm import HardwarePWM


def midi_number_to_note_number(midi_number: int):
    """MIDIのnote numberを鍵盤番号へ変換します"""
    # 鍵盤番号: 1:ラ0（A0） = MIDIのnote number: 21
    return midi_number - 20


def onkai(n):
    """鍵盤番号nに応じた周波数[Hz]を返します"""
    # 鍵盤番号は、ラ0（A0）を1とした連番
    # n=1（ラ0（A0））   -> 27.500Hz
    # n=2（ラ#0（A#0）） -> 29.135...Hz
    # n=3（シ0（B0））   -> 30.867...Hz
    # ...
    return 27.500 * math.pow(math.pow(2, 1 / 12), n)


# GPIO 13 = PWM 1 の33番PINに圧電スピーカーの+端子を接続する

# 圧電スピーカーの-端子はGroundならどこでもよい
# 今回は34番PINに接続する


for msg in mido.MidiFile(sys.argv[1]):
    time.sleep(msg.time)
    if msg.type == "note_on":
        # 圧電スピーカー
        freq = onkai(midi_number_to_note_number(msg.note))
        # Rpi 1,2,3,4 は chip=0, Rpi 5は chip=2
        pwm = HardwarePWM(pwm_channel=1, hz=freq, chip=0)
        pwm.start(50)
        print(f"note_on {freq} {msg}")
    if msg.type == "note_off":
        pwm.stop()
        print(f"note_off {msg}")
```

以下のコマンドで実行します。

```bash
python3 midi.py sample.mid
```


## 動作環境

- Raspberry Pi 4 Model B Rev 1.5
- Raspberry Pi OS Lite (64-bit)（Debian GNU/Linux 12.2.0 (bookworm)）
- Python 3.11.2
- mido 1.3.3
- rpi-hardware-pwm 0.2.2
美術系の人に見せる記事と機械学習用語(日記)

美術系の人が機械学習技術の中身に興味をもってくれないのでどうすればいいか、ネタを考えてみた。

https://yorozuipsc.com/blog/12114ai

https://ja.stability.ai/blog/shibi-sdxl

https://www.allow-web.com/flab-net/2024/laboratory/02.html

https://globis.jp/article/58103/

https://qiita.com/omiita/items/ecf8d60466c50ae8295b

https://note.com/chebunbun/n/nc3f9407a0f32



Stable Diffusionは、AIを使って画像を自動生成する技術です。たとえば、テキストで「海辺の風景」や「未来的な都市」と入力すると、それにぴったりな画像が生成されます。この技術の中心にあるのが**深層学習**です。深層学習は、コンピュータに大量の画像やテキストを学ばせ、そこから意味のある画像を作り出す力を持っています。

以下は、Stable Diffusionの中でどのように深層学習が働いているのかを説明します。

---

## 1. **Stable Diffusionとは？**

Stable Diffusionは、AIを使って画像を生成する技術です。ユーザーが入力したテキスト（例えば、「夜の街の風景」）に基づいて、AIがそれに合った画像を生成します。AIは、画像とテキストの関係を学ぶことで、どんなテキストにも適切な画像を作り出すことができるのです。

---

## 2. **Stable Diffusionにおける深層学習の役割**

### (1) **拡散モデル（Diffusion Model）**

#### **深層学習の活用**
Stable Diffusionの核心技術が「拡散モデル」です。これは、ノイズの入った画像から意味のある画像を作り出す方法を学ぶ技術です。具体的には、「U-Net」と呼ばれる深層学習のネットワークがこのプロセスを行います。

#### **仕組み**
- まず、元の画像にランダムなノイズを加えます（画像がどんどんぼやけていきます）。
- 次に、そのノイズを少しずつ取り除き、元の画像を再現します。
- U-Netは、この「ノイズを取り除く」作業を学び、画像を再構成する方法を習得します。

#### **重要性**
深層学習がなければ、膨大な選択肢の中から意味のある画像を選ぶことは非常に難しいです。しかし、深層学習を活用することで、画像を効果的に再構築することができます。

---

### (2) **潜在空間（Latent Space）とVAE**

#### **深層学習の活用**
Stable Diffusionは、VAE（変分オートエンコーダ）という技術を使っています。VAEは、画像を「潜在空間」と呼ばれる圧縮された状態に変換し、そこから元の画像を復元します。この技術は、画像の重要な特徴を保持しつつ、効率的にデータを処理するために使われます。

#### **仕組み**
- **エンコーダ**が画像を小さな潜在表現に圧縮。
- **デコーダ**がその圧縮データから元の画像を復元。

この圧縮と復元を通じて、画像の重要な情報を保ちながら、計算量を減らすことができます。

#### **重要性**
これにより、画像の重要な部分を無駄にせず、効率的に生成できるようになります。

---

### (3) **条件付き生成とCLIP**

#### **深層学習の活用**
Stable Diffusionでは、CLIP（Contrastive Language-Image Pretraining）という技術を使って、テキストと画像の関係を学んでいます。CLIPは、入力されたテキストに基づいて、関連する画像を生成する手助けをします。

#### **仕組み**
CLIPは、画像とテキストの関係を理解し、テキストにぴったりな画像を生成するために使われます。トランスフォーマーという技術が、言葉と画像の意味的なつながりを学びます。

#### **重要性**
深層学習がなければ、テキストと画像の関係を深く理解し、それを基に画像を生成するのは非常に難しいです。CLIPはその理解をAIに与え、画像を正確に生成します。

---

## 3. **深層学習がStable Diffusionを実現する理由**

### (1) **大量データの処理**
Stable Diffusionは、何十億枚もの画像データを使って学習されています。深層学習は、このような大量のデータからパターンや特徴を見つけ出すのが得意です。

### (2) **複雑な関係の把握**
画像とテキストの関連性は簡単にルール化できません。深層学習は、層が多いネットワークを使って、複雑なパターンを学び、現実的で意味のある画像を生成します。

### (3) **生成技術の進化**
以前はGAN（生成対向ネットワーク）が主流でしたが、現在では拡散モデルの方が安定性と多様性で優れているとされています。深層学習の進化により、Stable Diffusionはより効率的で高品質な画像生成を実現しています。

---

## 4. **深層学習技術の具体例**

### (1) **U-Net**
U-Netは、画像のノイズを取り除くためのネットワークで、画像の空間的な特徴を保ちながら、ノイズを除去することができます。

### (2) **トランスフォーマー**
トランスフォーマーは、テキストを処理して画像生成を行う技術です。テキストと画像の関係を理解するために使われています。

### (3) **事前学習**
CLIPやVAEは、事前に大規模なデータで学習されたモデルで、Stable Diffusionに組み込まれています。これにより、生成能力が大幅に向上します。

---

## 5. **GANとの違い**

GAN（生成対向ネットワーク）はかつて画像生成の主流でしたが、Stable Diffusionは「拡散モデル」を採用しています。拡散モデルは、GANよりも画像の安定性と多様性を保ちつつ、より効率的に生成することができます。

### **歴史的な位置づけ**
Stable Diffusionは、深層学習の進化の流れの中で、CNN、GAN、そして拡散モデルへと進化してきた最新技術です。

---

## **結論**

Stable Diffusionは、深層学習なくしては成り立たない技術です。画像を学習し、テキストと画像をつなげ、効率的に生成するプロセスを実現しているのはすべて深層学習によるものです。深層学習は、ただのツールではなく、Stable Diffusionの技術そのものを支える核心的な要素です。
TypeScript で LangGraph 入門① ～LangChain と LangGraph～

# はじめに

最近 LangGraph を使って AI エージェントを開発する機会があり、ある程度知見が溜まってきたので、少しずつ紹介していこうと思います。

LangChain や LangGraph に興味がある方や、TypeScript で AI エージェントを作ってみたい方の参考になれば幸いです。

この記事では LangChain と LangGraph について、それぞれ簡単にどのようなライブラリなのか紹介します。

# LangChain とは

LangGraph は LangChain を拡張するライブラリなので、先に LangChain について説明します。

LangChain は ChatGPT などの LLM を組み込んだアプリケーションを 開発 するためのライブラリです。

https://www.langchain.com/

LangChain は以下のように、鎖のように処理をつなげていくことでアプリケーションを作成します。

```ts
const chain = prompt.pipe(処理1).pipe(処理2).pipe(処理3)
```

```mermaid
graph LR;
    A(処理1)-->B(処理2);
    B-->C(処理3);
```

様々な OpenAI の GPT系モデルや Anthropic の Claude などの API に対応したライブラリが用意されていて、モデルの種類を問わず同じような記述で使用できます。

```ts:GPT-4o
import { ChatOpenAI } from "@langchain/openai"

const model = new ChatOpenAI({
  apiKey: process.env.OPENAI_API_KEY,
  modelName: "gpt-4o",
})
const response = await model.invoke(new HumanMessage("Hello world!"))
```

```ts:Claude 3.7
import { ChatAnthropic } from "@langchain/anthropic"

const model = new ChatAnthropic({
  apiKey: process.env.ANTHROPIC_API_KEY,
  model: "claude-3-7-sonnet-20250219",
})
const response = await model.invoke(new HumanMessage("Hello world!"))
```

# LangGraph とは

LangGraph は LangChain より柔軟に LLM を組み込んだアプリケーションが作成できるライブラリです。

https://www.langchain.com/langgraph

LangChain が鎖状にアプリケーションを作成していくのに対し、 LangGraph はグラフ（有効グラフ）を作るようにアプリケーションを作成していきます。

下記の例では初めに `agent` ノードが実行され、条件に応じて `tools` ノードか `__end__` (終了ノード) に分岐し、`tools` ノード に分岐した場合は `tools` ノード実行後、また `agent` ノードが実行されます。

```ts
const workflow = new StateGraph(MessagesAnnotation)
  .addNode("agent", callModel)
  .addEdge("__start__", "agent")
  .addNode("tools", toolNode)
  .addEdge("tools", "agent")
  .addConditionalEdges("agent", shouldContinue)
```

```mermaid
graph TD;
        __start__([<p>__start__</p>]):::first
        agent(agent)
        tools(tools)
        __end__([<p>__end__</p>]):::last
        __start__ --> agent;
        tools --> agent;
        agent -.-> tools;
        agent -.-> __end__;
```

ちなみに、上記の図は LangGraph の機能で自動で出力したものです。（色だけ変更しました）

```ts
const app = workflow.compile()
app.getGraphAsync().then((graph) => console.log(graph.drawMermaid()))
/*
$ tsx main.ts
%%{init: {'flowchart': {'curve': 'linear'}}}%%
graph TD;
        __start__([<p>__start__</p>]):::first
        agent(agent)
        tools(tools)
        __end__([<p>__end__</p>]):::last
        __start__ --> agent;
        tools --> agent;
        agent -.-> tools;
        agent -.-> __end__;
        classDef default fill:#f2f0ff,line-height:1.2;
        classDef first fill-opacity:0;
        classDef last fill:#bfb6fc;
*/
```

LangGraph は Python と JavaScript(TypeScript) に対応しています。

https://github.com/langchain-ai/langgraph

https://github.com/langchain-ai/langgraphjs

以降の記事では TypeScript で LangGraph を使って AI エージェントを開発するために必要な知識を紹介していきます。

次の記事: LangGraph の 状態管理（予定）
Qiitをgit管理するようにしてみた

# 気になっていたQiitのGitHub管理

## ついにGitHubでQiitaの記事管理
を始めてみました。
Node.jsを使って記事を管理することで、編集の履歴とか残るかな良いかなと思い初めて見ましたが・・・

結構動きがおもしろいですね。

今この記事もGitHubにPUSHしています。

設定方法は、Qiita公式の記事で十分に完了できるのでお試しでしてみるのは十分ありですね。

実際にWeb画面開かなくても書きながらPUSHして記事が投稿できるのは便利です。

ブランチを切ってプルリクエストしてメインにマージしたりすればレビューしつつ使える・・・？

会社技術ブログとして投稿したいとなったときに複数人でうまく機能できないかまずは個人でいろいろ触ってみます！

では、このあたり使いながら記事にして行きます！
（すでに誰かがやっているとは思いますが、試行錯誤できたらいいですねぇ）
C#でIoTプログラミング on ESP32 #11 - BMP280

気圧・温度センサーBMP280のサンプルプログラムの紹介です。

## ハードウェア
・Freenove ESP32-S3-WROOM Board or Espressif ESP32-S3-DevKitC
・Adafruit Pressure Sensor BMP280

## 配線
![BMP280.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/62606/02ee1cfa-16b1-422d-a6c6-ffb6506703bc.png)

## 導入パッケージ
・nanoFramework.Hardware.Esp32
・nanoFramework.System.Device.I2c
・nanoFramework.Iot.Device.Bmxx80

## プログラミング
```C#:Program.cs
using Iot.Device.Bmxx80;
using nanoFramework.Hardware.Esp32;
using System;
using System.Device.I2c;
using System.Threading;

namespace ESP32S3_BMP280
{
    public class Program
    {
        public static void Main()
        {
            Configuration.SetPinFunction(8, DeviceFunction.I2C1_DATA);
            Configuration.SetPinFunction(9, DeviceFunction.I2C1_CLOCK);

            I2cConnectionSettings settings = new(1, 0x76, I2cBusSpeed.FastMode);
            I2cDevice device = I2cDevice.Create(settings);
            using var bmp280 = new Bmp280(device);

            while (true)
            {
                bmp280.TemperatureSampling = Sampling.HighResolution;
                bmp280.PressureSampling = Sampling.HighResolution;
                var readResult = bmp280.Read();

                Console.WriteLine($"Temp: {readResult.Temperature.DegreesCelsius.ToString("F2")}℃, Pres: {readResult.Pressure.Hectopascals.ToString("F2")} hPs");

                Thread.Sleep(1000);
            }
        }
    }
}
```