12回は存在しない気がします

「任意の自然数の各桁を、一桁になるまで掛け算する回数の最大回数とその数を示せ」

という問題で、昨日からCore2 Q6600を動かし続けて電力を消費しまくっています。13回のパターンが存在しないことは数学的に証明されているらしく、一方で277777788888899が11回というのは容易に見つかりましたが、そこから先、12回が見つかりません。

で、高速化の手法を調べているうちに、桁数が多いほど回数が少なくなっていることに気づきました。で、こんなプログラムを作って動かしてみました。(concurrent.futuresを使っていますが、意味はありませんw)

import concurrent.futures

def check(m,n):
	s = list(str(m**n))
	return('0' in s)

def show(k):
	r = [i for i in range(1,10001) if not check(k,i)]
	print(k,':',r)

if __name__ == "__main__":
	kl = [7,8,9]
	with concurrent.futures.ProcessPoolExecutor(max_workers=4) as excuter:
		result_list = list(excuter.map(show,kl))

結果は、

7 : [1, 2, 3, 6, 7, 10, 11, 19, 35]
8 : [1, 2, 3, 5, 6, 8, 9, 11, 12, 13, 17, 24, 27]
9 : [1, 2, 3, 4, 6, 7, 12, 13, 14, 17, 34]

こうなりましたが、こんなめんどくさいことしなくても、

>>> [i for i in range(1,10001) if not '0' in list(str(7**i))]
[1, 2, 3, 6, 7, 10, 11, 19, 35]
>>> [i for i in range(1,10001) if not '0' in list(str(8**i))]
[1, 2, 3, 5, 6, 8, 9, 11, 12, 13, 17, 24, 27]
>>> [i for i in range(1,10001) if not '0' in list(str(9**i))]
[1, 2, 3, 4, 6, 7, 12, 13, 14, 17, 34]
>>> 

で十分ですね。

これは、7のみ36個以上(1万個以下)」、8のみ27個以上(1万個以下)、9のみ34個以上(1万個以下)の数値は各桁を乗算すると、出てきた数値の中に0の桁が含まれることを示します。

・・・ということはある桁数以上の検証は無意味かも、と思ったのですが、さらに他の数字をいくつか掛けたときに0の桁が残るかまではわからないのか・・・。

いずれにせよ、桁数がふえると、各桁の乗算結果に0だったり、5と2だったりという桁が出てくる確率が高くなりそうなので、桁数が増えるほど実は回数が少なくなりそうです。

Python処理の高速化

昨日の頭の体操は現在もCore2 Q6600で黙々と処理をしていて、10^617まできましたが、相変わらず12回のパターンはみつかりません。(たぶん、ないのだと思う)

それはさておき concurrent.futures を使用して、複数CPUコアで処理を高速化してみました。他にもちまちまと高速化の工夫を入れました。結果、Core i5-3570K(定格動作)で180桁まで5分かかったのが1分17秒と4倍近く早くなりました。(その過程で、4回のパターンをうまく抽出的できなくなったので誤魔化してあります^^;)

import datetime
import concurrent.futures

keta2 = [2,3,4,5,6,34,35]

def check(n):	# 引数の数値が何回かチェックする
	k = 1	# ループした回数を保持
	t = n	# 検査する対象をセット
	while True:
		u = 1
		for i in list(map(int,list(str(t)))) :
			u = u * i
		if u < 10 :
			break
		t = u
		k += 1
	return(k)

def kensho(keta):	# その桁数の数値の検証を行う
	maxk = 1		# 桁数を保持
	maxn = None		# 数値を保持
	now = datetime.datetime.now()
	print("keta: ",keta," / time: ",now-start)
	keta789 = keta - 2	# 
	for s1s2 in ['7'*i+'8'*j+'9'*(keta789-j-i) for i in range(0,keta789+1) for j in range(0,keta789-i+1)] :
		for k in keta2:	# 23456で構成される数値
			s3=str(k)
			n = int(s3+s1s2)
			r = check(n)
			if r > maxk :
				maxk = r
				maxn = n
	# print(keta,maxk,maxn)
	return(keta,maxk,maxn)

if __name__ == "__main__":
	maxk = 1		# 桁数を保持
	maxn = None		# 数値を保持	
	start = datetime.datetime.now()
	print('start: ',start)
	n = 2
	maxlist = {}
	while True:
		kl = range(n,n+50)
		n += 50
		with concurrent.futures.ProcessPoolExecutor(max_workers=4) as excuter:
			result_list = list(excuter.map(kensho,kl))
		# print(result_list)
		for k,mk,mn in result_list:
			if mk not in maxlist:
				maxlist[mk] = mn
			elif maxlist[mk] > mn :
				maxlist[mk] = mn
		print("result")
		for i in sorted(maxlist.keys()):
			if i>4 : print(i,maxlist[i])

頭の体操

Twitterで見かけた

を考えてみました。

最初はPython3で力ずくで試してみて、9回の34888999まではすぐに見つかりました。しかし、ここからは時間がかかります。傾向をみると、8と9がたくさん含まれる数値ということで、そこにターゲットを絞ってみたところ、277777788888899が11回というのも比較的短時間でわかりました。13回のものは存在しないことが証明されているらしいので、残りは12回のものということになります。

これを効率よく探す方法を考えてみました。

数値の並び順が結果には影響しないのはすぐにわかるので、個々の数値がどんな意味があるか考えてみました。もちろん、探すのは最短桁数のものです。

  • 0は乗算するといきなり0になってしまうので、候補になりえません。
  • 1は桁数を増やす効果しかないので、最小桁数のものを考える上では探索不要です。
  • 4は2x2、6は2x3、8は2x2x2、9は3x3なので、桁数を短縮する効果があります。
  • 7は素数ですから、桁数を短縮する効果はありません。
  • 5は2が出てこないケースにおいてのみ意味があります。2があると、結果に0が含まれてしまい、2回で終わってしまいます。
  • 2と3は素数ですから、これ以上桁数を短縮する効果はありません。

これらのことから、候補となる数値は7がx個、8がy個、9がz個とそれに付加して2が0〜2個、3が0〜1個、5が0〜1個登場するパターンと思われます。

5は2が0個のときのみ意味があるので、2356で構成される桁は最大3桁、4が2x2であることを考えると、23456で構成される最大2桁の範囲で探索すればよいということがわかります。

「23456で構成される最大2桁」+「7がx桁、8がy桁、9がz桁の組み合わせ」で、2桁の部分をさらに候補を絞ります。2が0〜2桁、3が0〜1桁、5が0〜1桁登場するパターンで、2が2桁=4が1桁であることを考えると、2が1桁=2、2が2桁=22=4と同じ、3が1桁=3、5が1桁=5、2が1桁+3が1桁=6と同じ、2が2桁+3が1桁=34と同じ、3が1桁+5が1桁=35と同じ、ということから、探索するのは[2,4,3,5,6,34,35]だけで良いと思われます。

そこで、[2,4,3,5,6,34,35]+(7,8,9の組み合わせからなる多数桁)をチェックするPython3プログラムを作成しました。(見直すと、もっときれいに書けそうですが・・・)

import datetime

keta2 = [2,3,4,5,6,34,35]

maxk = 1		# 桁数を保持
maxn = None		# 数値を保持

def once(n):	# 1回の計算を行う
	t = 1
	for i in list(str(n)) :
		t = t * int(i)
	return(t)

def check(n):	# 引数の数値が何回かチェックする
	global maxk,maxn
	k = 1	# ループした回数を保持
	t = n	# 検査する対象をセット
	while True:
		r = once(t)
		if r < 10 :
			break
		t = r
		k += 1
	if k == maxk :
		print('同等 ',n,k)
	if k > maxk :
		maxk = k
		maxn = n
		print('更新 ',n,k)

def check_print(n):	# 引数の数値が何回か出力しながらチェックする
	global maxk,maxn
	k = 1	# ループした回数を保持
	t = n	# 検査する対象をセット
	while True:
		r = once(t)
		if r < 10 :
			break
		t = r
		k += 1
		print(t,' ',end='')

def kensho(keta):	# その桁数の数値の検証を行う
	keta789 = keta - 2	# 
	for i in range(0,keta789+1):
		s1 = '7'*i
		keta89 = keta789 - i;
		for j in range(0,keta89+1):
			s2 = '8'*j + '9'*(keta89-j)
			for k in keta2:	# 23456で構成される数値
				s3=str(k)
				n = int(s3+s1+s2)
				check(n)

if __name__ == "__main__":
	start = datetime.datetime.now()
	print('start: ',start)
#	for keta in range(2,100+1) :
	keta = 2
	while True:
		now = datetime.datetime.now()
		print("keta: ",keta," / time: ",now-start," / record: ",maxn,maxk)
		kensho(keta)
		keta += 1
	print("result ",maxn,maxk)
	check_print(maxn)

これを Core2 Q6600(古い・・)で実行すると、

keta:  2  / time:  0:00:00.000048  / record:  None 1
同等  2 1
同等  3 1
同等  4 1
同等  5 1
同等  6 1
更新  34 2
同等  35 2
keta:  3  / time:  0:00:00.000200  / record:  34 2
同等  29 2
更新  39 3
同等  49 3
同等  59 3
同等  69 3
同等  359 3
同等  68 3
更新  348 4
keta:  4  / time:  0:00:00.000449  / record:  348 4
同等  699 4
同等  3499 4
同等  489 4
同等  3489 4
更新  688 5
同等  3488 5
同等  679 5
keta:  5  / time:  0:00:00.000870  / record:  688 5
同等  6999 5
同等  34999 5
同等  34888 5
更新  6788 6
keta:  6  / time:  0:00:00.001480  / record:  6788 6
同等  49999 6
更新  68889 7
同等  347799 7
keta:  7  / time:  0:00:00.002459  / record:  68889 7
同等  377889 7
更新  3477889 8
keta:  8  / time:  0:00:00.003920  / record:  3477889 8
同等  6999999 8
同等  6888999 8
更新  34888999 9
keta:  9  / time:  0:00:00.005924  / record:  34888999 9
keta:  10  / time:  0:00:00.008575  / record:  34888999 9
keta:  11  / time:  0:00:00.011963  / record:  34888999 9
更新  3778888999 10
keta:  12  / time:  0:00:00.016466  / record:  3778888999 10
keta:  13  / time:  0:00:00.022207  / record:  3778888999 10
keta:  14  / time:  0:00:00.029282  / record:  3778888999 10
同等  3888888888889 10
keta:  15  / time:  0:00:00.037926  / record:  3778888999 10
keta:  16  / time:  0:00:00.048363  / record:  3778888999 10
更新  277777788888899 11
keta:  17  / time:  0:00:00.060824  / record:  277777788888899 11
keta:  18  / time:  0:00:00.075424  / record:  277777788888899 11
同等  27777789999999999 11
keta:  19  / time:  0:00:00.092772  / record:  277777788888899 11
keta:  20  / time:  0:00:00.112918  / record:  277777788888899 11

ということで、60msほどで11回のものまで見つかりました。
これをひたすら回して、現在11時間で470桁くらいまで到達しているのですが、今の所12回のパターンは見つかっていません。横軸に桁数、縦軸にその桁数までの所要時間(秒)を取ると、

という感じで、概ね所要時間は 7.7×桁数^4×10^(-7) (秒)で近似できそうです。となると、1000桁までチェックすると9日くらいかかりそうです。

PIC12F1822でLチカ

PIC12F1822でLチカしたときの消費電流を測ってみました。どのくらい消費電流が抑えられるか(=どのくらい電池動作ができるか)を確認したいので。

レジスタの設定はすべてCode Cofiguratorにお任せ。
CPUのクロックは初期設定の500kHzです。
特にあれこれ設定はいじらず、TMR1でぼんやり明滅するタイミング(100ms周期)を生成、割り込みを発生させて、PWM2のデューティーを変動させます。TMR2は100us周期のクロックを生成して、ECCPでPWM波形を生成します。そして、PWM端子の出力に1kΩを通して高輝度LEDを接続して点灯させます。なお、不要な端子は入力端子として、貫通電流による消費電力増加を防ぐため内部プルアップを有効化してあります。

作成したソースコードは main.c の以下の部分だけです。見ての通り、初期化以外はすべて100msに1回の割り込みの際に処理しています。

#include "mcc_generated_files/mcc.h"
uint8_t c=0;
uint16_t pwmt[]={
       0, 2, 4, 6, 8,10,12,14,16,18,
      20,20,20,20,20,20,20,20,20,20,
      18,16,14,12,10, 8, 6, 4, 2, 0,
       0, 0, 0, 0, 0, 0, 0, 0, 0
};

void TMR1int(void){
    c++;
    if(c == sizeof(pwmt)/sizeof(uint16_t)) c=0;
    EPWM_LoadDutyValue(pwmt);
}

void main(void)
{
    SYSTEM_Initialize();
    TMR1_SetInterruptHandler(TMR1int);
    INTERRUPT_GlobalInterruptEnable();
    INTERRUPT_PeripheralInterruptEnable();

    while (1)
    {
        // Add your application code
    }
}

単4電池2本で動かしてみると、消費電流は平均で0.2mA〜0.6mAなので、こちらの資料を参考にすると2,900時間くらい持つ計算です。つまり、約4ヶ月くらいでしょうか。ただ、電圧が落ちてくるとLEDが点灯しなくなる可能性があるので、実際にはそんなに持たないかもしれませんが。

<追伸>
ソースコードの中の EPWM_LoadDutyValue(pwmt) という部分は、実際には EPWM_LoadDutyValue(pwmt[c])です。SyntaxHighlighter を使っているのですが、半角の[c]がタグとして扱われるのか、消えてしまいます。orz

温度ロガーを作成しました

3Dプリンタ用保温ボックスを作って、ABSでの出力がうまく行くようになったのはいいのですが、ボックス内がかなり高温になっている(あたりまえですが・・・)のが気になります。

実際どのくらいなのか、手持ちの部品で温度を測ってみることにしました。

PCへの取り込みはシリアルで、秋月で以前購入したFTDI USBシリアル変換ケーブル(5V)を使いました。マイコンはPIC12F1822、温度センサーはLM35DZを使っています。最初はESP8266かESP32でAmbientに直接送信させようかとも思ったのですが、今回は測定気温が80℃を超えているかもしれませんし、場合によっては100℃を超えているかもしれません。なので、自己発熱が少なく(=消費電力を抑えることができる)壊れてもすぐ交換可能なPICとデータシート上150℃まで動作可能なLM35を使用することにしました。

ソフトはMPLAB Code Configuratorで以下のように設定しました。

  • CPUの動作周波数は初期設定の内部クロック500kHz
  • RA4/RA5にEUSARTを割付け、通信パラメータは初期設定の9600bpsのままで、Enable EUSART Interrupts で割り込みを許可。送受信バッファは各16バイト(受信は機能として使ってませんが)
  • TMR1はプリスケーラを1:2に設定変更して、Timer Periodを1秒に設定、Enable Timer Interrupt でタイマ割り込みを発生させます。
  • FVRで1.024VのADC用の基準電圧を発生させます。
  • ADCはPositive ReferenceをFVRにして、基準電圧を1.024Vにします。これで10bit分解能なので、入力電圧換算で10mVの分解能となります。LM35DZは100mV/1℃なので、0.1℃単位でAD変換できることになります。Result Alignmentはrightに変更して、読んだ値を1/10すれば温度直読になるようにします。
  • ピンマネージャでADCはAN2を設定、EUSARTはRX/TXをRA5/RA4に割り付けます。

これでMPLAB Code Configuratorにソースを吐かせて、main.cを作成しました。

#include "mcc_generated_files/mcc.h"
#include <stdlib.h>

bool exec=false;
unsigned long int time=0;
void TMR1int(void){
    exec = true;
    time++;
}

/*
                         Main application
 */
inline void EUSART_putchar(unsigned char ch){
    EUSART_Write(ch);
}

void EUSART_puts(char *str){
    uint16_t p=0;
    while(str[p]!=0) EUSART_putchar(str[p++]);    // Write data
}

void EUSART_putdec(unsigned long int num){
    char buf[10];
    unsigned long int t,u;
    int8_t i=0;

    while(1){
        buf[i]=(num % 10)+'0';
        num = num / 10;
        if(num == 0)break;
        i++;
    }
    while(i>=0){
        EUSART_putchar(buf[i]);
        i--;
    }
}

void EUSART_putLM35(unsigned long int num){
    char buf[10];
    unsigned long int t,u;
    int8_t i=0;

    while(1){
        buf[i]=(num % 10)+'0';
        num = num / 10;
        if(num == 0)break;
        i++;
    }
    while(i>=0){
        if(i==0) EUSART_putchar('.');
        EUSART_putchar(buf[i]);
        i--;
    }
}

void main(void)
{
    // initialize the device
    SYSTEM_Initialize();

//    EUSART_Initialize();
    TMR1_SetInterruptHandler(TMR1int);
    exec = false;
    time=0;
    
    // Enable the Global Interrupts
    INTERRUPT_GlobalInterruptEnable();

    // Enable the Peripheral Interrupts
    INTERRUPT_PeripheralInterruptEnable();

    while (1)
    {
        if(exec){
            // Read LM35DZ
            uint16_t convertedValue;
            ADC_Initialize();
            convertedValue = ADC_GetConversion(channel_AN2);
            // Send to HOST
            EUSART_putdec(time);
            EUSART_putchar(' ');
            EUSART_putLM35((unsigned long int)convertedValue);
            EUSART_putchar(0x0d);
            EUSART_putchar(0x0a);
            exec = false;
        }
        // Add your application code
    }
}

ビルドして、PICkit3で書き込みます。書き込む際はICクリップを使うことで、基板上の書き込みに関する信号の配線作業を省略しています。DIPパッケージで20ピンまでの8bitのPICは電源/GNDを含めて1ピン側の8ピンに書き込みに必要な信号が共通で出ています。ですので、この8ピンのICクリップは20ピンまでの8ビットのPICで共通に使用することができます。(例外はあるかもしれませんが・・・)

実行すると1秒単位に電源投入からの経過秒数と温度を出力します。

 :
 :
898 29.7
899 29.8
900 29.6
901 29.7
902 29.6
903 29.7
904 29.6
905 29.8
906 29.7

測定したら、そのテキストファイルをLibre Officeに読み込ませてグラフ化させるつもりです。

ABS出力に成功しました

3D Benchyの出力風景です。

ABSのデフォルト設定のノズル温度 230℃、ベッド温度 80℃、ファン 100%、積層ピッチ 0.15mmだと1~2mm出力したところでベッドから剥がれてしまったのですが、ノズル温度 240℃、ベッド温度 100℃、ファン 60%、積層ピッチ 0.15mmだと最後まで剥がれずに出力できました。

しかし、かなり保温ボックスの中の温度が上がっているので、いったい何度になっているのかチェックしておかないと危険かもしれません。

3Dプリンタ用保温ボックスを作ってみた

以前試してみて全然ダメだったABSでの出力ですが、あきらめきれないので再度試してみました。

今度は、MDFとプラ段で保温ボックスを作ってみました。

底板はMDFで、側面と天板はクリアのプラ段で、結合は白色の養生テープで行っています。

  • MDF 9mm厚 560mm×480mm ×1枚
  • MDF 9mm厚 568mm×488mm ×1枚
  • プラ段 4mm厚 クリア 560mm×480mm ×3枚(両側面、天面用)
  • プラ段 4mm厚 クリア 480mm×480mm ×2枚(背面、前面用)

MDFは2枚重ねて底に敷いています。寸法を4mm差をつけることにより、ここにプラ段を載せています。本当はプラ段は前面用、背面用は488mm×484mmにするべきだったかもしれません。

プラ段はクリアを使うことにより外につけたLEDライトの光が中に入るので作業性の悪化は避けることができました。また、以前設置したTTGO T-Cameraもそのまま使うことができました。

ちょっとファンのカバーが白のせいで白飛び気味なので、ファンカバーを黒で作り直した方がよさそうですが。

写真はスプールにつけるホルダー用の柱をPLAで出力しているところですが、200℃/60℃でも内部はすごい熱気です。これで電源ユニットやコントローラが耐えられるのか心配ですので、今度温度計を放り込んでみます。

肝心のABSでの出力ですが、ノズル230℃、ベッド80℃では途中でベッドから剥がれてしまいます。現在、ノズル240℃、ベッド100℃、ファン速度60%でトライ中です。

内部はさらに熱気が高まると思いますので、その対策が必要かもしれません。

ESP8266+Ambientを試す

ESP8266/ESP32自体でWebサーバを持つのではなく、外部にWebサーバを持つ環境を考えていたのですが、「Ambientでいいんじゃない?」という気がして、ESP8266とAmbientを試してみました。環境はいつもの如く、LinutMint19 + Arduino環境です。Ambientのユーザー登録は別途してあるものとします。

1.Ambientライブラリのインストール

Zipで持ってきてインストールする方法もあるのですが、Linuxの場合はgitで持ってくるほうが簡単です。持ってきてからArduino IDEを起動します。

~$ cd Arduino/libraries/
~/Arduino/libraries$ git clone https://github.com/AmbientDataInc/Ambient_ESP8266_lib
Cloning into 'Ambient_ESP8266_lib'...
remote: Enumerating objects: 3, done.
remote: Counting objects: 100% (3/3), done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 101 (delta 0), reused 1 (delta 0), pack-reused 98
Receiving objects: 100% (101/101), 44.32 KiB | 242.00 KiB/s, done.
Resolving deltas: 100% (37/37), done.
~/Arduino/libraries$ 

2.ソースコードの作成

ソースコードは smartconfig が入っているものをベースにしました。

#include <ESP8266WiFi.h>
#include <WiFiUdp.h>
#include "Ambient.h"

/*****************************************************************************/
/* NTP Client                                                                */
#include <time.h>
#define JST     3600*9

/*****************************************************************************/
/* Ambient                                                                   */
unsigned int channelId =  xxxxx;
const char* writeKey = "xxxxxxxxxxxxxxxx";
WiFiClient client;
Ambient ambient;

/*****************************************************************************/
/* Timer interrupt                                                           */
#define MS2CLK(ms)    (ms * 80000L)
uint32_t nxTim;
uint32_t numTim = 0;
bool exec = false;

// 割り込みハンドラ
void timer0_ISR (void) {
  nxTim += MS2CLK(100); // 100msec 
  timer0_write( nxTim );
  
  // 割り込み処理
  numTim++;
  if( numTim % 10 == 0 ){
    exec = true;  // 1秒に1回処理するため
  }
}

/*************************** Sketch Code ************************************/
 
void setup() {
  uint8_t cnt = 0;  

  // set for STA mode
  WiFi.mode(WIFI_STA);
  
  // put your setup code here, to run once:
  Serial.begin(115200);
  Serial.flush();
  Serial.println("\r\n");
    
  // sensor input
  pinMode(14,INPUT);
  
  // LED
  pinMode(12,OUTPUT);
  pinMode(13,OUTPUT);
  
  //configure pin0 
  pinMode(0, INPUT_PULLUP);

  // deplay for 2 sec for smartConfig
  Serial.println("2 sec before clear SmartConfig");
  delay(2000);
  
  // read pullup
  bool isSmartConfig = digitalRead(0);
  if (isSmartConfig == false) {
    // bink for clear config
    blinkClearConfig();
    Serial.println("clear config");
    // reset default config
    WiFi.disconnect();

  }

  // if wifi cannot connect start smartconfig
  while(WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
    if(cnt++ >= 15){
       WiFi.beginSmartConfig();
       while(1){
           delay(500);
           if(WiFi.smartConfigDone()){
             Serial.println("SmartConfig Success");
             blinkSmartConfig();
             break;
           }
       }
    }
  }

  Serial.println("");

  WiFi.printDiag(Serial);

  // Print the IP address
  Serial.println(WiFi.localIP());

  // Ambientへ接続
  ambient.begin(channelId, writeKey, &client);

  // タイマ割り込みの設定
  noInterrupts();
  timer0_isr_init();
  timer0_attachInterrupt(timer0_ISR);
  nxTim = ESP.getCycleCount() + MS2CLK(100); // 100msec
  timer0_write( nxTim );
  exec = false;
  interrupts();
  
  // time set with NTP
  configTime( JST, 0, "ntp.nict.jp", "ntp.jst.mfeed.ad.jp");

}


void blinkSmartConfig() {
    digitalWrite(13, HIGH);   // turn the LED on (HIGH is the voltage level)
    delay(50);              // wait for a second 
    digitalWrite(13, LOW);    // turn the LED off by making the voltage LOW
    delay(50);
}

void blinkClearConfig() {
  int i=0;
  while(i<=3) {
    digitalWrite(13, HIGH);   // turn the LED on (HIGH is the voltage level)
    delay(100);              // wait for a second 
    digitalWrite(13, LOW);    // turn the LED off by making the voltage LOW
    delay(100);
    i++;
  }
}

int detcnt=0;
uint16_t SW1=0;

void loop() {
  time_t t;
  struct tm *tm;
  char msg[64]="";
    
  bool isSW1 = digitalRead(0);

  // チャタリング除去
  if(isSW1 == false){
    SW1 = (SW1 << 1) | 0x0001;
  } else {
    SW1 = (SW1 << 1);
  }

  // SW1を押した回数を数える
  if(SW1 == 0x0001){
    detcnt++;
    Serial.print(".");
  }
    
  if(exec){       // 1秒間に1回処理
    digitalWrite(12, 1-digitalRead(12));   // Blink the LED
    /*******/
    /* NTP */
    t = time(NULL);
    tm = localtime(&t);
    sprintf(msg,"%04d/%02d/%02d %02d:%02d:%02d %ld",
        tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday,
        tm->tm_hour, tm->tm_min, tm->tm_sec,
        (long)t);    
    int s = tm->tm_sec;
    //
    if( s==0 ){   // 1分間に一回、毎分0秒にAmbientにデータ送信
      ambient.set(1,detcnt);
      ambient.send();
      Serial.println("");
      Serial.print(msg);
      Serial.print(" - ");
      Serial.print(detcnt);
      Serial.print(" : ");
      detcnt=0;
    }
    exec = false;
  }

  delay(5);
}

タクトスイッチを押した回数を1分間に1回送信するものですが、非常に簡単にグラフ表示できました。簡単でいいかもです。

Python3でMQTTクライアント

ESP8266でMQTTを喋れるようになったので、受ける側を考えます。自分用のサービスまでしか考えていないので、記述が楽なPythonで作ります。調べると paho というMQTTクライアントがあるようなので、こちらを参考にそれを試してみます。

Python3でvenvを使って仮想環境を作ります。

~$ mkdir python
~$ cd python
~/python$ sudo apt install python3-venv
~/python$ python3 -m venv pytest
~/python$ source ./pytest/bin/activate
(pytest) ~/python$ cd pytest/
(pytest) ~/python/pytest$ 
(pytest) ~/python/pytest$ python -V
Python 3.6.8

pipをバージョンアップします。

(pytest) ~/python/pytest$ pip install --upgrade pip
Collecting pip
  Downloading https://files.pythonhosted.org/packages/5c/e0/be401c003291b56efc55aeba6a80ab790d3d4cece2778288d65323009420/pip-19.1.1-py2.py3-none-any.whl (1.4MB)
    100% |████████████████████████████████| 1.4MB 672kB/s 
Installing collected packages: pip
  Found existing installation: pip 9.0.1
    Uninstalling pip-9.0.1:
      Successfully uninstalled pip-9.0.1
Successfully installed pip-19.1.1
(pytest) ~/python/pytest$ 

pahoをインストールします。

(pytest) ~/python/pytest$ pip install paho-mqtt
Collecting paho-mqtt
  Downloading https://files.pythonhosted.org/packages/25/63/db25e62979c2a716a74950c9ed658dce431b5cb01fde29eb6cba9489a904/paho-mqtt-1.4.0.tar.gz (88kB)
     |████████████████████████████████| 92kB 12.6MB/s 
Installing collected packages: paho-mqtt
  Running setup.py install for paho-mqtt ... done
Successfully installed paho-mqtt-1.4.0
(pytest) ~/python/pytest$ 

Subscriberをテストしてみます。ソースコードは以下のとおりです。

# -*- coding:utf-8 -*-
import paho.mqtt.client as mqtt

def on_connect(client, userdata, flags, respons_code):
    topic = '/feeds/sensor/#'
    print('watch %s' % topic)
    client.subscribe(topic)

def on_message(client, userdata, msg):
    print(msg.topic + ' ' + str(msg.payload))

client = mqtt.Client()
client.on_connect = on_connect
client.on_message = on_message
client.connect('XXX.XXX.XXX.XXX', 1883, keepalive=60)
client.loop_forever()

結果は、

(pytest) ~/python/pytest$ python scbscriber.py 
watch /feeds/sensor/#
/feeds/sensor/0001 b'0001 4C11AEXXXXXX 1970/01/01 08:00:02 28802 0'
/feeds/sensor/0001 b'0001 4C11AEXXXXXX 2019/07/16 01:39:52 1563241192 1'
/feeds/sensor/0001 b'0001 4C11AEXXXXXX 2019/07/16 01:39:53 1563241193 2'
/feeds/sensor/0001 b'0001 18FE34XXXXXX 1970/01/01 08:00:02 28802 0'
/feeds/sensor/0001 b'0001 4C11AEXXXXXX 2019/07/16 01:39:54 1563241194 3'
/feeds/sensor/0001 b'0001 18FE34XXXXXX 2019/07/16 01:39:55 1563241195 1'
/feeds/sensor/0001 b'0001 4C11AEXXXXXX 2019/07/16 01:39:55 1563241195 4'
/feeds/sensor/0001 b'0001 18FE34XXXXXX 2019/07/16 01:39:56 1563241196 2'
/feeds/sensor/0001 b'0001 4C11AEXXXXXX 2019/07/16 01:39:56 1563241196 5'
/feeds/sensor/0001 b'0001 18FE34XXXXXX 2019/07/16 01:39:57 1563241197 3'
/feeds/sensor/0001 b'0001 4C11AEXXXXXX 2019/07/16 01:39:57 1563241197 6'
/feeds/sensor/0001 b'0001 18FE34XXXXXX 2019/07/16 01:39:58 1563241198 4'

ということで、無事に動作しました。

smartconfig+MQTT

ESP8266を便利な(笑)MQTTクライアントにするため、smartconfigと組み合わせてみます。センサーの情報をMQTTで一箇所に集めて、表示できるようにするのが目標です。

1.smartconfigの動作確認

まずベースは以前の記事のsmartconfigです。Androidのアプリ側はESP8266 smartconfigで動作することは確認しました。

2.ライブラリのインストール

ArduinoIDEで「ツール→ライブラリを管理」でライブラリマネージャーから「Adafruit MQTT Library」をインストールします。

3.ソースの結合

ソースを結合して以下の通りとしました。難しいところはありません。ついでに、NTPでの時刻合わせ、タイマ割り込みの処理も記述も追加しておきました。
トピックは /feed/sensor/番号 にすることにしました。
できればこの際、SSL化もしたかったのですが、mosquittoのブローカ側のSSL化すらうまく行っていないので今回は見送りです。どちらにせよ、プライベートIPアドレスの中での運用なのであまり気にする必要はないのですが。
※あちこちからの切り貼りです。

#include <ESP8266WiFi.h>
#include <WiFiUdp.h>
#include "Adafruit_MQTT.h"
#include "Adafruit_MQTT_Client.h"

/*****************************************************************************/
/* MQTT Client                                                               */

/************************* Adafruit.io Setup *********************************/
#define AIO_SERVER      "10.89.105.1"
#define AIO_SERVERPORT  1883                   // use 8883 for SSL
#define AIO_USERNAME    "username"  // 適当に変更(未使用)
#define AIO_KEY         "password"  // 適当に変更(未使用)
#define SERIALNO        "0001"

/************ Global State (you don't need to change this!) ******************/
// Create an ESP8266 WiFiClient class to connect to the MQTT server.
WiFiClient client;
// or... use WiFiFlientSecure for SSL
//WiFiClientSecure client;
 
// Setup the MQTT client class by passing in the WiFi client and MQTT server and login details.
Adafruit_MQTT_Client mqtt(&client, AIO_SERVER, AIO_SERVERPORT, AIO_USERNAME, AIO_KEY);

/****************************** Feeds ***************************************/
Adafruit_MQTT_Publish motion = Adafruit_MQTT_Publish(&mqtt, "/feeds/sensor/" SERIALNO);
Adafruit_MQTT_Subscribe command = Adafruit_MQTT_Subscribe(&mqtt, "/feeds/command/" SERIALNO);
char macstr[13];    // Station mode MAC address 

/*****************************************************************************/
/* NTP Client                                                                */
#include <time.h>
#define JST     3600*9

/*****************************************************************************/
/* Timer interrupt                                                           */
#define MS2CLK(ms)    (ms * 80000L)
uint32_t nxTim;

// 割り込みハンドラ
void timer0_ISR (void) {
  nxTim += MS2CLK(100); // 100msec 
  timer0_write( nxTim );
  
  // 割り込み処理
  digitalWrite(13, 1-digitalRead(13));   // Blink the LED
}

/*************************** Sketch Code ************************************/
 
void setup() {
  uint8_t cnt = 0;  

  // set for STA mode
  WiFi.mode(WIFI_STA);
  
  // put your setup code here, to run once:
  Serial.begin(115200);
  Serial.flush();
  Serial.println("\r\n");
    
  // led status at pin4
  pinMode(12,OUTPUT);
  digitalWrite(12, HIGH);   // turn the LED on (HIGH is the voltage level)
  
  // led status at pin5
  pinMode(13,OUTPUT);
  digitalWrite(13, HIGH);   // turn the LED on (HIGH is the voltage level)
  
  //configure pin0 
  pinMode(0, INPUT_PULLUP);

  // deplay for 2 sec for smartConfig
  Serial.println("2 sec before clear SmartConfig");
  delay(2000);
  
  // read pullup
  bool isSmartConfig = digitalRead(0);
  if (isSmartConfig == false) {
    // bink for clear config
    blinkClearConfig();
    Serial.println("clear config");
    // reset default config
    WiFi.disconnect();

  }

  // if wifi cannot connect start smartconfig
  while(WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
    if(cnt++ >= 15){
       WiFi.beginSmartConfig();
       while(1){
           delay(500);
           if(WiFi.smartConfigDone()){
             Serial.println("SmartConfig Success");
             blinkSmartConfig();
             break;
           }
       }
    }
  }

  Serial.println("");

  WiFi.printDiag(Serial);

  // Print the IP address
  Serial.println(WiFi.localIP());

  // タイマ割り込みの設定
  noInterrupts();
  timer0_isr_init();
  timer0_attachInterrupt(timer0_ISR);
  nxTim = ESP.getCycleCount() + MS2CLK(100); // 100msec
  timer0_write( nxTim );
  interrupts();
  
  // time set with NTP
  configTime( JST, 0, "ntp.nict.jp", "ntp.jst.mfeed.ad.jp");

  // Setup MQTT subscription for command feed.
  mqtt.subscribe(&command);

  //  
  uint8_t mac0[6];
  WiFi.macAddress(mac0);
  sprintf(macstr,"%02X%02X%02X%02X%02X%02X", mac0[0], mac0[1], mac0[2], mac0[3], mac0[4], mac0[5]);
}


void blinkSmartConfig() {
    digitalWrite(13, HIGH);   // turn the LED on (HIGH is the voltage level)
    delay(50);              // wait for a second 
    digitalWrite(13, LOW);    // turn the LED off by making the voltage LOW
    delay(50);
}

void blinkClearConfig() {
  int i=0;
  while(i<=3) { digitalWrite(13, HIGH); // turn the LED on (HIGH is the voltage level) delay(100); // wait for a second digitalWrite(13, LOW); // turn the LED off by making the voltage LOW delay(100); i++; } } uint32_t x=0; void loop() { time_t t; struct tm *tm; char msg[64]=""; bool isSW1 = digitalRead(0); if(isSW1 == false){ for(int i=5;i>0;i--){
      delay(1000);
      Serial.println(i);    
    }
    Serial.println("Power off");    
    delay(500);
    pinMode(15,OUTPUT);
    digitalWrite(15, HIGH);   // Power-off the board (HIGH is the voltage level)
    delay(5000);
  }
  digitalWrite(12, 1-digitalRead(12));   // Blink the LED

  /*******/
  /* NTP */
  t = time(NULL);
  tm = localtime(&t);
  sprintf(msg,"%s %s %04d/%02d/%02d %02d:%02d:%02d %ld %d",
        SERIALNO,macstr,
        tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday,
        tm->tm_hour, tm->tm_min, tm->tm_sec,
        (long)t,x++);

  /********/
  /* MQTT */
  // Ensure the connection to the MQTT server is alive (this will make the first
  // connection and automatically reconnect when disconnected).  See the MQTT_connect
  // function definition further below.
  MQTT_connect();
 
  // this is our 'wait for incoming subscription packets' busy subloop
  // try to spend your time here
 
  Adafruit_MQTT_Subscribe *subscription;
  while ((subscription = mqtt.readSubscription(1000))) {
    if (subscription == &command) {
      Serial.print(F("Got: "));
      Serial.println((char *)command.lastread);
    }
  }
 
  // Now we can publish stuff!
  Serial.print(F("\nSending message : "));
  Serial.print(msg);
  Serial.print("...");
  if (! motion.publish(msg)) {
    Serial.println(F("Failed"));
  } else {
    Serial.println(F("OK!"));
  }
 
  // ping the server to keep the mqtt connection alive
  // NOT required if you are publishing once every KEEPALIVE seconds
  /*
  if(! mqtt.ping()) {
    mqtt.disconnect();
  }
  */
}

// Function to connect and reconnect as necessary to the MQTT server.
// Should be called in the loop function and it will take care if connecting.
void MQTT_connect() {
  int8_t ret;
 
  // Stop if already connected.
  if (mqtt.connected()) {
    return;
  }
 
  Serial.print("Connecting to MQTT... ");
 
  uint8_t retries = 3;
  while ((ret = mqtt.connect()) != 0) { // connect will return 0 for connected
       Serial.println(mqtt.connectErrorString(ret));
       Serial.println("Retrying MQTT connection in 5 seconds...");
       mqtt.disconnect();
       delay(5000);  // wait 5 seconds
       retries--;
       if (retries == 0) {
         // basically die and wait for WDT to reset me
         while (1);
       }
  }
  Serial.println("MQTT Connected!");
}

ついでに、基板の回路図も再掲載です。

実装部品

ミニマム実装:
 半田面
    GS5 2-3
    GS6 2-3
    C6  0.1u
    C7  22u
    R1  470
    R2  4.7k
    R3  4.7k
    R4  100
    R7  20k
    R8  4.7k 
    R11 0Ω
    R19 22k
    R20 33k
    R21 470
    R22 470
    R23 470
    R24 470
 部品面
    U3  AZ1117-ADJ
    D1  LED
    D2  LED
    D3  LED
    SW1 タクトSW
    SW2 タクトSW
    C5  33u