輸入/newbot申請一個機器人
接下來幫機器人取個名字,中英文都可以~我取個 “哨兵”
接下來輸入機器人的帳號,他是有命名規則,必須是英數字,最後結尾必須是Bot 或是 _bot,要注意一下~
如果帳號重複就必須換個名字,成功了就會出現上面的畫面,紅色框框處就是機器人的API Token,把他複製起來,先存在其他地方,待會程式會用到。
注意不要洩漏你的Token,因為只要有這串Token就可以控制你的機器人。
最後搜尋你的機器人帳號,應該就可以找到,輸入一些文字,目前不會有反應,還需要寫一些程式去控制。
STEP 3. 將你的ESP32cam 線連接起來,因為ESP32cam板子需要用TTL寫入程式,另外我有外加一個PIR sensor 作為感測器。
PIR 是連接在GPIO 13
STEP 4. 上傳程式
首先Arduino IDE 要先安裝CTBOT的程式庫,
程式如下:
#include "WiFi.h"
#include "WiFiClientSecure.h"
#include "ArduinoOTA.h"
#include "soc/soc.h"
#include "soc/rtc_cntl_reg.h"
#include "esp_camera.h"
#include "EEPROM.h"
#include "WiFiManager.h"
#define BOT_TOKEN_LENGTH 50
#define PassWord_LENGTH 12
//CAMERA_MODEL_AI_THINKER
#define CAMERA_MODEL_AI_THINKER
#define PWDN_GPIO_NUM 32
#define RESET_GPIO_NUM -1
#define XCLK_GPIO_NUM 0
#define SIOD_GPIO_NUM 26
#define SIOC_GPIO_NUM 27
#define Y9_GPIO_NUM 35
#define Y8_GPIO_NUM 34
#define Y7_GPIO_NUM 39
#define Y6_GPIO_NUM 36
#define Y5_GPIO_NUM 21
#define Y4_GPIO_NUM 19
#define Y3_GPIO_NUM 18
#define Y2_GPIO_NUM 5
#define VSYNC_GPIO_NUM 25
#define HREF_GPIO_NUM 23
#define PCLK_GPIO_NUM 22
#define FLASH_LED_PIN 4 //閃光燈
bool flashState = LOW; //閃光燈狀態
char botToken[BOT_TOKEN_LENGTH] = "";
char PassWordd[PassWord_LENGTH] = "";
char new_pass[PassWord_LENGTH] = "";
char orgpass[12] = "123456";
char masterid[12] = "";
const char TEXT_START[] =
"***歡迎進入看哨兵系統***"
"\n"
"您可以輸入指令或是點擊按鈕"
"\n"
"啟動或關閉哨兵"
"\n"
"/startwatch -啟動哨兵"
"\n"
"/stopwatch -關閉哨兵"
"\n"
"/status -哨兵狀態"
"\n"
"/photo -拍照"
"\n"
"/setpass -變更密碼"
"\n"
"/help -指令列表";
WiFiClientSecure clientTCP;
#include "CTBot.h"
CTBot myBot;
CTBotReplyKeyboard myKbd; // reply keyboard object helper
bool isKeyboardActive;
//flag for saving data
bool shouldSaveConfig = false;
bool firstime = true;
bool watchdog = false;
bool pass_status = true;
bool changepass = false;
bool check_pass = false;
bool check_again = false;
bool ota_flag = false;
uint8_t ledr = 33;
const int OTA_Pin = 16;//OTA
const int pir = 13;
const int BotresetPin = 14;
unsigned long ttime;
unsigned long senderID;
unsigned long masterID;
String chatId = ""; //設定傳圖片的ID
String BOTtoken = ""; //設定傳圖的token
String sendPhotoTelegram();
String Router_SSID;
String Router_Pass;
String testpass = "";
String Oldpass = "";
void saveConfigCallback () {
Serial.println("Should save config");
shouldSaveConfig = true;
}
void readBotTokenFromEeprom(int offset) {
for (int i = offset; i < BOT_TOKEN_LENGTH; i++ ) {
botToken[i] = EEPROM.read(i);
}
//EEPROM.commit();
}
void readPassWord() {
for (int i = 0; i < 12; i++ ) {
PassWordd[i] = EEPROM.read(i + 60);
}
//EEPROM.commit();
}
void readmasterid() {
for (int i = 0; i < 12; i++ ) {
masterid[i] = EEPROM.read(i + 80);
}
//EEPROM.commit();
}
void writeBotTokenToEeprom(int offset) {
for (int i = offset; i < BOT_TOKEN_LENGTH; i++ ) {
EEPROM.write(i, botToken[i]);
}
Serial.println("Write EEPROM.....");
for (int i = 0; i < 10; i++) {
digitalWrite(ledr, HIGH);
delay(200);
digitalWrite(ledr, LOW);
delay(100);
}
EEPROM.commit();
}
void setup() {
// initialize the Serial
Serial.begin(115200);
Serial.println("Starting TelegramBot...");
EEPROM.begin(128);
pinMode(pir, INPUT);
pinMode(BotresetPin, INPUT_PULLUP);
pinMode(ledr, OUTPUT);
pinMode(FLASH_LED_PIN, OUTPUT);
pinMode(OTA_Pin, INPUT_PULLUP);
digitalWrite(FLASH_LED_PIN, flashState);
Serial.println("read bot token");
readBotTokenFromEeprom(0);
Serial.println(botToken);
Serial.println("password");
readPassWord();
Serial.println(PassWordd);
readmasterid();
Serial.println(masterid);
//wifimanager
WiFi.mode(WIFI_STA);
WiFiManager wm;
wm.setDebugOutput(true);
wm.setSaveConfigCallback(saveConfigCallback);
WiFiManagerParameter custom_bot_id("botid", "Bot Token", botToken, 50);
wm.addParameter(&custom_bot_id);
wm.autoConnect("WatchDog");
Serial.println("WiFi connected");
Serial.println("IP address: ");
IPAddress ip = WiFi.localIP();
Serial.println(ip);
strcpy(botToken, custom_bot_id.getValue());
if (shouldSaveConfig) {
writeBotTokenToEeprom(0);
}
//Start OTA 按住OTA鈕 燈閃3下放開進入OTA模式
if (digitalRead(OTA_Pin) == LOW) {
for (int i = 0; i < 3 ; i++) {
digitalWrite(ledr, HIGH);
delay(500);
digitalWrite(ledr, LOW);
delay(500);
}
delay(3000);
if (digitalRead(OTA_Pin) == HIGH) {
Serial.println("OTA.....");
ota_flag = true;
ArduinoOTA.onStart([]() {
String type;
if (ArduinoOTA.getCommand() == U_FLASH) {
type = "sketch";
} else { // U_FS
type = "filesystem";
}
// NOTE: if updating FS this would be the place to unmount FS using FS.end()
Serial.println("Start updating " + type);
});
ArduinoOTA.onEnd([]() {
Serial.println("\nEnd");
});
ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
});
ArduinoOTA.onError([](ota_error_t error) {
Serial.printf("Error[%u]: ", error);
if (error == OTA_AUTH_ERROR) {
Serial.println("Auth Failed");
} else if (error == OTA_BEGIN_ERROR) {
Serial.println("Begin Failed");
} else if (error == OTA_CONNECT_ERROR) {
Serial.println("Connect Failed");
} else if (error == OTA_RECEIVE_ERROR) {
Serial.println("Receive Failed");
} else if (error == OTA_END_ERROR) {
Serial.println("End Failed");
}
});
ArduinoOTA.begin();
Serial.println("Ready");
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
}
delay(1000);
if (digitalRead(OTA_Pin) == LOW) {//長按OTA鈕直到快閃6下,清除所有設定
Serial.println("Reset.....");
wm.resetSettings();
for (int i = 0; i < 128; i++ ) {
EEPROM.write(i, '\0');
}
EEPROM.commit();
for (int i = 0; i < 6 ; i++) {
digitalWrite(ledr, HIGH);
delay(100);
digitalWrite(ledr, LOW);
delay(200);
}
ESP.restart();
}
}
else {
// set the telegram bot token
myBot.setTelegramToken(botToken);
myBot.enableUTF8Encoding(true); //UTF8顯示中文
// check if all things are ok
if (myBot.testConnection()) {
Serial.println("\ntestConnection OK");
for (int i = 0; i < 5 ; ++i) {
digitalWrite(ledr, HIGH);
delay(200);
digitalWrite(ledr, LOW);
delay(200);
}
digitalWrite(ledr, HIGH);
delay(1000);
digitalWrite(ledr, LOW);
}
else {
Serial.println("\ntestConnection NOK");
// set the pin connected to the LED to act as output pin
digitalWrite(ledr, HIGH); // turn off the led (inverted logic!)
}
myKbd.addButton("啟動哨兵");
myKbd.addButton("關閉哨兵");
myKbd.addButton("哨兵狀態");
myKbd.addRow();
myKbd.addButton("拍照");
myKbd.addButton("指令列");
myKbd.addButton("變更密碼");
myKbd.enableResize();
isKeyboardActive = false;
//camra setting
camera_config_t config;
config.ledc_channel = LEDC_CHANNEL_0;
config.ledc_timer = LEDC_TIMER_0;
config.pin_d0 = Y2_GPIO_NUM;
config.pin_d1 = Y3_GPIO_NUM;
config.pin_d2 = Y4_GPIO_NUM;
config.pin_d3 = Y5_GPIO_NUM;
config.pin_d4 = Y6_GPIO_NUM;
config.pin_d5 = Y7_GPIO_NUM;
config.pin_d6 = Y8_GPIO_NUM;
config.pin_d7 = Y9_GPIO_NUM;
config.pin_xclk = XCLK_GPIO_NUM;
config.pin_pclk = PCLK_GPIO_NUM;
config.pin_vsync = VSYNC_GPIO_NUM;
config.pin_href = HREF_GPIO_NUM;
config.pin_sscb_sda = SIOD_GPIO_NUM;
config.pin_sscb_scl = SIOC_GPIO_NUM;
config.pin_pwdn = PWDN_GPIO_NUM;
config.pin_reset = RESET_GPIO_NUM;
config.xclk_freq_hz = 20000000;
config.pixel_format = PIXFORMAT_JPEG;
//init with high specs to pre-allocate larger buffers
if (psramFound()) {
config.frame_size = FRAMESIZE_UXGA;
config.jpeg_quality = 10; //0-63 lower number means higher quality
config.fb_count = 2;
Serial.println("UXGA");
} else {
config.frame_size = FRAMESIZE_SVGA;
config.jpeg_quality = 12; //0-63 lower number means higher quality
config.fb_count = 1;
Serial.println("SVGA");
}
// camera init
esp_err_t err = esp_camera_init(&config);
if (err != ESP_OK) {
Serial.printf("Camera init failed with error 0x%x", err);
digitalWrite(ledr, HIGH);
delay(1000);
ESP.restart();
}
// Drop down frame size for higher initial frame rate
sensor_t * s = esp_camera_sensor_get();
s->set_framesize(s, FRAMESIZE_SVGA); // UXGA|SXGA|XGA|SVGA|VGA|CIF|QVGA|HQVGA|QQVGA
//end camra setting
//開機傳送訊息
masterID = strtoul(masterid, NULL, 10);
Serial.println(masterID);
myBot.sendMessage(masterID, "哨兵系統啟動...請輸入密碼");
Serial.println("哨兵開機完成");
}
}
void loop() {
// a variable to store telegram message data
if (ota_flag) {
ArduinoOTA.handle();
}
else {
TBMessage msg;
botreset();
// 若接收到訊息
if (myBot.getNewMessage(msg)) {
// step1========================================================
if (pass_status) { //啟動輸入密碼
if (msg.text.equalsIgnoreCase(PassWordd)) {//驗證密碼
pass_status = false;
isKeyboardActive = true;
senderID = msg.sender.id;
String testpass = String(PassWordd); //設定傳圖ID
String Oldpass = String(orgpass);//設定傳圖token
Serial.println(testpass);
Serial.println(Oldpass);
myBot.sendMessage(msg.sender.id, TEXT_START, myKbd);
if (String(masterid) != String(msg.sender.id)) {
String(msg.sender.id).toCharArray(masterid, 12);
for (int i = 80, j = 0; i < 92 , j < 12; ++i, ++j) {
EEPROM.write(i, masterid[j]);
}
EEPROM.commit();
delay(100);
Serial.println(String(masterid));
}
}
else if (testpass == Oldpass)
myBot.sendMessage(msg.sender.id, "請輸入密碼 (預設值:123456)");
else
myBot.sendMessage(msg.sender.id, "請輸入密碼");
}
// step2========================================================
if (senderID == msg.sender.id) {
if (msg.text.equalsIgnoreCase("/startwatch") || msg.text.equalsIgnoreCase("啟動哨兵")) {
digitalWrite(ledr, HIGH);
myBot.sendMessage(msg.sender.id, "哨兵啟動");
watchdog = true;
}
else if (msg.text.equalsIgnoreCase("/stopwatch") || msg.text.equalsIgnoreCase("關閉哨兵")) {
digitalWrite(ledr, LOW);
myBot.sendMessage(msg.sender.id, "哨兵關閉");
watchdog = false;
}
else if (msg.text.equalsIgnoreCase("/status") || msg.text.equalsIgnoreCase("哨兵狀態")) {
if (watchdog)
myBot.sendMessage(msg.sender.id, "哨兵防護中...");
else
myBot.sendMessage(msg.sender.id, "哨兵休息中...");
}
else if (msg.text.equalsIgnoreCase("/help") || msg.text.equalsIgnoreCase("指令列")) {
myBot.sendMessage(msg.sender.id, TEXT_START);
}
else if (msg.text.equalsIgnoreCase("/photo") || msg.text.equalsIgnoreCase("拍照")) {
sendPhotoTelegram();
}
// step3========================================================
else if (check_again) {
if (msg.text.equalsIgnoreCase(new_pass)) {
myBot.sendMessage(senderID, "密碼變更成功,請重新啟動");
pass_status = true;
for (int i = 60, j = 0; i < 72 , j < 12; ++i, ++j) {
EEPROM.write(i, new_pass[j]);
}
EEPROM.commit();
delay(100);
ESP.restart();
}
else
myBot.sendMessage(senderID, "密碼錯誤,請重新操作");
check_again = false;
}
// step4========================================================
else if (check_pass) {
msg.text.toCharArray(new_pass, 12);
myBot.sendMessage(senderID, "請再次輸入密碼");
check_pass = false;
check_again = true;
}
// step5========================================================
else if (changepass) {
if (msg.text.equalsIgnoreCase(PassWordd)) {
myBot.sendMessage(senderID, "密碼正確,請輸入新密碼 (英數字6~12碼)");
check_pass = true;
}
else
myBot.sendMessage(senderID, "密碼錯誤,請重新操作");
changepass = false;
}
else if (msg.text.equalsIgnoreCase("/setpass") || msg.text.equalsIgnoreCase("變更密碼")) {
changepass = true;
myBot.sendMessage(msg.sender.id, "請輸入舊密碼");
}
//else{
// myBot.sendMessage(msg.sender.id, "汪汪...");
// }
}
} //mybot msg
if (watchdog) {
byte p = digitalRead(pir);
if (p == HIGH) {
sendPhotoTelegram();
for (int i = 0; i < 10 ; ++i) {
digitalWrite(ledr, HIGH);
delay(200);
digitalWrite(ledr, LOW);
delay(200);
}
digitalWrite(ledr, HIGH);
myBot.sendMessage(senderID, "注意注意...有人入侵");
delay(1000);
}
}
// wait 500 milliseconds
delay(500);
}
}
//************************
String sendPhotoTelegram() {
const char* myDomain = "api.telegram.org";
String getAll = "";
String getBody = "";
String chatId = String(senderID); //設定傳圖ID
String BOTtoken = String(botToken);//設定傳圖token
Serial.println(chatId);
Serial.println(BOTtoken);
camera_fb_t * fb = NULL;
fb = esp_camera_fb_get();
if (!fb) {
Serial.println("Camera capture failed");
delay(1000);
ESP.restart();
return "Camera capture failed";
}
Serial.println("Connect to " + String(myDomain));
if (clientTCP.connect(myDomain, 443)) {
Serial.println("Connection successful");
String head = "--RandomNerdTutorials\r\nContent-Disposition: form-data; name=\"chat_id\"; \r\n\r\n" + chatId + "\r\n--RandomNerdTutorials\r\nContent-Disposition: form-data; name=\"photo\"; filename=\"esp32-cam.jpg\"\r\nContent-Type: image/jpeg\r\n\r\n";
String tail = "\r\n--RandomNerdTutorials--\r\n";
Serial.println(head);
Serial.println(tail);
uint16_t imageLen = fb->len;
uint16_t extraLen = head.length() + tail.length();
uint16_t totalLen = imageLen + extraLen;
clientTCP.println("POST /bot" + BOTtoken + "/sendPhoto HTTP/1.1");
Serial.println("POST /bot" + BOTtoken + "/sendPhoto HTTP/1.1");
clientTCP.println("Host: " + String(myDomain));
Serial.println("Host: " + String(myDomain));
clientTCP.println("Content-Length: " + String(totalLen));
Serial.println("Content-Length: " + String(totalLen));
clientTCP.println("Content-Type: multipart/form-data; boundary=RandomNerdTutorials");
Serial.println("Content-Type: multipart/form-data; boundary=RandomNerdTutorials");
clientTCP.println();
clientTCP.print(head);
Serial.print(head);
uint8_t *fbBuf = fb->buf;
size_t fbLen = fb->len;
for (size_t n = 0; n < fbLen; n = n + 1024) {
if (n + 1024 < fbLen) {
clientTCP.write(fbBuf, 1024);
fbBuf += 1024;
}
else if (fbLen % 1024 > 0) {
size_t remainder = fbLen % 1024;
clientTCP.write(fbBuf, remainder);
}
}
clientTCP.print(tail);
esp_camera_fb_return(fb);
int waitTime = 10000; // timeout 10 seconds
long startTimer = millis();
boolean state = false;
while ((startTimer + waitTime) > millis()) {
Serial.print(".");
delay(100);
while (clientTCP.available()) {
char c = clientTCP.read();
if (c == '\n') {
if (getAll.length() == 0) state = true;
getAll = "";
}
else if (c != '\r') {
getAll += String(c);
}
if (state == true) {
getBody += String(c);
}
startTimer = millis();
}
if (getBody.length() > 0) break;
}
clientTCP.stop();
Serial.println(getBody);
}
else {
getBody = "Connected to api.telegram.org failed.";
Serial.println("Connected to api.telegram.org failed.");
}
return getBody;
}
//***************
void botreset() {
if (digitalRead(BotresetPin) == LOW)//重設密碼123456
delay(1000);
if (digitalRead(BotresetPin) == LOW) {
//orgpass.toCharArray(PassWordd,12);
for (int j = 0; j < 12; j++ ) {
EEPROM.write(j + 60, orgpass[j]);
}
EEPROM.commit();
WiFiManager wm;
wm.resetSettings();
Serial.println("Finished");
for (int j = 0; j < 20; j++ ) {
digitalWrite(ledr, HIGH);
delay(100);
digitalWrite(ledr, LOW);
delay(100);
}
ESP.restart();
}
}
==========================================================
程式上傳完成後,將ESP32cam reset,打開手機搜尋WiFi,找Watchdog,Configure WiFi
將WiFi密碼及Telegram 的Token填入
等待ESP32cam 重新啟動就完成了!
程式說明:
預設密碼 123456
若要修改指令列的內容可以直接修改
ESP32cam 背面有一個預設的led 連接GPIO33,設定當作指示燈用
若不想每次都要接TTL上傳程式,可用OTA的方式,需另外再加一個按鈕,按住OTA鈕 ,然後按一下reset, 燈慢閃3下放開進入OTA模式
若繼續長按OTA鈕直到快閃6下,清除所有設定
PIR sensor 連接GPIO13
若忘記密碼 GPIO 14 短接,當指示燈快閃可以清除密碼,(第一次上傳程式後請執行一次,將密碼改為預設值123456,要不然怎麼輸入都不行的)
WiFi SSID 名稱,可自行修改
Telegram 自訂按鈕,按鈕名稱可自行修改,254行是將按鈕分行,也可以變成3行
指令列跟程式內的文字要對應到,要不然機器人無法判讀
最後就發揮大家的創意,變成自己的監控機器人~~~
留言列表