Inkbird IBS-M1 with IBS-P01

I got an original ibs-m1 and p01r back in March 2021 for AUD $105.

I then replaced just the p01r for AUD $46 in November 2022 after it got damaged by a child throwing it.

I am finding that the newer p01r just goes through batteries way too quickly, like only lasting 3 weeks per set of batteries.

Given the poor battery life and the difficulties with the newer inkbird ibs-m1s talking to home assistant, I am starting to look around for alternatives.

I have found an alternative brand called ecowitt.

The floating pool thermometer model I am looking at is the wn36 and the wifi gateway model is GW1100.

The wn36 has a non removable battery due to the unit being hermetically sealed using “ultrasonic body welding” according to the manual.
The battery life is advertised as a minimum of 3 years or a maximum of 5 years if you put it to sleep for 6 months each year.

I assume the reason for the better battery life is due to the sample rate being 60 seconds, which I am completely fine with.

The price for these 2 together is AUD $116 delivered which is very close to what I originally paid for the inkbird gateway and thermometer bundle.

Anyone have experience or thoughts on this alternative before I decide to purchase?

Links:

https://www.ecowitt.com/shop/goodsDetail/252

https://www.ecowitt.com/shop/goodsDetail/107

1 Like

Hey guys, I figured out a way to decode my payload of the gateway. Looks like my sensor send a different string of yours, so that python code didn’t work. For those who are in the same situation, hope it helps! It’s a Node.js script (JavaScript), so you need to install it in your machine and run npm install at the root folder where the script is located.

I’ve created a GitHub repository with the script because it’s two files.GitHub - luizgununes/inkbird-ibs-m1-decoder: Script to retrieve temperature from IBS-P01 sensor with the IBS-M1 gateway.

Any doubts, reply this message.

I’ve never heard about this brand, but looking at the product description, looks like it’s easier to retrieve information from the sensor.

1 Like

Looks like Ecowitt have an integration in Home Assistant. If that works with their pool device it would be great.

Also found this as well for local.

No I dont have any other sensors you mention defined.

I have submitted a pull request for IBS-M1 support to Tuya Local (GitHub - make-all/tuya-local: Local support for Tuya devices in Home Assistant), it should support it since version 2023.7.0.

Hey All,

Just worked on this all weekend with the following:

IBS-M2S Wifi Base Station
ITH-20R-O Temp Sensors

All the previous people helped build the final solution and thankyou very much for that. Link the hub to your Tuya App on your phone. You’ll see in the first option a smiley face and some chinese writing - it loosely translates to add device. Slide this to 1,2,3,4 and each time power on and off your sensor by removing the battery. Eventually youll notice that the app will prompt you advising the snesor ch_0 has low battery, slide to the next 2,3,4 and repeat the process.

Do everything suggested above and create a tuya account
Link your account
You can either view the properties in the report or use the api explorer and query the properties.

The juicy stuff - it always defaults the value to F (just so you know)

This is the python code you can use to decode the values (so far I only have temp but i’m working on humidity).

import base64

#Decoding Why did i buy these Sensors InkBird Script

# Character conversion dictionary based on the provided method
character_conversion_new = {
    '0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9,
    'A': 10, 'B': 11, 'C': 12, 'D': 13, 'E': 14, 'F': 15, 'G': 16, 'H': 17, 'I': 18, 'J': 19,
    'K': 20, 'L': 21, 'M': 22, 'N': 23, 'O': 24, 'P': 25, 'Q': 26, 'R': 27, 'S': 28, 'T': 29,
    'U': 30, 'V': 31, 'W': 32, 'X': 33, 'Y': 34, 'Z': 35, 'a': 36, 'b': 37, 'c': 38, 'd': 39,
    'e': 40, 'f': 41, 'g': 42, 'h': 43, 'i': 44, 'j': 45, 'k': 46, 'l': 47, 'm': 48, 'n': 49,
    'o': 50, 'p': 51, 'q': 52, 'r': 53, 's': 54, 't': 55, 'u': 56, 'v': 57, 'w': 58, 'x': 59,
    'y': 60, 'z': 61, '+': 62, '/': 63
}

def fahrenheit_to_celsius(f):
    """Convert Fahrenheit to Celsius."""
    return (f - 32) * 5.0/9.0

# Applying the new decoding method on the initial encoded value
decoded_bytes = base64.b64decode("AXoAHwIUBf//YQ==")
raw_temp = decoded_bytes[8:11].decode(errors='ignore')

raw_temp_first_character = raw_temp[0:1]
raw_temp_second_character = raw_temp[1:2]
raw_temp_third_character = raw_temp[2:3]

temp_by_ten_new = (character_conversion_new[raw_temp_first_character] - 10) * 16 + character_conversion_new.get(raw_temp_second_character, 0)
if raw_temp_third_character == "B":
    temp_by_ten_new += 256

temperature_f = temp_by_ten_new / 10
temperature_c= fahrenheit_to_celsius(temperature_f)

print(temperature_c)

Thanks to @markpurcell and everyone else, I, too have just gotten an IBS-M2 WiFi and IBS-P02R to work nicely. I could not figure out how to, so I never enabled “DP Mode” as some have said. I did set up the WiFi controller and pool thermometer with the Inkbird app, and then I reset the network settings and paired with Tuya app.

Mark’s script seemed to use a different version and method of the Tuya API, so I’ve updated to v2 and called a different method (properties). This gave me everything I needed.

#!/usr/bin/env python3
import json
import requests
import hmac
import hashlib
import time
import sys

# Declare constants
ClientID="<< Client ID >>"
ClientSecret="<< Client Secret >>"
DeviceID = "<< Device ID >>"
ChannelID = "<< Channel ID >>"
BaseUrl = "https://openapi.tuyaeu.com"
EmptyBodyEncoded = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"

# If there is an argument, override ChannelID with it
if len( sys.argv ) > 1:
    ChannelID = sys.argv[1]

# Character conversion dictionary for decoding the temperature value
charcter_conversion = {
    "A": 0, "E": 1, "I": 2, "M": 3, "Q": 4, "U": 5, "Y": 6, "c": 7,
    "g": 8, "k": 9, "o": 10, "s": 11, "w": 12, "0": 13, "4": 14, "8": 15
}

# Get current time in milliseconds
tuyatime = int(time.time()) * 1000

# Prepare signing for Access Token
URL = "/v1.0/token?grant_type=1"
StringToSign = f"{ClientID}{tuyatime}GET\n{EmptyBodyEncoded}\n\n{URL}"
AccessTokenSign = hmac.new(ClientSecret.encode(), StringToSign.encode(), hashlib.sha256).hexdigest().upper()

# Get Access Token
headers = {
    'client_id': ClientID,
    'sign_method': 'HMAC-SHA256',
    't': str(tuyatime),
    'sign': AccessTokenSign
}
response = requests.get(f"{BaseUrl}{URL}", headers=headers)
AccessToken = response.json()['result']['access_token']

# Send Device status request
URL = f"/v2.0/cloud/thing/{DeviceID}/shadow/properties"
StringToSign = f"{ClientID}{AccessToken}{tuyatime}GET\n{EmptyBodyEncoded}\n\n{URL}"
RequestSign = hmac.new(ClientSecret.encode(), StringToSign.encode(), hashlib.sha256).hexdigest().upper()

headers.update({
    'sign': RequestSign,
    'access_token': AccessToken
})

response = requests.get(f"{BaseUrl}{URL}", headers=headers)
RequestResponse = response.text

# Parse and process the request response
device_param = json.loads(RequestResponse)
number_of_status = len(device_param["result"]["properties"])

for i in range(number_of_status):
    # print(device_param["result"]["properties"][i]["code"])
    if device_param["result"]["properties"][i]["code"] == ChannelID:
        raw_temp = device_param["result"]["properties"][i]["value"]

# Decode the temperature value
raw_temp_first_charecter = raw_temp[1:2]
raw_temp_second_charecter = raw_temp[2:3]
raw_temp_third_charecter = raw_temp[3:4]

temp_by_ten = (ord(raw_temp_first_charecter) - 65)*16 + charcter_conversion[raw_temp_second_charecter]

if raw_temp_third_charecter == "B":
    temp_by_ten = temp_by_ten + 256

# Calculate and print the pool temperature
Pool_temp = temp_by_ten / 10
print(Pool_temp)

And then, in configuration.yaml, I have:

command_line:
  - sensor:
      command: "python3 /config/scripts/get_pool_temp.py"
      name: Pool Temperature
      device_class: temperature
      unit_of_measurement: "°C"
      scan_interval: 60
      unique_id: << use your own unique_id >>

I also added the ability to pass the ChannelID as an argument. There was some interesting value under “ch_0”. I THOUGHT it might be the battery sensor, so I just set up another command_line sensor as follows:

  - sensor:
      command: "python3 /config/scripts/get_pool_temp.py ch_0"
      name: Pool Temperature Battery
      device_class: battery
      unit_of_measurement: "%"
      scan_interval: 60
      unique_id: << use your own unique_id >>

However, I don’t think it’s the battery, This is the past 24 hours:

Also, the pool thermometer fell offline from the WiFi base station until 8am, so it seems that is must be some data that must be coming directly from the WiFi vs the wireless pool thermometer.

@Nir @keeema Guys please tell me if I’m way off the mark here, but I found this thread because I’ve got an Inkbird hot tub/pool thermometer I’m wanting to integrate into HA, and seeing you guys doing some manual stab-in-the-dark character conversion thing for getting the temperature out of that string just seemed crazy!

Am I assuming right that the received data is a string like this? (from @keeema 's post above with a 19.8C reading) -

A8YAAAAAAAAARgD//////////wAA//////////8AAP//////////AAD//////////wAA//////////8AAP//////////AAD//////////wAA//////////8AAP//////////AAD//////////wAA//////////8AAP//////////AAD//////////wAA//////////8AAP//////////AAD//////////wAA//////////8AAP//////////AAD//////////wAA//////////8AAP//////////AAD//////////wAA//////////8AAP//////////AA==

If so, the “==” at the end stood out as it clearly being a b64 encoded string, with 0xff bytes causing all the “/” characters. I un-base64-ed some of your pasted data strings, converted to binary, and compared with the binary representation of the reported temperatures*10 (e.g. 261 instead of 26.1 degrees), and figured out that the second and third bytes extracted together are the temperature integer in little endian format.

So this code should work, for all cases except maybe negative values (since I didn’t have any negative examples to examine) -

>>> import base64
>>> code = "A8YAAAAAAAAARgD//////////wAA//////////8AAP//////////AAD//////////wAA//////////8AAP//////////AAD//////////wAA//////////8AAP//////////AAD//////////wAA//////////8AAP//////////AAD//////////wAA//////////8AAP//////////AAD//////////wAA//////////8AAP//////////AAD//////////wAA//////////8AAP//////////AAD//////////wAA//////////8AAP//////////AA=="
>>> int.from_bytes(base64.b64decode(code)[1:3], "little", signed=True) / 10
19.8

Again please let me know if I’m way off the mark here or have missed people figuring out a different/better way to get the data, but I’m also very curious to see what negative temperatures come out as!

Nice job! I’m sure that will help someone. Unfortunately, didn’t work for my string.

AP//////////AAD//////////wAA//////////8AAP//////////AAD//////////wADYQEUBRQF//85AP//////////AAD//////////wAA//////////8AAP//////////AAD//////////wAA//////////8AAP//////////AAD//////////wAA//////////8AAP//////////AAD//////////wAA//////////8AAP//////////AAD//////////wAA//////////8AAP//////////AAD//////////wAA//////////8AAP//////////AA==

It returns -0.1.

1 Like

Ah that’s a completely different format (edit - or temperature being reported from a different channel?) from the ones from Ni and keeema I was looking at above… I’ll give it a quick look though. Do you know what the actual temperature displayed at that point was?

So, I’m not home right now so I can’t tell you exactly. But using my code, supposed to be 35.2.

I might have it -

>>> import base64
>>> code = "AP//////////AAD//////////wAA//////////8AAP//////////AAD//////////wADYQEUBRQF//85AP//////////AAD//////////wAA//////////8AAP//////////AAD//////////wAA//////////8AAP//////////AAD//////////wAA//////////8AAP//////////AAD//////////wAA//////////8AAP//////////AAD//////////wAA//////////8AAP//////////AAD//////////wAA//////////8AAP//////////AA=="
>>> int.from_bytes(base64.b64decode(code)[51:53], "little", signed=True) / 10
35.3

If this happens to work for you, it might mean that your javascript thing needs this line (117):

let temperature = { sensor: (bytesSensor[51] + bytesSensor[15]) / 10};

Changing to say:

let temperature = { sensor: (bytesSensor[52] << 8 | bytesSensor[51]) / 10};

(Assuming that syntax for left-bitshifting and then bitwise ORing is correct, sorry I’m not familiar with js. Also I’m not sure why bytesSensor[15] was being added on? It’s just one of the 0xff padding bytes, but I guess that may have had the side effect of giving the correct result (albeit 0.1 less) when the temp is above 25.5 i.e. a number of 256 or more and therefore requiring the extra low-end bit from the neighbouring byte.)

To be honest, I tried a several number of attempts until it match the temperature I was expecting. So far it works for values above 20 degrees. Anyway, I’ve changed the line like you suggested and it worked too.

@Colgate2in1 Thanks for that! Sorry for the delayed response, I only now found some time to look into what you suggested.

It is working for me. I was sure they use some encoding method and I tried to google it but didn’t really know where to begin as I have no background in this area. I eventually found a pattern that worked by looking at many examples and used that. No doubt that your solution is much more elegant and short!

No problem! To be honest it’s pretty impressive how far you got with effectively trying to reverse engineer base64 encoding without realising it. The data being sent little-endian just added on an extra layer of confusion that you can’t see without looking at the raw bytes.

2 Likes

I went down this path too, and still have some work on some of the fields, but foudn a clean way to parse the temps, battery and humidity (if applicable):

After I got this working I realize HA has migrated from the original API (openapi.tuyaus) via tuya-iot-python-sdk to the new sdk (apigw.tuyaus) via tuya-device-sharing-sdk

Unfortunately, even with the DP mode enabled the API doesn’t seem to send the additional fields. I’ve opened an issue: API Difference Causing issues w/ Home Assistant Integration · Issue #11 · tuya/tuya-device-sharing-sdk · GitHub I’d appreciate upvoting or commenting there – if they expand the signature of that API we’ll be able to merge my PR and get IBS-M2 data natively.

2 Likes

I forked the core Tuya integration, reverting back to the openapi sdk, bundled in my new sensor and added it to HACS as a custom integration – it’s installable from here:

For posterity’s sake, here’s the parsing code for the sensor data:

from typing import Tuple, Optional
import base64
import struct

def from_raw(data: str) -> Tuple[float, Optional[float], int]:
    decoded_bytes = base64.b64decode(data)
    # TODO: Identify what the skipped bytes are in the base station data
    _temperature, _humidity, _, battery = struct.Struct("<hhIb").unpack(decoded_bytes[1:11])
    (temperature, humidity) = _temperature / 10.0, _humidity / 10.0
    if _humidity == -1: humidity = None
    return temperature, humidity, battery
    
examples = [
    "AXoAHwIUBf//YQ==",
    'AdYAhwEAEA4AZA==',
    'CKYA////////MQ==',
    'CL8A////////ZA=='
]

for example in examples:
    deg_c, humidity, battery = from_raw(example)
    deg_f = (deg_c+32)*9/5
    print(f"{example}: {deg_c:.1f}˚C / {deg_f:.1f}˚F @ {humidity}% Humidity & {battery}% Charge")