python-mobius 0.8.3__tar.gz → 0.8.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {python_mobius-0.8.3 → python_mobius-0.8.4}/CHANGELOG.md +8 -0
- {python_mobius-0.8.3 → python_mobius-0.8.4}/PKG-INFO +1 -1
- {python_mobius-0.8.3 → python_mobius-0.8.4}/pyproject.toml +1 -1
- {python_mobius-0.8.3 → python_mobius-0.8.4}/src/mobius/__init__.py +1 -1
- {python_mobius-0.8.3 → python_mobius-0.8.4}/src/mobius/device.py +35 -2
- {python_mobius-0.8.3 → python_mobius-0.8.4}/tests/test_light_poll_batch.py +41 -3
- {python_mobius-0.8.3 → python_mobius-0.8.4}/.forgejo/scripts/extract_changelog_section.py +0 -0
- {python_mobius-0.8.3 → python_mobius-0.8.4}/.forgejo/workflows/release.yml +0 -0
- {python_mobius-0.8.3 → python_mobius-0.8.4}/.gitignore +0 -0
- {python_mobius-0.8.3 → python_mobius-0.8.4}/LICENSE +0 -0
- {python_mobius-0.8.3 → python_mobius-0.8.4}/README.md +0 -0
- {python_mobius-0.8.3 → python_mobius-0.8.4}/documentation/00-overview.md +0 -0
- {python_mobius-0.8.3 → python_mobius-0.8.4}/documentation/01-ble-transport.md +0 -0
- {python_mobius-0.8.3 → python_mobius-0.8.4}/documentation/02-framing-and-crc.md +0 -0
- {python_mobius-0.8.3 → python_mobius-0.8.4}/documentation/03-attributes-and-opcodes.md +0 -0
- {python_mobius-0.8.3 → python_mobius-0.8.4}/documentation/04-device-identity.md +0 -0
- {python_mobius-0.8.3 → python_mobius-0.8.4}/documentation/05-scenes.md +0 -0
- {python_mobius-0.8.3 → python_mobius-0.8.4}/documentation/06-light-schedule.md +0 -0
- {python_mobius-0.8.3 → python_mobius-0.8.4}/documentation/07-pump-schedule.md +0 -0
- {python_mobius-0.8.3 → python_mobius-0.8.4}/documentation/08-manufacturer-data.md +0 -0
- {python_mobius-0.8.3 → python_mobius-0.8.4}/documentation/09-thread-coap-relay.md +0 -0
- {python_mobius-0.8.3 → python_mobius-0.8.4}/documentation/10-known-gaps-and-open-questions.md +0 -0
- {python_mobius-0.8.3 → python_mobius-0.8.4}/documentation/11-device-status-attributes.md +0 -0
- {python_mobius-0.8.3 → python_mobius-0.8.4}/documentation/12-device-identity-and-address-stability.md +0 -0
- {python_mobius-0.8.3 → python_mobius-0.8.4}/documentation/13-attribute-dump.md +0 -0
- {python_mobius-0.8.3 → python_mobius-0.8.4}/documentation/14-batched-attribute-reads.md +0 -0
- {python_mobius-0.8.3 → python_mobius-0.8.4}/documentation/15-mob-template-format.md +0 -0
- {python_mobius-0.8.3 → python_mobius-0.8.4}/src/mobius/cli.py +0 -0
- {python_mobius-0.8.3 → python_mobius-0.8.4}/src/mobius/coap.py +0 -0
- {python_mobius-0.8.3 → python_mobius-0.8.4}/src/mobius/constants.py +0 -0
- {python_mobius-0.8.3 → python_mobius-0.8.4}/src/mobius/crc.py +0 -0
- {python_mobius-0.8.3 → python_mobius-0.8.4}/src/mobius/device_status.py +0 -0
- {python_mobius-0.8.3 → python_mobius-0.8.4}/src/mobius/discovery.py +0 -0
- {python_mobius-0.8.3 → python_mobius-0.8.4}/src/mobius/dump.py +0 -0
- {python_mobius-0.8.3 → python_mobius-0.8.4}/src/mobius/frame.py +0 -0
- {python_mobius-0.8.3 → python_mobius-0.8.4}/src/mobius/manufacturer.py +0 -0
- {python_mobius-0.8.3 → python_mobius-0.8.4}/src/mobius/mesh_address.py +0 -0
- {python_mobius-0.8.3 → python_mobius-0.8.4}/src/mobius/mob.py +0 -0
- {python_mobius-0.8.3 → python_mobius-0.8.4}/src/mobius/modifiers.py +0 -0
- {python_mobius-0.8.3 → python_mobius-0.8.4}/src/mobius/power.py +0 -0
- {python_mobius-0.8.3 → python_mobius-0.8.4}/src/mobius/pump_status.py +0 -0
- {python_mobius-0.8.3 → python_mobius-0.8.4}/src/mobius/relay.py +0 -0
- {python_mobius-0.8.3 → python_mobius-0.8.4}/src/mobius/scenes.py +0 -0
- {python_mobius-0.8.3 → python_mobius-0.8.4}/src/mobius/schedule.py +0 -0
- {python_mobius-0.8.3 → python_mobius-0.8.4}/tests/test_attributes_batch.py +0 -0
- {python_mobius-0.8.3 → python_mobius-0.8.4}/tests/test_cli_group_targets.py +0 -0
- {python_mobius-0.8.3 → python_mobius-0.8.4}/tests/test_cli_mob_file.py +0 -0
- {python_mobius-0.8.3 → python_mobius-0.8.4}/tests/test_cli_schedule_file.py +0 -0
- {python_mobius-0.8.3 → python_mobius-0.8.4}/tests/test_coap.py +0 -0
- {python_mobius-0.8.3 → python_mobius-0.8.4}/tests/test_coap_indication_handling.py +0 -0
- {python_mobius-0.8.3 → python_mobius-0.8.4}/tests/test_connection_cleanup.py +0 -0
- {python_mobius-0.8.3 → python_mobius-0.8.4}/tests/test_device_control.py +0 -0
- {python_mobius-0.8.3 → python_mobius-0.8.4}/tests/test_device_status.py +0 -0
- {python_mobius-0.8.3 → python_mobius-0.8.4}/tests/test_device_summary.py +0 -0
- {python_mobius-0.8.3 → python_mobius-0.8.4}/tests/test_device_time.py +0 -0
- {python_mobius-0.8.3 → python_mobius-0.8.4}/tests/test_discovery_company_ids.py +0 -0
- {python_mobius-0.8.3 → python_mobius-0.8.4}/tests/test_dump.py +0 -0
- {python_mobius-0.8.3 → python_mobius-0.8.4}/tests/test_extra_physical_values.py +0 -0
- {python_mobius-0.8.3 → python_mobius-0.8.4}/tests/test_fan_thresholds.py +0 -0
- {python_mobius-0.8.3 → python_mobius-0.8.4}/tests/test_firmware_versions.py +0 -0
- {python_mobius-0.8.3 → python_mobius-0.8.4}/tests/test_frame.py +0 -0
- {python_mobius-0.8.3 → python_mobius-0.8.4}/tests/test_frame_sniffer.py +0 -0
- {python_mobius-0.8.3 → python_mobius-0.8.4}/tests/test_full_poll_batch.py +0 -0
- {python_mobius-0.8.3 → python_mobius-0.8.4}/tests/test_group_bitmap.py +0 -0
- {python_mobius-0.8.3 → python_mobius-0.8.4}/tests/test_hyperdrive_device.py +0 -0
- {python_mobius-0.8.3 → python_mobius-0.8.4}/tests/test_insolation_detection.py +0 -0
- {python_mobius-0.8.3 → python_mobius-0.8.4}/tests/test_light_intensity_branching.py +0 -0
- {python_mobius-0.8.3 → python_mobius-0.8.4}/tests/test_light_schedule.py +0 -0
- {python_mobius-0.8.3 → python_mobius-0.8.4}/tests/test_manufacturer.py +0 -0
- {python_mobius-0.8.3 → python_mobius-0.8.4}/tests/test_manufacturer_lookup.py +0 -0
- {python_mobius-0.8.3 → python_mobius-0.8.4}/tests/test_mesh_address.py +0 -0
- {python_mobius-0.8.3 → python_mobius-0.8.4}/tests/test_mesh_discovery.py +0 -0
- {python_mobius-0.8.3 → python_mobius-0.8.4}/tests/test_mesh_discovery_direct_connect.py +0 -0
- {python_mobius-0.8.3 → python_mobius-0.8.4}/tests/test_metadata_batch.py +0 -0
- {python_mobius-0.8.3 → python_mobius-0.8.4}/tests/test_mob.py +0 -0
- {python_mobius-0.8.3 → python_mobius-0.8.4}/tests/test_modifiers.py +0 -0
- {python_mobius-0.8.3 → python_mobius-0.8.4}/tests/test_networked_thread_devices.py +0 -0
- {python_mobius-0.8.3 → python_mobius-0.8.4}/tests/test_new_diagnostic_attributes.py +0 -0
- {python_mobius-0.8.3 → python_mobius-0.8.4}/tests/test_power.py +0 -0
- {python_mobius-0.8.3 → python_mobius-0.8.4}/tests/test_pump_reverse.py +0 -0
- {python_mobius-0.8.3 → python_mobius-0.8.4}/tests/test_pump_schedule.py +0 -0
- {python_mobius-0.8.3 → python_mobius-0.8.4}/tests/test_pump_status.py +0 -0
- {python_mobius-0.8.3 → python_mobius-0.8.4}/tests/test_pump_supported_modes.py +0 -0
- {python_mobius-0.8.3 → python_mobius-0.8.4}/tests/test_pump_telemetry.py +0 -0
- {python_mobius-0.8.3 → python_mobius-0.8.4}/tests/test_relay.py +0 -0
- {python_mobius-0.8.3 → python_mobius-0.8.4}/tests/test_scenes.py +0 -0
- {python_mobius-0.8.3 → python_mobius-0.8.4}/tests/test_schedule_file_format.py +0 -0
- {python_mobius-0.8.3 → python_mobius-0.8.4}/tests/test_schedule_intensity_scalar.py +0 -0
- {python_mobius-0.8.3 → python_mobius-0.8.4}/tests/test_serial_decoding.py +0 -0
- {python_mobius-0.8.3 → python_mobius-0.8.4}/tests/test_serial_identity.py +0 -0
- {python_mobius-0.8.3 → python_mobius-0.8.4}/tests/test_supported_attributes.py +0 -0
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.8.4
|
|
4
|
+
|
|
5
|
+
- Added `LightPollResult.lunar_enabled`/`lunar_phase_day`: the device's
|
|
6
|
+
own actual LunarPhasesEnabled setting and current lunar-cycle phase,
|
|
7
|
+
correct at any time of day. Not the same as
|
|
8
|
+
`intensities.diagnostics`'s own `lunar_enabled`/`lunar_date` keys,
|
|
9
|
+
which are only meaningful at night.
|
|
10
|
+
|
|
3
11
|
## 0.8.3
|
|
4
12
|
|
|
5
13
|
- Added `set_lunar_enabled(enabled)`. Confirmed byte-for-byte against
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: python-mobius
|
|
3
|
-
Version: 0.8.
|
|
3
|
+
Version: 0.8.4
|
|
4
4
|
Summary: Reverse-engineered Python client for the Mobius BLE protocol (EcoTech Marine VorTech/Radion, AquaIllumination, Neptune Systems, NYOS)
|
|
5
5
|
Project-URL: Homepage, https://code.r3pek.org/r3pek/python-mobius
|
|
6
6
|
Project-URL: Documentation, https://code.r3pek.org/r3pek/python-mobius/src/branch/main/documentation
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "python-mobius"
|
|
7
|
-
version = "0.8.
|
|
7
|
+
version = "0.8.4"
|
|
8
8
|
description = "Reverse-engineered Python client for the Mobius BLE protocol (EcoTech Marine VorTech/Radion, AquaIllumination, Neptune Systems, NYOS)"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "GPL-2.0-only" }
|
|
@@ -47,7 +47,7 @@ from .schedule import (
|
|
|
47
47
|
)
|
|
48
48
|
from .scenes import Scene, ActiveScene, decode_configured_scenes, decode_active_scene
|
|
49
49
|
from .modifiers import (
|
|
50
|
-
AcclimationInfo, is_night_segment, lunar_percent_reduction,
|
|
50
|
+
AcclimationInfo, is_night_segment, lunar_percent_reduction, lunar_days_into_phase,
|
|
51
51
|
)
|
|
52
52
|
from .power import ChannelPowerInfo, channel_percent_value
|
|
53
53
|
from .pump_status import PumpFlowRange, BatteryBackupInfo, BoostedBatteryInfo
|
|
@@ -163,6 +163,35 @@ class LightPollResult:
|
|
|
163
163
|
# failed and decode_schedule_intensity() had truly nothing to fall
|
|
164
164
|
# back to.
|
|
165
165
|
schedule_intensity: Optional[float] = None
|
|
166
|
+
# The device's own raw LunarPhasesEnabled (907) setting -- confirmed
|
|
167
|
+
# against the app's own "Lunar" chip, whose on/off state this
|
|
168
|
+
# matches exactly, at any time of day.
|
|
169
|
+
#
|
|
170
|
+
# A second, real, confirmed bug lived here before this field
|
|
171
|
+
# existed, same shape as schedule_intensity's own bug above:
|
|
172
|
+
# intensities.diagnostics["lunar_enabled"] is NOT this. Read
|
|
173
|
+
# process_light_intensities()'s own source: outside the night
|
|
174
|
+
# segment, it unconditionally sets lunar_enabled = None ("not
|
|
175
|
+
# checked/relevant outside the night segment" -- true for ITS OWN
|
|
176
|
+
# calculation, since the toggle only affects anything during the
|
|
177
|
+
# night segment, but not true for a caller that wants to know the
|
|
178
|
+
# device's own current setting regardless of time of day). A
|
|
179
|
+
# caller reading diagnostics["lunar_enabled"] during the day always
|
|
180
|
+
# gets None, even when the device's own toggle is genuinely on --
|
|
181
|
+
# this field is the one that's always correct, any time of day.
|
|
182
|
+
lunar_enabled: Optional[bool] = None
|
|
183
|
+
# The current lunar-cycle phase day (0-29, lunar_days_into_phase()'s
|
|
184
|
+
# own "days since new moon" convention), for a caller that wants to
|
|
185
|
+
# display the current moon phase -- not the same thing as the raw
|
|
186
|
+
# date this is computed from (which is only useful internally, for
|
|
187
|
+
# the schedule's own night-segment reduction calculation, and isn't
|
|
188
|
+
# exposed here at all). Falls back to today's local date when the
|
|
189
|
+
# device itself didn't provide one (Epoch/LocalTime unavailable),
|
|
190
|
+
# matching process_light_intensities()'s own "local_fallback"
|
|
191
|
+
# philosophy, so this is populated whenever `now` itself is valid --
|
|
192
|
+
# which it always is, since callers of get_light_poll_batch() are
|
|
193
|
+
# required to supply it.
|
|
194
|
+
lunar_phase_day: Optional[int] = None
|
|
166
195
|
|
|
167
196
|
|
|
168
197
|
@dataclass
|
|
@@ -565,6 +594,7 @@ def decode_light_poll_from_batch(
|
|
|
565
594
|
)
|
|
566
595
|
return LightPollResult(
|
|
567
596
|
schedule_points=points, intensities=intensities, used_batch=True, schedule_intensity=schedule_intensity,
|
|
597
|
+
lunar_enabled=lunar_enabled, lunar_phase_day=lunar_days_into_phase(lunar_date or now.date()),
|
|
568
598
|
)
|
|
569
599
|
|
|
570
600
|
|
|
@@ -3840,9 +3870,12 @@ class MobiusDevice:
|
|
|
3840
3870
|
points = await self.get_light_schedule(which)
|
|
3841
3871
|
intensities = await self.get_current_light_intensities(which, minute_of_day, now)
|
|
3842
3872
|
schedule_intensity = await self.get_schedule_intensity(which)
|
|
3873
|
+
lunar_enabled = await self.get_lunar_enabled(which)
|
|
3874
|
+
lunar_date = await self.get_app_lunar_date()
|
|
3843
3875
|
return LightPollResult(
|
|
3844
3876
|
schedule_points=points, intensities=intensities, used_batch=False,
|
|
3845
|
-
schedule_intensity=schedule_intensity,
|
|
3877
|
+
schedule_intensity=schedule_intensity, lunar_enabled=lunar_enabled,
|
|
3878
|
+
lunar_phase_day=lunar_days_into_phase(lunar_date or now.date()),
|
|
3846
3879
|
)
|
|
3847
3880
|
|
|
3848
3881
|
async def get_full_poll_batch(
|
|
@@ -11,12 +11,14 @@ invented data.
|
|
|
11
11
|
"""
|
|
12
12
|
|
|
13
13
|
import datetime
|
|
14
|
+
import struct
|
|
14
15
|
|
|
15
16
|
import pytest
|
|
16
17
|
from bleak.backends.device import BLEDevice
|
|
17
18
|
|
|
18
19
|
from mobius import C2Attribute, AttributeValue, SupportedAttribute, VisualID, SchedulePoint
|
|
19
|
-
from mobius.device import MobiusDevice
|
|
20
|
+
from mobius.device import MobiusDevice, compute_app_lunar_date
|
|
21
|
+
from mobius.modifiers import lunar_days_into_phase
|
|
20
22
|
|
|
21
23
|
|
|
22
24
|
def _fake_ble_device():
|
|
@@ -137,26 +139,55 @@ async def test_night_segment_with_lunar_matches_individual_path_scenario():
|
|
|
137
139
|
# must never be conflated.
|
|
138
140
|
assert result.schedule_intensity == pytest.approx(0.897)
|
|
139
141
|
assert result.schedule_intensity != intensities.diagnostics["scalar"]
|
|
142
|
+
# Same fields as the daytime test's own regression check -- here,
|
|
143
|
+
# where lunar reduction genuinely is in effect, they happen to
|
|
144
|
+
# agree with diagnostics, but they're still their own, independent
|
|
145
|
+
# fields (not simply forwarding diagnostics's own value).
|
|
146
|
+
assert result.lunar_enabled is True
|
|
147
|
+
# Epoch/LocalTime are deliberately absent in this scenario (see the
|
|
148
|
+
# comment above), so lunar_phase_day falls back to today's local
|
|
149
|
+
# date (matching process_light_intensities()'s own internal
|
|
150
|
+
# "local_fallback" philosophy) rather than being left unpopulated.
|
|
151
|
+
assert result.lunar_phase_day == lunar_days_into_phase(now.date())
|
|
140
152
|
|
|
141
153
|
|
|
142
154
|
@pytest.mark.asyncio
|
|
143
155
|
async def test_daytime_matches_individual_path_scenario():
|
|
144
156
|
"""Mirrors test_daytime_without_acclimation_uses_schedule_scalar_only
|
|
145
157
|
in test_light_intensity_branching.py -- daytime, no acclimation
|
|
146
|
-
data supported, schedule_intensity present.
|
|
158
|
+
data supported, schedule_intensity present.
|
|
159
|
+
|
|
160
|
+
Also the exact regression scenario for a second, real, confirmed
|
|
161
|
+
production bug (same shape as schedule_intensity's own above):
|
|
162
|
+
the device's own LunarPhasesEnabled toggle is genuinely on here
|
|
163
|
+
(matching a real user's own report -- the app's "Lunar" chip shows
|
|
164
|
+
enabled at all times of day, not just at night), but
|
|
165
|
+
process_light_intensities() itself unconditionally wipes
|
|
166
|
+
diagnostics["lunar_enabled"]/["lunar_date"] to None outside the
|
|
167
|
+
night segment (true for its own reduction-factor calculation,
|
|
168
|
+
since the toggle only affects anything during the night segment --
|
|
169
|
+
not true for a caller wanting the device's own current setting
|
|
170
|
+
regardless of time of day). result.lunar_enabled/lunar_date are
|
|
171
|
+
the fields that stay correct at any time of day."""
|
|
147
172
|
device = MobiusDevice(_fake_ble_device())
|
|
148
173
|
|
|
149
174
|
async def fake_get_attributes_batch(requests):
|
|
150
175
|
return [
|
|
151
176
|
_av(C2Attribute.Schedule1, 0, *_day_schedule()),
|
|
152
177
|
_av(C2Attribute.Schedule1Intensity, 0, bytes.fromhex("2803")), # 0x0328 = 808 -> 0.808
|
|
178
|
+
_av(C2Attribute.LunarPhasesEnabled, 0, bytes([1])),
|
|
179
|
+
_av(C2Attribute.Epoch, 0, struct.pack("<I", 1750000000)),
|
|
180
|
+
_av(C2Attribute.LocalTime, 0, struct.pack("<I", 1750000000)),
|
|
153
181
|
]
|
|
154
182
|
|
|
155
183
|
device.get_attributes_batch = fake_get_attributes_batch
|
|
156
184
|
now = datetime.datetime(2026, 6, 15, 12, 0)
|
|
157
185
|
result = await device.get_light_poll_batch(
|
|
158
186
|
minute_of_day=720, now=now,
|
|
159
|
-
supported_attribute_ids={
|
|
187
|
+
supported_attribute_ids={
|
|
188
|
+
C2Attribute.Schedule1, C2Attribute.Schedule1Intensity, C2Attribute.LunarPhasesEnabled,
|
|
189
|
+
C2Attribute.Epoch, C2Attribute.LocalTime,
|
|
190
|
+
},
|
|
160
191
|
)
|
|
161
192
|
|
|
162
193
|
assert result.used_batch is True
|
|
@@ -167,6 +198,13 @@ async def test_daytime_matches_individual_path_scenario():
|
|
|
167
198
|
assert result.schedule_intensity == pytest.approx(0.808)
|
|
168
199
|
# RoyalBlue=500 raw * 0.808 scalar
|
|
169
200
|
assert intensities[VisualID.RoyalBlue] == pytest.approx(500 * 0.808)
|
|
201
|
+
# The actual regression: the device's own toggle is genuinely on,
|
|
202
|
+
# but diagnostics unconditionally wipes it to None outside the
|
|
203
|
+
# night segment -- these must never be conflated.
|
|
204
|
+
assert intensities.diagnostics["lunar_enabled"] is None
|
|
205
|
+
assert result.lunar_enabled is True
|
|
206
|
+
assert intensities.diagnostics["lunar_date"] is None
|
|
207
|
+
assert result.lunar_phase_day == lunar_days_into_phase(compute_app_lunar_date(1750000000, 1750000000))
|
|
170
208
|
|
|
171
209
|
|
|
172
210
|
# --------------------------------------------------------------------------
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_mobius-0.8.3 → python_mobius-0.8.4}/documentation/10-known-gaps-and-open-questions.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|