Skip to content

Instantly share code, notes, and snippets.

@kirikiriyamama
Last active June 1, 2021 02:40
Show Gist options
  • Save kirikiriyamama/79c47a74e861e6a16d1b4106c70ec937 to your computer and use it in GitHub Desktop.
Save kirikiriyamama/79c47a74e861e6a16d1b4106c70ec937 to your computer and use it in GitHub Desktop.
#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);
}
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")
}
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,
}
})
}
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