技術をかじる猫

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

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)

こんなんもできた

Circuit Playground Express の各種機能を試す(3)

温度センサーを利用する

利用するライブラリは adafruit_thermistor.mpyでこれを lib に放り込む。

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

で、コードをしれっと

import time
import adafruit_thermistor
import board

thermo = adafruit_thermistor.Thermistor(board.TEMPERATURE, 10000, 10000, 25, 3950)
while True:
    temp = thermo.temperature
    print((temp,))
    time.sleep(1)

実行するとこんなかんじ

(27.9365,)
(27.9139,)
(27.959,)
(28.004,)
(27.8915,)
(27.959,)
(27.9139,)
(27.9139,)

大体 28 度位なのだが、これを手のひらで包むと

(29.4573,)
(29.5717,)
(29.7322,)
(29.7092,)
(29.824,)
(29.939,)
(30.0081,)
(30.1693,)
(30.2384,)
(30.2616,)

単位が 度というのが助かりますね。
海外だと単位が華氏で有ることも多いので…

他のライブラリを利用してみる

circuit playground のアクセスをもっと単純化したライブラリ adafruit_circuitplayground が存在したので使ってみた。

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

コードは

import time
from adafruit_circuitplayground import cp

while True:
    print("Temperature C:", cp.temperature)
    time.sleep(1)

うーんシンプル。

Temperature C: 27.3748
Temperature C: 27.3748
Temperature C: 27.3972
Temperature C: 27.4197
Temperature C: 27.4197
Temperature C: 27.4197

このライブラリを利用して温度を視覚化しようか

温度の視覚化

温度で LED 光らせてみようか。

import time
from adafruit_circuitplayground import cp

# LED の明るさ設定
cp.pixels.auto_write = False
cp.pixels.brightness = 0.3

# 25-30 度の間で 10 段階表示
minimum_temp = 25
maximum_temp = 30

# 平たく言えば、最小温度から最大温度までの比率を 10 段階計算
#まぁ、25 度下回ればマイナスにもなるし、30 度超えれば 10 以上になるが、ループとしては特に問題ない
def scale_range(value):
    return int((value - minimum_temp) / (maximum_temp - minimum_temp) * 10)

while True:
    peak = scale_range(cp.temperature)

    # 10 個ある LED を範囲内で発光させる
    for i in range(10):
        if i <= peak:
            cp.pixels[i] = (255, 255, 255)
        else:
            cp.pixels[i] = (0, 0, 0)

    # 発光!
    cp.pixels.show()
    time.sleep(0.5)

ということで、30 度でマックス、25 度からおよそ 0.5 度刻みで光らせるコードを書いた。
その様子がコレである。

f:id:white-azalea:20200706195029j:plain

ちなみに起動してると、次第に基盤自体に熱を持つので、27 度の室温なら気づくと 30 度とかになる…

Circuit Playground Express の各種機能を試す(2)

環境::

  • CircuitPlaygroundExpress : Bootloader 3.10.0
  • CircuitPython : 5.0

ライブラリで色彩/明度/彩度 を扱う

どちらかといえば CircuitPython の機能で、 ライブラリ からコピーして使う。

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

全部のライブラリをコピーはできないので、今回使うものだけ。
この上で

import time
import board
import neopixel
import adafruit_fancyled.adafruit_fancyled as fancy

npx = neopixel.NeoPixel(board.NEOPIXEL, 10, auto_write=False)

while True:
    for pp in range(10):
        h = (pp * (1.0/10))
        hsvcolor = fancy.CHSV(h, 1.0, 0.2)
        npx[pp] = hsvcolor.pack()
        npx.show()
        time.sleep(0.2)

すると

f:id:white-azalea:20200630194354j:plain

綺麗なグラデーションですね…

光学センサーで明るさ計測

import time
import analogio
import board

light = analogio.AnalogIn(board.LIGHT)

while True:
    print((light.value,))
    time.sleep(1.0)

としたとき部屋の蛍光灯でこんな感じです

(3168,)  # 蛍光灯の光
(3136,)
(800,)  # 手で隠した
(720,)
(720,)

タッチセンサー

タッチセンサーによって LED の色を変動させるプログラム

タッチセンサーは周辺の穴の空いた端子ですが、今回 A1, A2, A5, A6 を使います。
以下の写真にそれぞれの配置がプリントされてます。

f:id:white-azalea:20200630202607j:plain

A シリーズはすべて操作可能なのはわかってましたが、RGB いじりたいだけでしたので今回は避けました。

import time
import board
import neopixel
import touchio
from simpleio import map_range

# LED セットアップ
npx = neopixel.NeoPixel(board.NEOPIXEL, 10, auto_write=True)

# タッチセンサーを設定する
touch_A1 = touchio.TouchIn(board.A1)
touch_A2 = touchio.TouchIn(board.A2)
touch_A5 = touchio.TouchIn(board.A5)
touch_A6 = touchio.TouchIn(board.A6)

# 色のセット
r_in = 0
g_in = 0
b_in = 0

while True:
    # タッチするセンサーによって RGB を決定
    if touch_A1.value:
        r_in += 1
    if touch_A2.value:
        g_in += 1
    if touch_A5.value:
        b_in += 1
    if touch_A6.value:
        r_in -= 1
        g_in -= 1
        b_in -= 1
    
    # 0 - 255 までに抑える
    r_in = 0 if r_in < 0 else r_in
    g_in = 0 if g_in < 0 else g_in
    b_in = 0 if b_in < 0 else b_in
    r_in = 255 if r_in > 255 else r_in
    g_in = 255 if g_in > 255 else g_in
    b_in = 255 if b_in > 255 else b_in

    # LED に色を書き込む
    npx[7] = (r_in, g_in, b_in)
    time.sleep(0.2)

でちょっとやってみたのがこの 2 枚

f:id:white-azalea:20200630203107j:plainf:id:white-azalea:20200630203122j:plain

タッチ時間によって色が変化します。