火星でも見てみますか

ずーっと天気が悪くて、やっと晴れたと思ったら平日・・・出かけたいところですがそうも行かず、まぁ自宅前で火星でも見ますか。
といってもよく考えれば今回の接近で一度も望遠鏡で見ていない。土星・木星は高度が低いので窓から見えるのですが、火星は天頂近くまで上がるので外に持ち出さないと見られず、めんどくさくて見ていなかったのでした。我ながらズボラがひどい。

とはいえ最接近を過ぎて後は遠くなるだけ。今日はシーングも良さそうだし今のうちに見ておくか、と重い腰を上げた次第です。
PXL_20201020_140459882.NIGHT

FL80sとXL5.2の組み合わせで120倍。今更ながらかなり大きい。ほとんど大気のゆらぎもなく模様もよく見えますね。一緒に見た子供には、大シルチス見ても木星のような特徴的な模様でないせいか反応は今ひとつ。
対物の性能的にはまだまだ余裕がありますが、これ以上短いアイピースは持っていないので倍率が挙げられないのが残念。マシなバローレンズ一本買っとくかな。

PCを持ち出すのも面倒くさいので眼視のみ。スマホで撮ってみようと思いましたが、針穴のような射出瞳に針穴のようなスマホの入射瞳を手持ちで通すのは無理ゲーなので諦めました。

座標をBLEで送信する

前回PyIndiを Indi Driverから起動して、架台の座標を取得するようにしました。Indi DriverをdisconnectするとDriver自体が停止してしまう、という課題が残っていますが、とりあえず次に進みます。
次はこの座標値をBLEで送信できるようにします。Pythonは今ひとつ苦手で引っかかるところがいくつかあったのでメモ。

BLEのペリフェラルを実現するのに使うのはこれ。pybleno。PythonでBLEのペリフェラルを実現するライブラリ。node.jsでBLEのペリフェラルをを実現するライブラリblenoのPython版です。


インストールは簡単。
sudo pip3 install pybleno
以上。 自動的にsetupが走ってソースやexampleのダウンロード及びインストールをしてくれます。ダウンロードされたソースの中で、pybleno/examples/echo を参考にします。使い方はわかりやすいです。

まず定義するのは
onStateChange()
onAdvertisingState()
の2つ。
onStateChangeはBLEのOn/Offが切り替わったときのcallback。onになったときにはadvertiseする名前とUUIDを指定してadvertiseを開始します(1)。
onAdvertisingStateはAdvertiseが開始されたときのcallback。エラーなく開始された場合は、ここでserviceするcharacteristicsを定義します(2)。
BlenoPrimaryServiceの中のcharacteristicsはリストになっているので、複数のcharacteristicを使用する場合はここに複数記載できます(3)。
def onStateChange(state):
    if (state == 'poweredOn'):
        bleno.startAdvertising('NJP', ['ed00']) #<=(1)
    else:
        bleno.stopAdvertising()

def onAdvertisingStart(error):
    if not error:
        coordinateInterface = tele_skymap_bt2_ble.RaDecCharacteristic('ed0F')
        bleno.setServices([
            BlenoPrimaryService({#<=(2)
                'uuid': 'ed00',
                'characteristics': [ #<=(3)
                    coordinateInterface
                    ]
            })
        ])
    else:
bleno.stopAdvertising() bleno.startAdvertising('NJP', ['ed00']) def main(): bleno.on('stateChange', onStateChange) bleno.on('advertisingStart', onAdvertisingStart) bleno.start()

で、mainでBLE onと advertise開始のcallbackを設定し、blenoをスタートして完了。
次はcharacteristicの中で実際にやることの定義。こちらはCharacteristicクラスを継承して作ります。

やることは
・コンストラクタでCharacteristicのpropertyの定義(1)。ここではreadとwriteのみ使用します
・onReadRequest()で、値取得の要求に対する動作の定義(2)
・onWriteRequest()で、値設定の要求に対する動作の定義(3)
class RaDecCharacteristic(Characteristic):

    def __init__(self, uuid):
        Characteristic.__init__(self, {
            'uuid': uuid,
            'properties': ['read', 'write'],#<=(1)
            'value': None
          })
        
        self._value = array.array('B', [0] * 0)
        self._updateValueCallback = None
          
    def onReadRequest(self, offset, callback):#<=(2)
        listRaDec = list(struct.pack('>dd', *self._radec))#<=(2-1)
        callback(Characteristic.RESULT_SUCCESS, array.array('B', listRaDec))

    def onWriteRequest(self, data, offset, withoutResponse, callback):#<=(3)
        radec = struct.unpack('>dd', struct.pack('B'*len(data), *data))#<=(3-1)
        if radec[0] != self._radec[0] or radec[1] != self._radec[1]:
            self._lock.acquire()
            self._isSynced = True
            self._radec = list(radec)
            self._lock.release()

        callback(Characteristic.RESULT_SUCCESS)
        
    def onSubscribe(self, maxValueSize, updateValueCallback):
        print('EchoCharacteristic - onSubscribe')
        
        self._updateValueCallback = updateValueCallback

    def onUnsubscribe(self):
        print('EchoCharacteristic - onUnsubscribe')
        
        self._updateValueCallback = None
BLEでの送受信はすべてバイト列で送信します。今回送受信するのは赤経・赤緯の座標、double型で8bytes x2。double <=>バイト列の変換にはstruct.pack() / struct.unpack()を使うと楽にできます。これ今回の1つ目の勉強ポイント。

取得要求時は、doubleのリストをまとめてstruct.pack()でバイト列にしてリストとしてcallbackに渡してやります(2-1)。
設定要求時は、届いたデータを一旦struct.pack()でバイト列としてpackした後、doubleのリストにunpackします(3-1)
onSubscribe()/onUnsubscribe()は今回使用しません。

これで完了ですが、今回引っかかったことがあったのでここでメモ。今回Caracteristicクラスは別ファイルに記述し、main()のあるファイルからimportしていました。ただboost::pythonでpythonファイルを実行すると、このimportに失敗してしまうことがありました。その理由はこちらのサイトによると、Py_Initialize()でimportするpathを決めるが、それに失敗することがあるとのこと。それを避けるために、importするモジュールを以下のようにPy_Initialize()を実行した後で明示的に記述してやる必要があります。これ、今回の2つ目の勉強ポイント。
    Py_Initialize();

    const size_t bufSize = PATH_MAX + 1;
    char pyPath[bufSize];
    char dirPath[bufSize];
    if(getExePath(dirPath, bufSize) < 0){
        LOG_INFO("Can not get exe path.");
        return;
    }

    PyObject* sysPath = PySys_GetObject("path"); 
PyObject *path = PyUnicode_FromString(pyPath)#<=(1)
if(path != NULL){ PyList_Insert(sysPath, 0, path); Py_DECREF(path); }
ここで引っかかったのが(1)。文字列を PyObjectに変換するのにPython2ではPyString_FromString()を使います。Python3では、多くのWebサイトでは PyBytes_FromString() を使え、と書いてありますが、PyUnicode_FromString()でないと、Pythonの中でimportするときにエラーになるみたいです。

今回はpython moduleが実行しているIndi Driverと同じディレクトリにある前提としています。

これであとはBLEでの取得要求時に、最新の赤経・赤緯の値を渡すようにしてやればOK。これで動作しますが、今後Python部分を編集することを考えると、pythonでエラーが起こったときに何が起こったかわからないとデバッグのしようがありません。通常はエラーが起こったときにはPyErr_Print();を実行すると標準出力にstack traceを表示してくれます。が、indiserverからindidriverを起動すると、標準出力を全部隠してしまうため表示できない、というおせっかいな仕様があります。そこでpythonでのエラー時の出力を文字列として取得し、indiserverにログとして送ってやります。これが3つ目の勉強ポイント。
exec_file()でpythonファイルを実行してエラーが発生すると error_already_setという例外が投げられます。これをcatchして、indiserverにlogとして送信します。
  try{
        exec_file(pyPath, mMainNamespace, mMainNamespace);
    }
    catch(boost::python::error_already_set &ee){
        if (PyErr_Occurred()) {
            std::string msg = handle_pyerror(); 
            LOG_INFO(msg.c_str());
        }
        boost::python::handle_exception();
        PyErr_Clear();
        return;
  }   

で、このときstacktraceを以下のように文字列として取得します。
std::string handle_pyerror()
{
    using namespace boost::python;
    using namespace boost;

    PyObject *exc,*val,*tb;
    object formatted_list, formatted;
    PyErr_Fetch(&exc,&val,&tb);
    handle<> hexc(exc),hval(allow_null(val)),htb(allow_null(tb)); 
    object traceback(import("traceback"));
    if (!tb) {
        object format_exception_only(traceback.attr("format_exception_only"));
        formatted_list = format_exception_only(hexc,hval);
    } else {
        object format_exception(traceback.attr("format_exception"));
        formatted_list = format_exception(hexc,hval,htb);
    }
    formatted = str("\n").join(formatted_list);
    return extract<std::string>(formatted);
}


ではBLEでつないでみます。Android端末のBLE ScannerでScanすると、こんなふうにちゃんと検出されました。今回はデバイス名を"NJP"にしています。
Screenshot_20201019-220617

NJPの詳細を見ると、Custom Characterisitcで作成したCharacteristicが見えています。
Screenshot_20201019-222014

そこでReadを実行すると、値が取れました。前半の8bytesは倍精度の浮動小数点数で赤経、後半の8bytesは赤緯の値を示しています。
Screenshot_20201019-222022

こちらのサイトで値を変換してみると、
3fe18ae000000000  => 0.5482025146484375
4056800000000000 => 90
になります。このindi driverでは電源投入時に天の北極を向いていることを仮定して座標を初期化しているため、赤緯が90°になっています。



というわけで座標がBLEで送れるようになったので、今度はAndroidアプリの TeleSkymapBT2で座標を取得するようにします。なんかJavaでAndroidアプリを触るの久しぶりな気がする。

フィラメントの保管場所

3Dプリンタのフィラメント、外に出しておくと湿気を吸ってしまうため乾燥したところに保管する必要があります。以前はこんなジップロック的な袋に入れていましたが、すぐに乾燥剤が湿気ってしまってダメ。そのためホームセンターに売っているコンテナに押し入れに入れる乾燥剤を入れて使っていました。

しかし湿度が40-50%くらいまでしか下がらずどうしたもんかなぁと思っていました。

でamazonで見つけたのがこれ。比較的安いシールドコンテナ。評価を見るとパッキンが役に立ってないとかいうのもありますがどんなもんでしょうね。


これにドライフラワー用の乾燥剤を敷き詰めて、100均の猫こよけトゲトゲシートをひっくり返して下駄を履かせます。
PXL_20201018_080342911

このコンテナ一つでリールが5つ入ります。これまた百均の温湿度計を取り付けて湿度を管理します。
PXL_20201018_080318203

湿度25%くらいまで下がっています。いいですね。果たしてこれで何日持つのか。
PXL_20201018_080327062



プロフィール

nekomeshi312

タグ絞り込み検索
記事検索
  • ライブドアブログ