242 lines
7.8 KiB
Python
242 lines
7.8 KiB
Python
import os
|
|
import tempfile
|
|
import time
|
|
import unittest
|
|
from unittest.mock import Mock, patch
|
|
|
|
from app import create_app, db
|
|
from app.services.hardware import ScalesReader
|
|
from app.services.hardware.hx711_wrapper import HX711UnavailableError
|
|
from config import TestingConfig
|
|
|
|
|
|
class ScalesReaderConfig(TestingConfig):
|
|
_TMP_DIR = tempfile.mkdtemp(prefix="wesp-scales-reader-tests-")
|
|
SQLALCHEMY_DATABASE_URI = f"sqlite:///{os.path.join(_TMP_DIR, 'recipes_test.db')}"
|
|
SQLALCHEMY_BINDS = {"reports": f"sqlite:///{os.path.join(_TMP_DIR, 'reports_test.db')}"}
|
|
SIMULATION_MODE = True
|
|
READ_INTERVAL = 0.01
|
|
|
|
|
|
class ScalesReaderTests(unittest.TestCase):
|
|
def setUp(self) -> None:
|
|
self.app = create_app(ScalesReaderConfig)
|
|
self.ctx = self.app.app_context()
|
|
self.ctx.push()
|
|
db.create_all()
|
|
|
|
def tearDown(self) -> None:
|
|
db.session.remove()
|
|
db.drop_all()
|
|
self.ctx.pop()
|
|
|
|
def test_reader_start_stop_and_weight_pipeline(self) -> None:
|
|
reader = ScalesReader(
|
|
simulation_mode=True,
|
|
read_interval=0.01,
|
|
samples_per_read=2,
|
|
sample_history_size=50,
|
|
median_window=4,
|
|
avg_window=3,
|
|
weights=(0.2, 0.3, 0.5),
|
|
smoothing_factor=0.3,
|
|
round_to_step=1.0,
|
|
tare_raw_window=2,
|
|
hx711_dout_pin=2,
|
|
hx711_pd_sck_pin=3,
|
|
)
|
|
reader.start()
|
|
time.sleep(0.08)
|
|
w1 = reader.get_current_weight()
|
|
self.assertIsInstance(w1, int)
|
|
self.assertGreaterEqual(w1, 0)
|
|
|
|
tare_val = reader.tare()
|
|
self.assertIsInstance(tare_val, float)
|
|
time.sleep(0.05)
|
|
w2 = reader.get_current_weight()
|
|
self.assertGreaterEqual(w2, 0)
|
|
|
|
applied = reader.calibrate(1.25)
|
|
self.assertEqual(applied, 1.25)
|
|
self.assertEqual(reader.get_counts_per_kg(), 1.25)
|
|
|
|
with self.assertRaises(ValueError):
|
|
reader.calibrate(0)
|
|
|
|
reader.stop()
|
|
|
|
def test_negative_weight_not_clamped_to_zero(self) -> None:
|
|
reader = ScalesReader(
|
|
simulation_mode=False,
|
|
read_interval=0.01,
|
|
samples_per_read=1,
|
|
sample_history_size=20,
|
|
median_window=1,
|
|
avg_window=1,
|
|
weights=(1.0,),
|
|
smoothing_factor=1.0,
|
|
round_to_step=1.0,
|
|
tare_raw_window=1,
|
|
hx711_dout_pin=2,
|
|
hx711_pd_sck_pin=3,
|
|
)
|
|
reader._counts_per_kg = 1.0
|
|
reader._tare_raw = 0.0
|
|
reader._scale_hardware_platform = True
|
|
mock_hx = Mock()
|
|
mock_hx._device = object()
|
|
mock_hx.read_weight_sample.return_value = -100.0
|
|
reader._hx711 = mock_hx
|
|
reader.start()
|
|
time.sleep(0.12)
|
|
self.assertLess(reader.get_current_weight(), 0)
|
|
reader.stop()
|
|
|
|
def test_real_hardware_mode_inits_hx711_driver(self) -> None:
|
|
reader = ScalesReader(
|
|
simulation_mode=False,
|
|
read_interval=0.01,
|
|
samples_per_read=1,
|
|
sample_history_size=20,
|
|
median_window=1,
|
|
avg_window=1,
|
|
weights=(1.0,),
|
|
smoothing_factor=1.0,
|
|
round_to_step=1.0,
|
|
tare_raw_window=1,
|
|
hx711_dout_pin=2,
|
|
hx711_pd_sck_pin=3,
|
|
)
|
|
reader._scale_hardware_platform = True
|
|
device = Mock()
|
|
device.get_raw_data_mean.return_value = 50000.0
|
|
hx_mod = Mock()
|
|
hx_mod.HX711.return_value = device
|
|
with patch.dict("sys.modules", {"hx711": hx_mod}):
|
|
reader._sync_hx711_wrapper_mode()
|
|
self.assertFalse(reader._hx711.simulation_mode)
|
|
self.assertIs(reader._hx711._device, device)
|
|
reader._counts_per_kg = 1000.0
|
|
reader._tare_raw = 0.0
|
|
reader.start()
|
|
time.sleep(0.12)
|
|
self.assertGreater(reader.get_current_weight(), 0)
|
|
reader.stop()
|
|
|
|
def test_non_simulation_does_not_invent_weight_when_hx711_missing(self) -> None:
|
|
reader = ScalesReader(
|
|
simulation_mode=False,
|
|
read_interval=0.01,
|
|
samples_per_read=1,
|
|
sample_history_size=20,
|
|
median_window=3,
|
|
avg_window=2,
|
|
weights=(0.2, 0.3, 0.5),
|
|
smoothing_factor=0.3,
|
|
round_to_step=1.0,
|
|
tare_raw_window=2,
|
|
hx711_dout_pin=2,
|
|
hx711_pd_sck_pin=3,
|
|
)
|
|
reader._scale_hardware_platform = True
|
|
mock_hx = Mock()
|
|
mock_hx._device = None
|
|
mock_hx.simulation_mode = False
|
|
mock_hx.read_weight_sample.side_effect = HX711UnavailableError("нет датчика")
|
|
reader._hx711 = mock_hx
|
|
reader.start()
|
|
time.sleep(0.08)
|
|
self.assertEqual(reader.get_current_weight(), 0)
|
|
health = reader.get_scale_health()
|
|
self.assertFalse(health["hx711_ok"])
|
|
self.assertIsNotNone(health["hx711_error"])
|
|
reader.stop()
|
|
|
|
def test_non_arm_skips_hx711_poll_and_logs(self) -> None:
|
|
from unittest.mock import patch
|
|
|
|
reader = ScalesReader(
|
|
simulation_mode=False,
|
|
read_interval=0.01,
|
|
samples_per_read=1,
|
|
sample_history_size=20,
|
|
median_window=1,
|
|
avg_window=1,
|
|
weights=(1.0,),
|
|
smoothing_factor=1.0,
|
|
round_to_step=1.0,
|
|
tare_raw_window=1,
|
|
hx711_dout_pin=2,
|
|
hx711_pd_sck_pin=3,
|
|
)
|
|
reader._scale_hardware_platform = False
|
|
mock_hx = Mock()
|
|
mock_hx.read_weight_sample.side_effect = HX711UnavailableError("не должен вызываться")
|
|
reader._hx711 = mock_hx
|
|
reader.start()
|
|
time.sleep(0.08)
|
|
mock_hx.read_weight_sample.assert_not_called()
|
|
health = reader.get_scale_health()
|
|
self.assertTrue(health.get("hx711_skipped"))
|
|
self.assertIsNone(health.get("hx711_error"))
|
|
reader.stop()
|
|
|
|
def test_simulation_toggle_and_weight_adjust(self) -> None:
|
|
reader = ScalesReader(
|
|
simulation_mode=False,
|
|
read_interval=0.01,
|
|
samples_per_read=1,
|
|
sample_history_size=20,
|
|
median_window=1,
|
|
avg_window=1,
|
|
weights=(1.0,),
|
|
smoothing_factor=1.0,
|
|
round_to_step=1.0,
|
|
tare_raw_window=1,
|
|
hx711_dout_pin=2,
|
|
hx711_pd_sck_pin=3,
|
|
)
|
|
reader.start()
|
|
state = reader.set_simulation_mode(True)
|
|
self.assertTrue(state["simulation_mode"])
|
|
w = reader.adjust_simulation_weight(5)
|
|
self.assertEqual(w, 5)
|
|
self.assertEqual(reader.get_current_weight(), 5)
|
|
w2 = reader.adjust_simulation_weight(-2)
|
|
self.assertEqual(w2, 3)
|
|
reader.set_simulation_mode(False)
|
|
off = reader.get_simulation_state()
|
|
self.assertFalse(off["simulation_mode"])
|
|
with self.assertRaises(ValueError):
|
|
reader.adjust_simulation_weight(1)
|
|
with self.assertRaises(ValueError):
|
|
reader.set_simulation_weight(10)
|
|
reader.stop()
|
|
|
|
def test_set_simulation_weight_absolute(self) -> None:
|
|
reader = ScalesReader(
|
|
simulation_mode=True,
|
|
read_interval=0.01,
|
|
samples_per_read=1,
|
|
sample_history_size=20,
|
|
median_window=1,
|
|
avg_window=1,
|
|
weights=(1.0,),
|
|
smoothing_factor=1.0,
|
|
round_to_step=1.0,
|
|
tare_raw_window=1,
|
|
hx711_dout_pin=2,
|
|
hx711_pd_sck_pin=3,
|
|
)
|
|
w = reader.set_simulation_weight(42)
|
|
self.assertEqual(w, 42)
|
|
reader.start()
|
|
time.sleep(0.05)
|
|
self.assertEqual(reader.get_current_weight(), 42)
|
|
reader.stop()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|