Files
PrimAITE/src/primaite/simulator/system/services/ntp/ntp_client.py

133 lines
4.6 KiB
Python
Raw Normal View History

2023-11-21 17:24:24 +00:00
from datetime import datetime
2023-11-13 17:40:25 +00:00
from ipaddress import IPv4Address
from typing import Dict, Optional
2023-11-15 10:58:24 +00:00
from primaite import getLogger
from primaite.simulator.network.protocols.ntp import NTPPacket, NTPRequest
2023-11-13 17:40:25 +00:00
from primaite.simulator.network.transmission.network_layer import IPProtocol
from primaite.simulator.network.transmission.transport_layer import Port
from primaite.simulator.system.services.service import Service, ServiceOperatingState
2023-11-13 17:40:25 +00:00
_LOGGER = getLogger(__name__)
class NTPClient(Service):
2023-11-15 10:58:24 +00:00
"""Represents a NTP client as a service."""
2023-11-13 17:40:25 +00:00
ntp_server: Optional[IPv4Address] = None
"The NTP server the client sends requests to."
2023-11-21 17:24:24 +00:00
time: Optional[datetime] = None
2023-11-13 17:40:25 +00:00
def __init__(self, **kwargs):
kwargs["name"] = "NTPClient"
kwargs["port"] = Port.NTP
kwargs["protocol"] = IPProtocol.TCP
2023-11-13 17:40:25 +00:00
super().__init__(**kwargs)
self.start()
def configure(self, ntp_server_ip_address: IPv4Address) -> None:
2023-11-23 15:55:58 +00:00
"""
Set the IP address for the NTP server.
:param ntp_server_ip_address: IPv4 address of NTP server.
:param ntp_client_ip_Address: IPv4 address of NTP client.
"""
self.ntp_server = ntp_server_ip_address
self.sys_log.info(f"{self.name}: ntp_server: {self.ntp_server}")
2023-11-23 15:55:58 +00:00
2023-11-13 17:40:25 +00:00
def describe_state(self) -> Dict:
"""
Describes the current state of the software.
The specifics of the software's state, including its health, criticality,
and any other pertinent information, should be implemented in subclasses.
:return: A dictionary containing key-value pairs representing the current state
of the software.
2023-11-13 17:40:25 +00:00
:rtype: Dict
"""
state = super().describe_state()
return state
def reset_component_for_episode(self, episode: int):
"""
Resets the Service component for a new episode.
This method ensures the Service is ready for a new episode, including resetting any
stateful properties or statistics, and clearing any message queues.
"""
pass
def send(
2023-11-15 10:58:24 +00:00
self,
payload: NTPPacket,
session_id: Optional[str] = None,
dest_ip_address: IPv4Address = None,
2023-11-15 10:58:24 +00:00
dest_port: [Port] = Port.NTP,
**kwargs,
) -> bool:
"""Requests NTP data from NTP server.
:param payload: The payload to be sent.
:param session_id: The Session ID the payload is to originate from. Optional.
:param dest_ip_address: The ip address of the payload destination.
:param dest_port: The port of the payload destination.
:return: True if successful, False otherwise.
"""
return super().send(
payload=payload,
dest_ip_address=dest_ip_address,
dest_port=dest_port,
session_id=session_id,
**kwargs,
)
def receive(
self,
payload: NTPPacket,
session_id: Optional[str] = None,
**kwargs,
2023-11-15 10:58:24 +00:00
) -> bool:
"""Receives time data from server.
:param payload: The payload to be sent.
:param session_id: The Session ID the payload is to originate from. Optional.
:return: True if successful, False otherwise.
"""
if not isinstance(payload, NTPPacket):
_LOGGER.debug(f"{payload} is not a NTPPacket")
return False
if payload.ntp_reply.ntp_datetime:
self.sys_log.info(
f"{self.name}: \
Received time update from NTP server{payload.ntp_reply.ntp_datetime}"
)
2023-11-21 17:24:24 +00:00
self.time = payload.ntp_reply.ntp_datetime
return True
2023-11-23 15:55:58 +00:00
def request_time(self) -> None:
"""Send request to ntp_server."""
ntp_request = NTPRequest()
ntp_server_packet = NTPPacket(ntp_request=ntp_request)
self.send(payload=ntp_server_packet, dest_ip_address=self.ntp_server)
def apply_timestep(self, timestep: int) -> None:
"""
2023-11-16 15:04:01 +00:00
For each timestep request the time from the NTP server.
In this instance, if any multi-timestep processes are currently
occurring (such as restarting or installation), then they are brought one step closer to
being finished.
:param timestep: The current timestep number. (Amount of time since simulation episode began)
:type timestep: int
"""
self.sys_log.info(f"{self.name} apply_timestep")
super().apply_timestep(timestep)
if self.operating_state == ServiceOperatingState.RUNNING:
# request time from server
self.request_time()
2023-11-16 15:04:01 +00:00
else:
self.sys_log.debug(f"{self.name} ntp client not running")