技術をかじる猫

適当に気になった技術や言語、思ったこと考えた事など。

VRゲームに自分の姿を好きなキャラモデルで表示して撮影する

目標

この記事で目指すところ

youtu.be

事前準備

以上です。

ざっくり流れ

  1. VRゲームと LIV を入れる
  2. VRM モデルを用意する
  3. VirtualMotionCapture を突っ込む
  4. LIV に VirtualMotionCapture 映像を設定
  5. OBS で録画する

1. VRゲームと LIV を入れる

基本的に Steam 対応の VR ならそれなりにいけるっぽい。
ということで BeatSaber

store.steampowered.com

加えて、LIV (無料)を入れる

store.steampowered.com

ちょっと説明すると、LIV というツールは以下の機能がある。

  1. VR ゲーム中にカメラを配置してゲーム内の映像を表示できる
  2. 現実のWebカメラの映像を取り込む
  3. 上記二つの映像をクロマキーで合成する

起動順序は LIV が先で、「LIV コンポジターをスタートする」(コンポジッターとは、合成機って意味)

f:id:white-azalea:20200829122241p:plain

起動するとこんな感じ

f:id:white-azalea:20200829123006p:plain

「シンクロとスタート」を押すと、ゲームが起動します。
この時真っ黒の画面に映像が映ります。
これが LIV のVR中カメラ映像ですね。

f:id:white-azalea:20200829124913p:plain

ここでカメラ調整とかしたくないので、ゲームだけ修了します。
実写映像を合成するならこのままできますが…まぁ放置で。

2, VRM モデルを用意する

手段はいくつかありますが

  • VroidStudio とかで自作する
    https://vroid.com/studio/
  • Unity の Humanoid モデルを変換する
    VRM モデルをガチで作りたい人は コレ 見て頑張ってください。
    俺はあきらめたよ…
  • VroidHub から適当なフリー VRM を拾ってくる。
    https://hub.vroid.com/
  • BoothVRM のモデルを買う
    因みに、VroidStudio で使えるテクスチャとか有償無償含めて転がってるので、見てみるといい
  • ニコニコ立体 からライセンスを確認しつつ使う
    ライセンスに関しては慎重に。私はサイトは紹介しますが、利用は自己責任で。

因みに私は Booth から素材を集めつつ、VroiStudio で自作しました。
VroidStudio で一旦エクスポートした VRM モデルは編集できないので、服装とか変える場合はその度にエクスポートしましょう。

続きを読む

『「考える技術」と「地力頭」がいっきに身につく東大思考』を読んでみた

超ざっくり概要

この本は、東大生のような優秀な頭脳といわれる人間たちがどの様に思考し、また自分たちがどうすればその様な考え方ができるのかを教えてくれる本である。

超ざっくり要点

要点1 : 生まれ持った才能ではない

東大生は生まれがすごいとか、持って生まれた才能とかではない。
考え方が違うから、芋づるで学習効率がバカ高いのだ。

凡人とてそれが一切出来ない訳ではない。
意識的に練習していくことでそれを鍛えることができる。

要点2: 物事の本質をとらえることができるから学習効率もいいし、問題解決能力だって高い

物事や情報には必ず本質がある。
歴史的出来事には背景や原因があるし、単語なら派生元の語源の意味に引きずられるし、手段の前に目的はあり、ひらめきの前に視点がある。

本質を捉えることで、記憶も応用も解決もやりやすくなる。
そのためには以下の思考方法を行う。

  • 原因思考: すべての事に原因があると考え深掘りすること
  • 上流思考: すべての出来事にはそれに至る経緯が存在することを理解し、源流を見つけること
  • 目的思考: すべての手段には目的が存在するので、目的を追う事で、どうしてその手段を取るのかを理解する
  • 裏側思考: ある情報に対して、必ず複数の視点(極論すると対立・反対の視点)で検証する

感想

実践や練習課題大好き、実際に行ってみて覚えるタイプの人にハマり。
(というか自分がそれ系なので、体感できる系の本しか気力が沸かない)

技術書を読むような人(技術書展行くような人)なら、読んだ後直ぐに試したくなる系の本です。
内容も練習課題的なものもあって、実際に調べながら・体感しながら進められるので、やり方説明も十分してくれます。

Platform Development Basics

やったのコレ。で、課題がなんとなくどういう事してるのかって内容を要約してみる。

trailhead.salesforce.com

Get Started with the Salesforce Platform

ものすごく要約すると、Salesforce はSales CloudやService CloudなどのコアSalesforce製品アプリケーションを提供するだけじゃなくて、アプリケーションのカスタム、独自アプリの構築、アプリケーションの販売もろもろを提供するプラットフォームである。
既存のアプリケーションをカスタムする方向と、ゼロからアプリを作るパターンがあり、Salesforceプラットフォームが開発者は後者。

開発も Salesforce 上だけでPC/スマホ/タブレット向けのアプリ開発サポートするほか、Heroku と連携して好きな言語でひゃっほーもできる。
つーかデフォルトサポートが Apex とかいうク〇言語なわけだが…Salesforce APIなんてのもあって、外から呼べるし、モバイルSDKとかもあるので、それを使ってSalesforceをバックエンドにネイティブアプリも作れるぞい。

まーとりあえず DreamHouse なるアプリをインストール。

f:id:white-azalea:20200803211132p:plain f:id:white-azalea:20200803211213p:plain f:id:white-azalea:20200803211249p:plain

分かりやすいね。

因みに、パッケージIDが分かってるときの方法で、AppExchange というストアを通さずにパッケージインストールするための手段だ。
普通は AppExchange でクリックベースでインストールできるけど、開発練習用だからかね。

インストールが終わるとアプリにアクセスできる

f:id:white-azalea:20200803211557p:plain

指示としては DataImport タブ動かせってんで

f:id:white-azalea:20200803211657p:plain f:id:white-azalea:20200803211730p:plain

でサンプルデータを初期化っと…

Develop Without Code

このセクションの全力要約。

  • オブジェクト: TDB でいうテーブル定義
  • フィールド: RDB でいうところのカラム
  • レコード: RDBでいう row

標準オブジェクトは専用の名前があるだけで、基本的にはこの枠に収まってる。
で、こうした抽象化したレコードのデータをメタデータいいますねん。

メタデータは組織の構造を形成する。フィールド、ビジネスプロセス、またはより複雑なものを定義する場合でも、メタデータは構成を保持する。
で、このCRUD とかその他もろもろをメタデータを中心に開発することを メタデータ駆動開発 と呼んでる。

つーかメタデータには表示レイアウトや、更新画面設定とかも含まれてて、基本的なDB関連だけならマジでコードがいらない。

オブジェクトマネージャでオブジェクトの管理もできるけど、GUIで操作できるSchemaBuilder

f:id:white-azalea:20200803213347p:plain

WYSIWYG で画面のレイアウトを作成できる LightningAppBuilder

f:id:white-azalea:20200803213509p:plain

データの入力チェックや、データベーストリガ的なことができる ProcessBuilder

f:id:white-azalea:20200803213747p:plain

とかできる。
余程複雑なアプリでない限りはこれだけでアプリが作れる。

(っつーのも業務アプリって結局のところ、どうデータを入れて、データを加工して、どう見せるかってのが殆どだから)

Code with Salesforce Languages

もちろんコードも書ける。
手段はいくつかあるが

  • Lightning Components : Javascript + HTML + JSON で開発する独自 MVC フレームワークを持ったアプリケーション開発方法。クライアント側で動作。クライアントにロジック書く是非はあるかなと思ってる。
  • Apex : まじでハゲそうな位 Java1.4 以下な開発言語。
  • Visualdorce : Apex のフロントエンドで使う、イメージ的には ASP/JSP 敵な画面テンプレート。

一応ほかにも

なんてのもある。

Extend the Salesforce Platform

コード中にしれっとSQLライクなコードを書くことができる。
これを SOQL といって、類似で SOSL (全文検索とか別の検索体系を持った言語)もある。

Property__c property = [SELECT Name, Price__c from Property__c WHERE Id=:propId];

ただし、サブクエリは SELECT 内はリレーションを張ってるもののみ。
WHERE には、リレーションのあるものしか記述不可という徹底ぶり。

まぁ開発者の腕によってクッソ遅いクエリ書かれる位なら、必要最低限で止めたれってのはある意味で理にかなってるかな…

因みにAPIにはこんなんあるで

  • SOAP API
  • REST API
  • Metadata API: モデル情報を編集できるAPI
  • Tooling API: アプリケーションカスタムビルドするためのツール
  • Marketing Cloud API: REST APIを使用してMarketing Cloud機能を公開し、SOAP APIを使用してほとんどのメール機能に包括的にアクセスできます。[
  • Bulk API: 大量のデータを入出力するためにストリーム的にデータを扱うAPI
  • Streaming API: 内部データの変更などの通知を受け取ったり、何らかのイベントをたたくためのAPI
  • Chatter REST API : Chatter 機能を操作する API Chatter, Communities, Recommendations, Files, Topics, and more.
  • Mobile SDK: ネイティブまたはハイブリッドモバイルアプリケーションをSalesforceと連携させるためのSDK

Heroku であれば、DBとSalesforce オブジェクトをシームレスに同期するとかそうした機能がデフォルトサポートしてるし、数々なインターフェースから IoT と連携したり、Salesclowd のチャット+チャットボット(EinsteinBOT)なんかもあり、その機能を拡張するなんてこともできると。

家製協(AEHA)フォーマットを 16 進数で読み取ってみた

white-azalea.hatenablog.jp

ここで読み取ったエアコン起動コードをそのまま送りつけてもうまくエアコンがつかなかったので、フォーマットを解読できるのかやってみた。
お題は送りつけに失敗した下記

[3531, 1700, 448, 415, 446, 1289, 453, 409, 452, 411, 451, 410, 451, 412, 448, 414, 448, 414, 446, 416, 445, 417, 444, 418, 453, 409, 452, 411, 450, 1285, 447, 415, 446, 416, 446, 417, 453, 408, 453, 409, 452, 411, 450, 412, 449, 1286, 447, 1289, 453, 1283, 449, 413, 448, 414, 448, 1288, 455, 407, 453, 409, 452, 411, 450, 412, 448, 414, 447, 415, 446, 416, 445, 417, 444, 418, 453, 410, 451, 410, 452, 410, 451, 411, 450, 413, 447, 415, 447, 415, 445, 418, 443, 418, 453, 409, 452, 410, 451, 411, 450, 412, 449, 414, 451, 411, 446, 416, 445, 417, 444, 423, 448, 409, 452, 410, 453, 409, 450, 1286, 448, 414, 447, 1289, 453, 409, 452, 410, 454, 408, 449, 414, 448, 413, 447, 420, 441, 417, 444, 1292, 450, 1285, 448, 1287, 445, 418, 443, 419, 452, 1283, 449, 414, 447, 415, 447, 415, 445, 417, 445, 417, 454, 408, 452, 412, 449, 412, 449, 413, 448, 415, 446, 415, 446, 416, 445, 417, 444, 418, 454, 409, 451, 411, 450, 413, 448, 1286, 446, 1289, 453, 410, 452, 411, 449, 412, 449, 1286, 447, 417, 443, 418, 453, 1282, 451, 1286, 446, 416, 445, 417, 444, 418, 454, 409, 452, 410, 450, 412, 449, 413, 448, 414, 447, 415, 446, 1290, 453, 1282, 450, 1286, 446, 1290, 453, 1282, 450, 413, 448, 1288, 445, 417, 443, 1292, 451, 412, 449, 413, 448, 414, 447, 415, 446, 417, 444, 418, 453, 410, 451, 410, 451, 411, 450, 413, 448, 414, 447, 415, 446, 417, 444, 417, 444, 418, 453, 409, 452, 410, 451, 411, 450, 412, 449, 413, 448, 414, 447, 415, 446, 417, 444, 418, 443, 419, 453, 1283, 449, 1286, 446, 416, 445, 417, 444, 418, 453, 410, 451, 411, 450, 412, 449, 413, 448, 414, 446, 416, 445, 417, 444, 418, 443, 419, 452, 410, 451, 411, 450, 412, 449, 414, 447, 415, 446, 416, 449, 413, 444, 419, 453, 409, 452, 414, 447, 411, 450, 413, 448, 1287, 444, 418, 443, 420, 453, 408, 451, 411, 451, 411, 450, 413, 448, 414, 446, 416, 445, 417, 444, 1292, 451, 1284, 447, 416, 450, 1285, 453, 409, 452, 410, 451, 411, 450, 1286, 446, 1289, 454, 410, 451, 411, 450, 1285, 447, 415, 447, 416, 444, 1291, 452]

8T4T で始まってるはずなので調べてみると

>>> 3531 / 8
441.375
>>> 1700 / 4
425.0

あれ?
実際には T は T = 350~500μs とか抜かしてるので、ヘッダが 1T という仕様に合わせて、その値を使ってみるか

data = input_code[2:]
bit_header = data[0::2]
T = sum(bit_header) / len(bit_header)
print('T = ' + str(T))

としたら T = 448.6102564102564 みたいなので、 449 で割ってみると

>>> 3531 / 449
7.864142538975501
>>> 1700 / 449
3.7861915367483294

四捨五入すればこんなもんかと思わなくもない数字。
この論理で読んでいくか

ということで読んでみた

読み取りしてみたコードは次の通り

input_code = [3531, 1700, 448, 415, 446, 1289, 453, 409, 452, 411, 451, 410, 451, 412, 448, 414, 448, 414, 446, 416, 445, 417, 444, 418, 453, 409, 452, 411, 450, 1285, 447, 415, 446, 416, 446, 417, 453, 408, 453, 409, 452, 411, 450, 412, 449, 1286, 447, 1289, 453, 1283, 449, 413, 448, 414, 448, 1288, 455, 407, 453, 409, 452, 411, 450, 412, 448, 414, 447, 415, 446, 416, 445, 417, 444, 418, 453, 410, 451, 410, 452, 410, 451, 411, 450, 413, 447, 415, 447, 415, 445, 418, 443, 418, 453, 409, 452, 410, 451, 411, 450, 412, 449, 414, 451, 411, 446, 416, 445, 417, 444, 423, 448, 409, 452, 410, 453, 409, 450, 1286, 448, 414, 447, 1289, 453, 409, 452, 410, 454, 408, 449, 414, 448, 413, 447, 420, 441, 417, 444, 1292, 450, 1285, 448, 1287, 445, 418, 443, 419, 452, 1283, 449, 414, 447, 415, 447, 415, 445, 417, 445, 417, 454, 408, 452, 412, 449, 412, 449, 413, 448, 415, 446, 415, 446, 416, 445, 417, 444, 418, 454, 409, 451, 411, 450, 413, 448, 1286, 446, 1289, 453, 410, 452, 411, 449, 412, 449, 1286, 447, 417, 443, 418, 453, 1282, 451, 1286, 446, 416, 445, 417, 444, 418, 454, 409, 452, 410, 450, 412, 449, 413, 448, 414, 447, 415, 446, 1290, 453, 1282, 450, 1286, 446, 1290, 453, 1282, 450, 413, 448, 1288, 445, 417, 443, 1292, 451, 412, 449, 413, 448, 414, 447, 415, 446, 417, 444, 418, 453, 410, 451, 410, 451, 411, 450, 413, 448, 414, 447, 415, 446, 417, 444, 417, 444, 418, 453, 409, 452, 410, 451, 411, 450, 412, 449, 413, 448, 414, 447, 415, 446, 417, 444, 418, 443, 419, 453, 1283, 449, 1286, 446, 416, 445, 417, 444, 418, 453, 410, 451, 411, 450, 412, 449, 413, 448, 414, 446, 416, 445, 417, 444, 418, 443, 419, 452, 410, 451, 411, 450, 412, 449, 414, 447, 415, 446, 416, 449, 413, 444, 419, 453, 409, 452, 414, 447, 411, 450, 413, 448, 1287, 444, 418, 443, 420, 453, 408, 451, 411, 451, 411, 450, 413, 448, 414, 446, 416, 445, 417, 444, 1292, 451, 1284, 447, 416, 450, 1285, 453, 409, 452, 410, 451, 411, 450, 1286, 446, 1289, 454, 410, 451, 411, 450, 1285, 447, 415, 447, 416, 444, 1291, 452]

# ※ リピートデータ(8T, 8T データ)は考慮してないのであしからず

# 家製協(AEHA)フォーマット
# 参考の単位フレーム
T = 445

# ヘッダ部分
header = input_code[:2]
# print([int(n / 440) for n in header])

# データ部分
data = input_code[2:]

# bit header and bit body
bit_header = data[0::2]
bit_body = data[1::2]

# ヘッダは 1T と決まってるので、平均値から T の正式な数字を探してみる
T = int(sum(bit_header) / len(bit_header))
print('T = ' + str(T))

def extract_bit(num: int) -> str:
    '''
    与えられたデータを家製協(AEHA)フォーマット的なビット判定をして文字で返す
    1=1T1T, 0=1T3T で、偶数番目ビットだけ長さを見ればよく、2T 以上 = 0 とみなせば良さそう?
    '''
    return '1' if num > (T * 2) else '0'


def extract_to_bits(half_bite_arr: list) -> str:
    bit_str = ''.join([extract_bit(n) for n in half_bite_arr])
    #print(bit_str)
    return bit_str


def extract_halfbyte(bitList: list) -> list:
    '''
    与えられた赤外線ビット列を 4 ビット単位(16進数1文字)で整数リスト化する。
    余りがあると警告文字列を表示
    '''
    num_of_byte = int(len(bitList) / 4)
    mod_bits = len(bitList) % 4
    result = []
    for n in range(0, num_of_byte):
        half_bite_arr = bitList[n*4: n*4+4]
        half_byte_bits = extract_to_bits(half_bite_arr)
        #print(half_byte_bits)
        result.append(int(half_byte_bits, 2))
    if mod_bits > 0:
        mod_bit_arr = bitList[-mod_bits:]
        bit_bit_binary = ''.join([extract_bit(n) for n in mod_bit_arr])
        print('Exists cant extract bits: ' + str(mod_bit_arr) + '(' + bit_bit_binary + ')')
    return result


def as_hex_str(hex_arr):
    '''
    数字配列を 16 進数で応答する
    '''
    hex_str = ''
    for i in range(0, len(hex_arr)):
        hex_str += hex(hex_arr[i])[2:]
        if i % 2 == 1:
            hex_str += ' '
    return hex_str


# 16bitのカスタマーコード
customer_bits = bit_body[:16]
# print(len(customer_bits))
# 16 進数カスタマーコード
hex_customer = extract_halfbyte(customer_bits)
print('customer code: ' + as_hex_str(hex_customer))

# 4 bit のパリティ
parity_bits = bit_body[16:20]
parity_bit_str = extract_to_bits(parity_bits)
print('parity bits  : ' + parity_bit_str)

# その後のデータ
other_data = bit_body[20:]
hex_others = extract_halfbyte(other_data)
print('other code   : ' + as_hex_str(hex_others))

その結果がコレ

T = 448
customer code: 40 04 
parity bits  : 0000
Exists cant extract bits: [416, 1291](01)
other code   : 72 00 00 00 05 01 c8 00 03 13 00 7d 40 00 00 18 00 00 04 01 a3 2

25 度という情報はどこいったのか…

単純にビット配列文字列にするとこんな感じ
011100100000000000000000000000000000010100000001110010000000000000000011000100110000000001111101010000000000000000000000000110000000000000000000000001000000000110100011001001

仕様書見ないとデータ部の構成はわからんね…

改めて逆変換

上記コードでバイナリ文字列抽出してしまえば結構簡単に欲しい情報は取れる

binary_str = '011100100000000000000000000000000000010100000001110010000000000000000011000100110000000001111101010000000000000000000000000110000000000000000000000001000000000110100011001001'

T = 448

def enc_bit2sig(bit: str) -> list:
    return [T, T] if bit == '0' else [T, 3*T]

# ヘッダシグナル
header_sig = [8*T, 4*T]
vender_sig = []

all_sig = header_sig[:]

# ベンダーコード
for b in '0100000000000100':
    all_sig += enc_bit2sig(b)

# ベンダーパリティ
for b in '0000':
    all_sig += enc_bit2sig(b)

# データ部
for b in binary_str:
    all_sig += enc_bit2sig(b)

print(all_sig)

とすると

[3584, 1792, 448, 448, 448, 1344, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 1344, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 1344, 448, 1344, 448, 1344, 448, 448, 448, 448, 448, 1344, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 1344, 448, 448, 448, 1344, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 1344, 448, 1344, 448, 1344, 448, 448, 448, 448, 448, 1344, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 1344, 448, 1344, 448, 448, 448, 448, 448, 448, 448, 1344, 448, 448, 448, 448, 448, 1344, 448, 1344, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 1344, 448, 1344, 448, 1344, 448, 1344, 448, 1344, 448, 448, 448, 1344, 448, 448, 448, 1344, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 1344, 448, 1344, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 1344, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 1344, 448, 1344, 448, 448, 448, 1344, 448, 448, 448, 448, 448, 448, 448, 1344, 448, 1344, 448, 448, 448, 448, 448, 1344, 448, 448, 448, 448, 448, 1344]

コレを書き込んで見たが…
認識されずか…なんかノイズでも拾ってるのかなーと、cirguitplayground を読み取りモードでリモコンも受けずに放置してると…

Heard 7 Pulses: [181, 1679, 178, 5799, 188, 2765, 181]
----------------------------
Heard 1 Pulses: [219]
----------------------------
Heard 1 Pulses: [180]
----------------------------
Heard 1 Pulses: [171]
----------------------------
Heard 1 Pulses: [171]
----------------------------
Heard 9 Pulses: [276, 249, 227, 812, 180, 1011, 704, 5838, 2286]
----------------------------
Heard 5 Pulses: [625, 550, 2179, 5754, 2192]
----------------------------
Heard 7 Pulses: [624, 459, 681, 387, 1191, 5821, 1815]
----------------------------
Heard 3 Pulses: [181, 5201, 4674]
----------------------------
Heard 5 Pulses: [578, 265, 2587, 106, 1378]
----------------------------
Heard 1 Pulses: [4267]
----------------------------

誰だおまえええええ(TT

結局短いコードでもないとノイズ拾って正しい設定を拾えないご様子…。
物理的な問題となると物理で解決…となるのだろうけど、真面目にリモコン操作しようと思ったら rasberrypi にリモコンモジュール買ってきて組み込んだ方が良さそうだ…

Salesforce プラットフォーム基礎

Salesforce 関連企業に転職して1年、延々開発ばかりやってきた結果、開発以外知らん状況になってきたので、管理側の勉強。
2日に1バッチ分やればいいかな…

Salesforce admin ってなんやろ?

Trailhead はまともに日本語化してるといい難い状況にあるので、読めるなら英語の方が良い…というか日本語だと正解できない場合すらあるという(汗

Salesforce は名前の通りセールスに必要なものを事前に大体持ってます。
なんのプラグインを入れてなくても、少なくともこれだけのことができます。

  • 社員管理
  • 顧客管理
  • 取引先管理
  • 商談管理
  • ビジネスプロセス、フロー管理

会社につきものの ERP って結構こういうの管理してるはずで、Salesforce はこれを用意してくれてる。
で何が強烈かって、クラウド上、つまり設定次第でどこからでもしれっと入って作業できる。 (当然権限管理その他もあります)

で、日本あるあるは、企業ごとの独自仕様。
日本の ERP が数年ごとに焼き直しされるのは、ビジネスプロセスが変わるたびにゼロから構築なんてしてるから。

でも Salesforce であれば、そこそこの内容をノンコードで強力にカスタムできるということ。
そうした開発を伴わないカスタム方法が admin の基本ルートっぽい。

因みに、何か専用の機能欲しいなーと思ったら、 AppExchange でソフトがある。

appexchangejp.salesforce.com

サブスクリプションサービスするにあたって利用から請求の管理するなら soasc とか…

お題

取引先責任者の情報に、「ローン金額(Loan Amount)」を追加する。

ということでしれっとやってみる

  1. 設定を開いて
    f:id:white-azalea:20200727204954p:plain
  2. 「オブジェクトマネージャ」を探して選択
    f:id:white-azalea:20200727205056p:plain
  3. 「取引先責任者(英名: Contract)」を探して選び
    f:id:white-azalea:20200727205210p:plain
  4. 「項目とリレーション」を選んで追加
    f:id:white-azalea:20200727205444p:plain
  5. 金額を選んで
    f:id:white-azalea:20200727205650p:plain
  6. フィールド名に「Loan Amount」を指定して保存
    f:id:white-azalea:20200727205809p:plain
  7. アクセス範囲を指定して
    f:id:white-azalea:20200727205913p:plain
  8. 画面レイアウトに追加して終了
    f:id:white-azalea:20200727210031p:plain

ここまですると、取引先責任者に項目が追加される。

f:id:white-azalea:20200727210311p:plain

これを元に取引データとかあればレポートとかいろいろやれますが…今やってるモジュールはここまで

Circuit Playground Express で赤外線読み取り

環境

  • Circuit Playground Express (Bootloader3.10.0)
  • Circuit Python v5

エアコンの電源を入れるコードを読み取る

import pulseio
import board
import adafruit_irremote

pulsein = pulseio.PulseIn(board.REMOTEIN, maxlen=120, idle_state=True)
decoder = adafruit_irremote.GenericDecode()

while True:
    pulses = decoder.read_pulses(pulsein)
    print("Heard", len(pulses), "Pulses:", pulses)
    try:
        code = decoder.decode_bits(pulses)
        print("Decoded:", code)
    except adafruit_irremote.IRNECRepeatException:  # unusual short code!
        print("NEC repeat!")
    except adafruit_irremote.IRDecodeException as e:     # failed to decode
        print("Failed to decode: ", e.args)

    print("----------------------------")

まずは赤外線読み取り

Heard 310 Pulses: [3504, 1726, 453, 409, 451, 1285, 447, 416, 445, 417, 444, 418, 411, 450, 412, 448, 414, 447, 415, 446, 416, 445, 417, 444, 419, 452, 410, 451, 411, 455, 408, 449, 412, 448, 414, 447, 419, 441, 417, 445, 418, 442, 420, 451, 411, 451, 412, 449, 412, 448, 415, 447, 415, 445, 417, 444, 418, 443, 419, 452, 411, 450, 1284, 449, 1287, 444, 418, 443, 420, 451, 410, 451, 412, 449, 411, 450, 9967, 3505, 1725, 444, 418, 452, 1284, 448, 414, 446, 416, 445, 417, 444, 418, 443, 421, 451, 410, 450, 411, 450, 413, 448, 414, 447, 415, 446, 416, 444, 1291, 452, 411, 450, 412, 448, 414, 447, 416, 445, 416, 445, 417, 444, 418, 443, 1293, 449, 1286, 446, 1290, 452, 410, 451, 411, 454, 1284, 444, 416, 445, 417, 444, 418, 443, 419, 452, 410, 451, 411, 450, 412, 449, 413, 447, 415, 446, 417, 444, 418, 443, 419, 452, 410, 451, 416, 446, 415, 445, 418, 443, 419, 452, 411, 450, 411, 450, 412, 449, 413, 448, 414, 447, 415, 445, 417, 444, 418, 453, 1283, 449, 1286, 446, 417, 444, 417, 443, 420, 452, 410, 451, 411, 451, 411, 449, 414, 448, 414, 446, 416, 445, 417, 443, 1293, 451, 1284, 447, 415, 445, 418, 443, 418, 453, 409, 452, 410, 451, 411, 450, 412, 448, 414, 451, 411, 446, 416, 445, 418, 443, 423, 449, 410, 451, 410, 450, 412, 449, 413, 448, 415, 446, 416, 445, 417, 444, 418, 454, 408, 453, 409, 451, 412, 449, 413, 448, 1288, 445, 417, 453, 408, 453, 410, 451, 412, 449, 412, 449, 414, 447, 415, 446, 416, 445, 417, 443, 1292, 451, 1285, 447, 415, 446, 1290, 452, 411, 450, 411, 450, 412, 449, 414, 447, 415, 447, 1288, 453, 409, 452, 1284, 450, 412, 448, 415, 445, 1290, 453]
Failed to decode:  ('Pulses outside mark/space',)
----------------------------

な…なげぇ…と思ったら、

TVや照明のリモコンが「どのボタンを押されたのか」という情報だけを赤外線信号で送っているのに対し、エアコンのリモコンは、以下の画像のような「リモコンの液晶に表示されている全ての情報」を毎回送っているためです。つまり、同じ「温度を下げる」ボタンを押した場合でも、それまでの操作によって送信される信号が異なります。

[https://qiita.com/gorohash/items/598d69a63bd6b4308291:title]

こ…これは…w

気を取り直して読み取ってみる。この数字は on-off のそれぞれの時間を記録してて、「3542 マイクロ秒光って、1703 マイクロ秒消えて…」てなかんじ。
で単位時間はおよそ 400 ms 位(この辺の値が多い)となると、「家製協(AEHA)フォーマット」と予測。基本単位が 425μs なので合ってそう。

ナルホドこうして操作するのか…理解はしたけど、目的はエアコンの On/Off できればいいので、気温 25 度で on がとれたならいっかー

ということで Off も記録する。

[3536, 2, 479, 383, 478, 384, 477, 387, 474, 386, 475, 387, 474, 389, 472, 390, 481, 381, 480, 383, 478, 383, 478, 385, 476, 385, 476, 388, 473, 388, 473, 389, 472, 391, 480, 381, 481, 381, 479, 384, 477, 386, 475, 386, 480, 382, 476, 388, 472, 389, 472, 390, 481, 381, 481, 382, 448, 413, 479, 383, 477, 1259, 453, 1284, 448, 413, 479, 383, 478, 384, 478, 384, 476, 384, 477, 9942, 3532, 1698, 482, 380, 480, 1255, 477, 386, 475, 387, 474, 388, 473, 390, 471, 391, 480, 382, 480, 382, 478, 385, 477, 384, 477, 386, 474, 387, 474, 1262, 482, 380, 480, 382, 479, 383, 478, 385, 476, 387, 475, 386, 474, 388, 472, 1263, 480, 1256, 476, 1259, 473, 389, 472, 391, 481, 1255, 477, 385, 476, 386, 475, 387, 474, 388, 473, 389, 482, 0, 1262, 481, 380, 486, 377, 479, 384, 478, 383, 478, 385, 476, 386, 475, 388, 473, 388, 473, 389, 472, 390, 482, 380, 481, 386, 475, 383, 478, 384, 477, 386, 475, 386, 474, 388, 473, 1263, 480, 1256, 476, 386, 475, 388, 473, 388, 473, 389, 472, 391, 481, 381, 480, 382, 479, 383, 478, 385, 476, 386, 475, 1260, 483, 1253, 479, 384, 477, 384, 477, 385, 476, 387, 474, 388, 473, 389, 482, 380, 481, 382, 479, 383, 478, 384, 477, 385, 476, 386, 475, 388, 473, 390, 471, 391, 481, 381, 480, 381, 480, 382, 479, 383, 478, 384, 476, 387, 474, 387, 475, 388, 472, 390, 471, 1264, 479, 384, 477, 385, 476, 387, 474, 387, 474, 388, 473, 389, 482, 380, 481, 381, 480, 383, 478, 1258, 475, 1260, 482, 380, 481, 1255, 477, 385, 476, 386, 475, 387, 474, 1262, 480, 1255, 477, 385, 476, 386, 475, 1261, 482, 380, 481, 382, 479, 1255, 477]
Failed to decode:  ('Both even/odd pulses differ',)
----------------------------

このデコーダ微妙…で、ソースみたら NEC しか対応してないらしい |||orz

やってみておもったのはこの赤外線読み取り安定しない…家製協(AEHA)フォーマットなのはおよそ把握できたのだけど、ヘッダの読み取りからして安定しない(8T4T が先頭に入る)どういうこっちゃ…

何回か取り直したら良さげな数字が出た

Heard 391 Pulses: [3531, 1700, 448, 415, 446, 1289, 453, 409, 452, 411, 451, 410, 451, 412, 448, 414, 448, 414, 446, 416, 445, 417, 444, 418, 453, 409, 452, 411, 450, 1285, 447, 415, 446, 416, 446, 417, 453, 408, 453, 409, 452, 411, 450, 412, 449, 1286, 447, 1289, 453, 1283, 449, 413, 448, 414, 448, 1288, 455, 407, 453, 409, 452, 411, 450, 412, 448, 414, 447, 415, 446, 416, 445, 417, 444, 418, 453, 410, 451, 410, 452, 410, 451, 411, 450, 413, 447, 415, 447, 415, 445, 418, 443, 418, 453, 409, 452, 410, 451, 411, 450, 412, 449, 414, 451, 411, 446, 416, 445, 417, 444, 423, 448, 409, 452, 410, 453, 409, 450, 1286, 448, 414, 447, 1289, 453, 409, 452, 410, 454, 408, 449, 414, 448, 413, 447, 420, 441, 417, 444, 1292, 450, 1285, 448, 1287, 445, 418, 443, 419, 452, 1283, 449, 414, 447, 415, 447, 415, 445, 417, 445, 417, 454, 408, 452, 412, 449, 412, 449, 413, 448, 415, 446, 415, 446, 416, 445, 417, 444, 418, 454, 409, 451, 411, 450, 413, 448, 1286, 446, 1289, 453, 410, 452, 411, 449, 412, 449, 1286, 447, 417, 443, 418, 453, 1282, 451, 1286, 446, 416, 445, 417, 444, 418, 454, 409, 452, 410, 450, 412, 449, 413, 448, 414, 447, 415, 446, 1290, 453, 1282, 450, 1286, 446, 1290, 453, 1282, 450, 413, 448, 1288, 445, 417, 443, 1292, 451, 412, 449, 413, 448, 414, 447, 415, 446, 417, 444, 418, 453, 410, 451, 410, 451, 411, 450, 413, 448, 414, 447, 415, 446, 417, 444, 417, 444, 418, 453, 409, 452, 410, 451, 411, 450, 412, 449, 413, 448, 414, 447, 415, 446, 417, 444, 418, 443, 419, 453, 1283, 449, 1286, 446, 416, 445, 417, 444, 418, 453, 410, 451, 411, 450, 412, 449, 413, 448, 414, 446, 416, 445, 417, 444, 418, 443, 419, 452, 410, 451, 411, 450, 412, 449, 414, 447, 415, 446, 416, 449, 413, 444, 419, 453, 409, 452, 414, 447, 411, 450, 413, 448, 1287, 444, 418, 443, 420, 453, 408, 451, 411, 451, 411, 450, 413, 448, 414, 446, 416, 445, 417, 444, 1292, 451, 1284, 447, 416, 450, 1285, 453, 409, 452, 410, 451, 411, 450, 1286, 446, 1289, 454, 410, 451, 411, 450, 1285, 447, 415, 447, 416, 444, 1291, 452]
Failed to decode:  ('Pulses outside mark/space',)
----------------------------

試しに発信してみたけど

import array
import pulseio
import board

# 50% duty cycle at 38kHz.
pwm = pulseio.PWMOut(board.IR_TX, frequency=38000, duty_cycle=2 ** 15)
pulse = pulseio.PulseOut(pwm)

onCommand = [3503, 1727, 452, 410, 452, 1284, 447, 416, 445, 416, 445, 418, 453, 408, 453, 410, 451, 411, 450, 416, 445, 413, 448, 415, 446, 416, 445, 417, 443, 1293, 450, 411, 450, 413, 448, 414, 447, 415, 446, 416, 445, 417, 444, 418, 453, 0, 454, 409, 452, 409, 452, 411, 449, 413, 448, 414, 447, 415, 446, 416, 445, 417, 445, 1291, 451, 1285, 447, 416, 445, 417, 444, 418, 443, 419, 453, 407, 454, 9964, 3510, 1721, 447, 415, 446, 1289, 454, 408, 454, 409, 450, 412, 449, 413, 448, 414, 447, 416, 445, 416, 445, 417, 444, 419, 457, 405, 452, 410, 451, 1288, 443, 415, 446, 416, 445, 417, 444, 419, 452, 410, 451, 411, 450, 412, 449, 1286, 447, 1289, 453, 1283, 449, 413, 448, 414, 447, 1288, 444, 419, 452, 411, 451, 411, 450, 412, 449, 412, 449, 414, 447, 415, 447, 415, 445, 417, 445, 417, 453, 409, 453, 409, 451, 413, 448, 1287, 446, 415, 446, 417, 444, 418, 453, 1282, 450, 1286, 446, 416, 445, 417, 444, 418, 453, 1283, 450, 412, 449, 413, 447, 8192, 1282, 450, 413, 448, 414, 447, 416, 445, 416, 445, 418, 443, 420, 451, 410, 451, 412, 449, 412, 450, 412, 448, 1288, 445, 1290, 452, 410, 451, 412, 449, 413, 448, 414, 447, 415, 446, 416, 445, 417, 444, 418, 452, 410, 451, 411, 450, 412, 451, 411, 448, 414, 447, 416, 445, 419, 442, 418, 443, 419, 453, 409, 452, 411, 450, 412, 449, 413, 448, 415, 446, 416, 445, 417, 444, 1291, 452, 411, 450, 411, 450, 412, 449, 413, 448, 415, 446, 416, 445, 417, 444, 419, 453, 408, 453, 1287, 444, 1287, 445, 418, 443, 1292, 451, 412, 449, 413, 448, 414, 447, 415, 446, 417, 444, 1291, 452, 410, 451, 1284, 447, 415, 446, 417, 444, 1291, 451]
offCommand = [3531, 1700, 448, 415, 446, 1289, 453, 409, 452, 411, 451, 410, 451, 412, 448, 414, 448, 414, 446, 416, 445, 417, 444, 418, 453, 409, 452, 411, 450, 1285, 447, 415, 446, 416, 446, 417, 453, 408, 453, 409, 452, 411, 450, 412, 449, 1286, 447, 1289, 453, 1283, 449, 413, 448, 414, 448, 1288, 455, 407, 453, 409, 452, 411, 450, 412, 448, 414, 447, 415, 446, 416, 445, 417, 444, 418, 453, 410, 451, 410, 452, 410, 451, 411, 450, 413, 447, 415, 447, 415, 445, 418, 443, 418, 453, 409, 452, 410, 451, 411, 450, 412, 449, 414, 451, 411, 446, 416, 445, 417, 444, 423, 448, 409, 452, 410, 453, 409, 450, 1286, 448, 414, 447, 1289, 453, 409, 452, 410, 454, 408, 449, 414, 448, 413, 447, 420, 441, 417, 444, 1292, 450, 1285, 448, 1287, 445, 418, 443, 419, 452, 1283, 449, 414, 447, 415, 447, 415, 445, 417, 445, 417, 454, 408, 452, 412, 449, 412, 449, 413, 448, 415, 446, 415, 446, 416, 445, 417, 444, 418, 454, 409, 451, 411, 450, 413, 448, 1286, 446, 1289, 453, 410, 452, 411, 449, 412, 449, 1286, 447, 417, 443, 418, 453, 1282, 451, 1286, 446, 416, 445, 417, 444, 418, 454, 409, 452, 410, 450, 412, 449, 413, 448, 414, 447, 415, 446, 1290, 453, 1282, 450, 1286, 446, 1290, 453, 1282, 450, 413, 448, 1288, 445, 417, 443, 1292, 451, 412, 449, 413, 448, 414, 447, 415, 446, 417, 444, 418, 453, 410, 451, 410, 451, 411, 450, 413, 448, 414, 447, 415, 446, 417, 444, 417, 444, 418, 453, 409, 452, 410, 451, 411, 450, 412, 449, 413, 448, 414, 447, 415, 446, 417, 444, 418, 443, 419, 453, 1283, 449, 1286, 446, 416, 445, 417, 444, 418, 453, 410, 451, 411, 450, 412, 449, 413, 448, 414, 446, 416, 445, 417, 444, 418, 443, 419, 452, 410, 451, 411, 450, 412, 449, 414, 447, 415, 446, 416, 449, 413, 444, 419, 453, 409, 452, 414, 447, 411, 450, 413, 448, 1287, 444, 418, 443, 420, 453, 408, 451, 411, 451, 411, 450, 413, 448, 414, 446, 416, 445, 417, 444, 1292, 451, 1284, 447, 416, 450, 1285, 453, 409, 452, 410, 451, 411, 450, 1286, 446, 1289, 454, 410, 451, 411, 450, 1285, 447, 415, 447, 416, 444, 1291, 452]

pulses = array.array('H', offCommand)
pulse.send(pulses)

うん、エラーは出ないんだけどね…多分データがどっか破損してる。

やはりもっと精度が欲しいのか…とはいえ、うちに NEC 製のリモコンないから正しいのかも検証できなかった |||orz

Circuit Playground Express の各種機能を試す(音声周り)

マイクを使って音をサンプリングする

ということで、音のサンプリングを実行してみた。
具体的には、音量を検出して、10 個の LED (Pixcel) を段階的に光らせた。

前回のライブラリも利用し、

white-azalea.hatenablog.jp

追加で simpleio を入れた。

f:id:white-azalea:20200707210032p:plain

実際のコードはこんな感じ

import board
import math
import array
import audiobusio
import simpleio
from adafruit_circuitplayground import cp

def rms(value):
    # 音は波なので、波の振れ幅 = 音量。
    # 音の振幅の平均値(ここからの振れ幅=音量)
    dc_offset = int(sum(value) / len(value))
    # (1 サンプル毎の音量) の二乗和 (2 乗すると、マイナスがなくなるため)
    s = sum((i - dc_offset) ** 2 for i in value)
    # 二乗平均 = 分散
    m = s / len(value)
    # 標準偏差(二乗平均平方根 : root mean square)
    r = math.sqrt(m)
    return r

cp.pixels.auto_write = False
cp.pixels.brightness = 0.3

mic = audiobusio.PDMIn(
    board.MICROPHONE_CLOCK,
    board.MICROPHONE_DATA,
    sample_rate=16000, # サンプリングビットレート 16kHz
    bit_depth=16       # ビット深度 0 - 65535 の値
)

# サンプリングデータを入れるための空の 配列(長さ 80 のリスト)を作る(H は型コード)
# 16kHz, 16000 / 80 = 0.005 sec 分のサンプリング
samples = array.array('H', [0] * 80)
# samples リストの長さぶんの録音をする
mic.record(samples, len(samples))

while True:
    mic.record(samples, len(samples))
    magnitude = rms(samples)
    i = int(simpleio.map_range(int(magnitude), 0, 1000, 0, 9))  # 0-1000 までの値を 0-9 の値にマップする

    for p in range(10):
        cp.pixels[p] = (0, 5, 0) if p < i + 1 else (0, 0, 0)
    cp.pixels.show()

すると、こんな感じの光り方をする。

f:id:white-azalea:20200707210337p:plain

近くで「あー」と声を出しながら撮影

f:id:white-azalea:20200707210649p:plain

咳き込むと一瞬で上がるのだけど、その瞬間を取るわけにも行かず…大音量で音楽聞く訳にも行かなかったので断念…

スピーカーで音を鳴らす

from adafruit_circuitplayground import cp

while True:
    if cp.button_a:
        cp.start_tone(262)
    elif cp.button_b:
        cp.start_tone(294)
    else:
        cp.stop_tone()

音声の公開は難しいのでコードだけ。
ボタンを押している間だけビープ音がなります。

数字は周波数。

ちなみにトーンは、秒指定で再生もできるので

from adafruit_circuitplayground import cp

cp.play_tone(262, 1)
cp.play_tone(294, 1)

こんなんもできた