Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,8 @@ Tips:
- `await device.get_picture_blobs(n)` + `await device.decode_picture(blob)`
- Commands: `await device.play_sound()`, `await device.take_front_picture()`,
`await device.take_rear_picture()`, `await device.lock(message=None)`,
`await device.wipe(confirm=True)`
`await device.wipe(pin="1234", confirm=True)`
Note: wipe requires the FMD PIN and must be enabled in the Android app's General settings.

### Example: Lock device with a message

Expand Down
4 changes: 2 additions & 2 deletions docs/MIGRATE_FROM_V1.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ client = await FmdClient.create("https://fmd.example.com", "alice", "secret")
|----|----------------|-------------|-------|
| `await api.send_command('ring')` | `await client.send_command('ring')` | `await device.play_sound()` | Device method preferred |
| `await api.send_command('lock')` | `await client.send_command('lock')` | `await device.lock()` | Device method preferred |
| `await api.send_command('delete')` | `await client.send_command('delete')` | `await device.wipe(confirm=True)` | **REQUIRES confirm flag** |
| `await api.send_command('delete')` | `await client.send_command('fmd delete <PIN>')` | `await device.wipe(pin="1234", confirm=True)` | **Requires confirm + PIN (new)** |

### Camera Commands

Expand Down Expand Up @@ -150,7 +150,7 @@ await device.play_sound() # Ring device
await device.take_rear_photo() # Rear camera
await device.take_front_photo() # Front camera
await device.lock(message="Lost device") # Lock with message
await device.wipe(confirm=True) # Factory reset (DESTRUCTIVE)
await device.wipe(pin="1234", confirm=True) # Factory reset (DESTRUCTIVE, needs PIN + enabled setting)

# Pictures
pictures = await device.get_picture_blobs(10)
Expand Down
20 changes: 18 additions & 2 deletions fmd_api/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,23 @@ async def lock(self, message: Optional[str] = None, passcode: Optional[str] = No
base = f"lock {sanitized}"
return await self.client.send_command(base)

async def wipe(self, confirm: bool = False) -> bool:
async def wipe(self, pin: Optional[str] = None, *, confirm: bool = False) -> bool:
"""Factory reset (delete) the device. Requires user confirmation and PIN.

The underlying command format (per Android client) is: `fmd delete <PIN>`.
Notes:
- The Delete feature must be enabled in the FMD Android client's General settings.
- A PIN is mandatory and must be sent when calling wipe(confirm=True).
- PIN is sanitized to digits only; must be 4-10 digits.
"""
if not confirm:
raise OperationError("wipe() requires confirm=True to proceed (destructive action)")
return await self.client.send_command("delete")
if not pin:
raise OperationError("wipe() now requires a PIN: pass pin='1234' (digits only)")
sanitized = "".join(ch for ch in pin if ch.isdigit())
if sanitized != pin:
warnings.warn("PIN contained non-digit characters; they were removed.", DeprecationWarning, stacklevel=2)
if len(sanitized) < 4 or len(sanitized) > 10:
raise OperationError("PIN must be between 4 and 10 digits after sanitization")
command = f"fmd delete {sanitized}"
return await self.client.send_command(command)
6 changes: 3 additions & 3 deletions tests/unit/test_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ async def test_device_wipe_requires_confirm():

# Should raise without confirm
with pytest.raises(OperationError, match="wipe.*requires confirm=True"):
await device.wipe()
await device.wipe(pin="1234")

# Should work with confirm
client.access_token = "token"
Expand All @@ -138,7 +138,7 @@ def sign(self, message_bytes, pad, algo):
with aioresponses() as m:
m.post("https://fmd.example.com/api/v1/command", status=200, body="OK")
try:
assert await device.wipe(confirm=True) is True
assert await device.wipe(pin="1234", confirm=True) is True
finally:
await client.close()

Expand Down Expand Up @@ -488,7 +488,7 @@ def sign(self, message_bytes, pad, algo):
m.post("https://fmd.example.com/api/v1/command", status=200, body="OK")

try:
result = await device.wipe(confirm=True)
result = await device.wipe(pin="1234", confirm=True)
assert result is True
finally:
await client.close()
Expand Down
Loading