Machinechat with Battery Powered Xbee3 Wireless Zigbee MS8607 Environmental Sensor

This project uses a Digi Xbee3 wireless Zigbee module and a TE Connectivity MS8607 sensor to implement a battery powered Zigbee environmental sensor. The ADC on the Xbee3 is used to measure system battery voltage.Sensor data and battery voltage is sent to and monitored by Machinechat’s JEDI One IoT platform.

Project Description

The project use case is monitoring a freezer’s temperature and the battery voltage of the sensor system. The MS8607 is capable of measuring barometric pressure, temperature, and humidity but this project is focused on the temperature and voltage parameters. The MS8607 is tethered to the Xbee radio via cabling so it can be placed inside a freezer to measure temperature (humidity is also measured for additional insight into freezer performance). Battery voltage is monitored to determine when the batteries have degraded to the point where they need to be replaced.
A MicroPython application runs on the Xbee3 module to connect to the Zigbee network and read the sensor and ADC. Sensor data and battery voltage is sent to the Zigbee coordinator which forwards on to the JEDI One IoT platform using Machinechat with Zigbee to WiFi sensor bridge (see project for details). JEDI One is running on a Raspberry Pi 4 with ethernet/WiFi network connectivity.

Hardware

  • RASPBERRY PI 4B/4GB
    Raspberry PI 4 Model B with 4GB SDRAM (used to run Machinechat JEDI One)
  • XB3-24Z8PT-J
    Digi Xbee3 Zigbee PCB antenna THT module
  • DPP901G000
    TE Connectivity MS8607 Pressure/Temperature/Humidity sensor breakout board
  • TPS2042P
    Texas Instruments TPS2042P Power Distribution Switch

Software

  • JEDI One
    JEDI One is a ready-to-use IoT data management software solution. Capabilities include: collect data from sensors, devices and machines; build intuitive real-time and historical data and system view dashboards; create rules to monitor and respond to data conditions automatically; receive alert notifications by email and SMS.
  • MicroPython
    MicroPython is a lean and efficient implementation of the Python 3 programming language that includes a small subset of the Python standard library and is optimised to run on microcontrollers and in constrained environments.

Implementation

The sensor circuitry is powered by two AA alkaline batteries. Primary components consists of a Digi Xbee 3 Zigbee module, a Texas Instruments TPS2042 power distribution IC and TE Connectivity MS8607 sensor board. The TPS2042 is used to control power to the MS8607 and battery voltage divider circuit. The Micropython application code implements a sleep period of 60 seconds between reading and transmitting sensor data to minimize current consumption and increase battery life. Below schematic diagram illustrates how the circuit is implemented.


image

Zigbee sensor Scheme-it schematic and BOM link

Digi Xbee3 MicroPython MS8607 sensor project details

In the MicroPython project an Xbee3 module is configured as a Zigbee end device with the same network ID as the Xbee3 coordinator used in the Zigbee to WiFi bridge. The MicroPython application joins the Zigbee network and begins an infinite loop that enables the TPS2042P switch, reads the MS8607 sensor, reads the battery voltage, disables the TPS2042P switch, builds the sensor data message, sends to the Zigbee coordinator, sleeps for 60 seconds and repeats.

1 - Set up MicroPython on Xbee3 and flash application firmware. See reference Digi MicroPython Programming Guide

2 - Code walkthrough (PyCharm project: sensor_ms8607THV, filename: main.py)

Initial setup and connect to Zigbee network

# project: sensor_ms8607THV
# description: Xbee3 Zigbee Micropython MS8607 sensor project that sleeps x seconds, then wakes to send temperature,
#              humidity, and battery voltage readings to coordinator
# Default template for Digi Xbee3 projects

import utime
import ustruct
import machine
import time
import xbee
from machine import Pin


x = xbee.XBee()

# list of commands in hex for MS8607 pressure sensor
c_reset = 0x1E  # reset command
r_c1 = 0xA2  # read PROM C1 command
r_c2 = 0xA4  # read PROM C2 command
r_c3 = 0xA6  # read PROM C3 command
r_c4 = 0xA8  # read PROM C4 command
r_c5 = 0xAA  # read PROM C5 command
r_c6 = 0xAC  # read PROM C6 command
r_adc = 0x00  # read ADC command
r_d1 = 0x44  # convert D1 (OSR=1024)
r_d2 = 0x54  # convert D2 (OSR=1024)
p_address = 0x76  # pressure sensor i2c address

# list of commands in hex for MS8607 humidity sensor
h_address = 0x40  # humidty sensor i2c address
r_user = 0xE7  # read user register command
w_user = 0xE6  # write user register command
t_temp = 0xE3  # trigger temperature measurement, hold master
t_humi = 0xE5  # trigger humidity measurement, hold master
# set register format
REGISTER_FORMAT = '>h'  # ">" big endian, "h" 2 bytes
REGISTER_SHIFT = 4  # rightshift 4 for 12 bit resolution

# set i2c clock to 100KHz
i2c = machine.I2C(1, freq=100000)

#Zigbee coordinator adddress in house
TARGET_64BIT_ADDR = b'\x00\x13\xA2\x00\x41\xAA\xCB\xBA'



# slave_addr = 0x76

# reset pressure sensor
def reset_ps():
    slave_addr = p_address
    data = bytearray([c_reset])
    i2c.writeto(slave_addr, data)
    return data


# scan i2c bus for active addresses
def scan_I2C():
    devices = i2c.scan()
    return devices


def read_c1():  # read PROM value C1
    data = bytearray([r_c1])
    i2c.writeto(slave_addr, data)
    raw_c = i2c.readfrom(slave_addr, 2)  # raw C is 2 bytes
    value = int.from_bytes(raw_c, "big")  # use builtin to convert to integer
    return value


def read_c2():  # read PROM value C2
    data = bytearray([r_c2])
    i2c.writeto(slave_addr, data)
    raw_c = i2c.readfrom(slave_addr, 2)  # raw C is 2 bytes
    value = int.from_bytes(raw_c, "big")  # use builtin to convert to unsigned integer
    return value


def read_c3():  # read PROM value C3
    data = bytearray([r_c3])
    i2c.writeto(slave_addr, data)
    raw_c = i2c.readfrom(slave_addr, 2)  # raw C is 2 bytes
    value = int.from_bytes(raw_c, "big")  # use builtin to convert to unsigned integer
    return value


def read_c4():  # read PROM value C4
    data = bytearray([r_c4])
    i2c.writeto(slave_addr, data)
    raw_c = i2c.readfrom(slave_addr, 2)  # raw C is 2 bytes
    value = int.from_bytes(raw_c, "big")  # use builtin to convert to unsigned integer
    return value


def read_c5():  # read PROM value C5
    data = bytearray([r_c5])
    i2c.writeto(slave_addr, data)
    raw_c = i2c.readfrom(slave_addr, 2)  # raw C is 2 bytes
    value = int.from_bytes(raw_c, "big")  # use builtin to convert to unsigned integer
    return value


def read_c6():  # read PROM value C6
    data = bytearray([r_c6])
    i2c.writeto(slave_addr, data)
    raw_c = i2c.readfrom(slave_addr, 2)  # raw C is 2 bytes
    value = int.from_bytes(raw_c, "big")  # use builtin to convert to unsigned integer
    return value


# start D1 conversion - pressure (24 bit unsigned)
def start_d1():
    # print ('start D1 ')
    data = bytearray([r_d1])
    i2c.writeto(slave_addr, data)


# start D2 conversion - temperature (24 bit unsigned)
def start_d2():
    # print ('start D2 ')
    data = bytearray([r_d2])
    i2c.writeto(slave_addr, data)


# read pressure sensor ADC
def read_adc():  # read ADC 24 bits unsigned
    data = bytearray([r_adc])
    i2c.writeto(slave_addr, data)
    adc = i2c.readfrom(slave_addr, 3)  # ADC is 3 bytes
    value = int.from_bytes(adc, "big")  # use builtin to convert to integer
    return value


# read humidity sensor user register command 0xE7, default value = 0x02
# default resolution RH 12bit, T 14bit
def read_user():
    data = bytearray([r_user])
    i2c.writeto(slave_addr, data)
    value = i2c.readfrom(slave_addr, 1)
    return value


# read rh: send trigger rh command 0xE5
def read_rh():
    data = bytearray([t_humi])
    i2c.writeto(slave_addr, data)
    raw_rh = i2c.readfrom(slave_addr, 2)  # raw RH is 2 bytes
    raw_value = int.from_bytes(raw_rh, "big")  # use builtin to convert to integer
    # rh_tc = (-0.15)*(25 - read_temp())# RH temp compensation
    rh_value = (((raw_value / 65536) * 125) - 6)  # calculate RH
    return rh_value


# **************** Main Program *********************************


# set up variables d2, d3, d4 as pin outputs and set to 0
# use D4 to /EN TPS2042D power distribution switch, power MS8607 and voltage divider for ADC pin D0
d2 = Pin.board.D2
d2.mode(Pin.OUT)
d2.value(0)
d3 = Pin.board.D3
d3.mode(Pin.OUT)
d3.value(0)
d4 = Pin.board.D4
d4.mode(Pin.OUT)
d4.value(0)  # set low to enable TPS2042P to enable i2c during startup

# set DIO9 and DIO10 to outputs with value 0
d9 = Pin.board.D9
d9.mode(Pin.OUT)
d9.value(0)
d10 = Pin.board.D10
d10.mode(Pin.OUT)
d10.value(0)

# set up ADC with 2.5V reference for pin D0 as analog input
#x = xbee.Xbee()
xbee.atcmd('AV', 1)
apin = machine.ADC('D0')

# delay for 30 seconds (long delay to connect to device if needed after reset)
#print("start 90 seconds delay")
utime.sleep(90)
#print("end 90 seconds delay")



try:
    print('i2c scan addresses found: ', scan_I2C())
except:
    print('i2c scan addresses not found')
try:
    print('perform reset on pressure sensor, code = ', reset_ps())
except:
    print('cannot reset pressure sensor')


# read and print humidity sensor user register
# slave_addr = h_address  # set humidity sensor i2c address
# print('user register: ', read_user())

# read press sensor calibration PROM
slave_addr = p_address
try:
    C1 = read_c1()
    C2 = read_c2()
    C3 = read_c3()
    C4 = read_c4()
    C5 = read_c5()
    C6 = read_c6()
except:
    print('cannot read pressure sensor calibration')

#print('PROM C1 = ', C1)
#print('PROM C2 = ', C2)
#print('PROM C3 = ', C3)
#print('PROM C4 = ', C4)
#print('PROM C5 = ', C5)
#print('PROM C6 = ', C6)

# check zigbee connection
while xbee.atcmd("AI") != 0:
    print("#Trying to Connect...")
    utime.sleep(1)

print("#Online...")

main loop

# Main loop
while True:
    # set pin d4 low to enable TPS2042
#    d2.value(1)
    d4.value(0)
    # delay for 2 seconds to stabilize
    utime.sleep(2)

    # take ADC reading on pin D0 (voltage divider across supply/battery)
    raw_val = apin.read()
    val_mv = int((raw_val * 2500)/4095 * 2)
    print('supply voltage %d mV' % val_mv)

    # start on D1 conversion for pressure sensor
    try:
        slave_addr = p_address  # set i2c address to pressure sensor
        start_d1()  # start D1 conversion
        utime.sleep(1)  # short delay during conversion
        raw_d1 = read_adc()
        #print("D1= ", raw_d1)
    except:
        print("D1 conversion failed")

    # start D2 conversion for temperature
    try:
        start_d2()  # start D2 conversion
        utime.sleep(1)
        raw_d2 = read_adc()
        #print("D2= ", raw_d2)
    except:
        print("D2 conversion failed")

    # calulate pressure and temperature
    try:
        # difference between actual and ref P temp
        dT = raw_d2 - (C5 * 256)
        #print("dT= ", dT)
        #
        Temp = 2000 + (dT * (C6 / 8388608))
        if Temp < 2000:  # add 2nd order correction when temp < 20C
            T2 = 3 * dT ** 2 / 8589934592
            OFF2 = (61 * (Temp - 2000) ** 2) / 16
            SENS2 = (29 * (Temp - 2000) ** 2) / 16
        else:
            T2 = 5 * dT ** 2 / 274877906944
            OFF2 = 0
            SENS2 = 0
        if Temp < -1500:  # add 2nd order correction when temp < -15C
            OFF2 = OFF2 + (17 * (Temp + 1500) ** 2)
            SENS2 = SENS2 + (9 * (Temp + 1500) ** 2)
        # calculate corrected temp
        Temp = (2000 - T2 + (dT * (C6 / 8388608))) / 100
        fTemp = int(Temp * 9 / 5 + 32)
        OFF = (C2 * 131072) + (C4 * dT / 64) - OFF2  # offset at actual P temperature
        #print("OFF= ", OFF)
        SENS = (C1 * 65536) + (C3 * dT / 128) - SENS2  # pressure offset at actual temperature
        #print("SENS= ", SENS)
        Pres = (raw_d1 * SENS / 2097152 - OFF) / 3276800  # barometric pressure
        #print('P Temp = ', '%.1fC' % Temp)
        #print('P Temp = ', '%.1fF' % fTemp)
        #print('T2 = ', T2)
        #print('OFF2 = ', OFF2)
        #print('SENS2 = ', SENS2)
        #print('Pressure = ', '%.1f ' % Pres)
        utime.sleep(1)
    except:
        print("Temp and Pressure calculation failed")

    # start on humidity sensor
    try:
        slave_addr = h_address
        RH = int(read_rh() - 3.6 - (0.18 * Temp))
        print('relative humidity: ', '%.1f percent' % RH)  # temp compensated humidity
    except:
        print("humidity sensor conversion failed")

    # turn off TPS2042
    d4.value(1)
    # build PTH sensor data payload for battery voltage, temp and humidity
    try:
        time_snapshot = str(utime.ticks_cpu())
        # print_PTH = "PTH_sensor:" + time_snapshot + ":Press:" + str(Pres) + "mB:Temp:" + str(fTemp) + "F:Temp1:" + str(ftemp9) + "F:Humidity:" + str(RH) + "%:#"
        # print_PTH = "PTH_sensor:" + time_snapshot + ":Press:" + str(Pres) + "mB:Temp:" + str(fTemp) + "F:Humidity:" + str(RH) + "%:#" #remove MCP9808
        print_PTH = "PTH_sensor:" + time_snapshot + ":V_Ba:" + str(val_mv) + ":T_F:" + str(fTemp) + ":H_%:" + str(
            RH) + ":#"
        print(print_PTH)
    except:
        print("sensor payload build failed")
        Pres = 99
        fTemp = 99
        RH = 99

    # transmit PTH data over Zigbee to coordinator
    d3.value(1)
    try:
        xbee.transmit(TARGET_64BIT_ADDR, print_PTH)
        # xbee.transmit(ROUTER_64BIT_x1B2D, print_PTH)
        #
    except:
        print("xbee coordinator transmit failed")


    # set pin d4 high to turn off TPS2042P
    #d4.value(1)


#    print("set d4 high and delay for 1 seconds")
#    utime.sleep(1)
#    d2.value(0)
#    d3.value(0)
    print("sleeping for 60 seconds")
    #sleep_ms = x.sleep_now(10000, True)
    sleep_ms = x.sleep_now(60000, True)
    print("woke up ")

Latest source code for the project: sensor_ms8607THV is on github at below link:

Set up the JEDI One

1 - If machinechat JEDI One is not already installed on the Raspberry Pi see below:

2 - Set up Machinechat with Zigbee to WiFi sensor bridge if not already implemented (see project for details).

3 - Set up the JEDI One dasboard

In the JEDI One, select “Dashboards” tab, then select “+” to add a new chart and configure.

Name the chart, select “Chart Type”, select “Source” (PTH_xxx), select “Property” (data2), enter “Units” and enter “Refresh Interval”. Repeat for the second chart, name the chart, select “Source” (PTH_xxx), select “Property” (data1), enter “Units” and enter “Refresh Interval”. When complete the dashboard should look similar to below.

Conclusion

The combination of Digi’s Xbee3 Zigbee module, TE Connectivity’s MS8607 sensor, TI’s TPS2042P power IC and Machinechat’s JEDI One IoT platform results in a highly capable wireless monitoring system that monitors a freezer’s temperature and the wireless sensor’s battery state. The project can be easily modified to monitor other parameters and sensors. JEDI One can easily be configured to provide email or SMS alerts when conditions fall below pre-established limits.

References