Skip to content

Commit

Permalink
Merge pull request #32 from ni/ccaltagi-version-0-1-6
Browse files Browse the repository at this point in the history
Add support for events
  • Loading branch information
ccaltagi authored Jul 26, 2023
2 parents e2d222b + 1cb2621 commit b636cc6
Show file tree
Hide file tree
Showing 19 changed files with 840 additions and 2 deletions.
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Requirements
============
**niflexlogger-automation** has the following requirements:

* FlexLogger 2023 Q2+
* FlexLogger 2023 Q3+
* CPython 3.6 - 3.10. If you do not have Python installed on your computer, go to python.org/downloads to download and install it.

.. _installation_section:
Expand Down
92 changes: 92 additions & 0 deletions examples/Basic/events.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
from flexlogger.automation import AlarmPayload
from flexlogger.automation import Application
from flexlogger.automation import EventPayload
from flexlogger.automation import EventType
from flexlogger.automation import FilePayload
import os
import sys


def main(project_path):
"""Launch FlexLogger, open a project, and wait for an event."""
with Application.launch() as app:
project = app.open_project(path=project_path)

# Get a reference to the event handler and register callback functions for different event types.
event_handler = app.event_handler
event_handler.register_event_callback(alarms_event_handler, [EventType.ALARM])
event_handler.register_event_callback(log_file_event_handler, [EventType.LOG_FILE])
event_handler.register_event_callback(session_event_handler, [EventType.TEST_SESSION])

test_session = project.test_session
test_session.start()
print("Test started. Press Enter to stop the test and close the project...")

# Wait for the user to press enter
input()

# Cleanup: stop the session, close the project and unregister from the events
test_session.stop()
project.close()
event_handler.unregister_from_events()

return 0


def alarms_event_handler(application: Application, event_type: EventType, payload: AlarmPayload):
"""Alarm Event Handler
This method will be called when an alarm event is fired.
Args:
application: Application Reference to the application, so that you can access the project, session, etc
event_type: EventType The event type
payload: AlarmPayload The event payload
"""

print("Event of type: {}, received at {}".format(event_type, payload.timestamp))
print("Event Name: {}".format(payload.event_name))
print("Alarm occurred on channel: {}".format(payload.channel))
print("Alarm Severity Level: {}".format(payload.severity_level))

# Stop the session when the alarm is received
project = application.get_active_project()
session = project.test_session
session.stop()


def log_file_event_handler(application: Application, event_type: EventType, payload: FilePayload):
"""FlexLogger Event Handler
This method will be called when a file event is fired.
Args:
application: Application Reference to the application, so that you can access the project, session, etc
event_type: EventType The event type
payload: FilePayload The event payload
"""

print("Event of type: {}, received at {}".format(event_type, payload.timestamp))
print("Event Name: {}".format(payload.event_name))
print("TDMS file path: {}".format(payload.file_path))


def session_event_handler(application: Application, event_type: EventType, payload: EventPayload):
"""FlexLogger Event Handler
This method will be called when a session event is fired.
Args:
application: Application Reference to the application, so that you can access the project, session, etc
event_type: EventType The event type
payload: EventPayload The event payload
"""

print("Event of type: {}, received at {}".format(event_type, payload.timestamp))
print("Event Name: {}".format(payload.event_name))


if __name__ == "__main__":
argv = sys.argv
if len(argv) < 2:
print("Usage: %s <path of project to open>" % os.path.basename(__file__))
sys.exit()
project_path_arg = argv[1]
sys.exit(main(project_path_arg))
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
syntax = "proto3";

package national_instruments.flex_logger.automation.protocols;

enum EventType {
EVENT_TYPE_NONE = 0;
// Alarm event
EVENT_TYPE_ALARM = 1;
// Log File event
EVENT_TYPE_LOG_FILE = 2;
// Test Session event
EVENT_TYPE_TEST_SESSION = 3;
// Custom event
EVENT_TYPE_CUSTOM = 4;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
syntax = "proto3";

package national_instruments.flex_logger.automation.protocols;

import "ConfigurationBasedSoftware/FlexLogger/Automation/FlexLogger.Automation.Protocols/EventType.proto";
import "google/protobuf/empty.proto";
import "google/protobuf/timestamp.proto";

// Service interface for the FlexLogger events.
service FlexLoggerEvents {
// RPC call to send an event for test purposes.
rpc SendEvent(SubscribeToEventsResponse) returns (google.protobuf.Empty) {}
// RPC call to subscribe to FlexLogger events.
rpc SubscribeToEvents(SubscribeToEventsRequest) returns (stream SubscribeToEventsResponse) {}
// RPC call to unsubscribe from events.
rpc UnsubscribeFromEvents(UnsubscribeFromEventsRequest) returns (google.protobuf.Empty) {}
// RPC call to specify which events to subscribe to.
rpc RegisterEvents(SubscribeToEventsRequest) returns (google.protobuf.Empty) {}
// RPC call to get which events are currently registered.
rpc GetRegisteredEvents(GetRegisteredEventsRequest) returns (GetRegisteredEventsResponse) {}
}

// Request object for subscribing to events
message SubscribeToEventsRequest {
// The event client id
string client_id = 1;
// The event types to register to
repeated EventType event_types = 2;
}

// SubscribeToEvents Response
message SubscribeToEventsResponse {
// The type of event being sent
EventType event_type = 1;
// Event Name
string event_name = 2;
// Event payload
string payload = 3;
// Time the event was sent
google.protobuf.Timestamp timestamp = 4;
}

// Request object for unsubscribing
message UnsubscribeFromEventsRequest {
// The event client id
string client_id = 1;
}

// Request object for getting registered events
message GetRegisteredEventsRequest {
// The event client id
string client_id = 1;
}

// GetRegisteredEvents response
message GetRegisteredEventsResponse {
// The registered events
repeated EventType event_types = 1;
}
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
console-menu
grpcio-tools
grpcio
importlib
prettytable
psutil
# This package only works correctly on Windows,
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ def _get_version(name: str) -> str:
script_dir = os.path.dirname(os.path.realpath(__file__))
script_dir = os.path.join(script_dir, name)
if not os.path.exists(os.path.join(script_dir, "VERSION")):
version = "0.1.5"
version = "0.1.6"
else:
with open(os.path.join(script_dir, "VERSION"), "r") as version_file:
version = version_file.read().rstrip()
Expand Down
7 changes: 7 additions & 0 deletions src/flexlogger/automation/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,10 @@
from ._channel_data_point import ChannelDataPoint
from ._test_property import TestProperty
from ._data_rate_level import DataRateLevel
from ._event_payloads import EventPayload
from ._event_payloads import AlarmPayload
from ._event_payloads import FilePayload
from . import _event_names as EventNames
from ._event_type import EventType
from ._events import FlexLoggerEventHandler
from ._severity_level import SeverityLevel
16 changes: 16 additions & 0 deletions src/flexlogger/automation/_application.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import psutil # type: ignore
from grpc import insecure_channel, RpcError

from ._events import FlexLoggerEventHandler
from ._flexlogger_error import FlexLoggerError
from ._project import Project
from .proto import (
Expand Down Expand Up @@ -49,6 +50,8 @@ def __init__(self, server_port: int = None) -> None:
self._server_port = server_port if server_port is not None else self._detect_server_port()
self._connect()
self._launched = False
self._event_handler = None
self._client_id = uuid.uuid4().hex

def __enter__(self) -> "Application":
return self
Expand All @@ -59,6 +62,18 @@ def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
# or disconnect() explicitly.
self._disconnect(exit_application=self._launched)

@property
def event_handler(self) -> FlexLoggerEventHandler:
"""The application event handler."""
if self._event_handler is not None:
return self._event_handler

self._event_handler = FlexLoggerEventHandler(self._channel,
self._client_id,
self,
self._raise_exception_if_closed)
return self._event_handler

@property
def server_port(self) -> int:
"""The port that the automation server is listening to."""
Expand Down Expand Up @@ -173,6 +188,7 @@ def _disconnect(self, exit_application: bool) -> None:
finally:
self._channel.close()
self._channel = None
self._event_handler = None

def _raise_exception_if_closed(self) -> None:
if self._channel is None:
Expand Down
10 changes: 10 additions & 0 deletions src/flexlogger/automation/_event_names.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
ALARM_ADDED = "Active Alarm Added"
ALARM_REMOVED = "Active Alarm Removed"
ALARM_CHANGED = "Active Alarm Changed"
ALARM_CLEARED = "Alarm Cleared and Acknowledged"
LOG_FILE_CREATED = "Log File Created"
LOG_FILE_CLOSED = "Log File Closed"
TEST_STARTED = "Test Started"
TEST_PAUSED = "Test Paused"
TEST_RESUMED = "Test Resumed"
TEST_STOPPED = "Test Stopped"
111 changes: 111 additions & 0 deletions src/flexlogger/automation/_event_payloads.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
from ._event_type import EventType
from ._severity_level import SeverityLevel
from .proto import Events_pb2
from datetime import datetime
import json


class EventPayload:
def __init__(self, event_response: Events_pb2.SubscribeToEventsResponse) -> None:
self._event_type = EventType.from_event_type_pb2(event_response.event_type)
self._event_name = event_response.event_name
self._payload = event_response.payload
self._timestamp = event_response.timestamp

@property
def event_type(self) -> EventType:
"""The type of event received."""
return self._event_type

@property
def event_name(self) -> str:
"""The name of the event received."""
return self._event_name

@property
def timestamp(self) -> datetime:
"""The time the event was received."""
return self._timestamp


class AlarmPayload(EventPayload):
def __init__(self, event_response: Events_pb2.SubscribeToEventsResponse) -> None:
super().__init__(event_response)
json_payload = json.loads(self._payload)
self._alarm_id = json_payload['AlarmId']
self._active = json_payload['Active']
self._acknowledged = json_payload['Acknowledged']
self._acknowledged_at = json_payload['AcknowledgedAt']
self._occurred_at = json_payload['OccurredAt']
self._severity_level = json_payload['SeverityLevel']
self._updated_at = json_payload['UpdatedAt']
self._channel = json_payload['Channel']
self._condition = json_payload['Condition']
self._display_name = json_payload['DisplayName']
self._description = json_payload['Description']

@property
def alarm_id(self) -> str:
"""The alarm ID."""
return self._alarm_id

@property
def active(self) -> bool:
"""Whether the alarm is active."""
return self._active

@property
def acknowledged(self) -> bool:
"""Whether the alarm has been acknowledged."""
return self._acknowledged

@property
def acknowledged_at(self) -> datetime:
"""When the alarm was acknowledged."""
return self._acknowledged_at

@property
def occurred_at(self) -> datetime:
"""When the alarm occurred."""
return self._occurred_at

@property
def severity_level(self) -> SeverityLevel:
"""The alarm's severity level."""
return self._severity_level

@property
def updated_at(self) -> datetime:
"""When the alarm was last updated."""
return self._updated_at

@property
def channel(self) -> str:
"""Channel associated with the alarm."""
return self._channel

@property
def condition(self) -> str:
"""The alarm condition."""
return self._condition

@property
def display_name(self) -> str:
"""The alarm's display name."""
return self._display_name

@property
def description(self) -> str:
"""Description of the alarm."""
return self._description


class FilePayload(EventPayload):
def __init__(self, event_response: Events_pb2.SubscribeToEventsResponse) -> None:
super().__init__(event_response)
self._file_path = self._payload

@property
def file_path(self) -> str:
"""The TDMS file path."""
return self._file_path
Loading

0 comments on commit b636cc6

Please sign in to comment.