農業IoTの準備として、TWELITEを使った環境情報の取得をやってみた-その1(環境情報の取得)
知人が農業をやっているのですが、温度や湿度などを計測できないかと相談を受けました。
ラズベリーパイと無線マイコンTWELITEを使って、システムを考えてみました。
📑本記事のシリーズ
- その1:🌡️ 環境情報の取得 !!!本記事!!!
- その2:📶 中継器
- その3:🔋 電池持ちと死活監視
- その4:🔧 試作機の作成
💡やりたいこと
- 子機は、環境情報(温度、湿度等)を計測し、親機に送出する
- 子機は、電池に動作する
- 太陽光パネルの検討もしましたが、コストが上がるので却下
- 子機は、送出後スリープし、省電力で動作する
- 子機は、識別子を持つこととする
- 簡単に設置できることが必要
- 親機は、子機の環境情報を受信し、ストレージに保存する
- 今回は、csvファイルとして保存する
- 利用者は、データを確認できる
- ※今回は実施しない
- 後々、解析もできるようにする
子機は、TWELITE DIPを使用します。採用した理由は、
- 省電力動作ができる
- 無線通信可能である
- 中継機としても使用できる(今回は使用なし)
また、親機は、ラズベリーパイを使用します。
🏁つっくたもの
子機をブレッドボードで作りました↓。
🔧パーツ一覧
子機と親機で分けて記載します。
子機
no | 部品名 | 個数 | 備考 |
---|---|---|---|
1 | TWELITE DIP | 1 | Amazon - マッチ棒アンテナ実装済み |
2 | 環境センサーBMP280 | 1 | Amazon - 温度, 湿度, 気圧計測 |
3 | 3.3V出力昇圧DCDCコンバータ | 1 | 秋月電子 |
4 | 単3電池 | 2 | 1本でも可能 |
5 | 電池ケース | 1 | 使用する電池数によって1本用 or 2本用を使用のこと※本記事は2本で実施 |
6 | ブレッドボード | 1 | 必要に応じて |
7 | SBアダプター TWELITE R2-トワイライター2 & アタッチメントセット | 1セット | Amazon - TWELITE DIPへのプログラム書き込み |
親機
no | 部品名 | 個数 | 備考 |
---|---|---|---|
1 | ラズベリーパイ | 1 | 今回は3を使用(4でも可能) |
2 | モノワイヤレスUSBスティック | 1 | Amazon |
接続図
子機側
TWELITEの入力電圧は、2.3V~3.6Vです。
電池1本でも動作させたかったため、昇圧し3.3VにしてTWELITEに入力します。
親機側
USBを差し込むだけです。
💻環境
開発環境
作成するファームは3つとなります。
子機側
- ①子機側ファーム
- Windows10 Home Edition - TWELITE Stage v1-0-9
親機側
- ②モノワイヤレスUSBスティック側のファームウェア
- Windows10 Home Edition - TWELITE Stage v1-0-9
- ③モノワイヤレスUSBスティック制御用アプリ
- RaspberryPi - Python3.7
TWELITE環境の構築
TWELITE Stageのインストールおよび操作方法については、公式のサイトにて確認ください。
- TWELITE Stageのダウンロードサイト
- TWELITEのプログラミング
- ライブラリの詳細
- 各OS毎のインストール方法
- サンプルプログラムの説明
- API仕様の詳細
RaspberryPiの補足
モジュール関連
シリアル通信のためにpyserial、
csvファイルの作成のためにpandasをインストールします。
$ sudo apt-get install python-pandas
$ python3 -m venv env
$ source env/bin/activate
(env) $ pip install pyserial
(env) $ pip install pandas
モノスティックの認識
モノスティックは、/dev/ttyUSB*にて認識されます。
$ ls -l /dev/ttyUSB*
crw-rw---- 1 root dialout 188, 1 1月 3 12:23 /dev/ttyUSB0
📝手順
各プログラムの作成について記載します。
方針として、サンプルプログラムに対し修正を行います。
①子機側ファーム
子機側ファームは、サンプルプログラム - ActEx_Sns_BME280_SHT30をベースに修正します。
コード
// use twelite mwx c++ template library
#include <TWELITE>
#include <NWK_SIMPLE>
#include <SNS_BME280>
#include <SNS_SHT3X>
#include <STG_STD>
/// if use with PAL board, define this.
#undef USE_PAL
#ifdef USE_PAL
// X_STR makes string literals.
#define X_STR(s) TO_STR(s)
#define TO_STR(s) #s
#include X_STR(USE_PAL) // just use with PAL board (to handle WDT)
#endif
/*** Config part */
// application ID
const uint32_t DEF_APPID = 0x1234abcd;
uint32_t APP_ID = DEF_APPID;
// channel
uint8_t CHANNEL = 13;
// id
uint8_t u8ID = 0;
// application use
const char_t APP_NAME[] = "ENV SENSOR";
const uint8_t FOURCHARS[] = "SBS1";
uint8_t u8txid = 0;
uint32_t u32tick_tx;
// very simple state machine
enum class E_STATE {
INIT = 0,
CAPTURE_PRE,
CAPTURE,
TX,
TX_WAIT_COMP,
SETTING_MODE,
SUCCESS,
ERROR
};
E_STATE eState = E_STATE::INIT;
/*** Local function prototypes */
void sleepNow();
/*** sensor objects */
SNS_BME280 sns_bme280;
char_t bme280_model[8] = "BME280";
bool b_found_bme280 = false;
bool b_bme280_w_humid = false;
SNS_SHT3X sns_sht3x;
bool b_found_sht3x = false;
uint16_t u16_volt_vcc = 0;
uint16_t u16_volt_a1 = 0;
/*** setup procedure (run once at cold boot) */
void setup() {
// !!!add - wait
delay(1000); // just in case, wait for devices to listen furthre I2C comm.
Serial << crlf << format("run : setup() - %08Xms", 1000*1);
/*** SETUP section */
#ifdef USE_PAL
/// use PAL board (for WDT handling)
auto&& brd = the_twelite.board.use<USE_PAL>(); // register board (PAL)
#endif
/// interactive mode settings
auto&& set = the_twelite.settings.use<STG_STD>();
// declare to use interactive setting.
// once activated, use `set.serial' instead of `Serial'.
set << SETTINGS::appname(APP_NAME) // set application name appears in interactive setting menu.
<< SETTINGS::appid_default(DEF_APPID); // set the default application ID.
set.hide_items(
E_STGSTD_SETID::POWER_N_RETRY
, E_STGSTD_SETID::OPTBITS
, E_STGSTD_SETID::OPT_DWORD2
, E_STGSTD_SETID::OPT_DWORD3
, E_STGSTD_SETID::OPT_DWORD4
, E_STGSTD_SETID::ENC_MODE
, E_STGSTD_SETID::ENC_KEY_STRING
);
// read SET pin (DIO12)
pinMode(PIN_DIGITAL::DIO12, PIN_MODE::INPUT_PULLUP);
if (digitalRead(PIN_DIGITAL::DIO12) == LOW) {
set << SETTINGS::open_at_start(); // start interactive mode immediately.
eState = E_STATE::SETTING_MODE; // set state in loop() as dedicated mode for settings.
return; // skip standard initialization.
}
// acquired EEPROM saved data
set.reload(); // must call this before getting data, if configuring method is called.
APP_ID = set.u32appid();
CHANNEL = set.u8ch();
u8ID = set.u8devid();
// the twelite main object.
the_twelite
<< TWENET::appid(APP_ID) // set application ID (identify network group)
<< TWENET::channel(CHANNEL); // set channel (pysical channel)
// Register Network
auto&& nwk = the_twelite.network.use<NWK_SIMPLE>();
nwk << NWK_SIMPLE::logical_id(u8ID); // set Logical ID. (0xFE means a child device with no ID)
/*** BEGIN section */
/*** INIT message */
Serial << crlf << "--- " << APP_NAME << ":" << FOURCHARS << " ---";
Serial << crlf << format("..APP_ID = %08X", APP_ID);
Serial << crlf << format("..CHANNEL = %d", CHANNEL);
Serial << crlf << format("..LID = %d(0x%02X)", u8ID, u8ID);
// sensors.setup() may call Wire during initialization.
Wire.begin(WIRE_CONF::WIRE_100KHZ);
// setup analogue
Analogue.setup();
// check SHT3x
{
bool b_alt_id = false;
sns_sht3x.setup();
if (!sns_sht3x.probe()) {
bool b_alt_id = false;
delayMicroseconds(100); // just in case, wait for devices to listen furthre I2C comm.
sns_sht3x.setup(0x45); // alternative ID
if (sns_sht3x.probe()) b_found_sht3x = true;
} else {
b_found_sht3x = true;
}
if (b_found_sht3x) {
Serial << crlf << "..found sht3x" << (b_alt_id ? " at 0x45" : " at 0x44");
}
}
delayMicroseconds(100); // just in case, wait for devices to listen furthre I2C comm.
// check BMx280
{
bool b_alt_id = false;
sns_bme280.setup();
if (!sns_bme280.probe()) {
b_alt_id = true;
delayMicroseconds(100); // just in case, wait for devices to listen furthre I2C comm.
sns_bme280.setup(0x77); // alternative ID
if (sns_bme280.probe()) b_found_bme280 = true;
} else {
b_found_bme280 = true;
}
if (b_found_bme280) {
// check if BME280 or BMP280
if ((sns_bme280.sns_stat() & 0xFF) == 0x60) {
b_bme280_w_humid = true;
} else
if ((sns_bme280.sns_stat() & 0xFF) == 0x58) {
b_bme280_w_humid = false;
bme280_model[2] = 'P';
}
Serial << crlf
<< format("..found %s ID=%02X", bme280_model, (sns_bme280.sns_stat() & 0xFF))
<< (b_alt_id ? " at 0x77" : " at 0x76");
}
}
/*** let the_twelite begin! */
the_twelite.begin(); // start twelite!
}
/*** loop procedure (called every event) */
void loop() {
bool new_state;
if (Analogue.available()) {
if (!u16_volt_vcc) {
u16_volt_vcc = Analogue.read(PIN_ANALOGUE::VCC);
u16_volt_a1 = Analogue.read(PIN_ANALOGUE::A1);
}
}
do {
new_state = false;
switch(eState) {
case E_STATE::SETTING_MODE: // while in setting (interactive mode)
break;
case E_STATE::INIT:
Serial << crlf << format("..%04d/start sensor capture.", millis() & 8191);
// start sensor capture
Analogue.begin(pack_bits(PIN_ANALOGUE::A1, PIN_ANALOGUE::VCC)); // _start continuous adc capture.
if (b_found_sht3x) {
sns_sht3x.begin();
}
if (b_found_bme280) {
sns_bme280.begin();
}
eState = E_STATE::CAPTURE_PRE;
break;
case E_STATE::CAPTURE_PRE: // wait for sensor capture completion
if (TickTimer.available()) {
if (b_found_bme280 && !sns_bme280.available()) {
sns_bme280.process_ev(E_EVENT_TICK_TIMER);
}
if (b_found_sht3x && !sns_sht3x.available()) {
sns_sht3x.process_ev(E_EVENT_TICK_TIMER);
}
// both sensors are finished.
if ( (!b_found_bme280 || (b_found_bme280 && sns_bme280.available()))
&& (!b_found_sht3x || (b_found_sht3x && sns_sht3x.available()))
) {
new_state = true; // do next state immediately.
eState = E_STATE::CAPTURE;
}
}
break;
case E_STATE::CAPTURE: // display sensor results
if (b_found_sht3x) {
Serial
<< crlf << format("..%04d/finish sensor capture.", millis() & 8191)
<< crlf << " SHT3X : T=" << sns_sht3x.get_temp() << 'C'
<< " H=" << sns_sht3x.get_humid() << '%';
}
if (b_found_bme280) {
Serial
<< crlf << " " << bme280_model << " : T=" << sns_bme280.get_temp() << 'C'
<< " P=" << int(sns_bme280.get_press()) << "hP";
if (b_bme280_w_humid)
Serial
<< " H=" << sns_bme280.get_humid() << '%';
}
if (1) {
Serial
<< crlf << format(" ADC : Vcc=%dmV A1=%04dmV", u16_volt_vcc, u16_volt_a1);
}
new_state = true; // do next state immediately.
eState = E_STATE::TX;
break;
case E_STATE::TX: // place TX packet requiest.
eState = E_STATE::ERROR; // change this when success TX request...
if (auto&& pkt = the_twelite.network.use<NWK_SIMPLE>().prepare_tx_packet()) {
// set tx packet behavior
pkt << tx_addr(0x00) // 0..0xFF (LID 0:parent, FE:child w/ no id, FF:LID broad cast), 0x8XXXXXXX (long address)
<< tx_retry(0x1) // set retry (0x1 send two times in total)
<< tx_packet_delay(0, 0, 2); // send packet w/ delay
// prepare packet payload
pack_bytes(pkt.get_payload() // set payload data objects.
, make_pair(FOURCHARS, 4) // just to see packet identification, you can design in any.
, uint16_t(sns_sht3x.get_temp_cent()) // temp
, uint16_t(sns_sht3x.get_humid_per_dmil())
, uint16_t(sns_bme280.get_temp_cent()) // temp
, uint16_t(sns_bme280.get_humid_per_dmil())
, uint16_t(sns_bme280.get_press())
, uint16_t(u16_volt_vcc)
, uint16_t(u16_volt_a1)
);
// do transmit
MWX_APIRET ret = pkt.transmit();
Serial << crlf << format("..%04d/transmit request by id = %d.", millis() & 8191, ret.get_value());
if (ret) {
u8txid = ret.get_value() & 0xFF;
u32tick_tx = millis();
eState = E_STATE::TX_WAIT_COMP;
} else {
Serial << crlf << "!FATAL: TX REQUEST FAILS. reset the system." << crlf;
}
} else {
Serial << crlf << "!FATAL: MWX TX OBJECT FAILS. reset the system." << crlf;
}
break;
case E_STATE::TX_WAIT_COMP: // wait TX packet completion.
if (the_twelite.tx_status.is_complete(u8txid)) {
Serial << crlf << format("..%04d/transmit complete.", millis() & 8191);
// success on TX
eState = E_STATE::SUCCESS;
new_state = true;
} else if (millis() - u32tick_tx > 3000) {
Serial << crlf << "!FATAL: MWX TX OBJECT FAILS. reset the system." << crlf;
eState = E_STATE::ERROR;
new_state = true;
}
// !!!add - error
if( false == b_found_bme280 )
{
Serial << crlf << "!FATAL: not found bme280. reset the system." << crlf;
eState = E_STATE::ERROR;
new_state = true;
}
break;
case E_STATE::ERROR: // FATAL ERROR
Serial.flush();
delay(100);
the_twelite.reset_system();
break;
case E_STATE::SUCCESS: // NORMAL EXIT (go into sleeping...)
sleepNow();
break;
}
} while(new_state);
}
// perform sleeping
void sleepNow() {
// !!!add - sleep
// uint32_t u32ct = 1750 + random(0,500);
uint32_t u32ct = 60 * 1000 * 5; //5 min
Serial << crlf << format("..%04d/sleeping %dms.", millis() % 8191, u32ct);
Serial.flush();
the_twelite.sleep(u32ct);
}
// wakeup procedure
void wakeup() {
Wire.begin();
Serial << crlf << "--- " << APP_NAME << ":" << FOURCHARS << " wake up ---";
eState = E_STATE::INIT; // go into INIT state in the loop()
}
ポイント
-
起動 - setup()
- 起動時、setup()が呼ばれる
- 基板内のeepromより、APP_ID, CHANNEL, u8IDをロードする
- APP_ID : アプリケーションID(子機、親機とも同じIDでないと、親機の通信ができない)
- CHANNEL : 周波数帯(子機、親機とも同じ周波数帯でないと、親機の通信ができない)
- u8ID : 子機の識別子として使用する
- =>↑の設定は、TWELITE Stageのインタラクティブモードより変更可能です
- check BME280の箇所
- BME280用のAPIが用意されている
- I2CラインにBME280のデバイスが見つかった場合、b_found_bme280をtrue。
-
イベントループ - loop()
- イベント(データを受信するなど)が発生するたびに本関数が呼ばれる
- 正常時、本loopは以下の状態を遷移する
- INIT -> CAPTURE_PRE -> CAPTURE -> TX -> TX_WAIT_COMP -> SUCCESS
- INIT
- BME280アクセスための準備
- CAPTURE_PRE, CAPTURE
- BME280から環境情報の取得
- TX, TX_WAIT_COMP
- 環境情報を無線で送信
- 送信には時間がかかるため、TX_WAIT_COMPで完了待ち
- SUCCESS
- 5分を指定し、省電力モードへ遷移
- =>5分後、起床しwakeup()へ遷移
- wakeup() -> loop()-INITへ遷移
-
追加箇所(!!!add)について
- // !!!add - wait
- まれに、I2Cアクセスに失敗することがあり、起動時に追加
- // !!!add - error
- bme280が見つからない場合は、即時リセットしたかったため追加
- // !!!add - sleep
- サンプルプログラムはランダム値になっていたため、明示的に5分に変更
- // !!!add - wait
②モノワイヤレスUSBスティック側のファームウェア
親機側ファームは、サンプルプログラム - Parent-MONOSTICKをベースに修正します。
コード
// use twelite mwx c++ template library
#include <TWELITE>
#include <NWK_SIMPLE>
#include <MONOSTICK>
#include <STG_STD>
/*** Config part */
// application ID
const uint32_t DEFAULT_APP_ID = 0x1234abcd;
// channel
const uint8_t DEFAULT_CHANNEL = 13;
// option bits
uint32_t OPT_BITS = 0;
/*** function prototype */
bool analyze_payload(packet_rx& rx);
/*** application defs */
/*** setup procedure (run once at cold boot) */
void setup() {
/*** SETUP section */
auto&& brd = the_twelite.board.use<MONOSTICK>();
auto&& set = the_twelite.settings.use<STG_STD>();
auto&& nwk = the_twelite.network.use<NWK_SIMPLE>();
// settings: configure items
set << SETTINGS::appname("PARENT");
set << SETTINGS::appid_default(DEFAULT_APP_ID); // set default appID
set << SETTINGS::ch_default(DEFAULT_CHANNEL); // set default channel
set << SETTINGS::lid_default(0x00); // set default LID
set.hide_items(E_STGSTD_SETID::OPT_DWORD2, E_STGSTD_SETID::OPT_DWORD3, E_STGSTD_SETID::OPT_DWORD4, E_STGSTD_SETID::ENC_KEY_STRING, E_STGSTD_SETID::ENC_MODE);
set.reload(); // load from EEPROM.
OPT_BITS = set.u32opt1(); // this value is not used in this example.
// the twelite main class
the_twelite
<< set // apply settings (appid, ch, power)
<< TWENET::rx_when_idle() // open receive circuit (if not set, it can't listen packts from others)
;
// Register Network
nwk << set; // apply settings (LID and retry)
nwk << NWK_SIMPLE::logical_id(0x00) // set Logical ID. (0x00 means parent device)
;
// configure hardware
brd.set_led_red(LED_TIMER::ON_RX, 200); // RED (on receiving)
brd.set_led_yellow(LED_TIMER::BLINK, 500); // YELLOW (blinking)
/*** BEGIN section */
the_twelite.begin(); // start twelite!
/*** INIT message */
Serial << "--- MONOSTICK_Parent act ---" << mwx::crlf;
}
/*** loop procedure (called every event) */
void loop() {
}
void on_rx_packet(packet_rx& rx, bool_t &handled) {
Serial << ".. coming packet (" << int(millis()&0xffff) << ')' << mwx::crlf;
// output type1 (raw packet)
// uint8_t : 0x01
// uint8_t : src addr (LID)
// uint32_t : src addr (long)
// uint32_t : dst addr (LID/long)
// uint8_t : repeat count
// total 11 bytes of header.
//
// N : payload
// !!!add - enable - raw packet
if(1) { // this part is disabed.
serparser_attach pout;
pout.begin(PARSER::ASCII, rx.get_psRxDataApp()->auData, rx.get_psRxDataApp()->u8Len, rx.get_psRxDataApp()->u8Len);
Serial << "RAW PACKET -> ";
pout >> Serial;
Serial.flush();
}
// output type2 (generate ASCII FORMAT)
// :0DCC3881025A17000000008D000F424154310F0D2F01D200940100006B39
// *1*2*3*4------*5------*6*7--*8
if (1) {
smplbuf_u8<256> buf;
pack_bytes(buf
, uint8_t(rx.get_addr_src_lid()) // *1:src addr (LID)
, uint8_t(0xCC) // *2:cmd id (0xCC, fixed)
, uint8_t(rx.get_psRxDataApp()->u8Seq) // *3:seqence number
, uint32_t(rx.get_addr_src_long()) // *4:src addr (long)
, uint32_t(rx.get_addr_dst()) // *5:dst addr
, uint8_t(rx.get_lqi()) // *6:LQI
, uint16_t(rx.get_length()) // *7:payload length
, rx.get_payload() // *8:payload
// , make_pair(rx.get_payload().begin() + 4, rx.get_payload().size() - 4)
// note: if you want the part of payload, use make_pair().
);
serparser_attach pout;
pout.begin(PARSER::ASCII, buf.begin(), buf.size(), buf.size());
Serial << "ASCII FMT -> ";
pout >> Serial;
Serial.flush();
}
// packet analyze
analyze_payload(rx);
}
bool analyze_payload(packet_rx& rx) {
省略
}
ポイント
-
起動 - setup()
- 起動時、setup()が呼ばれる
- APP_ID, CHANNEL, u8IDを設定する(以下デフォルト値を記載)
- APP_ID : DEFAULT_APP_ID = 0x1234abcd(※子機と一致しているため、通信可)
- CHANNEL : DEFAULT_CHANNEL = 13(※子機と一致しているため、通信可)
- ID : 0を指定(lid_default(0x00), logical_id(0x00))。 0は、ネットワーク上の親機を指す
- =>↑の設定は、TWELITE Stageのインタラクティブモードより変更可能です
-
データ受信 - on_rx_packet()
- 子機からのデータ受信時、本関数が呼ばれる
- 受信したデータを "RAW PACKET -> "の文字列をつけて、シリアル通信で送信する
- このシリアル通信のデータを③のプログラムで受信し、データをパースする
-
追加箇所(!!!add)について
- // !!!add - enable - raw packet
- データの中身を把握したかったため、生データをみるためにenableに変更
- ③で本データを解析していますが、"ASCII FMT -> "でも可
- // !!!add - enable - raw packet
③モノワイヤレスUSBスティック制御用アプリ
モノワイヤレスUSBスティック制御用アプリは、twelite_read_write.txtをベースに修正します。
コード
from serial import *
from sys import stdout, stdin, stderr, exit
import threading
from datetime import datetime
import time
import pandas as pd
import os
ENVTEMP_CSV = "env_temp.csv"
SENSOR_NAME = "twelite"
# global
ser = None # serial port
t1 = None # thread
bTerm = False # flag
def readThread():
"""Interpret data from the serial port one line at a time.
"""
global ser, bTerm
while True:
time.sleep(0.1)
if bTerm:
return
line = ser.readline().rstrip()
bCommand = False
bStr = False
if len(line) > 0:
print(datetime.now())
print(line)
c = line[0]
# 58 - 0x3a":" <= Delimiter
if c == 58:
bCommand = True
if not bCommand and b'RAW PACKET -> :' in line:
print("recv:", datetime.now())
add_csv(line)
def DoTerminate():
"""end process
"""
global bTerm
# stop - thread
bTerm = True
print ("... quitting")
time.sleep(1.0) # スリープでスレッドの終了待ちをする
exit(0)
def add_csv(rdata, sensor_name=SENSOR_NAME, csv_file=ENVTEMP_CSV):
"""Create a csv file
"""
# devid
val = int(rdata[17:(17+2)], 16)
devid = str(val)
# temperature
val = int(rdata[53:(53+4)], 16)
val = (-1) * (0x10000 - val) if val > 0x7FFF else val
temp = str(val / 100)
# humidity
hum = str(int(rdata[57:(57+4)], 16) / 100)
# pressure
pressure = str(int(rdata[61:(61+4)], 16))
# row data
l = [sensor_name, datetime.now(), devid, temp, hum, pressure]
# header?
hd_flg = False if os.path.isfile(ENVTEMP_CSV) else True
df = pd.DataFrame([l], columns=['date', 'sensor', 'devid', 'temp', 'hum', 'pressure'])
df.to_csv(csv_file, index=False, encoding="utf-8", mode='a', header=hd_flg)
return
if __name__=='__main__':
# Check the parameters.
if len(sys.argv) != 2:
print ("%s {serial port name}" % sys.argv[0])
exit(1)
# Open the serial port.
try:
ser = Serial(sys.argv[1], 115200, timeout=0.1)
print ("open serial port: %s" % sys.argv[1])
except:
print ("cannot open serial port: %s" % sys.argv[1])
exit(1)
# Start the read thread.
t1 = threading.Thread(target=readThread)
t1.setDaemon(True)
t1.start()
# Input processing from stdin
while True:
time.sleep(1.0)
try:
l = stdin.readline().rstrip()
if len(l) > 0:
if l[0] == 'q': # quit
DoTerminate()
if l[0] == ':': # tx
cmd = l + "\r\n"
print ("--> "+ l)
ser.write(cmd)
except KeyboardInterrupt: # Ctrl+C
DoTerminate()
except SystemExit:
exit(0)
except:
print ("... unknown exception detected")
break
exit(0)
実行例
^C... quitting
(env) $ python app_twelite.py /dev/ttyUSB0
open serial port: /dev/ttyUSB0
2022-01-03 21:01:26.924440
b'.. coming packet (59050)'
2022-01-03 21:01:27.029359
b'RAW PACKET -> :010D810BFBA30000000000534253310000000009140B7703E80D0009A36C'
recv: 2022-01-03 21:01:27.029540
2022-01-03 21:01:27.148278
b'ASCII FMT -> :0DCC01810BFBA300000000840012534253310000000009140B7703E80D0009A30A'
2022-01-03 21:01:27.251004
b'SBS1(ID=13/LQ=132)-> ..not analyzed..'
2022-01-03 21:06:27.222859
b'.. coming packet (31644)'
2022-01-03 21:06:27.327868
b'RAW PACKET -> :010D810BFBA30000000000534253310000000009C50A7003E80D0009A3C3'
recv: 2022-01-03 21:06:27.328069
2022-01-03 21:06:27.444305
...
作成されるcsvファイルは、以下のような感じです。
date | sensor | devid | temp | hum | pressure |
---|---|---|---|---|---|
2022-01-03 21:00:02.709852 | twelite | 13 | 22.97 | 30.2 | 1000 |
2022-01-03 21:00:24.469994 | twelite | 13 | 23.84 | 28.9 | 1000 |
2022-01-03 21:01:27.029656 | twelite | 13 | 23.24 | 29.35 | 1000 |
2022-01-03 21:06:27.328218 | twelite | 13 | 25.01 | 26.72 | 1000 |
2022-01-03 21:11:27.546072 | twelite | 13 | 25.24 | 26.56 | 1000 |
2022-01-03 21:16:27.772859 | twelite | 13 | 25.41 | 26.32 | 1000 |
2022-01-03 21:21:27.943229 | twelite | 13 | 25.65 | 26.14 | 1000 |
2022-01-03 21:23:08.479403 | twelite | 13 | 24.63 | 27.07 | 1000 |
2022-01-03 21:28:08.676965 | twelite | 13 | 26.02 | 25.74 | 1000 |
ポイント
- 受信スレッド - readThread
- シリアルで下記のコマンドを監視します
- b'RAW PACKET -> :010D810BFBA30000000000534253310000000009C50A7003E80D0009A3C3'
重要な部分を記載します。
no | data | 役割 | 備考 |
---|---|---|---|
0バイト目 | 01 | スタートデータ | 固定値 |
1バイト目 | 0D | デバイスID | 子機に割り振られたID |
19-20バイト目 | 09C5 | 温度 | 0x09C5 / 100 => 25.01℃ |
21-22バイト目 | 0A70 | 湿度 | 0x0A70 / 100 => 26.72% |
23-24バイト目 | 03E8 | 気圧 | 0x03E8 => 1000hPa |
さいごに
1日程度エージングを行い、温度/湿度とも安定して取得できていることを確認できました。
実際の運用を考えると足りない部分がまだまだあるので、いろいろと検討していきたいと思います。
Discussion