Notice. New forum software under development. It's going to miss a few functions and look a bit ugly for a while, but I'm working on it full time now as the old forum was too unstable. Couple days, all good. If you notice any issues, please contact me.
|
Forum Index : Microcontroller and PC projects : Cheap Thermostat
Author | Message | ||||
lew247 Guru Joined: 23/12/2015 Location: United KingdomPosts: 1676 |
Has anyone seen These Cheap less than $4 bluetooth temp and humidity sensors that can have custom firmware installed Web firmware flashing page I ordered 5 and they were here within a week Just flashed one tonight with the new firmware but I'm not even going to try and figure out how to get bluetooth working and decoding it I'm using them as cheap temp sensors but I'm sure someone knowledgeable here could do something with them Unless they aren't worth the money |
||||
Nimue Guru Joined: 06/08/2020 Location: United KingdomPosts: 367 |
Remote sensors for the CMM2 -- there's an idea. Where did you buy yours from (UK)? Nim Entropy is not what it used to be |
||||
lew247 Guru Joined: 23/12/2015 Location: United KingdomPosts: 1676 |
Took me ages to find the email I got them here Gearbest in China An exampl;e script to get the bluetooth data from the thermometer #!/usr/bin/env python3 import sys from datetime import datetime import bluetooth._bluetooth as bluez from bluetooth_utils import (toggle_device, enable_le_scan, parse_le_advertising_events, disable_le_scan, raw_packet_to_str) # Use 0 for hci0 dev_id = 0 toggle_device(dev_id, True) try: sock = bluez.hci_open_dev(dev_id) except: print("Cannot open bluetooth device %i" % dev_id) raise # Set filter to "True" to see only one packet per device enable_le_scan(sock, filter_duplicates=False) try: def le_advertise_packet_handler(mac, adv_type, data, rssi): data_str = raw_packet_to_str(data) # Check for ATC preamble if data_str[6:10] == '1a18': temp = int(data_str[22:26], 16) / 10 hum = int(data_str[26:28], 16) batt = int(data_str[28:30], 16) print("%s - Device: %s Temp: %sc Humidity: %s%% Batt: %s%%" % \ (datetime.now().strftime("%Y-%m-%d %H:%M:%S"), mac, temp, hum, batt)) # Called on new LE packet parse_le_advertising_events(sock, handler=le_advertise_packet_handler, debug=False) # Scan until Ctrl-C except KeyboardInterrupt: disable_le_scan(sock) Yes I know it's in Python not MM but it came from hackaday.com/2020/12/08/exploring-custom-firmware-on-xiaomi-thermometers/ Edited 2021-01-06 23:51 by lew247 |
||||
Nimue Guru Joined: 06/08/2020 Location: United KingdomPosts: 367 |
Thank you -- happy with a bit of Big P ;-) Bought..... Worth a "fiddle" and nice to introduce to schools to remote sensing and Python. Q: Is there a bluetooth implementation for CMM2 / MMBasic? Nim Edited 2021-01-07 00:44 by Nimue Entropy is not what it used to be |
||||
jirsoft Guru Joined: 18/09/2020 Location: Czech RepublicPosts: 532 |
I have ordered it here Jiri Napoleon Commander and SimplEd for CMM2 (GitHub), CMM2.fun |
||||
lew247 Guru Joined: 23/12/2015 Location: United KingdomPosts: 1676 |
I wish I could say the same, there's so much more I could do but it's too advanced for me |
||||
TassyJim Guru Joined: 07/08/2011 Location: AustraliaPosts: 5878 |
I did have a play with the HC-05 and HC-06 Bluetooth modules but it was a while ago. They are serial to Bluetooth so easy enough to drive from MMBasic. I will have to dust off the modules (and the brain). Jim VK7JH MMedit MMBasic Help |
||||
Nimue Guru Joined: 06/08/2020 Location: United KingdomPosts: 367 |
Having obtained these and flashed the custom rom (easy) I can confirm that the bluetooth low energy implementation seems to not be compatible with Python 3.9 running on Windows 10 (a Python issue). Apparently it works fine on Linux (not my OS of choice). I'll keep plugging away. Nim Entropy is not what it used to be |
||||
Nimue Guru Joined: 06/08/2020 Location: United KingdomPosts: 367 |
Some progress.... The "Bleak" library : https://bleak.readthedocs.io/en/latest/scanning.html works for reading the advertised bluetooth data... with one caveat..... not under Anaconda / Jupyter notebooks. So, under Windows 10 with the above libraries, I am able to "see" the sensior: In the screenshot, the device is "ATC_E1815F" and the next couple of lines is the packet info for the data being sent. Need to "park" this and do some paying work for a bit.... Nim Entropy is not what it used to be |
||||
jirsoft Guru Joined: 18/09/2020 Location: Czech RepublicPosts: 532 |
It will be nice to find it with ESP32 and then connect it to CMM2 (serial, SPI or IIC). I'm still waiting for my sensors... Jiri Napoleon Commander and SimplEd for CMM2 (GitHub), CMM2.fun |
||||
TheFRB Newbie Joined: 03/04/2021 Location: United StatesPosts: 3 |
If anyone's still interested in these thermometers I can give some information. I've done two installations using them: one at my house and one at a friend's greenhouse. Some high level details about what I did: 1) Flashed the custom firmware found at https://github.com/atc1441/ATC_MiThermometer (not necessary, but unless you want to stay in the Xiaomi ecosystem its better to use to the custom firmware). 2) Have a Raspberry Pi Zero W reading the BLE advertising data (the thermometers broadcast temperature, humidity and battery status every 5 or 6 seconds, no need to pair) 3) Pushing this data to an MQTT broker running on another machine (RPi 3) over TCP/IP (wireless) 4) On the same RPi 3 I'm running InfluxDB and Grafana that ingest this data via MQTT subscripitons. I did something similar for my friend's greenhouse -- 6 thermometers (5 in the greenhouse in different zones, 1 outside as reference), but added LoRa to the mix (Long Range / Low Bandwidth wireless) since the greenhouse is a good 50 / 60 yards from his house. The only difference is the RPiZero pushes the thermometer data across LoRa (its a serial connection) via a LoRa hat. The other machine also has a LoRa hat (both Waveshare SX1268s, ~$25 apiece) from which it receives the data, then pushes to the MQTT broker. Phase 2 at his greenhouse is to start adding some automation (i.e. turn on fans to circulate air if the differential between zones hits a certain metrics, etc.) I'm looking at some bluetooth controlled relays that we can plug the fans into. If anyone has any questions I'll gladly answer them. |
||||
lizby Guru Joined: 17/05/2016 Location: United StatesPosts: 3008 |
Thanks for the info. I got a couple, but haven't played yet. Can you provide a little more information about item 2) -- unpaired bluetooth reception? PicoMite, Armmite F4, SensorKits, MMBasic Hardware, Games, etc. on fruitoftheshed |
||||
TheFRB Newbie Joined: 03/04/2021 Location: United StatesPosts: 3 |
The thermometers will broadcast what's called a BLE advertising packet (what Apple calls iBeacon). You don't have to be paired to read the data; you just 'pluck' it out of the air. I'm using Python on the PiZeroW (which has really good bluetooth reception). There is a python package called "Bleak" that I have been using to grab and parse this data (https://pypi.org/project/bleak/). One thing of note about the firmware that comes with the thermometer: this advertising data is encrypted. You cannot extract any info out of it without a key. The webpage I discussed about the custom firmware (https://github.com/atc1441/ATC_MiThermometer) has a tool that will generate that key for you. However, if you flash the new custom firmware the broadcast message isn't encrypted. Another good reason to flash the custom firmware. I believe (because I personally haven't tried it), even if you're on the original firmware if you pair/connect with the thermometer you can read the data, no problem. But pairing/connecting will actually drain your battery a little faster than just passively reading. For me, its just easier to read the advertising packets (I don't have to code anything re: connecting to each thermometer). The ATC_MiTHermometer site has really good information about how the data is stored in the packet. You have to convert stuff from hex to decimal, but its pretty easy to figure out once you understand it. |
||||
lizby Guru Joined: 17/05/2016 Location: United StatesPosts: 3008 |
Ok. Good to know. Thanks. PicoMite, Armmite F4, SensorKits, MMBasic Hardware, Games, etc. on fruitoftheshed |
||||
TheFRB Newbie Joined: 03/04/2021 Location: United StatesPosts: 3 |
A quick note on this code. It was the basis for the first revision of my system and the temperature calculation isn't quite correct. The temperature (which is always reported in C, even if you configure the screen to show F) is stored as tenths of a degree in an INT16, but its a signed INT16, i.e. the first bit represents the sign, and, if a negative number, the bits are inverted. The above code assumes its an unsigned INT16, so things go a bit haywire when the temperature drops below zero. For instance, at -1C (which would be passed as -10) the hex representation you get in the BLE packet is 0xFFF6, which as an unsigned INT16 is 65526 (or 6552.6 C). When I hit the first cold snap my data looked really, really funky. |
||||
Tinine Guru Joined: 30/03/2016 Location: United KingdomPosts: 1646 |
If you would like to use these devices on an Android phone/tablet, the attached "GrauBasic" makes it really simple. Just remove ".zip" from the file name and allow your device to accept an app from "other sources" (developer options). BLE sample programs are included. GRAU BASIC!_2.01_de.grauonline.basic_2010.apk.zip |
||||
TassyJim Guru Joined: 07/08/2011 Location: AustraliaPosts: 5878 |
Reading the thermometers with MMBasic and a ESP32 is easy. Change the com port setting to suit your setup. ' ESP32 BLE logger ' TassyJim April 2021 OPTION EXPLICIT OPTION DEFAULT INTEGER ' Global variables CONST qt$=CHR$(34) CONST crlf$=CHR$(13)+CHR$(10) ' dim string comPort$ = "com1:921600,1024" '"com1:115200,1024" dim float temperature, humidity, batpc, batV dim inTxt$, ink$ dim integer quit, CMM2, x, Verbose = 0, showDebug OPEN comPort$ AS #1 PAUSE 2000 'wait for ESP32 to wake up afer power on CLOSE #1 'clear the buffer OPEN comPort$ AS #1 IF instr(MM.DEVICE$,"Colour Maximite 2") THEN CMM2 = 1 ELSE CMM2 = 0 ENDIF ESPcommand("AT") ' test ESP for responce IF OK()THEN PRINT "ESP alive!" ELSE PAUSE 200 ESPcommand("AT") IF OK()THEN PRINT "ESP alive!" ELSE PRINT inTxt$ ENDIF ENDIF pause 500 getinput ESPcommand("AT+CWMODE=0") if not OK() then print "Error 1" ESPcommand("AT+BLEINIT=1") if not OK() then print "Error 2" ESPcommand("AT+BLESCANPARAM?") if not OK() then print "Error 3" ESPcommand("AT+BLESCAN=1,0") DO ink$ = INKEY$ SELECT CASE ink$ CASE CHR$(27),"Q","q" quit = 1 CASE "D","d" showDebug = 1 - showDebug IF showDebug THEN PRINT "Debug on" ELSE PRINT "Debug off" CASE "V","v" verbose = 1 - verbose END SELECT ' x = waitFor(30000,250,crlf$) if x = 1 then ' we have a crlf pair decode inTxt$ endif LOOP UNTIL quit = 1 PRINT "Shutting down..." ESPcommand "AT+BLESCAN=0" if not OK() then print "Error shutting down" END ' 'send ESP related command SUB ESPcommand(txt$) getInput PRINT #1,txt$+crlf$; IF showDebug THEN PRINT txt$ 'DEBUG END SUB sub getInput IF LOC(#1) > 0 THEN do inTxt$=INPUT$(255,#1) if verbose then print inTxt$ loop until LOC(#1)=0 ENDIF END sub ' wait timeout% mS or 250 bytes for "OK" FUNCTION OK(timeout%) IF timeout% = 0 THEN timeout% = 500 IF waitFor(timeout%,250,"OK") = 1 THEN OK = 1 END FUNCTION ' wait for text string(s). tested in supplied order and stops after first blank text ' returns index to found txt or 0 if timeout ' if maxlen is non zero, function will return 255 if max len is reached ' and global variable inTxt$ will contain the received text. FUNCTION waitFor(timeout%,maxlen%,a1$,a2$,a3$,a4$,a5$,a6$) AS INTEGER LOCAL INTEGER n, c, endTime = TIMER + timeout% LOCAL a$ DO IF LOC(#1) THEN a$ = a$ + INPUT$(1,#1) INC c IF c >= maxlen% THEN n = 255 : EXIT DO IF RIGHT$(a$,LEN(a1$)) = a1$ THEN n = 1 : EXIT DO IF a2$="" THEN CONTINUE DO IF RIGHT$(a$,LEN(a2$)) = a2$ THEN n = 2 : EXIT DO IF a3$="" THEN CONTINUE DO IF RIGHT$(a$,LEN(a3$)) = a3$ THEN n = 3 : EXIT DO IF a4$="" THEN CONTINUE DO IF RIGHT$(a$,LEN(a4$)) = a4$ THEN n = 4 : EXIT DO IF a5$="" THEN CONTINUE DO IF RIGHT$(a$,LEN(a5$)) = a5$ THEN n = 5 : EXIT DO IF a6$="" THEN CONTINUE DO IF RIGHT$(a$,LEN(a6$)) = a6$ THEN n = 6 : EXIT DO ENDIF LOOP UNTIL TIMER > endtime OR c > 254 IF maxlen% THEN inTxt$ = a$ waitFor = n END FUNCTION sub decode txt$ local integer startTxt local adv_data$, fld$ startTxt = instr(txt$,"BLESCAN") if startTxt then txt$ = mid$(txt$,starttxt+8) adv_data$ = field$(txt$,3,",") if verbose then print adv_data$ fld$ = mid$(adv_data$,5,4) if fld$ = "1a18" then ' we have the correct data fld$ = mid$(adv_data$,9,12) print "MAC address: ";fld$ fld$ = mid$(adv_data$,21,4) ': print fld$ temperature = signed16(val("&h"+fld$)) temperature = temperature/10 fld$ = mid$(adv_data$,25,2)': print fld$ humidity = val("&h"+fld$) fld$ = mid$(adv_data$,27,2)': print fld$ batpc = val("&h"+fld$) fld$ = mid$(adv_data$,29,4)': print fld$ batV = val("&h"+fld$)/1000 print Time$;" ";temperature;"`C ";Humidity;"% ";batpc;"% ";batV;"V " endif endif end sub function signed16(x) if x > 32767 then signed16 = x - 65536 else signed16 = x endif end function Output: 18:13:27 23.9`C 52% 79% 2.919V MAC address: a4c1382865be 18:13:48 23.9`C 52% 79% 2.919V This is with the standard ESP32 firmware and module firmware from the first post. Jim Edited 2021-04-06 18:25 by TassyJim VK7JH MMedit MMBasic Help |
||||
TassyJim Guru Joined: 07/08/2011 Location: AustraliaPosts: 5878 |
This version is a bit tidier. It should run on any of the 'mites V to toggle verbose mode and Q or esc to quit. ' ESP32 BLE logger ' TassyJim April 2021 OPTION EXPLICIT OPTION DEFAULT INTEGER ' Global variables CONST qt$=CHR$(34) CONST crlf$=CHR$(13)+CHR$(10) ' DIM STRING comPort$ = "com1:921600,2048" '"com1:115200,2048" DIM FLOAT temperature, humidity, batpc, batV, counter DIM inTxt$, ink$, j$, MACadr$ DIM INTEGER quit, Verbose = 1 OPEN comPort$ AS #1 PAUSE 2000 'wait for ESP32 to wake up afer power on CLOSE #1 'clear the buffer OPEN comPort$ AS #1 ESPcommand("AT") ' test ESP for responce IF OK()THEN PRINT "ESP alive!" ELSE PAUSE 200 ESPcommand("AT") IF OK()THEN PRINT "ESP alive!!" ELSE PRINT inTxt$ ENDIF ENDIF DO ' flush the comms buffer j$ = INPUT$(255,#1) IF verbose THEN PRINT j$ LOOP UNTIL j$ = "" ESPcommand("AT+CWMODE=0") IF NOT OK() THEN PRINT "Error 1" ESPcommand("AT+BLEINIT=1") IF NOT OK() THEN PRINT "Error 2" ESPcommand("AT+BLESCANPARAM?") IF NOT OK() THEN PRINT "Error 3" IF verbose THEN PRINT inTxt$ ESPcommand("AT+BLESCAN=1,0") IF verbose THEN PRINT "Scanning..." DO ink$ = INKEY$ SELECT CASE ink$ CASE CHR$(27),"Q","q" quit = 1 CASE "V","v" verbose = 1 - verbose END SELECT IF LOC(#1) THEN getInput ' LOOP UNTIL quit = 1 PRINT "Shutting down..." ESPcommand "AT+BLESCAN=0" IF NOT OK() THEN PRINT "Error shutting down" CLOSE #1 END ' 'send ESP related command SUB ESPcommand(txt$) PRINT #1,txt$+crlf$; IF verbose THEN PRINT txt$ END SUB SUB getInput LOCAL INTEGER x x = waitfor(500,crlf$) IF verbose THEN PRINT inTxt$; IF x = 1 THEN decode inTxt$ ENDIF END SUB ' wait timeout% mS or 250 bytes for "OK" FUNCTION OK(timeout%) IF timeout% = 0 THEN timeout% = 500 IF waitFor(timeout%,"OK"+crlf$) = 1 THEN OK = 1 IF verbose THEN PRINT "OK" ENDIF END FUNCTION ' wait for text string. ' returns 1 for end match, 255 if 250 characters reached or 0 if timeout ' global variable inTxt$ will contain the received text. FUNCTION waitFor(timeout%,a1$) AS INTEGER LOCAL INTEGER n, c, endTime = TIMER + timeout% LOCAL a$ DO IF LOC(#1) THEN a$ = a$ + INPUT$(1,#1) 'if verbose then print right$(a$,1); c = c + 1 IF c >= 250 THEN n = 255 : EXIT DO IF RIGHT$(a$,LEN(a1$)) = a1$ THEN n = 1 : EXIT DO ENDIF LOOP UNTIL TIMER > endtime IF n THEN inTxt$ = a$ waitFor = n END FUNCTION SUB decode txt$ LOCAL INTEGER startTxt LOCAL adv_data$, fld$ startTxt = INSTR(txt$,"BLESCAN") IF startTxt THEN txt$ = MID$(txt$,starttxt+8) adv_data$ = FIELD$(txt$,3,",") 'if verbose then print adv_data$ fld$ = MID$(adv_data$,5,4) IF fld$ = "1a18" THEN ' we have the correct data MACadr$ = MID$(adv_data$,9,12) fld$ = MID$(adv_data$,21,4) ': print fld$ temperature = signed16(VAL("&h"+fld$)) temperature = temperature/10 fld$ = MID$(adv_data$,25,2)': print fld$ humidity = VAL("&h"+fld$) fld$ = MID$(adv_data$,27,2)': print fld$ batpc = VAL("&h"+fld$) fld$ = MID$(adv_data$,29,4)': print fld$ batV = VAL("&h"+fld$)/1000 fld$ = MID$(adv_data$,33,2)': print fld$ counter = VAL("&h"+fld$) PRINT TIME$;" ";MACadr$;" ";temperature;"`C ";Humidity;"%rh ";batpc;"% ";batV;"V ";counter ENDIF ENDIF END SUB FUNCTION signed16(x%) AS INTEGER IF x% > 32767 THEN signed16 = x% - 65536 ELSE signed16 = x% ENDIF END FUNCTION Jim VK7JH MMedit MMBasic Help |
||||
Print this page |