broox / python-nuheat Goto Github PK
View Code? Open in Web Editor NEWA Python library that allows control of connected NuHeat Signature radiant floor thermostats
License: MIT License
A Python library that allows control of connected NuHeat Signature radiant floor thermostats
License: MIT License
Per @keithnet, the APIs are the same.
The hostname for warm tiles is: warmtiles.mythermostat.info
The data returned by a Warm Tiles thermostat seems to have a few properties that are different than that of NuHeat.
WarmTiles has these additional fields:
ComfortEndTime
ComfortTemperature
DistributorId
EarlyStartOfHeating
LastPrimaryModeIsAuto
LoadManuallySetWatt
LoadMeasuredWatt
LoadMeasuringActive
ManualTemperature
RegulationMode
- maybe the same as ScheduleMode
?VacationBeginDay
VacationEnabled
VacationEndDay
VacationTemperature
ScheduleType
values (0-5)WarmTiles is missing these NuHeat fields:
EnergyOverview
HoldSetPointDateTime
OperatingMode
ScheduleMode
WPerSquareUnit
Example Warm Tiles response:
{
"Assigned":true,
"ComfortEndTime":"11/01/2023 12:00:00 +00:00",
"ComfortTemperature":500,
"Confirmed":true,
"DistributerId":18231,
"EarlyStartOfHeating":false,
"Email":"[email protected]",
"ErrorCode":0,
"GroupId":-1,
"GroupName":"Home",
"HasBeenAssigned":true,
"Heating":false,
"KwhCharge":0.25,
"LastPrimaryModeIsAuto":false,
"LoadManuallySetWatt":100,
"LoadMeasuredWatt":2346,
"LoadMeasuringActive":true,
"ManualTemperature":500,
"MaxTemp":4000,
"MinTemp":500,
"Online":true,
"RegulationMode":3,
"Room":"Porch",
"SerialNumber":"0000000",
"SetPointTemp":500,
"SWVersion":"1012S202",
"Temperature":714,
"TZOffset":"-06:00",
"VacationBeginDay":"01/01/1970 00:00:00",
"VacationEnabled":false,
"VacationEndDay":"01/01/1970 00:00:00",
"VacationTemperature":500,
"Schedules":[
{
"WeekDayGrpNo":1,
"Events":[
{
"ScheduleType":0,
"Clock":"06:00:00",
"TempFloor":500,
"Active":true
},
{
"ScheduleType":1,
"Clock":"09:00:00",
"TempFloor":2333,
"Active":false
},
{
"ScheduleType":2,
"Clock":"12:00:00",
"TempFloor":2777,
"Active":false
},
{
"ScheduleType":3,
"Clock":"13:00:00",
"TempFloor":2333,
"Active":false
},
{
"ScheduleType":4,
"Clock":"17:00:00",
"TempFloor":2777,
"Active":false
},
{
"ScheduleType":5,
"Clock":"23:00:00",
"TempFloor":2333,
"Active":false
}
]
},
{
"WeekDayGrpNo":2,
"Events":[
{
"ScheduleType":0,
"Clock":"06:00:00",
"TempFloor":500,
"Active":true
},
{
"ScheduleType":1,
"Clock":"09:00:00",
"TempFloor":2333,
"Active":false
},
{
"ScheduleType":2,
"Clock":"12:00:00",
"TempFloor":2777,
"Active":false
},
{
"ScheduleType":3,
"Clock":"13:00:00",
"TempFloor":2333,
"Active":false
},
{
"ScheduleType":4,
"Clock":"17:00:00",
"TempFloor":2777,
"Active":false
},
{
"ScheduleType":5,
"Clock":"23:00:00",
"TempFloor":2333,
"Active":false
}
]
},
{
"WeekDayGrpNo":3,
"Events":[
{
"ScheduleType":0,
"Clock":"06:00:00",
"TempFloor":500,
"Active":true
},
{
"ScheduleType":1,
"Clock":"09:00:00",
"TempFloor":2333,
"Active":false
},
{
"ScheduleType":2,
"Clock":"12:00:00",
"TempFloor":2777,
"Active":false
},
{
"ScheduleType":3,
"Clock":"13:00:00",
"TempFloor":2333,
"Active":false
},
{
"ScheduleType":4,
"Clock":"17:00:00",
"TempFloor":2777,
"Active":false
},
{
"ScheduleType":5,
"Clock":"23:00:00",
"TempFloor":2333,
"Active":false
}
]
},
{
"WeekDayGrpNo":4,
"Events":[
{
"ScheduleType":0,
"Clock":"06:00:00",
"TempFloor":500,
"Active":true
},
{
"ScheduleType":1,
"Clock":"09:00:00",
"TempFloor":2333,
"Active":false
},
{
"ScheduleType":2,
"Clock":"12:00:00",
"TempFloor":2777,
"Active":false
},
{
"ScheduleType":3,
"Clock":"13:00:00",
"TempFloor":2333,
"Active":false
},
{
"ScheduleType":4,
"Clock":"17:00:00",
"TempFloor":2777,
"Active":false
},
{
"ScheduleType":5,
"Clock":"23:00:00",
"TempFloor":2333,
"Active":false
}
]
},
{
"WeekDayGrpNo":5,
"Events":[
{
"ScheduleType":0,
"Clock":"06:00:00",
"TempFloor":500,
"Active":true
},
{
"ScheduleType":1,
"Clock":"09:00:00",
"TempFloor":2333,
"Active":false
},
{
"ScheduleType":2,
"Clock":"12:00:00",
"TempFloor":2777,
"Active":false
},
{
"ScheduleType":3,
"Clock":"13:00:00",
"TempFloor":2333,
"Active":false
},
{
"ScheduleType":4,
"Clock":"17:00:00",
"TempFloor":2777,
"Active":false
},
{
"ScheduleType":5,
"Clock":"23:00:00",
"TempFloor":2333,
"Active":false
}
]
},
{
"WeekDayGrpNo":6,
"Events":[
{
"ScheduleType":0,
"Clock":"06:00:00",
"TempFloor":500,
"Active":true
},
{
"ScheduleType":1,
"Clock":"09:00:00",
"TempFloor":2333,
"Active":false
},
{
"ScheduleType":2,
"Clock":"12:00:00",
"TempFloor":2777,
"Active":false
},
{
"ScheduleType":3,
"Clock":"13:00:00",
"TempFloor":2333,
"Active":false
},
{
"ScheduleType":4,
"Clock":"17:00:00",
"TempFloor":2777,
"Active":false
},
{
"ScheduleType":5,
"Clock":"23:00:00",
"TempFloor":2333,
"Active":false
}
]
},
{
"WeekDayGrpNo":7,
"Events":[
{
"ScheduleType":0,
"Clock":"06:00:00",
"TempFloor":500,
"Active":true
},
{
"ScheduleType":1,
"Clock":"09:00:00",
"TempFloor":2333,
"Active":false
},
{
"ScheduleType":2,
"Clock":"12:00:00",
"TempFloor":2777,
"Active":false
},
{
"ScheduleType":3,
"Clock":"13:00:00",
"TempFloor":2333,
"Active":false
},
{
"ScheduleType":4,
"Clock":"17:00:00",
"TempFloor":2777,
"Active":false
},
{
"ScheduleType":5,
"Clock":"23:00:00",
"TempFloor":2333,
"Active":false
}
]
}
],
"Support":"Wifi Line Voltage Thermostat"
}
I see that you've updated setup.py for version 1.1.0 but haven't yet published it on PyPI. If you wouldn't mind, could you please do that? I've gotten back to a point where I'd like to pull this into HomeAssistant and finally get my MapeHeat thermostat set up properly.
Much appreciated, thanks for being the Maintainer! :)
Wondering Energy usage can be added?
I'm thinking maybe Hourly could be reported downstream to Home Assistant to make NuHeat contribute to the Energy stats for consumption devices in a similar manner to how some smart plugs report energy consumption.
I've done the this work to update the Neurio component, hopefully that will arrive with in HA's 2021.12.1 release. I'd be cool to try to make this work downstream for HA's NuHeat component (though I'm not a native python dev; so might take a while to get merged). :)
=================================== FAILURES ===================================
_______________________ TestThermostat.test_get_data_401 _______________________
self = <tests.test_thermostat.TestThermostat testMethod=test_get_data_401>
@responses.activate
def test_get_data_401(self):
# First request (when initializing the thermostat) is successful
response_data = load_fixture("thermostat.json")
responses.add(
responses.GET,
config.THERMOSTAT_URL,
status=200,
body=json.dumps(response_data),
content_type="application/json"
)
# A later, second request throws 401 Unauthorized
responses.add(
responses.GET,
config.THERMOSTAT_URL,
status=401
)
# Attempt to reauthenticate
auth_data = load_fixture("auth_success.json")
responses.add(
responses.POST,
config.AUTH_URL,
status=200,
body=json.dumps(auth_data),
content_type="application/json"
)
# Third request is successful
responses.add(
responses.GET,
config.THERMOSTAT_URL,
status=200,
body=json.dumps(response_data),
content_type="application/json"
)
bad_session_id = "my-bad-session"
good_session_id = auth_data.get("SessionId")
api = NuHeat(None, None, session_id=bad_session_id)
serial_number = response_data.get("SerialNumber")
thermostat = NuHeatThermostat(api, serial_number)
> thermostat.get_data()
tests/test_thermostat.py:206:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
nuheat/thermostat.py:140: in get_data
data = self._session.request(config.THERMOSTAT_URL, params=params)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <NuHeat username='None'>, url = 'https://www.mynuheat.com/api/thermostat'
method = 'GET', data = None
params = {'serialnumber': '12345', 'sessionid': 'my-bad-session'}, retry = True
def request(self, url, method="GET", data=None, params=None, retry=True):
"""
Make a request to the NuHeat API
:param url: The URL to request
:param method: The type of request to make (GET, POST)
:param data: Data to be sent along with POST requests
:param params: Querystring parameters
:param retry: Attempt to re-authenticate and retry request if necessary
"""
headers = config.REQUEST_HEADERS
if params and self._session_id:
params['sessionid'] = self._session_id
if method == "GET":
response = requests.get(url, headers=headers, params=params)
elif method == "POST":
response = requests.post(url, headers=headers, params=params, data=data)
# Handle expired sessions
if response.status_code == 401 and retry:
> _LOGGER.warn("NuHeat APIrequest unauthorized [401]. Try to re-authenticate.")
E AttributeError: 'Logger' object has no attribute 'warn'
nuheat/api.py:78: AttributeError
=========================== short test summary info ============================
FAILED tests/test_thermostat.py::TestThermostat::test_get_data_401 - Attribut...
========================= 1 failed, 35 passed in 0.18s =========================
See also https://bugzilla.redhat.com/show_bug.cgi?id=2252059
This library is pinning the requests
dependency too strictly.
install_requires=[
'requests==2.28.1',
],
Line 33 in 41db294
As a result of that, any upstream project using this library, cannot upgrade requests
to a newer version. Please ping a range or a minimal version that is required for this library instead.
Context: I ran into this when upgrading the requests
package @ Home Assistant to 2.28.2, for which this package caused a conflict.
../Frenck
git cloned your project, and created nuheat.py:
from nuheat import NuHeat
# Initalize an API session with your login credentials
api = NuHeat("######", "######")
api.authenticate()
# Fetch a thermostat by serial number / ID. This can be found on the NuHeat website by selecting
# your thermostat and noting the Thermostat ID
thermostat = api.get_thermostat("######")
# Get the current temperature of the thermostat
print thermostat.fahrenheit
The output:
[root@docker python-nuheat]# python nuheat.py
No handlers could be found for logger "nuheat.api"
/usr/lib/python2.6/site-packages/urllib3/util/ssl_.py:339: SNIMissingWarning: An HTTPS request has been made, but the SNI (Subject Name Indication) extension to TLS is not available on this platform. This may cause the server to present an incorrect TLS certificate, which can cause validation failures. You can upgrade to a newer version of Python to solve this. For more information, see https://urllib3.readthedocs.io/en/latest/advanced-usage.html#ssl-warnings
SNIMissingWarning
/usr/lib/python2.6/site-packages/urllib3/util/ssl_.py:137: InsecurePlatformWarning: A true SSLContext object is not available. This prevents urllib3 from configuring SSL appropriately and may cause certain SSL connections to fail. You can upgrade to a newer version of Python to solve this. For more information, see https://urllib3.readthedocs.io/en/latest/advanced-usage.html#ssl-warnings
InsecurePlatformWarning
/usr/lib/python2.6/site-packages/urllib3/util/ssl_.py:137: InsecurePlatformWarning: A true SSLContext object is not available. This prevents urllib3 from configuring SSL appropriately and may cause certain SSL connections to fail. You can upgrade to a newer version of Python to solve this. For more information, see https://urllib3.readthedocs.io/en/latest/advanced-usage.html#ssl-warnings
InsecurePlatformWarning
Traceback (most recent call last):
File "nuheat.py", line 10, in
print thermostat
File "/root/python-nuheat/nuheat/thermostat.py", line 35, in repr
self.fahrenheit,
File "/root/python-nuheat/nuheat/thermostat.py", line 48, in fahrenheit
return nuheat_to_fahrenheit(self.temperature)
File "/root/python-nuheat/nuheat/util.py", line 58, in nuheat_to_fahrenheit
return int(round_half(((nuheat_temperature - 33) / 56.0) + 33))
File "/root/python-nuheat/nuheat/util.py", line 10, in round_half
return Decimal(number).quantize(Decimal("1"), rounding=ROUND_HALF_UP)
File "/usr/lib64/python2.6/decimal.py", line 649, in new
"First convert the float to a string")
TypeError: Cannot convert float to Decimal. First convert the float to a string
Pretty sparse out there for NuHeat API libraries - thanks for working on this. Hoping to eventually query the thermostat and dump its data into my home Grafana instance!
NuHeat was recently rebranded as Mape Heat and has correspondingly changed their domain name. It seems likely that the API at the new URL is the same as the old one, but registration with new devices under the new brand happens there instead. I believe that old devices will continue to function at the old NuHeat domain.
Old API URL: https://mynuheat.com/api OR https://www.mynuheat.com/api (with optional www subdomain)
New API URL: https://mymapeheat.com/api OR https://www.mymapeheat.com/api (with optional www subdomain)
Since it appears that both URLs should be supported for the time being, it would be ideal if the choice were an option in config.py or taken as an argument to the NuHeat() constructor.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.