├── .gitignore ├── .travis.yml ├── .vscode ├── extensions.json └── settings.json ├── README.md ├── include ├── mqtt.h ├── mytime.h └── userconfig.h ├── lib └── README ├── partitions_custom1.csv ├── partitions_custom2.csv ├── partitions_default.csv ├── platformio.ini └── src └── main.ino /.gitignore: -------------------------------------------------------------------------------- 1 | .pio 2 | .pioenvs 3 | .piolibdeps 4 | .vscode/.browse.c_cpp.db* 5 | .vscode/c_cpp_properties.json 6 | .vscode/launch.json 7 | private/ 8 | include/myprivatedate.h -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # Continuous Integration (CI) is the practice, in software 2 | # engineering, of merging all developer working copies with a shared mainline 3 | # several times a day < https://docs.platformio.org/page/ci/index.html > 4 | # 5 | # Documentation: 6 | # 7 | # * Travis CI Embedded Builds with PlatformIO 8 | # < https://docs.travis-ci.com/user/integration/platformio/ > 9 | # 10 | # * PlatformIO integration with Travis CI 11 | # < https://docs.platformio.org/page/ci/travis.html > 12 | # 13 | # * User Guide for `platformio ci` command 14 | # < https://docs.platformio.org/page/userguide/cmd_ci.html > 15 | # 16 | # 17 | # Please choose one of the following templates (proposed below) and uncomment 18 | # it (remove "# " before each line) or use own configuration according to the 19 | # Travis CI documentation (see above). 20 | # 21 | 22 | 23 | # 24 | # Template #1: General project. Test it using existing `platformio.ini`. 25 | # 26 | 27 | # language: python 28 | # python: 29 | # - "2.7" 30 | # 31 | # sudo: false 32 | # cache: 33 | # directories: 34 | # - "~/.platformio" 35 | # 36 | # install: 37 | # - pip install -U platformio 38 | # - platformio update 39 | # 40 | # script: 41 | # - platformio run 42 | 43 | 44 | # 45 | # Template #2: The project is intended to be used as a library with examples. 46 | # 47 | 48 | # language: python 49 | # python: 50 | # - "2.7" 51 | # 52 | # sudo: false 53 | # cache: 54 | # directories: 55 | # - "~/.platformio" 56 | # 57 | # env: 58 | # - PLATFORMIO_CI_SRC=path/to/test/file.c 59 | # - PLATFORMIO_CI_SRC=examples/file.ino 60 | # - PLATFORMIO_CI_SRC=path/to/test/directory 61 | # 62 | # install: 63 | # - pip install -U platformio 64 | # - platformio update 65 | # 66 | # script: 67 | # - platformio ci --lib="." --board=ID_1 --board=ID_2 --board=ID_N 68 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "platformio.platformio-ide" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "terminal.integrated.env.windows": { 3 | "PATH": "C:\\Users\\polcl\\.platformio\\penv\\Scripts;C:\\Users\\polcl\\.platformio\\penv;C:\\Program Files\\Espressif\\ESP-IDF Tools\\mconf-idf;C:\\Program Files\\Espressif\\ESP-IDF Tools\\tools\\bin;C:\\Program Files (x86)\\Common Files\\Oracle\\Java\\javapath;C:\\ProgramData\\Oracle\\Java\\javapath;C:\\WINDOWS\\system32;C:\\WINDOWS;C:\\WINDOWS\\System32\\Wbem;C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\;C:\\Program Files\\Git\\cmd;C:\\Program Files\\PuTTY\\;C:\\WINDOWS\\System32\\OpenSSH\\;C:\\Program Files\\Microsoft VS Code\\bin;C:\\Program Files (x86)\\IVI Foundation\\VISA\\WinNT\\Bin\\;C:\\Program Files\\IVI Foundation\\VISA\\Win64\\Bin\\;C:\\Program Files (x86)\\Mitov\\Visuino;C:\\Program Files\\nodejs\\;C:\\Program Files\\CMake\\bin;D:\\ESP32\\esp-idf\\tools;C:\\Program Files\\Java\\jdk-12.0.1\\bin;;C:\\Program Files\\Zerynth\\ztc\\windows64;C:\\Users\\polcl\\AppData\\Local\\Microsoft\\WindowsApps;C:\\Program Files\\Microsoft VS Code\\bin;C:\\Users\\polcl\\AppData\\Local\\Programs\\Python\\Python37\\Scripts;C:\\Users\\polcl\\AppData\\Local\\Programs\\Microsoft VS Code\\bin;C:\\Users\\polcl\\AppData\\Local\\Programs\\Python\\Python37;c:\\adb;C:\\Users\\polcl\\AppData\\Roaming\\npm;C:\\Program Files\\Espressif\\ESP-IDF Tools\\mconf-idf;C:\\Program Files\\Espressif\\ESP-IDF Tools\\tools\\bin;C:\\Program Files (x86)\\Common Files\\Oracle\\Java\\javapath;C:\\ProgramData\\Oracle\\Java\\javapath;C:\\WINDOWS\\system32;C:\\WINDOWS;C:\\WINDOWS\\System32\\Wbem;C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\;C:\\Program Files\\Git\\cmd;C:\\Program Files\\PuTTY\\;C:\\WINDOWS\\System32\\OpenSSH\\;C:\\Program Files\\Microsoft VS Code\\bin;C:\\Program Files (x86)\\IVI Foundation\\VISA\\WinNT\\Bin\\;C:\\Program Files\\IVI Foundation\\VISA\\Win64\\Bin\\;C:\\Program Files (x86)\\Mitov\\Visuino;C:\\Program Files\\nodejs\\;C:\\Program Files\\CMake\\bin;D:\\ESP32\\esp-idf\\tools;C:\\Program Files\\Java\\jdk-12.0.1\\bin;;C:\\Program Files\\Zerynth\\ztc\\windows64;C:\\Users\\polcl\\AppData\\Local\\Microsoft\\WindowsApps;C:\\Program Files\\Microsoft VS Code\\bin;C:\\Users\\polcl\\AppData\\Local\\Programs\\Python\\Python37\\Scripts;C:\\Users\\polcl\\AppData\\Local\\Programs\\Microsoft VS Code\\bin;C:\\Users\\polcl\\AppData\\Local\\Programs\\Python\\Python37;c:\\adb;C:\\Users\\polcl\\AppData\\Roaming\\npm", 4 | "PLATFORMIO_CALLER": "vscode" 5 | }, 6 | "cSpell.ignoreWords": [ 7 | "addr", 8 | "lywsd", 9 | "mmc", 10 | "pclient", 11 | "q" 12 | ], 13 | "files.associations": { 14 | "array": "cpp", 15 | "deque": "cpp", 16 | "string": "cpp", 17 | "unordered_map": "cpp", 18 | "unordered_set": "cpp", 19 | "vector": "cpp", 20 | "initializer_list": "cpp", 21 | "typeinfo": "cpp" 22 | } 23 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ESP32 Mijia LYWSD03MMC MQTT hub 2 | 3 | ## What's intended for 4 | 5 | Basic Arduino C ESP32 code for Mijia LYWSD03MMC cheap BLE temperature and humidity sensors to MQTT. Self device configuration for Home assitant so that you don't need to configure them manually. 6 | 7 | It basically loops over all the sensors you configure in include/userconfig.h. Here is where your are to change the parameters to fit your needs. 8 | 9 | After each and every loop step the device goes into deep sleep for what ever the time period defined and after waking up again it requests next sensor. That helps save power but it also helps clean memory as I didn't find a reliable way to move from one sensor to another without crashing after 2 or 3 attempts. 10 | 11 | ## Platformio users 12 | 13 | It's developed using platformio. Four different esp32 boards are configured so if you use a diferent board you must make consequent changes in platformio.ini file. 14 | 15 | About platformio.ini. 16 | Two very important considerations: 17 | 18 | board_build.partitions = partitions_custom2.csv 19 | 20 | Three partition table files are provided, the default one will not allow code to fit as BLE, MQTT and Wifi combined all together take too much memory. So you may play with the other two alternatives. I used custom2 and it worked fine. 21 | 22 | build_flags = 23 | -D MQTT_MAX_PACKET_SIZE=600 24 | 25 | Play with this flag at will. Adjust it if your json config files are bigger or smaller. By default, pubsubclient uses 128 which is never enough for large json MQTT configuration messages. 26 | 27 | ## MQTT 28 | 29 | Every time the device turns on or off a message is submitted to a status topic, helping you keep track of the results remotely via MQTT without the need to check the serial console and keeping in mind your device might be sleeping most of the time. 30 | 31 | I suggest using http://mqtt-explorer.com/ to track all messages. 32 | 33 | Here you are a link to where I bought the sensors: 34 | https://www.banggood.com/Newest-Version-XIAOMI-Mijia-Bluetooth-Thermometer-2-Wireless-Smart-Electric-Digital-Hygrometer-Thermometer-1Pcs-Work-with-Mijia-APP-p-1595119.html?rmmds=myorder&cur_warehouse=CN 35 | 36 | In my latest tests I've been able to run for 5 days on a single (chinese) 18650 battery. 37 | 38 | ## IMPORTANT! 39 | 40 | Some USB cables as well as some usb power supplies do not provide enough power for BT and Wifi to work together so devices tent to reboot. 41 | Should this occur loop count resets too so only first device is checked on and on. In some cases they even fail and reboot before first Wifi connection try. 42 | 43 | Enjoy! 44 | -------------------------------------------------------------------------------- /include/mqtt.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | const String mqtt_topic_base = devName(); 5 | 6 | void mqtt_callBack(char *topic, byte *payload, unsigned int length) 7 | { 8 | Serial.print("Message arrived ["); 9 | Serial.print(topic); 10 | Serial.print("] "); 11 | String payload_str; 12 | for (int i = 0; i < length; i++) { 13 | payload_str += (char)payload[i]; 14 | } 15 | Serial.println(payload_str); 16 | 17 | if ((char)payload[0] == '1') 18 | { 19 | digitalWrite(BUILTIN_LED, LOW); 20 | } 21 | else 22 | { 23 | digitalWrite(BUILTIN_LED, HIGH); // Turn the LED off by making the voltage HIGH 24 | } 25 | } 26 | 27 | void publishStatus(Status s, String result) 28 | { 29 | String topic = mqtt_topic_base + "/status/" + status[s]; 30 | topic.toLowerCase(); 31 | Serial.print("Status topic: " + topic); 32 | String payload; 33 | 34 | // #define min 500 35 | const size_t capacity = JSON_OBJECT_SIZE(12) + 230; 36 | // if (capacity < min) 37 | // capacity = min; 38 | DynamicJsonDocument doc(capacity); 39 | 40 | doc["Timestamp"] = convertDateTime2ISO(myRealTime()); 41 | doc["MAC"] = WiFi.macAddress(); 42 | doc["Bootcount"] = bootCount; 43 | doc["Option"] = opt; 44 | doc["On_secs"] = ontime + millis() / 1000; 45 | if (s == on) 46 | { 47 | doc["Updatetime"] = updatetime; 48 | doc["SensorName"] = name[opt]; 49 | doc["SensorAddress"] = MAC[opt]; 50 | doc["SSID"] = WiFi.SSID(); 51 | doc["BSSID"] = WiFi.BSSIDstr(); 52 | doc["IP"] = WiFi.localIP().toString(); 53 | doc["RSSI"] = WiFi.RSSI(); 54 | } 55 | else 56 | { 57 | doc["Result"] = result; 58 | } 59 | 60 | Serial.println(); 61 | serializeJson(doc, payload); 62 | doc.clear(); 63 | if (!client.connected()) 64 | { 65 | String clientId = mqtt_topic_base + "_" + String(random(0xFFFF), HEX); 66 | if (!client.connect(clientId.c_str(), mqtt_user, mqtt_pw)) 67 | { 68 | Serial.println(". Unable to reconnect to MQTT server!"); 69 | return; 70 | } 71 | } 72 | 73 | Serial.print(payload); 74 | if (client.publish(topic.c_str(), payload.c_str(), true)) 75 | { 76 | if (s == off) 77 | client.disconnect(); 78 | Serial.println(", done."); 79 | } 80 | else 81 | Serial.println(". Error publishing!"); 82 | } 83 | 84 | void defineDevices() 85 | { 86 | const size_t capacity = JSON_OBJECT_SIZE(5) + JSON_OBJECT_SIZE(6)+500; 87 | DynamicJsonDocument doc(capacity); 88 | for (size_t i = 0; i < opts; i++) 89 | for (size_t s = 0; s < types; s++) 90 | { 91 | String nm = name[i] + " via " + mqtt_topic_base; 92 | doc["name"] = sensTypeTxt[s] + " " + nm; 93 | 94 | JsonObject device = doc.createNestedObject("device"); 95 | String n = nm; 96 | n.replace(" ", "_"); 97 | n.toLowerCase(); 98 | device["name"] = nm; 99 | device["identifiers"] = n; 100 | device["sw_version"] = "1.0.0_0106"; 101 | device["manufacturer"] = "Xiaomi"; 102 | device["model"] = modelName[model[i]]; 103 | 104 | String u = mqtt_topic_base + "/sensor/" + name[i] + "_" + sensTypeTxt[s] + "/state"; 105 | u.toLowerCase(); 106 | doc["state_topic"] = u; 107 | u = name[i] + " " + sensTypeTxt[s] + " " + mqtt_topic_base; 108 | u.replace(" ", "_"); 109 | u.toLowerCase(); 110 | doc["unique_id"] = u; 111 | if (sensType[s] != voltage) 112 | doc["device_class"] = sensTypeTxt[s]; 113 | doc["unit_of_measurement"] = unitMesurement[s]; 114 | 115 | String topic = "homeassistant/sensor/"; 116 | topic += u; 117 | topic += "/config"; 118 | Serial.print("\r"); 119 | String payload; 120 | serializeJson(doc, payload); 121 | Serial.printf("i: %d s: %d", i, s); 122 | // Serial.println(payload); 123 | if (client.publish(topic.c_str(), payload.c_str(), true)) 124 | Serial.print(", "); 125 | else 126 | Serial.println(", Error!"); 127 | doc.clear(); 128 | } 129 | Serial.println("done."); 130 | } 131 | 132 | void reconnect() 133 | { 134 | // Loop until we're reconnected 135 | digitalWrite(BUILTIN_LED, LOW); 136 | while (!client.connected()) 137 | { 138 | // Attempt to connect 139 | String clientId = mqtt_topic_base + "_" + String(random(0xFFFF), HEX); 140 | Serial.print("Attempting MQTT connection as: " + clientId); 141 | if (client.connect(clientId.c_str(), mqtt_user, mqtt_pw)) 142 | { 143 | Serial.println(", connected!"); 144 | do_updateSensors = true; 145 | 146 | if (bootCount == 1) 147 | defineDevices(); 148 | 149 | publishStatus(on, ""); 150 | 151 | String topic = mqtt_topic_base + "/cmd"; 152 | if (client.subscribe(topic.c_str())) 153 | { 154 | client.setCallback(mqtt_callBack); 155 | Serial.println("Subscribed to: " + topic); 156 | } 157 | 158 | digitalWrite(BUILTIN_LED, HIGH); 159 | } 160 | else 161 | { 162 | digitalWrite(BUILTIN_LED, HIGH); 163 | Serial.print("failed, rc="); 164 | Serial.print(client.state()); 165 | Serial.println(" try again in 5 seconds"); 166 | delay(2500); 167 | digitalWrite(BUILTIN_LED, LOW); 168 | delay(2500); 169 | // Wait 5 seconds before retrying 170 | } 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /include/mytime.h: -------------------------------------------------------------------------------- 1 | #ifndef my_time 2 | #define my_time 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | // Define NTP properties 11 | #define NTP_OFFSET 60 * 60 // In seconds 12 | #define NTP_INTERVAL 60 * 1000 // In miliseconds 13 | #define NTP_ADDRESS "es.pool.ntp.org" // change this to whatever pool is closest (see ntp.org) 14 | 15 | // Set up the NTP UDP client_MQTT 16 | WiFiUDP ntpUDP; 17 | NTPClient timeClient(ntpUDP, NTP_ADDRESS, NTP_OFFSET, NTP_INTERVAL); 18 | 19 | // time_t myUpdateTime ; 20 | // time_t myUpdatePass ; 21 | 22 | String TimeStr(time_t mytime) 23 | { 24 | String date; 25 | 26 | // now format the Time variables into strings with proper names for month, day etc 27 | date = ""; 28 | if (hour(mytime) < 10) 29 | date += "0"; 30 | date += hour(mytime); 31 | date += ":"; 32 | if (minute(mytime) < 10) // add a zero if minute is under 10 33 | date += "0"; 34 | date += minute(mytime); 35 | date += ":"; 36 | if (second(mytime) < 10) // add a zero if minute is under 10 37 | date += "0"; 38 | date += second(mytime); 39 | return date; 40 | } 41 | String TimeStrShort(time_t mytime, bool h12) // h12 -> true 12h false 24h 42 | { 43 | String date; 44 | const char *ampm[] = {"AM", "PM"}; 45 | 46 | // now format the Time variables into strings with proper names for month, day etc 47 | date = ""; 48 | if (!h12) 49 | date += hour(mytime); 50 | else 51 | date += hourFormat12(mytime); 52 | date += ":"; 53 | if (minute(mytime) < 10) // add a zero if minute is under 10 54 | date += "0"; 55 | date += minute(mytime); 56 | if (h12) 57 | { 58 | date += " "; 59 | date += ampm[isPM(mytime)]; 60 | } 61 | return date; 62 | } 63 | String DateStrShort(time_t mytime) 64 | { 65 | String date; 66 | 67 | // now format the Time variables into strings with proper names for month, day etc 68 | date = ""; 69 | if (day(mytime) < 10) 70 | date += "0"; 71 | date += day(mytime); 72 | date += "/"; 73 | if (month(mytime) < 10) // add a zero if minute is under 10 74 | date += "0"; 75 | date += month(mytime); 76 | date += "/"; 77 | date += year(mytime) - 2000; 78 | return date; 79 | } 80 | String DateStr(time_t mytime) 81 | { 82 | String date; 83 | const char *days[] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}; 84 | const char *months[] = {"Jan", "Feb", "Mar", "Apr", "May", "June", "July", "Aug", "Sep", "Oct", "Nov", "Dec"}; 85 | 86 | // now format the Time variables into strings with proper names for month, day etc 87 | date = days[weekday(mytime) - 1]; 88 | date += ","; 89 | date += months[month(mytime) - 1]; 90 | date += " "; 91 | date += day(mytime); 92 | date += ","; 93 | date += year(mytime) - 2000; 94 | 95 | return date; 96 | } 97 | 98 | String TimeDateStr(time_t mytime) 99 | { 100 | return DateStrShort(mytime) + ", " + TimeStr(mytime); 101 | } 102 | 103 | time_t millisOffLine = 0; 104 | time_t myTimeOffline = 0; 105 | 106 | time_t UTC2Local(time_t utc) 107 | { 108 | //Central European Time (Frankfurt, Paris, Barcelona) 109 | TimeChangeRule CEST = {"CEST", Last, Sun, Mar, 25, 60}; //Central European Summer Time 110 | TimeChangeRule CET = {"CET ", Last, Sun, Oct, 29, 0}; //Central European Standard Time 111 | 112 | Timezone myTime(CEST, CET); 113 | return myTime.toLocal(utc); 114 | } 115 | 116 | time_t myRealTime() 117 | { 118 | // update the NTP client_MQTT and get the UNIX UTC timestamp 119 | time_t local, utc; 120 | if (timeClient.update()) 121 | { 122 | unsigned long epochTime = timeClient.getEpochTime(); 123 | // convert received time stamp to time_t object 124 | utc = epochTime; 125 | local = UTC2Local(utc); 126 | myTimeOffline = local; 127 | millisOffLine = millis() / 1000; 128 | } 129 | else 130 | local = myTimeOffline + millis() / 1000 - millisOffLine; 131 | return local; 132 | } 133 | 134 | time_t convertISO2DateTime(const String &calTimestamp) 135 | { 136 | TimeElements tm; 137 | int yr, mnth, d, h, m, s; 138 | const char *tmp; 139 | if (calTimestamp.length() > 16) 140 | // tmp = "%4d-%2d-%2dT%2d:%2d:%06.2fZ"; 141 | tmp = "%04d-%02d-%02dT%02d:%02d:%02d.000Z"; 142 | else 143 | tmp = "%4d%2d%2dT%2d%2d%2dZ"; 144 | sscanf(calTimestamp.c_str(), tmp, &yr, &mnth, &d, &h, &m, &s); 145 | tm.Year = yr - 1970; 146 | tm.Month = mnth; 147 | tm.Day = d; 148 | tm.Hour = h; 149 | tm.Minute = m; 150 | tm.Second = s; 151 | time_t t = makeTime(tm); 152 | return t; 153 | } 154 | String convertDateTime2ISO(time_t t) 155 | { 156 | char buf[34]; 157 | // sprintf(buf, "%02d-%02d-%02dT%02d:%02d:%06.2fZ", year(t), month(t), day(t), hour(t), minute(t), second(t)); 158 | sprintf(buf, "%02d-%02d-%02dT%02d:%02d:%02d.000Z", year(t), month(t), day(t), hour(t), minute(t), second(t)); 159 | String r = String(buf); 160 | return r; 161 | } 162 | void setup_time() 163 | { 164 | timeClient.begin(); // Start the NTP UDP client_MQTT 165 | delay(500); 166 | Serial.print("updated time: "); 167 | Serial.print(TimeStrShort(myRealTime(), false)); 168 | Serial.print(", "); 169 | Serial.println("done!"); 170 | } 171 | #endif -------------------------------------------------------------------------------- /include/userconfig.h: -------------------------------------------------------------------------------- 1 | #include // might be deleted once your data is provided 2 | 3 | // next data must be subsituted by yours 4 | const char *ssid = WIFINAME; 5 | const char *password = PASSWORD; 6 | const char *mqtt_server = MQTT_SERVER; 7 | const char *mqtt_user = MQTT_USER; 8 | const char *mqtt_pw = MQTT_PW; 9 | 10 | uint8_t updatetime = 180; // secs 11 | uint8_t wifi_watchdog = 60; // secs 12 | 13 | // planned for future use of other sensors 14 | const String modelName[] = {"LYWSD03MMC", "CGD1"}; 15 | 16 | const Model model[] = {LYWSD03MMC, LYWSD03MMC, LYWSD03MMC}; 17 | // add your MAC addresses 18 | const std::string MAC[] = {"A4:C1:38:D3:A4:5D", "A4:C1:38:89:F7:CA", "A4:C1:38:85:65:E4"}; 19 | // give them a name 20 | const String name[] = {"Mobile", "Outdoors", "Downstairs"}; 21 | // give them a number/id in case you need additional coding, I myself wrote this numbers to the back of the sensor 22 | const String number[] = {"1", "2", "3"}; 23 | -------------------------------------------------------------------------------- /lib/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project specific (private) libraries. 3 | PlatformIO will compile them to static libraries and link into executable file. 4 | 5 | The source code of each library should be placed in a an own separate directory 6 | ("lib/your_library_name/[here are source files]"). 7 | 8 | For example, see a structure of the following two libraries `Foo` and `Bar`: 9 | 10 | |--lib 11 | | | 12 | | |--Bar 13 | | | |--docs 14 | | | |--examples 15 | | | |--src 16 | | | |- Bar.c 17 | | | |- Bar.h 18 | | | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html 19 | | | 20 | | |--Foo 21 | | | |- Foo.c 22 | | | |- Foo.h 23 | | | 24 | | |- README --> THIS FILE 25 | | 26 | |- platformio.ini 27 | |--src 28 | |- main.c 29 | 30 | and a contents of `src/main.c`: 31 | ``` 32 | #include 33 | #include 34 | 35 | int main (void) 36 | { 37 | ... 38 | } 39 | 40 | ``` 41 | 42 | PlatformIO Library Dependency Finder will find automatically dependent 43 | libraries scanning project source files. 44 | 45 | More information about PlatformIO Library Dependency Finder 46 | - https://docs.platformio.org/page/librarymanager/ldf.html 47 | -------------------------------------------------------------------------------- /partitions_custom1.csv: -------------------------------------------------------------------------------- 1 | # Name, Type, SubType, Offset, Size, Flags 2 | nvs, data, nvs, 0x9000, 0x5000, 3 | otadata, data, ota, 0xe000, 0x2000, 4 | app0, app, ota_0, 0x10000, 0x190000, 5 | app1, app, ota_1, 0x1A0000,0x190000, 6 | eeprom, data, 0x99, 0x330000,0x1000, 7 | spiffs, data, spiffs, 0x331000,0x0CF000, 8 | 9 | # DATA: [== ] 18.7% (used 61136 bytes from 327680 bytes) 10 | # PROGRAM: [========= ] 87.7% (used 1436915 bytes from 1638400 bytes) 11 | -------------------------------------------------------------------------------- /partitions_custom2.csv: -------------------------------------------------------------------------------- 1 | # Name, Type, SubType, Offset, Size, Flags 2 | nvs, data, nvs, 0x9000, 0x5000, 3 | otadata, data, ota, 0xe000, 0x2000, 4 | app0, app, ota_0, 0x10000, 0x1F0000, 5 | app1, app, ota_1, 0x200000,0x1F0000, 6 | spiffs, data, spiffs, 0x3F0000,0x10000, 7 | 8 | # DATA: [== ] 18.7% (used 61136 bytes from 327680 bytes) 9 | # PROGRAM: [======= ] 70.7% (used 1436915 bytes from 2031616 bytes) 10 | -------------------------------------------------------------------------------- /partitions_default.csv: -------------------------------------------------------------------------------- 1 | # Name, Type, SubType, Offset, Size, Flags 2 | nvs, data, nvs, 0x9000, 0x5000, 3 | otadata, data, ota, 0xe000, 0x2000, 4 | app0, app, ota_0, 0x10000, 0x140000, 5 | app1, app, ota_1, 0x150000,0x140000, 6 | spiffs, data, spiffs, 0x290000,0x170000, -------------------------------------------------------------------------------- /platformio.ini: -------------------------------------------------------------------------------- 1 | ; PlatformIO Project Configuration File 2 | ; 3 | ; Build options: build flags, source filter 4 | ; Upload options: custom upload port, speed and extra flags 5 | ; Library options: dependencies, extra library storages 6 | ; Advanced options: extra scripting 7 | ; 8 | ; Please visit documentation for the other options and examples 9 | ; https://docs.platformio.org/page/projectconf.html 10 | 11 | [platformio] 12 | ; default_envs = lolin_d32 13 | default_envs = mh_et_esp32_mini_kit 14 | ; default_envs = ttgo 15 | ; default_envs = esp32doitdevkit 16 | 17 | [env:lolin_d32] 18 | platform = espressif32 19 | board = lolin_d32 20 | framework = arduino 21 | board_build.mcu = esp32 22 | monitor_speed = 115200 23 | upload_speed = 921600 24 | ; upload_port = COM2 25 | ; monitor_port = COM2 26 | board_build.partitions = partitions_custom2.csv 27 | build_flags = 28 | -D ENV=$PIOENV 29 | -D DEV=DEVA 30 | -D MQTT_MAX_PACKET_SIZE=600 31 | lib_deps = 32 | SimpleTimer 33 | PubSubClient 34 | ArduinoJson 35 | NTPClient 36 | Timezone 37 | TimeLib 38 | 39 | [env:mh_et_esp32_mini_kit] 40 | platform = espressif32 41 | board = mhetesp32minikit 42 | ; board = esp32dev 43 | framework = arduino 44 | board_build.mcu = esp32 45 | monitor_speed = 115200 46 | upload_speed = 921600 47 | ; upload_speed = 115200 48 | ; upload_port = COM2 49 | ; monitor_port = COM2 50 | ; (DIO/40MHz/no PSRAM/115200 baud) 51 | board_build.partitions = partitions_custom2.csv 52 | ; board_build.flash_mode = dio 53 | ; board_build.f_flash = 40000000L 54 | build_flags = 55 | -D ENV=$PIOENV 56 | -D DEV=DEVA 57 | -D MQTT_MAX_PACKET_SIZE=600 58 | -D BUILTIN_LED=2 59 | lib_deps = 60 | SimpleTimer 61 | PubSubClient 62 | ArduinoJson 63 | NTPClient 64 | Timezone 65 | TimeLib 66 | 67 | [env:ttgo] 68 | platform = espressif32 69 | board = lolin32 70 | framework = arduino 71 | board_build.mcu = esp32 72 | monitor_speed = 115200 73 | upload_speed = 921600 74 | ; upload_port = COM2 75 | ; monitor_port = COM2 76 | board_build.partitions = partitions_custom2.csv 77 | build_flags = 78 | -D ENV=$PIOENV 79 | -D DEV=DEVA 80 | -D MQTT_MAX_PACKET_SIZE=600 81 | lib_deps = 82 | SimpleTimer 83 | PubSubClient 84 | ArduinoJson 85 | NTPClient 86 | Timezone 87 | TimeLib 88 | 89 | [env:esp32doitdevkit] 90 | platform = espressif32 91 | board = esp32doit-devkit-v1 92 | framework = arduino 93 | board_build.mcu = esp32 94 | monitor_speed = 115200 95 | upload_speed = 921600 96 | ; upload_port = COM2 97 | ; monitor_port = COM2 98 | board_build.partitions = partitions_custom2.csv 99 | build_flags = 100 | -D ENV=$PIOENV 101 | -D DEV=DEVA 102 | -D MQTT_MAX_PACKET_SIZE=600 103 | lib_deps = 104 | SimpleTimer 105 | PubSubClient 106 | ArduinoJson 107 | NTPClient 108 | Timezone 109 | TimeLib 110 | -------------------------------------------------------------------------------- /src/main.ino: -------------------------------------------------------------------------------- 1 | // inspired by: 2 | // https://github.com/karolkalinski/esp32-snippets/tree/master/Mijia-LYWSD03MMC-Client 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | RTC_DATA_ATTR int bootCount = 0; 10 | RTC_DATA_ATTR int opt = 0; 11 | RTC_DATA_ATTR unsigned long ontime = 0; 12 | 13 | WiFiClient espClient; 14 | PubSubClient client(espClient); 15 | 16 | enum Model 17 | { 18 | LYWSD03MMC, 19 | CGD1 20 | }; 21 | enum Types 22 | { 23 | temperature, 24 | humidity, 25 | battery, 26 | voltage 27 | }; 28 | enum Status 29 | { 30 | on, 31 | off 32 | }; 33 | 34 | const String status[] = {"on", "off"}; 35 | 36 | #include 37 | // You should not change this 38 | const Types sensType[] = {temperature, humidity, battery, voltage}; 39 | const String sensTypeTxt[] = {"Temperature", "Humidity", "Battery", "Voltage"}; 40 | const String unitMesurement[] = {"°C", "%", "%", "v"}; 41 | 42 | auto opts = sizeof(model) / sizeof(model[0]); 43 | auto types = sizeof(sensType) / sizeof(sensType[0]); 44 | 45 | String devName() 46 | { 47 | char ssid[23]; 48 | uint64_t chipid = ESP.getEfuseMac(); // The chip ID is essentially its MAC address(length: 6 bytes). 49 | uint16_t chip = (uint16_t)(chipid >> 32); 50 | snprintf(ssid, 23, "esp32BleHub_%04X%08X", chip, (uint32_t)chipid); 51 | return String(ssid); 52 | } 53 | 54 | bool done = false; 55 | bool do_updateSensors = false; 56 | bool notified = false; 57 | 58 | #include 59 | 60 | BLEClient *pClient; 61 | 62 | const std::string service[] = {"ebe0ccb0-7a0a-4b0c-8a1a-6ff2997da3a6", "0000fe95-0000-1000-8000-00805f9b34fb"}; 63 | const std::string charact[] = {"ebe0ccc1-7a0a-4b0c-8a1a-6ff2997da3a6", "00000017-0000-1000-8000-00805f9b34fb"}; 64 | 65 | class MyClientCallback : public BLEClientCallbacks 66 | { 67 | void onConnect(BLEClient *pclient) 68 | { 69 | Serial.println("Connected!"); 70 | } 71 | 72 | void onDisconnect(BLEClient *pclient) 73 | { 74 | do_updateSensors = false; 75 | done = true; 76 | } 77 | }; 78 | 79 | void notifyCallback( 80 | BLERemoteCharacteristic *pBLERemoteCharacteristic, 81 | uint8_t *pData, 82 | size_t length, 83 | bool isNotify) 84 | { 85 | 86 | Serial.print("Notify callback from: " + name[opt] + " (" + number[opt] + "), characteristic "); 87 | Serial.println(pBLERemoteCharacteristic->getUUID().toString().c_str()); 88 | 89 | String result; 90 | if (length == 5) 91 | { 92 | float states[types]; 93 | float volt = ((pData[4] * 256) + pData[3]) / 1000.0; 94 | for (size_t s = 0; s < types; s++) 95 | { 96 | switch (s) 97 | { 98 | case temperature: 99 | /* code */ 100 | states[temperature] = (pData[0] | (pData[1] << 8)) * 0.01; //little endian 101 | break; 102 | case humidity: 103 | states[humidity] = pData[2]; 104 | break; 105 | case voltage: 106 | states[voltage] = volt; 107 | break; 108 | case battery: 109 | states[battery] = (volt - 2.1) * 100.0; 110 | break; 111 | } 112 | 113 | String topic = mqtt_topic_base + "/sensor/" + name[opt] + "_" + sensTypeTxt[s] + "/state"; 114 | topic.toLowerCase(); 115 | Serial.print(topic + ": " + String(states[s], 2)); 116 | if (client.publish(topic.c_str(), String(states[s], 2).c_str())) 117 | { 118 | result = "published!"; 119 | Serial.println(", " + result); 120 | for (size_t i = 0; i < 3; i++) 121 | { 122 | digitalWrite(BUILTIN_LED, LOW); 123 | delay(300); 124 | digitalWrite(BUILTIN_LED, HIGH); 125 | } 126 | } 127 | else 128 | { 129 | result = "ERROR!"; 130 | Serial.println(". " + result); 131 | } 132 | } 133 | do_updateSensors = false; 134 | done = true; 135 | } 136 | else 137 | { 138 | for (size_t i = 0; i < length; i++) 139 | Serial.printf("byte %d:%d, ", i, pData[i]); 140 | Serial.println(); 141 | result = "Unknown data length: "; 142 | result += String(length); 143 | result += "Provably is not a LYWSD03MMC device!"; 144 | } 145 | // Serial.println(result); 146 | publishStatus(off, result); 147 | notified = true; 148 | do_updateSensors = false; 149 | done = true; 150 | pClient->disconnect(); 151 | } 152 | 153 | void registerNotification(std::string serviceUUID_str, std::string charUUID_str) 154 | { 155 | 156 | BLEUUID serviceUUID(serviceUUID_str); 157 | BLEUUID charUUID(charUUID_str); 158 | 159 | // Obtain a reference to the service we are after in the remote BLE server. 160 | BLERemoteService *pRemoteService = pClient->getService(serviceUUID); 161 | if (pRemoteService == nullptr) 162 | { 163 | Serial.print("-Failed to find our service UUID: "); 164 | Serial.println(serviceUUID_str.c_str()); 165 | pClient->disconnect(); 166 | return; 167 | } 168 | else 169 | Serial.printf("-Found our service: %s\n", serviceUUID_str.c_str()); 170 | 171 | // Obtain a reference to the characteristic in the service of the remote BLE server. 172 | if (pClient->isConnected()) 173 | { 174 | BLERemoteCharacteristic *pRemoteCharacteristic = pRemoteService->getCharacteristic(charUUID); 175 | if (pRemoteCharacteristic == nullptr) 176 | { 177 | Serial.print("-Failed to find our characteristic UUID: "); 178 | Serial.println(charUUID.toString().c_str()); 179 | pClient->disconnect(); 180 | } 181 | else 182 | { 183 | Serial.printf("-Found our characteristic: %s\n", charUUID_str.c_str()); 184 | Serial.println("Waiting for notifications!"); 185 | pRemoteCharacteristic->registerForNotify(notifyCallback); 186 | } 187 | } 188 | } 189 | 190 | void wifi_setup() 191 | { 192 | Serial.print("Connecting to "); 193 | Serial.print(ssid); 194 | WiFi.begin(ssid, password); 195 | 196 | pinMode(BUILTIN_LED, OUTPUT); 197 | uint8_t c = 0; 198 | while (WiFi.status() != WL_CONNECTED) 199 | { 200 | // if wifi takes too long to connect then reset 201 | if (c * 300 < wifi_watchdog) 202 | c++; 203 | else 204 | ESP.restart(); 205 | digitalWrite(BUILTIN_LED, LOW); 206 | delay(300); 207 | digitalWrite(BUILTIN_LED, HIGH); 208 | Serial.print("."); 209 | } 210 | 211 | #define warmup 3 212 | int i = warmup; 213 | Serial.print("\nWarming up for " + String(warmup) + "secs"); 214 | while (i > 0) 215 | { 216 | i--; 217 | Serial.print("."); 218 | delay(1000); 219 | } 220 | Serial.print(", done!\n"); 221 | 222 | // Serial.println(); // I read somewhere that it used to cause hungups after wifi init! 223 | Serial.print("WiFi connected\n"); 224 | Serial.print("IP address: "); 225 | Serial.print(WiFi.localIP()); 226 | Serial.print("\n"); 227 | } 228 | 229 | void setup() 230 | { 231 | Serial.begin(115200); 232 | Serial.println("Starting BLE client: " + devName()); 233 | ++bootCount; 234 | Serial.println("Boot number: " + String(bootCount)); 235 | Serial.println("Loop option: " + String(opt)); 236 | 237 | esp_sleep_enable_timer_wakeup(updatetime * 1000000); 238 | 239 | wifi_setup(); 240 | client.setServer(mqtt_server, 1883); 241 | setup_time(); 242 | } 243 | 244 | void createBleClientWithCallbacks() 245 | { 246 | pClient = BLEDevice::createClient(); 247 | pClient->setClientCallbacks(new MyClientCallback()); 248 | } 249 | 250 | bool connectSensor(std::string mac) 251 | { 252 | BLEAddress htSensorAddress(mac); 253 | return pClient->connect(htSensorAddress); 254 | } 255 | 256 | void updateSensors() 257 | { 258 | do_updateSensors = false; 259 | Serial.println("Updating BLE device: " + name[opt] + " (" + number[opt] + "), " + MAC[opt].c_str()); 260 | BLEDevice::init(devName().c_str()); 261 | createBleClientWithCallbacks(); 262 | delay(500); 263 | if (connectSensor(MAC[opt])) 264 | registerNotification(service[model[opt]], charact[model[opt]]); 265 | } 266 | 267 | void updateSensors_t() 268 | { 269 | do_updateSensors = true; 270 | } 271 | 272 | void loop() 273 | { 274 | digitalWrite(BUILTIN_LED, LOW); 275 | delay(300); 276 | digitalWrite(BUILTIN_LED, HIGH); 277 | delay(300); 278 | if (done) 279 | { 280 | BLEDevice::deinit(); 281 | String result = "Disconnected"; 282 | if (notified) 283 | result += "!"; 284 | else 285 | { 286 | result += ", without result!"; 287 | publishStatus(off, result); 288 | } 289 | Serial.println(result); 290 | if (opt < opts - 1) 291 | opt++; 292 | else 293 | opt = 0; 294 | Serial.println("Going into deepsleep for: " + String(updatetime) + "secs."); 295 | Serial.flush(); 296 | ontime += millis() / 1000; 297 | delay(1000); 298 | esp_deep_sleep_start(); 299 | } 300 | if (!client.connected()) 301 | { 302 | reconnect(); 303 | } 304 | if (do_updateSensors) 305 | { 306 | do_updateSensors = false; 307 | updateSensors(); 308 | } 309 | client.loop(); 310 | } --------------------------------------------------------------------------------