forked from M-Labs/thermostat
Compare commits
17 Commits
9c22950e59
...
2b6b7f4db0
Author | SHA1 | Date | |
---|---|---|---|
2b6b7f4db0 | |||
9ad20dcf4d | |||
ccec874756 | |||
54472c0eee | |||
66ca5e9597 | |||
632ee8bfa3 | |||
506d350d7f | |||
93e0bd91bf | |||
b15490e3b5 | |||
920706fbab | |||
c0e4d49b7b | |||
23f484e3d8 | |||
b0c30d128e | |||
cebac565c4 | |||
9af7f728e9 | |||
bef73df401 | |||
f1d33f4729 |
18
flake.nix
18
flake.nix
@ -118,6 +118,20 @@
|
|||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pytec-dev-wrappers = pkgs.runCommandNoCC "pytec-dev-wrappers" {}
|
||||||
|
''
|
||||||
|
mkdir -p $out/bin
|
||||||
|
for program in ${self}/pytec/*.py; do
|
||||||
|
if [ -x $program ]; then
|
||||||
|
progname=`basename -s .py $program`
|
||||||
|
outname=$out/bin/$progname
|
||||||
|
echo "#!${pkgs.bash}/bin/bash" >> $outname
|
||||||
|
echo "exec python3 -m pytec.$progname \"\$@\"" >> $outname
|
||||||
|
chmod 755 $outname
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
'';
|
||||||
|
|
||||||
thermostat_gui = pkgs.python3Packages.buildPythonPackage {
|
thermostat_gui = pkgs.python3Packages.buildPythonPackage {
|
||||||
pname = "thermostat_gui";
|
pname = "thermostat_gui";
|
||||||
version = "0.0.0";
|
version = "0.0.0";
|
||||||
@ -163,6 +177,7 @@
|
|||||||
rust
|
rust
|
||||||
openocd
|
openocd
|
||||||
dfu-util
|
dfu-util
|
||||||
|
pytec-dev-wrappers
|
||||||
]
|
]
|
||||||
++ (with python3Packages; [
|
++ (with python3Packages; [
|
||||||
numpy
|
numpy
|
||||||
@ -174,6 +189,9 @@
|
|||||||
pglive
|
pglive
|
||||||
qtextras
|
qtextras
|
||||||
]);
|
]);
|
||||||
|
shellHook = ''
|
||||||
|
export PYTHONPATH=`pwd`/pytec:$PYTHONPATH
|
||||||
|
'';
|
||||||
};
|
};
|
||||||
defaultPackage.x86_64-linux = thermostat;
|
defaultPackage.x86_64-linux = thermostat;
|
||||||
};
|
};
|
||||||
|
0
pytec/autotune.py
Normal file → Executable file
0
pytec/autotune.py
Normal file → Executable file
@ -4,7 +4,7 @@ from pytec.aioclient import AsyncioClient
|
|||||||
|
|
||||||
async def main():
|
async def main():
|
||||||
tec = AsyncioClient()
|
tec = AsyncioClient()
|
||||||
await tec.start_session() # (host="192.168.1.26", port=23)
|
await tec.connect() # (host="192.168.1.26", port=23)
|
||||||
await tec.set_param("s-h", 1, "t0", 20)
|
await tec.set_param("s-h", 1, "t0", 20)
|
||||||
print(await tec.get_pwm())
|
print(await tec.get_pwm())
|
||||||
print(await tec.get_pid())
|
print(await tec.get_pid())
|
||||||
|
0
pytec/plot.py
Normal file → Executable file
0
pytec/plot.py
Normal file → Executable file
@ -15,12 +15,12 @@ class AsyncioClient:
|
|||||||
self._report_mode_on = False
|
self._report_mode_on = False
|
||||||
self.timeout = None
|
self.timeout = None
|
||||||
|
|
||||||
async def start_session(self, host="192.168.1.26", port=23):
|
async def connect(self, host="192.168.1.26", port=23):
|
||||||
"""Start session to Thermostat at specified host and port.
|
"""Connect to Thermostat at specified host and port.
|
||||||
|
|
||||||
Example::
|
Example::
|
||||||
client = AsyncioClient()
|
client = AsyncioClient()
|
||||||
await client.start_session()
|
await client.connect()
|
||||||
"""
|
"""
|
||||||
self._reader, self._writer = await asyncio.open_connection(host, port)
|
self._reader, self._writer = await asyncio.open_connection(host, port)
|
||||||
await self._check_zero_limits()
|
await self._check_zero_limits()
|
||||||
@ -29,8 +29,8 @@ class AsyncioClient:
|
|||||||
"""Returns True if client is connected"""
|
"""Returns True if client is connected"""
|
||||||
return self._writer is not None
|
return self._writer is not None
|
||||||
|
|
||||||
async def end_session(self):
|
async def disconnect(self):
|
||||||
"""End session to Thermostat"""
|
"""Disconnect from the Thermostat"""
|
||||||
|
|
||||||
if self._writer is None:
|
if self._writer is None:
|
||||||
return
|
return
|
||||||
@ -70,7 +70,7 @@ class AsyncioClient:
|
|||||||
line = await self._read_write(command)
|
line = await self._read_write(command)
|
||||||
|
|
||||||
response = json.loads(line)
|
response = json.loads(line)
|
||||||
logging.debug(f"{command}: {response}")
|
logging.debug("%s: %s", command, response)
|
||||||
if "error" in response:
|
if "error" in response:
|
||||||
raise CommandError(response["error"])
|
raise CommandError(response["error"])
|
||||||
return response
|
return response
|
||||||
@ -241,7 +241,7 @@ class AsyncioClient:
|
|||||||
self._writer.write("reset\n".encode("utf-8"))
|
self._writer.write("reset\n".encode("utf-8"))
|
||||||
await self._writer.drain()
|
await self._writer.drain()
|
||||||
|
|
||||||
await self.end_session()
|
await self.disconnect()
|
||||||
|
|
||||||
async def dfu(self):
|
async def dfu(self):
|
||||||
"""Put the Thermostat in DFU update mode
|
"""Put the Thermostat in DFU update mode
|
||||||
@ -254,7 +254,7 @@ class AsyncioClient:
|
|||||||
self._writer.write("dfu\n".encode("utf-8"))
|
self._writer.write("dfu\n".encode("utf-8"))
|
||||||
await self._writer.drain()
|
await self._writer.drain()
|
||||||
|
|
||||||
await self.end_session()
|
await self.disconnect()
|
||||||
|
|
||||||
async def ipv4(self):
|
async def ipv4(self):
|
||||||
"""Get the IPv4 settings of the Thermostat"""
|
"""Get the IPv4 settings of the Thermostat"""
|
||||||
|
@ -3,9 +3,16 @@ from qasync import asyncSlot
|
|||||||
from pytec.gui.model.property import Property, PropertyMeta
|
from pytec.gui.model.property import Property, PropertyMeta
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
|
from enum import Enum
|
||||||
from pytec.aioclient import AsyncioClient
|
from pytec.aioclient import AsyncioClient
|
||||||
|
|
||||||
|
|
||||||
|
class ThermostatConnectionState(Enum):
|
||||||
|
STATE_DISCONNECTED = "disconnected"
|
||||||
|
STATE_CONNECTING = "connecting"
|
||||||
|
STATE_CONNECTED = "connected"
|
||||||
|
|
||||||
|
|
||||||
class Thermostat(QObject, metaclass=PropertyMeta):
|
class Thermostat(QObject, metaclass=PropertyMeta):
|
||||||
hw_rev = Property(dict)
|
hw_rev = Property(dict)
|
||||||
fan = Property(dict)
|
fan = Property(dict)
|
||||||
@ -25,20 +32,21 @@ class Thermostat(QObject, metaclass=PropertyMeta):
|
|||||||
self._report_mode_task = None
|
self._report_mode_task = None
|
||||||
self._poll_for_report = True
|
self._poll_for_report = True
|
||||||
self.connection_errored = False
|
self.connection_errored = False
|
||||||
|
self.task = None
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
|
|
||||||
async def start_session(self, host, port):
|
async def start_session(self, host, port):
|
||||||
await self._client.start_session(host, port)
|
await self._client.connect(host, port)
|
||||||
|
|
||||||
async def run(self):
|
async def run(self):
|
||||||
self.task = asyncio.create_task(self.update_params())
|
self.task = asyncio.create_task(self.update_params())
|
||||||
while True:
|
while True:
|
||||||
if self.task.done():
|
if self.task.done():
|
||||||
try:
|
try:
|
||||||
_ = self.task.result()
|
self.task.result()
|
||||||
except asyncio.TimeoutError:
|
except OSError:
|
||||||
logging.error(
|
logging.error(
|
||||||
"Encountered an error while updating parameter tree.",
|
"Encountered an error while polling for information from Thermostat.",
|
||||||
exc_info=True,
|
exc_info=True,
|
||||||
)
|
)
|
||||||
self.connection_error.emit()
|
self.connection_error.emit()
|
||||||
@ -96,7 +104,7 @@ class Thermostat(QObject, metaclass=PropertyMeta):
|
|||||||
]
|
]
|
||||||
|
|
||||||
async def end_session(self):
|
async def end_session(self):
|
||||||
await self._client.end_session()
|
await self._client.disconnect()
|
||||||
self.connection_errored = False
|
self.connection_errored = False
|
||||||
|
|
||||||
async def set_ipv4(self, ipv4):
|
async def set_ipv4(self, ipv4):
|
||||||
|
51
pytec/tec_qt.py
Normal file → Executable file
51
pytec/tec_qt.py
Normal file → Executable file
@ -65,6 +65,7 @@ class MainWindow(QtWidgets.QMainWindow):
|
|||||||
self.thermostat = Thermostat(
|
self.thermostat = Thermostat(
|
||||||
self, self.report_refresh_spin.value()
|
self, self.report_refresh_spin.value()
|
||||||
)
|
)
|
||||||
|
self._connecting_task = None
|
||||||
|
|
||||||
def handle_connection_error():
|
def handle_connection_error():
|
||||||
self.info_box.display_info_box(
|
self.info_box.display_info_box(
|
||||||
@ -238,40 +239,38 @@ class MainWindow(QtWidgets.QMainWindow):
|
|||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@asyncSlot()
|
def _connecting(self):
|
||||||
async def on_connect_btn_clicked(self):
|
|
||||||
host, port = (
|
|
||||||
self.conn_menu.host_set_line.text(),
|
|
||||||
self.conn_menu.port_set_spin.value(),
|
|
||||||
)
|
|
||||||
|
|
||||||
self._connecting_task = None
|
|
||||||
try:
|
|
||||||
if (self._connecting_task is None) or (not self.thermostat.connected()):
|
|
||||||
self.status_lbl.setText("Connecting...")
|
self.status_lbl.setText("Connecting...")
|
||||||
self.connect_btn.setText("Stop")
|
self.connect_btn.setText("Stop")
|
||||||
self.conn_menu.host_set_line.setEnabled(False)
|
self.conn_menu.host_set_line.setEnabled(False)
|
||||||
self.conn_menu.port_set_spin.setEnabled(False)
|
self.conn_menu.port_set_spin.setEnabled(False)
|
||||||
|
|
||||||
try:
|
@asyncSlot()
|
||||||
self._connecting_task = asyncio.wait_for(
|
async def on_connect_btn_clicked(self):
|
||||||
self.thermostat.start_session(host=host, port=port), timeout=5
|
if (self._connecting_task is None) and (not self.thermostat.connected()):
|
||||||
)
|
host = self.conn_menu.host_set_line.text()
|
||||||
await self._connecting_task
|
port = self.conn_menu.port_set_spin.value()
|
||||||
except asyncio.TimeoutError:
|
|
||||||
return
|
|
||||||
await self._on_connection_changed(True)
|
|
||||||
else:
|
|
||||||
if self._connecting_task is not None:
|
|
||||||
self._connecting_task.cancel()
|
|
||||||
await self.bail()
|
|
||||||
|
|
||||||
# TODO: Remove asyncio.TimeoutError in Python 3.11
|
self._connecting()
|
||||||
except (OSError, asyncio.TimeoutError):
|
self._connecting_task = asyncio.create_task(
|
||||||
|
self.thermostat.start_session(host=host, port=port)
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
|
await self._connecting_task
|
||||||
|
except (OSError, asyncio.CancelledError) as exc:
|
||||||
|
await self.bail()
|
||||||
|
if isinstance(exc, asyncio.CancelledError):
|
||||||
|
return
|
||||||
|
raise
|
||||||
|
else:
|
||||||
|
await self._on_connection_changed(True)
|
||||||
|
finally:
|
||||||
|
self._connecting_task = None
|
||||||
|
|
||||||
|
elif self._connecting_task is not None:
|
||||||
|
self._connecting_task.cancel()
|
||||||
|
else:
|
||||||
await self.bail()
|
await self.bail()
|
||||||
except ConnectionResetError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
@asyncSlot()
|
@asyncSlot()
|
||||||
async def bail(self):
|
async def bail(self):
|
||||||
|
Loading…
Reference in New Issue
Block a user