13
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

kintoneAdvent Calendar 2020

Day 20

タイムカードぽちぽち by M5StickC

Last updated at Posted at 2020-12-20

🎄 Happy Holidays!
kintone Advent Calendar 2020 20日目の記事です🎉

はじめに

昨年は M5StickC 開発にチャレンジしたのですが、その時買った Dual Button Unit を使ってみたい!ということで、今年は**「ぽちぽちボタンでタイムカード登録する」**プログラムを開発しました٩( ᐛ )و

会社でも kintone でタイムカード管理しているのですが、よく忘れる & 時間を入力するのが手間...
はい、物理ボタンの出番ですね!
(押し心地が良いんで、ちょっとしたストレス解消グッズにもなります笑)

作ったもの

デモです🎥

demo.png

仕組み

ざっくりこんな感じです。
structure.png

セットアップ

kintone アプリ作成

まず、このようなタイムカードアプリを作成します。
kintone_app.png

必須項目はこちらです。

フィールド名 フィールドタイプ フィールドコード
日付 日付 Date
出勤時刻 時刻 In
退勤時刻 時刻 Out

次に、Arduino プログラムから HTTP リクエストするための API トークン(認証情報)を生成します。
以下のアクセス権をつけてください。

  • レコード閲覧
  • レコード追加
  • レコード編集

api_token.png

M5StickC 開発環境準備

前回書いたので、こちらの記事を参考にしてください。

最近 Mac OSX を Big Sur にアップデートしてしまったが故に、ESP32 のセットアップに苦戦しました^^;
トラブルシュートで参考にした記事も参考リンク欄に載せてます。

プログラム開発

下準備ができたら、Arduino IDE で以下のスケッチを作成して保存します。
書き終わったらコンパイルして、マイコンボードに書き込みをして完成です!👏
(開発中はシリアルモニタを使うとデバッグしやすいのでおすすめ)

※ プログラム内こちらの変数の値は書き換えてください。

  • ドメイン名
  • アプリ ID
  • API トークン
  • WiFi SSID
  • WiFi PASSWORD
timecard.ino
#include <M5StickC.h>
#include <WiFi.h>
#include <time.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>
#include <stdio.h>
#include <string.h>

#define LED_PIN 10
#define LED_ON LOW
#define LED_OFF HIGH

// kintone setting
const char* domain = "[ドメイン名]";
const int appId = [アプリ ID];
const char* token = "[API トークン]";
char url[128];
char json[256];

// WiFi setting
const char* ssid = "[WiFi SSID]";
const char* password = "[WiFi PASSWORD]";

// Time setting
const char* ntpServer = "ntp.jst.mfeed.ad.jp";
const long  gmtOffset_sec = 9 * 3600;
const int   daylightOffset_sec = 0;
struct tm timeinfo;
char s[20];

// Button setting
int lastValueRed = 0;
int curValueRed = 0;
int lastValueBlue = 0;
int curValueBlue = 0;

// LED function
void blinkLed() {
  digitalWrite(LED_PIN, LED_ON);
  delay(500);
  digitalWrite(LED_PIN, LED_OFF);
  delay(500);
  digitalWrite(LED_PIN, LED_ON);
  delay(500);
  digitalWrite(LED_PIN, LED_OFF);
  delay(500);
}

void setup() {
  M5.begin();
  Serial.begin(115200);
  Serial.println("\n*********************\nStart processing...\n*********************");
  
  M5.Axp.ScreenBreath(9);
  M5.Lcd.setRotation(3);
  pinMode(32, INPUT);
  pinMode(33, INPUT);
  pinMode(LED_PIN, OUTPUT);
  digitalWrite(LED_PIN, LED_OFF);
  M5.Lcd.setTextColor(YELLOW);
  M5.Lcd.setCursor(15, 2);
  M5.Lcd.println("Timecard PochiPochi");
  M5.Lcd.setTextColor(WHITE);

  // Check WiFi connection
  WiFi.begin(ssid, password);
  int i = 0;
  while (WiFi.status() != WL_CONNECTED) {
    if (i > 10) break;
    delay(100);
    Serial.println("Connecting to WiFi...");
    i++;
  }
  Serial.println("Successfully connected to the WiFi\n");
  delay(2000);
}

void loop() {
  curValueRed = digitalRead(32);
  curValueBlue = digitalRead(33);
  
  M5.Lcd.setCursor(0,15); M5.Lcd.print("Blue Status: ");
  M5.Lcd.setCursor(0,30); M5.Lcd.print("IN Time: ");
  M5.Lcd.setCursor(0,45); M5.Lcd.print("Red Status: ");
  M5.Lcd.setCursor(0,60); M5.Lcd.print("OUT Time: ");

  // Click blue button (In time)
  if (curValueBlue != lastValueBlue) {
    M5.Lcd.fillRect(95,15,100,15,BLACK);
    M5.Lcd.fillRect(95,30,100,15,BLACK);
    if (curValueBlue==0) {
      M5.Lcd.setCursor(95,15); M5.Lcd.print("pressed");
      Serial.println("Button Status: blue pressed");
      Serial.println(" value: 0");

      // Get the current time
      configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
      if (!getLocalTime(&timeinfo)) {
        Serial.println("Error getting current time\n");
      } else {
        sprintf(s, "%02d:%02d", timeinfo.tm_hour, timeinfo.tm_min);
        Serial.printf("\nCurrent Time = %s \n", s);
      }

      // GET Request to check today's record
      int respCode = 0;
      HTTPClient http;
      sprintf(url, "https://%s.cybozu.com/k/v1/records.json?app=%d&query=Date=TODAY()&totalCount=true", domain, appId);
      http.begin(url);
      http.addHeader("X-Cybozu-API-Token", token);
      respCode = http.GET();
      Serial.printf("\n------ GET records request ------\n");
      Serial.printf("HTTP Response Code = %d \n", respCode);
      String payload = http.getString();
      Serial.println(payload);
      DynamicJsonDocument doc(50000);
      deserializeJson(doc, payload);

      // Check totalCount
      const char* totalCount = doc["totalCount"];
      Serial.printf("totalCount = %s \n", totalCount);
      int numTotalCount = atoi(totalCount);

      if (numTotalCount == 0) {
        Serial.println("No today's record yet\n");

        // POST request to register IN time
        Serial.printf("\n------ POST record request ------\n");
        sprintf(json, "{\"app\":\"%d\",\"record\":{\"In\":{\"value\":\"%s\"}}}", appId, s);
        Serial.printf("Request body = %s \n", json);
  
        int postRespCode = 0;
        sprintf(url, "https://%s.cybozu.com/k/v1/record.json", domain);
        http.begin(url);
        http.addHeader("X-Cybozu-API-Token", token);
        http.addHeader("Content-Type", "application/json");
        postRespCode = http.POST(json);
        Serial.printf("HTTP Response Code = %d \n", postRespCode);
        String payload = http.getString();
        Serial.println(payload);
        Serial.println("----------");
        M5.Lcd.setCursor(95,30); M5.Lcd.println(s);
        blinkLed();
        M5.Lcd.setTextColor(GREEN);
        M5.Lcd.setCursor(15,70); M5.Lcd.println("Have a nice day!");
        M5.Lcd.setTextColor(WHITE);
        delay(5000);
        M5.Lcd.fillRect(15,70,100,70,BLACK);
      } else if (numTotalCount == 1) {
        // Check the record number
        JsonObject obj = doc.as<JsonObject>();
        String getRecords = obj[String("records")];
        Serial.println(getRecords);
        DynamicJsonDocument doc(50000);
        deserializeJson(doc, getRecords);
        JsonObject datas = doc[0];
        const char* recordNum = datas["RecordNumber"]["value"];
        Serial.printf("Today's record number = %s \n", recordNum);

        // PUT Request to update IN time
        Serial.printf("\n------ PUT record request ------\n");
        sprintf(json, "{\"app\":\"%d\",\"id\":\"%s\",\"record\":{\"In\":{\"value\":\"%s\"}}}", appId, recordNum, s);
        Serial.printf("Request body = %s \n", json);
  
        int putRespCode = 0;
        sprintf(url, "https://%s.cybozu.com/k/v1/record.json", domain);
        http.begin(url);
        http.addHeader("X-Cybozu-API-Token", token);
        http.addHeader("Content-Type", "application/json");
        putRespCode = http.PUT(json);
        Serial.printf("HTTP Response Code = %d \n", putRespCode);
        String payload = http.getString();
        Serial.println(payload);
        Serial.println("----------");
        M5.Lcd.setCursor(95,30); M5.Lcd.println(s);
        blinkLed();
        M5.Lcd.setTextColor(GREEN);
        M5.Lcd.setCursor(15,70); M5.Lcd.println("Have a nice day!");
        M5.Lcd.setTextColor(WHITE);
        delay(5000);
        M5.Lcd.fillRect(15,70,100,70,BLACK);
      } else {
        Serial.println("Error! Today's record is duplicated\n----------");
      }
    }
    else{
      M5.Lcd.setCursor(95,15); M5.Lcd.print("released");
      M5.Lcd.setCursor(95,30); M5.Lcd.println(s);
      Serial.println("Button Status: blue released");
      Serial.println(" value: 1");
    }
    lastValueBlue = curValueBlue;
  }

  // Click red button (Out time)
  if (curValueRed != lastValueRed) {
    M5.Lcd.fillRect(95,45,100,15,BLACK);
    M5.Lcd.fillRect(95,60,100,15,BLACK);
    if (curValueRed==0) {
      M5.Lcd.setCursor(95,45); M5.Lcd.print("pressed");
      Serial.println("Button Status: red pressed");
      Serial.println(" value: 0");

      // Get the current time
      configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
      if (!getLocalTime(&timeinfo)) {
        Serial.println("Error getting current time\n");
      } else {
        sprintf(s, "%02d:%02d", timeinfo.tm_hour, timeinfo.tm_min);
        Serial.printf("\nCurrent Time = %s \n", s);
      }

      // GET Request to check today's record
      int respCode = 0;
      HTTPClient http;
      sprintf(url, "https://%s.cybozu.com/k/v1/records.json?app=%d&query=Date=TODAY()&totalCount=true", domain, appId);
      http.begin(url);
      http.addHeader("X-Cybozu-API-Token", token);
      respCode = http.GET();
      Serial.printf("\n------ GET records request ------\n");
      Serial.printf("HTTP GET Response Code = %d \n", respCode);
      String payload = http.getString();
      Serial.println(payload);
      DynamicJsonDocument doc(50000);
      deserializeJson(doc, payload);

      // Check totalCount
      const char* totalCount = doc["totalCount"];
      Serial.printf("totalCount = %s \n", totalCount);
      int numTotalCount = atoi(totalCount);

      if (numTotalCount == 0) {
        Serial.println("No today's record yet\n");

        // POST request to register OUT time
        sprintf(json, "{\"app\":\"%d\",\"record\":{\"Out\":{\"value\":\"%s\"}}}", appId, s);
        Serial.printf("Request body = %s \n", json);
  
        int postRespCode = 0;
        sprintf(url, "https://%s.cybozu.com/k/v1/record.json", domain);
        http.begin(url);
        http.addHeader("X-Cybozu-API-Token", token);
        http.addHeader("Content-Type", "application/json");
        postRespCode = http.POST(json);
        Serial.printf("HTTP Response Code = %d \n", postRespCode);
        String payload = http.getString();
        Serial.println(payload);
        Serial.println("----------");
        M5.Lcd.setCursor(95,60); M5.Lcd.println(s);
        blinkLed();
        M5.Lcd.setTextColor(GREEN);
        M5.Lcd.setCursor(15,70); M5.Lcd.println("Good job!");
        M5.Lcd.setTextColor(WHITE);
        delay(5000);
        M5.Lcd.fillRect(15,70,100,70,BLACK);
      } else if (numTotalCount == 1) {
        // Check the record number
        JsonObject obj = doc.as<JsonObject>();
        String getRecords = obj[String("records")];
        Serial.println(getRecords);
        DynamicJsonDocument doc(50000);
        deserializeJson(doc, getRecords);
        JsonObject datas = doc[0];
        const char* recordNum = datas["RecordNumber"]["value"];
        Serial.printf("Today's record number = %s \n", recordNum);

        // PUT Request to update OUT time
        Serial.printf("\n------ PUT record request ------\n");
        sprintf(json, "{\"app\":\"%d\",\"id\":\"%s\",\"record\":{\"Out\":{\"value\":\"%s\"}}}", appId, recordNum, s);
        Serial.printf("Request body = %s \n", json);
  
        int putRespCode = 0;
        sprintf(url, "https://%s.cybozu.com/k/v1/record.json", domain);
        http.begin(url);
        http.addHeader("X-Cybozu-API-Token", token);
        http.addHeader("Content-Type", "application/json");
        putRespCode = http.PUT(json);
        Serial.printf("HTTP Response Code = %d \n", putRespCode);
        String payload = http.getString();
        Serial.println(payload);
        Serial.println("----------");
        M5.Lcd.setCursor(95,60); M5.Lcd.println(s);
        blinkLed();
        M5.Lcd.setTextColor(GREEN);
        M5.Lcd.setCursor(15,70); M5.Lcd.println("Good job!");
        M5.Lcd.setTextColor(WHITE);
        delay(5000);
        M5.Lcd.fillRect(15,70,100,70,BLACK);
      } else {
        Serial.println("Error! Today's record is duplicated\n----------");
      }
    } else {
      M5.Lcd.setCursor(95,45); M5.Lcd.print("released");
      M5.Lcd.setCursor(95,60); M5.Lcd.println(s);
      Serial.println("Button Status: red released");
      Serial.println(" value: 1");
    }
    lastValueRed = curValueRed;
  }
  M5.update();
}

※ HTTP リクエストの部分は同じような処理が複数あるのでほんとは独自関数作って呼び出すのが良いですが、途中の型変換がうまくいかず冗長な書き方になってしまいました。良きようにアレンジしてください。(๑˃̵ᴗ˂̵)

参考リンク

最後まで読んでくださりありがとうございます。
みなさん良いお年を🎍

13
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?