いっぺーちゃんの いろいろやってみよ~

micropython on ESP32 で時刻設定を可能にする

ESP32のmicropythonのutimeモジュールにはtimeという時刻(ある時点からの経過秒)を取得するメソッドがあるのですが、時刻を設定する方法が見当たりません。
「無いのなら作ってしまえ、ホトトギス」ということで作ってみました。
ついでに、経過秒から年月日時分秒を取得するlocaltimeメソッド、年月日時分秒から経過秒を取得するmktimeメソッドも作りました(というか、ESP8266からパクってきました)。

以下のパッチをあててmicropythonを再buildしてください。

diff --git a/esp32/modutime.c b/esp32/modutime.c
index f037faa..f1fd43f 100644
--- a/esp32/modutime.c
+++ b/esp32/modutime.c
@@ -31,7 +31,12 @@
 #include <sys/time.h>
 
 #include "extmod/utime_mphal.h"
+#include "lib/timeutils/timeutils.h"
+#include "py/nlr.h"
 
+/// \function time()
+/// get current time.
+/// It returns an integer which is the number of seconds since Jan 1, 1970.
 STATIC mp_obj_t time_time(void) {
     struct timeval tv;
     gettimeofday(&tv, NULL);
@@ -39,10 +44,98 @@ STATIC mp_obj_t time_time(void) {
 }
 MP_DEFINE_CONST_FUN_OBJ_0(time_time_obj, time_time);
 
+/// \function set_time(secs)
+/// set current time.
+/// argument is integer which is the number of seconds since Jan 1, 1970.
+STATIC mp_obj_t time_set_time(mp_obj_t arg) {
+    struct timeval tv;
+    mp_int_t sec = mp_obj_get_int(arg);
+
+    tv.tv_sec = sec;
+    tv.tv_usec = 0;
+    settimeofday(&tv, NULL);
+    return mp_const_none;
+}
+MP_DEFINE_CONST_FUN_OBJ_1(time_set_time_obj, time_set_time);
+
+/// \function localtime([secs])
+/// Convert a time expressed in seconds since Jan 1, 1970 into an 8-tuple which
+/// contains: (year, month, mday, hour, minute, second, weekday, yearday)
+/// If secs is not provided or None, then the current time from the RTC is used.
+/// year includes the century (for example 2014)
+/// month   is 1-12
+/// mday    is 1-31
+/// hour    is 0-23
+/// minute  is 0-59
+/// second  is 0-59
+/// weekday is 0-6 for Mon-Sun.
+/// yearday is 1-366
+STATIC mp_obj_t time_localtime(mp_uint_t n_args, const mp_obj_t *args) {
+    timeutils_struct_time_t tm;
+    mp_int_t seconds;
+    if (n_args == 0 || args[0] == mp_const_none) {
+        struct timeval tv;
+        gettimeofday(&tv, NULL);
+        seconds = tv.tv_sec;
+    } else {
+        seconds = mp_obj_get_int(args[0]);
+    }
+    seconds -= 946684800;        // 946684800 means seconds of 1st January 1970 00:00:00 to 1st January 2000 00:00:00
+    // reflect TimeZone
+    seconds += 9 * 60 * 60;                 // FIX to "JST"
+
+    timeutils_seconds_since_2000_to_struct_time(seconds, &tm);
+    mp_obj_t tuple[8] = {
+        tuple[0] = mp_obj_new_int(tm.tm_year),
+        tuple[1] = mp_obj_new_int(tm.tm_mon),
+        tuple[2] = mp_obj_new_int(tm.tm_mday),
+        tuple[3] = mp_obj_new_int(tm.tm_hour),
+        tuple[4] = mp_obj_new_int(tm.tm_min),
+        tuple[5] = mp_obj_new_int(tm.tm_sec),
+        tuple[6] = mp_obj_new_int(tm.tm_wday),
+        tuple[7] = mp_obj_new_int(tm.tm_yday),
+    };
+    return mp_obj_new_tuple(8, tuple);
+}
+MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(time_localtime_obj, 0, 1, time_localtime);
+
+/// \function mktime()
+/// This is inverse function of localtime. It's argument is a full 8-tuple
+/// which expresses a time as per localtime. It returns an integer which is
+/// the number of seconds since Jan 1, 2000.
+STATIC mp_obj_t time_mktime(mp_obj_t tuple) {
+
+    size_t len;
+    mp_obj_t *elem;
+
+    mp_obj_get_array(tuple, &len, &elem);
+
+    // localtime generates a tuple of len 8. CPython uses 9, so we accept both.
+    if (len < 8 || len > 9) {
+        nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_TypeError, "mktime needs a tuple of length 8 or 9 (%d given)", len));
+    }
+
+    mp_int_t t = timeutils_mktime(  mp_obj_get_int(elem[0]),
+                                    mp_obj_get_int(elem[1]),
+                                    mp_obj_get_int(elem[2]),
+                                    mp_obj_get_int(elem[3]),
+                                    mp_obj_get_int(elem[4]), 
+                                    mp_obj_get_int(elem[5])    );
+    // reflect TimeZone
+    t -= 9 * 60 * 60;               // FIX to "JST"
+    t += 946684800;                 // 946684800 means seconds of 1st January 1970 00:00:00 to 1st January 2000 00:00:00
+
+    return mp_obj_new_int_from_uint(t);
+}
+MP_DEFINE_CONST_FUN_OBJ_1(time_mktime_obj, time_mktime);
+
 STATIC const mp_rom_map_elem_t time_module_globals_table[] = {
     { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_utime) },
 
     { MP_ROM_QSTR(MP_QSTR_time), MP_ROM_PTR(&time_time_obj) },
+    { MP_ROM_QSTR(MP_QSTR_set_time), MP_ROM_PTR(&time_set_time_obj) },
+    { MP_ROM_QSTR(MP_QSTR_localtime), MP_ROM_PTR(&time_localtime_obj) },
+    { MP_ROM_QSTR(MP_QSTR_mktime), MP_ROM_PTR(&time_mktime_obj) },
     { MP_ROM_QSTR(MP_QSTR_sleep), MP_ROM_PTR(&mp_utime_sleep_obj) },
     { MP_ROM_QSTR(MP_QSTR_sleep_ms), MP_ROM_PTR(&mp_utime_sleep_ms_obj) },
     { MP_ROM_QSTR(MP_QSTR_sleep_us), MP_ROM_PTR(&mp_utime_sleep_us_obj) },

 

ESP8266などではtime()で取得できる経過秒は2000年1月1日 00:00:00(UTC) からの経過秒となっていますが、ここでは他のシステム同様1970年1月1日00:00:00(UTC)からの経過秒にしてあります。
2000年1月1日 00:00:00(UTC) を基準にしたい場合は、946684800を加減している部分を削除すると変更できると思います(たぶん)。

また、localtime/mktimeのタイムゾーンは+9(JST)に固定してあります。
これも 9*60*60 を 加減している部分を変更すれば任意のタイムゾーンに変更できます(たぶん)。

 

以下のように使用します。

import utime

# 年月日時分秒とダミー2個のタプルをmktimeに入力するとその時刻の経過秒が取得できます。
# 2017年8月18日8時3分0秒の場合
# メソッド呼び出しのための括弧とタプル作成のための括弧で括弧が2重になっています。1重にするとエラーになりますので、注意してください
utime.mktime((2017, 8, 18, 8, 3, 0, 0, 0))
    ==> 1503010980

# 時刻の設定 mktimeで取得した値をset_timeで設定します。
utime.set_time(1503010980)

# この2つを組み合わせて、以下のようにすることも可能です。
utime.set_time(utime.mktime((2017, 8, 18, 8, 3, 0, 0, 0)))


# timeで現在時刻の経過秒が取得できます
utime.time()
    ==> 1503010982

# localtimeをパラメータなしで実行すると現在時刻を年月日時分秒形式のタプルで取得できます。
# 後ろから2個目は曜日で、0:月曜、1:火曜、・・・6:日曜 を表します。
# 最後はその年の1月1日からの経過日数です。
utime.localtime()
    ==> (2017, 8, 18, 8, 3, 2, 4, 230)



# localtimeをパラメータを指定して実行するとパラメータで指定した経過秒を年月日時分秒形式に変換できます。
utime.localtime(1503010980)
    ==> (2017, 8, 18, 8, 3, 0, 4, 230)

# パラメータなしのutime.localtime()は、utime.localtime(utime.time())と等価です。

 ここで設定した時刻はソフトリセットでは保持されますが、ハードリセットや電源OFFでは保持されませんので、再度時刻を設定してください。

ちなみに、 mktimeは1970年1月1日09:00:00 以前の値を与えると間違った値を返します。