In order for others to be able to use this code, I am posting it here until I get my act together to make it more robust and pop it onto GitHub.
The ClimateMaster DXM2 control has a lot of data that can be used to troubleshoot or know how your system is running. As an example, my HVAC system now has a graphical display telling us when its operating at various stages (purple line), and includes the drop/rise in water temperature from the ground loops (Light blue line):

This project uses a Raspberry Pi 4 and a Waveshare RS485 board https://www.waveshare.com/wiki/RS485_RS232_HAT.
The ClimateMaster DXM2 control board has two headers (P5 and P4) shown marked in green exposing an RS485 bus. These headers are used for a tech tool and or a communicating (but limited) thermostat. To use the bus, it must be switched to “slave” mode by ensuring dipswitch 3-1 is OFF as shown with the red dot. If you have nothing else connected to the P4/P5 headers this is a non issue. You need no other connections between these boards other than the A and B wires shown in blue below:

#!/usr/bin/python
# -*- coding:utf-8 -*-
# Raspberry Pi Climatemaster logging code.
# 2022 - Jeremy Laurenson
# Based on Waveshare RS485 Hat and Pi 4
# https://www.waveshare.com/wiki/RS485_RS232_HAT
import serial
import os
import sys
import logging
import urllib.request
logging.basicConfig(level=logging.INFO)
libdir = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), 'lib')
if os.path.exists(libdir):
sys.path.append(libdir)
import RPi.GPIO as GPIO
import time
from waveshare_RS485_RS232_HAT import config
RS485EN = 22
print("Connecting to serial...")
ser = config.config(Baudrate = 9600 , dev = "/dev/ttySC0")
data = ''
GPIO.output(RS485EN, GPIO.LOW)
time.sleep(0.5)#Waiting to send
print("Climatemaster DXM2 data logger.")
cmd_lt1=bytearray([0x30,0x03,0x51,0x20,0x00,0x01,0x91,0x1D])
cmd_lt2=bytearray([0x30,0x03,0x51,0x21,0x00,0x01,0xC0,0xDD])
cmd_ewt=bytearray([0x30,0x03,0x51,0x23,0x00,0x01,0x61,0x1D])
cmd_lwt=bytearray([0x30,0x03,0x51,0x24,0x00,0x01,0xD0,0xDC])
cmd_lat=bytearray([0x30,0x03,0x51,0x25,0x00,0x01,0x81,0x1C])
cmd_bls=bytearray([0x30,0x03,0x51,0x28,0x00,0x01,0x10,0xDF])
cmd_dischargetemp =bytearray([0x30,0x03,0x51,0x27,0x00,0x01,0x20,0xDC])
cmd_pumpspeed =bytearray([0x30,0x03,0x51,0x15,0x00,0x01,0x81,0x13])
cmd_targetairflow =bytearray([0x30,0x03,0x51,0x14,0x00,0x01,0xD0,0xD3])
cmd_controlvoltage=bytearray([0x30,0x03,0x51,0x2A,0x00,0x01,0xB1,0x1F])
cmd_getfaults =bytearray([0x30,0x03,0x51,0x1C,0x00,0x03,0xD0,0xD0])
cmd_getserial =bytearray([0x30,0x03,0x51,0x1F,0x00,0x01,0xA1,0x11])
def get_data(code):
print("Getting ", code)
GPIO.output(RS485EN, GPIO.LOW)
time.sleep(0.5)#Waiting to send
ser.serial.write(code)
time.sleep(0.2)
GPIO.output(RS485EN, GPIO.HIGH)
packetcount=7
dataarray=[]
try:
while(packetcount>0):
data_t = ser.serial.read(1)
int_val = int.from_bytes(data_t, "big")
dataarray.append(int_val)
packetcount=packetcount-1
except:
dataarray=[]
if len(dataarray)==7:
if dataarray[0]==48:
if dataarray[1]==3:
if(dataarray[2]==2):
tempval=(dataarray[3]*255+dataarray[4])
return tempval
return 0
def get_faults():
GPIO.output(RS485EN, GPIO.LOW)
time.sleep(0.5)#Waiting to send
ser.serial.write(cmd_getfaults)
time.sleep(0.2)
GPIO.output(RS485EN, GPIO.HIGH)
packetcount=11
dataarray=[]
try:
while(packetcount>0):
data_t = ser.serial.read(1)
int_val = int.from_bytes(data_t, "big")
dataarray.append(int_val)
packetcount=packetcount-1
except:
dataarray=[]
if len(dataarray)==11:
if dataarray[0]==48:
if dataarray[1]==3:
if dataarray[2]==6:
dataarray.pop(10)
dataarray.pop(9)
dataarray.pop(0)
dataarray.pop(0)
dataarray.pop(0)
return dataarray;
return 0
def listToString(s):
# initialize an empty string
str1 = ""
# traverse in the string
for ele in s:
str1 += str(ele)
str1 += ":"
# return string
return str1
lt1=get_data(cmd_lt1)/10
lt2=get_data(cmd_lt2)/10
ewt=get_data(cmd_ewt)/10
lwt=get_data(cmd_lwt)/10
lat=get_data(cmd_lat)/10
taf=get_data(cmd_targetairflow)
bls=get_data(cmd_bls)
pspeed=get_data(cmd_pumpspeed)
cdt=get_data(cmd_dischargetemp)/10
cvol=get_data(cmd_controlvoltage)/10
fcodes=get_faults()
postcommand="http://10.0.1.10/environmentals/climatemaster.php?"
postcommand = postcommand + "lt1=" + str(lt1)
postcommand = postcommand + "<2=" + str(lt2)
postcommand = postcommand + "&ewt=" + str(ewt)
postcommand = postcommand + "&lwt=" + str(lwt)
postcommand = postcommand + "&lat=" + str(lat)
postcommand = postcommand + "&taf=" + str(taf)
postcommand = postcommand + "&bls=" + str(bls)
postcommand = postcommand + "&pspeed=" + str(pspeed)
postcommand = postcommand + "&cdt=" + str(cdt)
postcommand = postcommand + "&cvol=" + str(cvol)
postcommand = postcommand + "&fcodes=" + listToString(fcodes)
print(postcommand)
contents = urllib.request.urlopen(postcommand).read()