Last active
June 1, 2021 02:40
-
-
Save kirikiriyamama/79c47a74e861e6a16d1b4106c70ec937 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <M5StickC.h> | |
#include <HTTPClient.h> | |
#include <Ticker.h> | |
#include <WiFi.h> | |
#define M5_LED_OFF HIGH | |
#define M5_LED_ON LOW | |
#define LCD_BACKGROUND_COLOR BLACK | |
#define LCD_BRIGHTNESS 10 | |
#define LCD_BRIGHTNESS_SLEEP 7 | |
#define CPU_FREQUENCY_MHZ 240 | |
#define CPU_FREQUENCY_MHZ_SLEEP 10 | |
#define SLEEP_SEC 30 | |
#define BATTERY_CHECK_PACE_SEC 10 | |
#define BLINK_TASK_SEMAPHORE_SIZE 8 | |
const char* ssid = "xxxxx"; | |
const char* passphrase = "xxxxx"; | |
const char* url = "https://example.com/"; | |
SemaphoreHandle_t ledMutex; | |
Ticker sleepTicker; | |
volatile bool onSleepTimerFlag = false; | |
void IRAM_ATTR onSleepTimer() { | |
onSleepTimerFlag = true; | |
} | |
bool sleeping = false; | |
Ticker checkBatteryTicker; | |
volatile bool onCheckBatteryTimerFlag = false; | |
void IRAM_ATTR onCheckBatteryTimer() { | |
onCheckBatteryTimerFlag = true; | |
} | |
bool lowBattery = false; | |
xSemaphoreHandle blinkTaskSemphr; | |
void blinkTask(void *pvParameters) { | |
while(1) { | |
xSemaphoreTake(blinkTaskSemphr, portMAX_DELAY); | |
xSemaphoreTake(ledMutex, portMAX_DELAY); | |
for (int i = 0; i < 6; i++) { | |
digitalWrite(M5_LED, !digitalRead(M5_LED)); | |
delay(200); | |
} | |
xSemaphoreGive(ledMutex); | |
} | |
} | |
void setup() { | |
M5.begin(); | |
setCpuFrequencyMhz(CPU_FREQUENCY_MHZ); | |
Serial.begin(9600); | |
delay(1000); // wait for serial initialization | |
Serial.print("\n"); | |
M5.Axp.ScreenBreath(LCD_BRIGHTNESS); | |
M5.Lcd.setRotation(1); | |
M5.Lcd.setTextSize(1); | |
M5.Lcd.fillScreen(LCD_BACKGROUND_COLOR); | |
pinMode(M5_LED, OUTPUT); | |
digitalWrite(M5_LED, M5_LED_OFF); | |
ledMutex = xSemaphoreCreateMutex(); | |
if (ledMutex == NULL) { | |
lcdPrint("error!\n"); | |
Serial.println("cannot create mutex"); | |
} | |
blinkTaskSemphr = xSemaphoreCreateCounting(BLINK_TASK_SEMAPHORE_SIZE, 0); | |
if (blinkTaskSemphr == NULL) { | |
lcdPrint("error!\n"); | |
Serial.println("cannot create semaphore"); | |
} | |
xTaskCreateUniversal(blinkTask, "blink", 8192, NULL, 1, NULL, APP_CPU_NUM); | |
sleepTicker.once(SLEEP_SEC, onSleepTimer); | |
checkBatteryTicker.attach(BATTERY_CHECK_PACE_SEC, onCheckBatteryTimer); | |
connectWifi(); | |
} | |
void loop() { | |
M5.update(); | |
if (onSleepTimerFlag) { | |
sleep(); | |
onSleepTimerFlag = false; | |
} | |
if (onCheckBatteryTimerFlag) { | |
checkBattery(); | |
onCheckBatteryTimerFlag = false; | |
} | |
if (M5.BtnA.wasReleased()) { | |
sendReportWithResetSleepTimer(100); | |
} | |
if (M5.BtnB.wasReleased()) { | |
sendReportWithResetSleepTimer(50); | |
} | |
delay(1); | |
} | |
bool connectWifi() { | |
const int interval_ms = 500; | |
const int attempts = 20; | |
lcdPrint("connecting..."); | |
WiFi.begin(ssid, passphrase); | |
for (int i = 0; i < attempts; i++) { | |
delay(interval_ms); | |
if (WiFi.isConnected()) { | |
Serial.println(WiFi.localIP()); | |
lcdPrint("ok\n"); | |
return true; | |
} | |
} | |
char msg[32]; | |
sprintf(msg, "failed\n%d\n", WiFi.status()); | |
lcdPrint(msg); | |
return false; | |
} | |
void sleep() { | |
if (sleeping) { return; } | |
lcdPrint("disconnecting..."); | |
if (!WiFi.disconnect(false, false)) { | |
lcdPrint("failed\n"); | |
return; | |
} | |
lcdPrint("ok\n"); | |
M5.Axp.ScreenBreath(LCD_BRIGHTNESS_SLEEP); | |
setCpuFrequencyMhz(CPU_FREQUENCY_MHZ_SLEEP); | |
sleeping = true; | |
} | |
void wakeup() { | |
if (!sleeping) { return; } | |
setCpuFrequencyMhz(CPU_FREQUENCY_MHZ); | |
M5.Axp.ScreenBreath(LCD_BRIGHTNESS); | |
connectWifi(); | |
sleeping = false; | |
} | |
void checkBattery() { | |
if (xSemaphoreTake(ledMutex, 0) != pdTRUE) { return; } | |
Serial.printf("%fV\n", M5.Axp.GetBatVoltage()); | |
if (!lowBattery) { | |
if (M5.Axp.GetWarningLeve()) { | |
lowBattery = true; | |
digitalWrite(M5_LED, M5_LED_ON); | |
} | |
} else if (!M5.Axp.GetWarningLeve()) { | |
lowBattery = false; | |
digitalWrite(M5_LED, M5_LED_OFF); | |
} | |
xSemaphoreGive(ledMutex); | |
} | |
void sendReportWithResetSleepTimer(int amount_ml) { | |
if (sleeping) { wakeup(); } | |
sleepTicker.detach(); | |
sendReport(amount_ml); | |
sleepTicker.once(SLEEP_SEC, onSleepTimer); | |
} | |
void sendReport(int amount_ml) { | |
char msg[32]; | |
sprintf(msg, "%dml sending...", amount_ml); | |
lcdPrint(msg); | |
HTTPClient http; | |
http.setTimeout(10000); // ms | |
http.begin(url); | |
http.addHeader("Content-Type", "application/json"); | |
char body[32]; | |
sprintf(body, "{\"amount_ml\":%d}", amount_ml); | |
int code = http.POST(body); | |
Serial.println(http.getString()); | |
// TODO: follow redirects | |
if (code == 302) { | |
lcdPrint("ok\n"); | |
} else { | |
lcdPrint("failed\n"); | |
if (xSemaphoreGive(blinkTaskSemphr) == pdFALSE) { | |
Serial.println("cannot give semaphore"); | |
} | |
if (code > 0) { | |
char err[16]; | |
sprintf(err, "%d\n", code); | |
lcdPrint(err); | |
} else { | |
lcdPrint(http.errorToString(code).c_str()); | |
lcdPrint("\n"); | |
} | |
} | |
http.end(); | |
} | |
void lcdPrint(const char* message) { | |
if (M5.Lcd.getCursorY() == M5.Lcd.height()) { | |
M5.Lcd.fillScreen(LCD_BACKGROUND_COLOR); | |
M5.Lcd.setCursor(0, 0); | |
} | |
M5.Lcd.print(message); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function doPost(e) { | |
const amount_ml = JSON.parse(e.postData.getDataAsString()).amount_ml | |
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("data") | |
const index = sheet.getRange(1, 2).getNextDataCell(SpreadsheetApp.Direction.DOWN).getRowIndex() | |
sheet.getRange(index + 1, 1, 1, 2).setValues([[new Date(), amount_ml]]) | |
return ContentService.createTextOutput("ok") | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function sendHourlyReport() { | |
const start_h = 11 | |
const end_h = 20 | |
const date = new Date() | |
if (!isWorkday(date)) { return } | |
const h = date.getHours() | |
if (h <= start_h || end_h <= h) { return } | |
const duration_h = end_h - start_h | |
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("summary") | |
const goal = sheet.getRange(2, 1).getValue() | |
const amount = sheet.getRange(2, 2).getValue() | |
if (goal / duration_h * (h - start_h) < amount) { return } | |
slack(`${goal - amount}ml left`) | |
} | |
function sendDailyReport() { | |
if (!isWorkday(new Date())) { return } | |
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("summary") | |
const goal = sheet.getRange(2, 1).getValue() | |
const amount = sheet.getRange(2, 2).getValue() | |
slack(`today: ${amount}ml`) | |
} | |
// TODO: holiday | |
function isWorkday(date) { | |
const day = date.getDay() | |
if (day == 0 || day == 6) { return false } | |
return true | |
} | |
function slack(message) { | |
const token = "xxxxx" | |
UrlFetchApp.fetch("https://slack.com/api/chat.postMessage", { | |
method: "POST", | |
payload: { | |
token: token, | |
channel: "#general", | |
text: message, | |
} | |
}) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function doPost(e) { | |
const token = "xxxxx" | |
if (e.parameter.token != token) { | |
return // TODO | |
} | |
const amount_ml = Number(e.parameter.text) | |
const url = "https://example.com/" | |
const res = UrlFetchApp.fetch(url, { | |
"method": "POST", | |
"contentType": "application/json", | |
"payload": JSON.stringify({ "amount_ml": amount_ml }) | |
}) | |
return ContentService.createTextOutput(JSON.stringify({ | |
"response_type": "in_channel", | |
"text": res.getContentText() | |
})).setMimeType(ContentService.MimeType.JSON) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment