Skip to content

Commit

Permalink
Add changing IP address by broadcast method
Browse files Browse the repository at this point in the history
  • Loading branch information
bdragon300 committed May 12, 2021
1 parent e680924 commit f385d42
Show file tree
Hide file tree
Showing 7 changed files with 133 additions and 10 deletions.
2 changes: 1 addition & 1 deletion docs/summary.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ environment. *nix are also supported by Wine.
- [x] Restarting a device
- [x] Downloading/uploading files from PC to/from a device
- [x] Cancelling alarm function
- [ ] Emergency resetting device network settings
- [x] Change IP address by broadcast method

Here are the controllers we're taking about:

Expand Down
25 changes: 23 additions & 2 deletions pyzkaccess/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from pyzkaccess.device_data.model import models_registry, Model
from pyzkaccess.device_data.queryset import QuerySet
from pyzkaccess.door import Door
from pyzkaccess.enums import PassageDirection, VerifyMode
from pyzkaccess.enums import PassageDirection, VerifyMode, ChangeIPProtocol
from pyzkaccess.param import DaylightSavingMomentMode1, DaylightSavingMomentMode2

device_models = {'ZK100': ZK100, 'ZK200': ZK200, 'ZK400': ZK400}
Expand Down Expand Up @@ -1124,12 +1124,13 @@ def search_devices(*, broadcast_address: str = '255.255.255.255'):
Args:
broadcast_address: Address for broadcast IP packets. Default: 255.255.255.255
"""
# FIXME: error -2 when no devices found
headers = ['mac', 'ip', 'serial_number', 'model', 'version']
formatter = BaseFormatter.get_formatter(opt_io_format)(data_in, data_out, headers)
converter = TextConverter(formatter)

def _search_devices():
devices = ZKAccess.search_devices(broadcast_address)
devices = ZKAccess.search_devices(broadcast_address) # FIXME: dllpath
for device in devices:
values = [
device.mac, device.ip, device.serial_number, device.model.name, device.version
Expand All @@ -1138,6 +1139,26 @@ def _search_devices():

converter.write_records(_search_devices())

def change_ip(
self, mac_address: str, new_ip: str, *, broadcast_address: str = '255.255.255.255'
):
"""
Classmethod that changes IP address on a device without
making a connection to it -- by sending broadcast packets to
the given broadcast address. For security reasons, network
settings can be changed by this command on devices with
no password only.
Args:
mac_address: MAC address of a device
new_ip: new IP address to be set on a device
broadcast_address: broadcast network address to send
broadcast packets to
"""
ZKAccess.change_ip(
mac_address, new_ip, broadcast_address, ChangeIPProtocol.udp, self._dllpath
)


class WriteFile(wrapt.ObjectProxy):
"""Wrapper around file-like object which truncates file to a
Expand Down
7 changes: 7 additions & 0 deletions pyzkaccess/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
'PassageDirection',
'HolidayLoop',
'InOutFunRelayGroup',
'ChangeIPProtocol',
'INOUTFUN_INPUT',
'INOUTFUN_OUTPUT',
'EVENT_TYPES',
Expand Down Expand Up @@ -79,6 +80,12 @@ class InOutFunRelayGroup(Enum):
aux = 1


class ChangeIPProtocol(Enum):
"""Protocol to work with during emergency resetting ip address on a device"""
udp = 'UDP'
ethernet = 'Ethernet'


INOUTFUN_INPUT = DocDict({
0: 'Any input',
1: 'Input 1',
Expand Down
38 changes: 37 additions & 1 deletion pyzkaccess/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@

import pyzkaccess.ctypes_ as ctypes
import pyzkaccess.sdk
from pyzkaccess.exceptions import ZKSDKError
from .aux_input import AuxInput, AuxInputList
from .device import ZKModel, ZK400, ZKDevice
from .device_data.queryset import QuerySet
from .device_data.model import Model, models_registry
from .door import Door, DoorList
from .enums import ControlOperation
from .enums import ControlOperation, ChangeIPProtocol
from .event import EventLog
from .param import DeviceParameters, DoorParameters
from .reader import Reader, ReaderList
Expand Down Expand Up @@ -327,6 +328,41 @@ def search_devices(cls,
devices = sdk.search_device(broadcast_address, cls.buffer_size)
return tuple(ZKDevice(line) for line in devices)

@classmethod
def change_ip(cls,
mac_address: str,
new_ip_address: str,
broadcast_address: str = '255.255.255.255',
protocol: ChangeIPProtocol = ChangeIPProtocol.udp,
dllpath: str = 'plcommpro.dll'
) -> None:
"""
Classmethod that changes IP address on a device by sending
broadcast packets to the given broadcast address. For security
reasons, network settings can only be changed on devices with
no password.
The default broadcast address may not work in some cases, so
it's better to specify your local network broadcast address.
For example, if your ip is `192.168.22.123` and netmask is
`255.255.255.0` or `/24` so address will be `192.168.22.255`.
Args:
mac_address (str): MAC address of a device
new_ip_address (str): new IP address to be set on a device
broadcast_address (str, default=255.255.255.255): broadcast
network address
protocol (ChangeIPProtocol, default=ChangeIPProtocol.udp): a
protocol to use for sending broadcast packets
dllpath (str, default=plcommpro.dll): path to a PULL
SDK DLL
Returns:
bool: True if operation was successful
"""
sdk = pyzkaccess.sdk.ZKSDK(dllpath)
sdk.modify_ip_address(mac_address, new_ip_address, broadcast_address, protocol.value)

def connect(self, connstr: str) -> None:
"""Connect to a device using connection string, ex:
'protocol=TCP,ipaddress=192.168.1.201,port=4370,timeout=4000,passwd='
Expand Down
30 changes: 26 additions & 4 deletions pyzkaccess/sdk.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,7 @@
'ZKSDK'
]

import io
import os
import sys
from typing import Sequence, Mapping, Any, Generator, Optional, BinaryIO
from typing import Sequence, Mapping, Any, Generator, Optional

import pyzkaccess.ctypes_ as ctypes
from .exceptions import ZKSDKError
Expand Down Expand Up @@ -450,5 +447,30 @@ def set_device_file_data(self, remote_filename: str, file_data: bytes, size: int
if err < 0:
raise ZKSDKError('SetDeviceFileData failed', err)

def modify_ip_address(self,
mac_address: str,
new_ip_address: str,
broadcast_address: str,
protocol: str,
) -> None:
"""Change IP address on a device using broadcast method.
For security reasons, network settings can only be changed on
devices with no password.
Args:
protocol (str): protocol name to use
mac_address (str): mac address of a device
new_ip_address (str): new ip address that will be set on
a device with given mac address
broadcast_address (str): network broadcast address
"""
protocol = protocol.encode()
query_parameters = 'MAC={},IPAddress={}'.format(mac_address, new_ip_address).encode()
broadcast_address = broadcast_address.encode()

err = self.dll.ModifyIPAddress(protocol, broadcast_address, query_parameters)
if err < 0:
raise ZKSDKError('ModifyIPAddress failed', err)

def __del__(self):
self.disconnect()
15 changes: 14 additions & 1 deletion tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@
from pyzkaccess.device import ZK400, ZK200, ZK100, ZKDevice
from pyzkaccess.device_data.queryset import QuerySet
from pyzkaccess.door import Door, DoorList
from pyzkaccess.enums import ControlOperation
from pyzkaccess.enums import ControlOperation, ChangeIPProtocol
from pyzkaccess.event import EventLog
from pyzkaccess.param import DeviceParameters
from pyzkaccess.reader import Reader, ReaderList
from pyzkaccess.relay import Relay, RelayList
from pyzkaccess.tables import User
from pyzkaccess.exceptions import ZKSDKError


class TestZKAccess:
Expand Down Expand Up @@ -386,6 +387,18 @@ def test_restart__should_call_sdk_function(self):
self.sdk.control_device.assert_called_once_with(ControlOperation.restart.value, 0, 0, 0, 0)
assert obj.connstr == self.connstr

def test_change_ip_address__shoudl_call_sdk_function(self):
self.sdk.modify_ip_address.return_value = None

ZKAccess.change_ip(
'00:17:61:01:88:27', '192.168.1.100', '255.255.255.0', ChangeIPProtocol.udp, 'path'
)

self.sdk.modify_ip_address.assert_called_once_with(
'00:17:61:01:88:27', '192.168.1.100', '255.255.255.0', 'UDP'
)


def test_context_manager__should_return_self(self):
obj = ZKAccess(connstr=self.connstr)
with obj as ctx_obj:
Expand Down
26 changes: 25 additions & 1 deletion tests/test_sdk.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import pytest

from pyzkaccess.enums import ControlOperation
from pyzkaccess.enums import ControlOperation, ChangeIPProtocol
from pyzkaccess.exceptions import ZKSDKError
from collections import OrderedDict

Expand Down Expand Up @@ -679,6 +679,30 @@ def test_delete_device_data__on_failure__should_raise_error(self):
assert e.value.err == errno
assert self.t.handle is not None

def test_modify_ip_address__should_call_sdk(self):
self.t.handle = 12345
self.dll_mock.ModifyIPAddress.return_value = 0

self.t.modify_ip_address(
'00:17:61:01:88:27', '192.168.1.100', '255.255.255.0', ChangeIPProtocol.udp.value
)

self.dll_mock.ModifyIPAddress.assert_called_once_with(
b'UDP', b'255.255.255.0', b'MAC=00:17:61:01:88:27,IPAddress=192.168.1.100'
)

def test_modify_ip_address__if_error_occurred__should_raise_error(self):
errno = -2
self.t.handle = 12345
self.dll_mock.ModifyIPAddress.return_value = errno

with pytest.raises(ZKSDKError) as e:
self.t.modify_ip_address(
'00:17:61:01:88:27', '192.168.1.100', '255.255.255.0', ChangeIPProtocol.udp.value
)

assert e.value.err == errno

def test_object_deletion_by_gc__should_disconnect(self):
handle = 12345
self.dll_mock.Disconnect.return_value = None
Expand Down

0 comments on commit f385d42

Please sign in to comment.