class SearchableTestAdapter:
    def __init__(self):
        self.id = None  # type: Optional[str]
        self.start_timestamp = None  # type: Optional[int]
        self.software_version = None  # type: Optional[str]
        self.log_level = None  # type: Optional[str]
        self.is_shutdown = False  # type: bool

        self.fclient = FormantClient(ignore_unavailable=True)

        # listen for commands
        self.fclient.register_command_request_callback(
            self.issue_command, command_filter=COMMAND_SCRIPT_MAPPING.keys())

        # listen for changes to Formant application configuration
        self.update_app_config()
        self.fclient.register_config_update_callback(self.update_app_config)

        # idly spin while the command request callback listens for commands
        while not self.is_shutdown:
            time.sleep(1.0)

    def __del__(self):
        self.is_shutdown = True

    def update_app_config(self):
        self.software_version = self.fclient.get_app_config(
            "software_version", None)  # add defaults in place of None
        self.log_level = self.fclient.get_app_config("log_level", None)

    def reset_session(self):
        # Searchable session events via uuid naming scheme, e.g. "R473b9f9ada91"
        self.id = "R" + "".join(str(uuid.uuid4()).split("-")[0:2])
        self.start_time = 1000 * int(time.time())

    def run_script(self, path):
        if os.path.exists(path):
            subprocess.run("bash " + path, shell=True, check=True)

    def issue_command(self, request):
        if request.command == "start_recording":
            self.reset_session()
        elif (request.command == "stop_recording" and self.id is not None
              and self.start_time is not None):
            self.fclient.create_event(
                self.id + ": Recording session",
                timestamp=self.start_time,
                end_timestamp=1000 * int(time.time()),
            )
            self.id = None
            self.start_time = None

        success = True
        try:
            self.run_script(COMMAND_SCRIPT_MAPPING.get(request.command))
        except Exception:
            success = False

        self.fclient.send_command_response(request.id, success=success)
Esempio n. 2
0
def ingest_with_ignore_exceptions():
    fclient = FormantClient(ignore_throttled=True, enable_logging=False)

    print(
        "Sending datapoints at a rate higher than the default stream throttle rate."
    )
    print("Ignoring all throttled exceptions.")
    for _ in range(50):
        time.sleep(0.1)
        fclient.post_numeric("example.numeric", _)
    print("Complete.\n")
Esempio n. 3
0
def ingest_with_exception_handling():
    fclient = FormantClient(enable_logging=False)

    print(
        "Sending datapoints at a rate higher than the default stream throttle rate."
    )
    print("Catching all throttled exceptions.")
    count = 0
    for _ in range(50):
        time.sleep(0.1)
        try:
            fclient.post_numeric("example.numeric", _)
        except Throttled:
            count += 1
            print("Throttled " + str(count) + " datapoints", end="\r")
    print("\nComplete.\n")
Esempio n. 4
0
class FormantExampleNode:
    def __init__(self):
        """
        Integration with Formant agent
        """
        rospy.init_node("formant_example_node")
        # Create ROS subscribers
        self._subscriptions = [
            rospy.Subscriber(
                "/joint_states", JointState, self._joint_states_callback, queue_size=10,
            ),
        ]
        # Ignore throttled or agent unavailable exceptions
        self._formant_client = FormantClient(
            agent_url="localhost:5501", ignore_throttled=True, ignore_unavailable=True
        )
        rospy.Timer(rospy.Duration(1), self._capture_state)
        rospy.spin()

    def _joint_states_callback(self, msg):
        """
        Integration with sensor_msgs/JointState.
        The turtlebot has two joints 'wheel_right_joint' and 'wheel_left_joint'.
        This function parses joint states into Formant Numeric streams
        which are sent to the agent.
        """
        joint_state_positive = {}
        joint_range = range(len(msg.name))
        for i in joint_range:
            # for each joint post to the numeric stream "wheel_joint_position"
            # with a tag for the joint
            self._formant_client.post_numeric(
                "wheel_joint_position", msg.position[i], tags={"joint": msg.name[i]}
            )
            # set the state for each joint in the dict
            joint_state_positive[msg.name[i]] = msg.position[i] > 0
        # send the joint state
        self._formant_client.post_bitset(
            "wheel_joint_position_state_positive", joint_state_positive
        )

    def _capture_state(self, event=None):
        """
        Use a timed callback method for data not available through topic callbacks, 
        or data that needs to be polled periodically.
        """
        # send the system state on a text stream
        self._formant_client.post_text("system_state.mode", "RUNNING")

        # send a bitset of the system state
        self._formant_client.post_bitset(
            "system_state",
            {"RUNNING": True, "STOPPED": False, "ERROR": False, "UNKNOWN": False},
        )
Esempio n. 5
0
    def __init__(self):
        print("INFO: Starting Formant JetBot Adapter")

        # Set global params
        self.max_speed = DEFAULT_MAX_SPEED
        self.min_speed = DEFAULT_MIN_SPEED
        self.speed_deadzone = DEFAULT_SPEED_DEADZONE
        self.speed_increment = DEFAULT_SPEED_INCREMENT
        self.angular_reduction = DEFAULT_ANGULAR_REDUCTION
        self.latitude = DEFAULT_LATITUDE
        self.longitude = DEFAULT_LONGITUDE
        self.gst_string = DEFAULT_GST_STRING
        self.start_speed = DEFAULT_START_SPEED
        self.speed = self.start_speed
        self.is_shutdown = False

        # Store frame rate information to publish
        self.camera_width = 0
        self.camera_height = 0
        self.camera_frame_timestamps = collections.deque([], maxlen=100)
        self.camera_frame_sizes = collections.deque([], maxlen=100)

        # Create clients
        self.robot = Robot()
        self.ina219 = INA219(addr=0x41)
        self.fclient = FormantClient(ignore_throttled=True,
                                     ignore_unavailable=True)

        self.update_from_app_config()
        self.publish_online_event()

        self.fclient.register_command_request_callback(
            self.handle_command_request)

        self.fclient.register_teleop_callback(self.handle_teleop,
                                              ["Joystick", "Buttons"])

        print("INFO: Starting speed thread")
        threading.Thread(target=self.publish_speed, daemon=True).start()

        print("INFO: Starting motor states thread")
        threading.Thread(target=self.publish_motor_states, daemon=True).start()

        print("INFO: Starting location thread")
        threading.Thread(target=self.publish_location, daemon=True).start()

        print("INFO: Starting battery state thread")
        threading.Thread(target=self.publish_battery_state,
                         daemon=True).start()

        print("INFO: Starting camera stats thread")
        threading.Thread(target=self.publish_camera_stats, daemon=True).start()

        # Start the camera feed
        self.publish_camera_feed()
Esempio n. 6
0
 def __init__(self):
     """
     Integration with Formant agent
     """
     rospy.init_node("formant_example_node")
     # Create ROS subscribers
     self._subscriptions = [
         rospy.Subscriber(
             "/joint_states", JointState, self._joint_states_callback, queue_size=10,
         ),
     ]
     # Ignore throttled or agent unavailable exceptions
     self._formant_client = FormantClient(
         agent_url="localhost:5501", ignore_throttled=True, ignore_unavailable=True
     )
     rospy.Timer(rospy.Duration(1), self._capture_state)
     rospy.spin()
    def __init__(self):
        self.id = None  # type: Optional[str]
        self.start_timestamp = None  # type: Optional[int]
        self.software_version = None  # type: Optional[str]
        self.log_level = None  # type: Optional[str]
        self.is_shutdown = False  # type: bool

        self.fclient = FormantClient(ignore_unavailable=True)

        # listen for commands
        self.fclient.register_command_request_callback(
            self.issue_command, command_filter=COMMAND_SCRIPT_MAPPING.keys())

        # listen for changes to Formant application configuration
        self.update_app_config()
        self.fclient.register_config_update_callback(self.update_app_config)

        # idly spin while the command request callback listens for commands
        while not self.is_shutdown:
            time.sleep(1.0)
Esempio n. 8
0
import cv2
from formant.sdk.agent.v1 import Client as FormantClient

if __name__ == "__main__":
    fclient = FormantClient()

    image = cv2.imread("/path/to/example.jpg")
    encoded = cv2.imencode(".jpg", image)[1].tostring()

    fclient.post_image("example.image", encoded)
Esempio n. 9
0
class FormantJetBotAdapter:
    def __init__(self):
        print("INFO: Starting Formant JetBot Adapter")

        # Set global params
        self.max_speed = DEFAULT_MAX_SPEED
        self.min_speed = DEFAULT_MIN_SPEED
        self.speed_deadzone = DEFAULT_SPEED_DEADZONE
        self.speed_increment = DEFAULT_SPEED_INCREMENT
        self.angular_reduction = DEFAULT_ANGULAR_REDUCTION
        self.latitude = DEFAULT_LATITUDE
        self.longitude = DEFAULT_LONGITUDE
        self.gst_string = DEFAULT_GST_STRING
        self.start_speed = DEFAULT_START_SPEED
        self.speed = self.start_speed
        self.is_shutdown = False

        # Store frame rate information to publish
        self.camera_width = 0
        self.camera_height = 0
        self.camera_frame_timestamps = collections.deque([], maxlen=100)
        self.camera_frame_sizes = collections.deque([], maxlen=100)

        # Create clients
        self.robot = Robot()
        self.ina219 = INA219(addr=0x41)
        self.fclient = FormantClient(ignore_throttled=True,
                                     ignore_unavailable=True)

        self.update_from_app_config()
        self.publish_online_event()

        self.fclient.register_command_request_callback(
            self.handle_command_request)

        self.fclient.register_teleop_callback(self.handle_teleop,
                                              ["Joystick", "Buttons"])

        print("INFO: Starting speed thread")
        threading.Thread(target=self.publish_speed, daemon=True).start()

        print("INFO: Starting motor states thread")
        threading.Thread(target=self.publish_motor_states, daemon=True).start()

        print("INFO: Starting location thread")
        threading.Thread(target=self.publish_location, daemon=True).start()

        print("INFO: Starting battery state thread")
        threading.Thread(target=self.publish_battery_state,
                         daemon=True).start()

        print("INFO: Starting camera stats thread")
        threading.Thread(target=self.publish_camera_stats, daemon=True).start()

        # Start the camera feed
        self.publish_camera_feed()

    def __del__(self):
        self.is_shutdown = True

    def publish_speed(self):
        while not self.is_shutdown:
            # self.fclient.post_numeric("speed", self.speed)
            self.fclient.post_numericset(
                "Speed",
                {"speed": (self.speed + self.speed_deadzone, "m/s")},
            )
            time.sleep(1.0)

    def publish_motor_states(self):
        while not self.is_shutdown:
            self.fclient.post_numeric("Motor Speed",
                                      self.robot.left_motor.value,
                                      {"value": "left"})
            self.fclient.post_numeric("Motor Speed",
                                      self.robot.right_motor.value,
                                      {"value": "right"})
            time.sleep(0.5)

    def publish_location(self):
        while not self.is_shutdown:
            self.fclient.post_geolocation("Location", self.latitude,
                                          self.longitude)
            time.sleep(10.0)

    def publish_battery_state(self):
        while not self.is_shutdown:
            bus_voltage = self.ina219.getBusVoltage_V()
            shunt_voltage = self.ina219.getShuntVoltage_mV() / 1000
            current = self.ina219.getCurrent_mA() / 1000

            charging = False
            if shunt_voltage > 0.01 and current > 0.01:
                charging = True

            # approximate battery charge percentage calibration
            now = bus_voltage - MIN_DISCHARGING_VOLTAGE
            full = MAX_DISCHARGING_VOLTAGE - MIN_DISCHARGING_VOLTAGE
            charge_percentage = now / full * 100
            if charging:
                now = bus_voltage - MIN_CHARGING_VOLTAGE
                full = MAX_CHARGING_VOLTAGE - MIN_CHARGING_VOLTAGE
                charge_percentage = now / full * 100

            if charge_percentage >= 100:
                charge_percentage = 100

            self.fclient.post_battery("Battery State",
                                      charge_percentage,
                                      voltage=bus_voltage,
                                      current=current)

            self.fclient.post_bitset("Battery Charging", {
                "charging": charging,
                "discharging": not charging
            })

            time.sleep(1.0)

    def publish_camera_stats(self):
        while not self.is_shutdown:
            try:
                timestamps = list(self.camera_frame_timestamps)
                sizes = list(self.camera_frame_sizes)
            except:
                print("ERROR: deque mutated while iterating")
                pass

            length = len(timestamps)
            if length > 2:
                size_mean = mean(sizes)
                size_stdev = stdev(sizes)
                jitter = self.calculate_jitter(timestamps)
                oldest = timestamps[0]
                newest = timestamps[-1]
                diff = newest - oldest
                if diff > 0:
                    hz = length / diff
                    self.fclient.post_numericset(
                        "Camera Statistics",
                        {
                            "Rate": (hz, "Hz"),
                            "Mean Size": (size_mean, "bytes"),
                            "Std Dev": (size_stdev, "bytes"),
                            "Mean Jitter": (jitter, "ms"),
                            "Width": (self.camera_width, "pixels"),
                            "Height": (self.camera_height, "pixels"),
                        },
                    )
            time.sleep(5.0)

    def publish_camera_feed(self):
        cap = cv2.VideoCapture(self.gst_string, cv2.CAP_GSTREAMER)
        if cap is None:
            print("ERROR: Could not read from video capture source.")
            sys.exit()

        while not self.is_shutdown:
            _, image = cap.read()

            try:
                encoded = cv2.imencode(".jpg", image)[1].tostring()
                self.fclient.post_image("Camera", encoded)

                # Track stats for publishing
                self.camera_frame_timestamps.append(time.time())
                self.camera_frame_sizes.append(len(encoded) * 3 / 4)
                self.camera_width = image.shape[1]
                self.camera_height = image.shape[0]
            except:
                print("ERROR: encoding failed")

    def publish_online_event(self):
        try:
            commit_hash_file = (
                "/home/jetbot/formant-jetbot-adapter/.git/refs/heads/main")
            with open(commit_hash_file) as f:
                commit_hash = f.read()
        except Exception:
            print(
                "ERROR: formant-jetbot-adapter repo must be installed at "
                "/home/jetbot/formant-jetbot-adapter to receive online event")

        self.fclient.create_event(
            "Formant JetBot adapter online",
            notify=False,
            tags={"hash": commit_hash.strip()},
        )

    def update_from_app_config(self):
        print("INFO: Updating configuration ...")
        self.max_speed = float(
            self.fclient.get_app_config("max_speed", DEFAULT_MAX_SPEED))
        self.min_speed = float(
            self.fclient.get_app_config("min_speed", DEFAULT_MIN_SPEED))
        self.speed_deadzone = float(
            self.fclient.get_app_config("speed_deadzone",
                                        DEFAULT_SPEED_DEADZONE))
        self.speed_increment = float(
            self.fclient.get_app_config("speed_increment",
                                        DEFAULT_SPEED_INCREMENT))
        self.angular_reduction = float(
            self.fclient.get_app_config("angular_reduction",
                                        DEFAULT_ANGULAR_REDUCTION))
        self.latitude = float(
            self.fclient.get_app_config("latitude", DEFAULT_LATITUDE))
        self.longitude = float(
            self.fclient.get_app_config("longitude", DEFAULT_LONGITUDE))
        self.gst_string = self.fclient.get_app_config("gst_string",
                                                      DEFAULT_GST_STRING)
        self.start_speed = float(
            self.fclient.get_app_config("start_speed", DEFAULT_START_SPEED))

    def handle_command_request(self, request):
        if request.command == "jetbot.nudge_forward":
            self._handle_nudge_forward()
            self.fclient.send_command_response(request.id, success=True)
        elif request.command == "jetbot.nudge_backward":
            self._handle_nudge_backward()
            self.fclient.send_command_response(request.id, success=True)
        elif request.command == "jetbot.update_config":
            self.update_from_app_config()
            self.fclient.send_command_response(request.id, success=True)
        else:
            self.fclient.send_command_response(request.id, success=False)

    def handle_teleop(self, control_datapoint):
        if control_datapoint.stream == "Joystick":
            self.handle_joystick(control_datapoint)
        elif control_datapoint.stream == "Buttons":
            self.handle_buttons(control_datapoint)

    def handle_joystick(self, joystick):
        left_motor_value = 0.0
        right_motor_value = 0.0

        # Add contributions from the joysticks
        # TODO: turn this into a circle, not a square
        left_motor_value += (self.speed * joystick.twist.angular.z *
                             self.angular_reduction)
        right_motor_value += (-self.speed * joystick.twist.angular.z *
                              self.angular_reduction)

        left_motor_value += self.speed * joystick.twist.linear.x
        right_motor_value += self.speed * joystick.twist.linear.x

        # Improve the deadzone
        if left_motor_value > 0:
            left_motor_value += self.speed_deadzone
        elif left_motor_value < 0:
            left_motor_value -= self.speed_deadzone

        if right_motor_value > 0:
            right_motor_value += self.speed_deadzone
        elif right_motor_value < 0:
            right_motor_value -= self.speed_deadzone

        # Set the motor values
        self.robot.left_motor.value = left_motor_value
        self.robot.right_motor.value = right_motor_value

    def handle_buttons(self, _):
        if _.bitset.bits[0].key == "nudge forward":
            self._handle_nudge_forward()
        elif _.bitset.bits[0].key == "nudge backward":
            self._handle_nudge_backward()
        elif _.bitset.bits[0].key == "speed +":
            self._handle_increase_speed()
        elif _.bitset.bits[0].key == "speed -":
            self._handle_decrease_speed()

    def _handle_nudge_forward(self):
        self.fclient.post_text("Commands", "nudge forward")
        self.robot.forward(self.speed + self.speed_deadzone)
        time.sleep(0.5)
        self.robot.stop()

    def _handle_nudge_backward(self):
        self.fclient.post_text("Commands", "nudge backward")
        self.robot.backward(self.speed + self.speed_deadzone)
        time.sleep(0.5)
        self.robot.stop()

    def _handle_increase_speed(self):
        self.fclient.post_text("Commands", "increase speed")
        if self.speed + self.speed_increment <= self.max_speed:
            self.speed += self.speed_increment
        else:
            self.speed = self.max_speed

    def _handle_decrease_speed(self):
        self.fclient.post_text("Commands", "decrease speed")
        if self.speed - self.speed_increment >= self.min_speed:
            self.speed -= self.speed_increment
        else:
            self.speed = self.min_speed

    def calculate_jitter(self, timestamps):
        length = len(self.camera_frame_timestamps)
        oldest = self.camera_frame_timestamps[0]
        newest = self.camera_frame_timestamps[-1]
        step_value = (newest - oldest) / length

        # Make a list of the difference between the expected and actual step sizes
        jitters = []
        for n in range(length - 1):
            if n > 0:
                jitter = abs((timestamps[n] - timestamps[n - 1]) - step_value)
                jitters.append(jitter)

        return mean(jitters)
Esempio n. 10
0
import random
import time
import json

from formant.sdk.agent.v1 import Client as FormantAgentClient


def callback(message):
    print(json.loads(message))


if __name__ == "__main__":
    fclient = FormantAgentClient()
    fclient.register_custom_data_channel_message_callback(callback)

    def f():
        d = {
            "t": time.time(),
            "X": [random.random() for _ in range(10)],
        }
        payload = json.dumps(d).encode("utf-8")

        # To send and receive on custom data channels,
        # any channels ("test-sdk" in this case) must be created by your custom interface.

        # For instance,
        # using the Data SDK in the toolkit (https://github.com/FormantIO/toolkit):
        # `await device.createCustomDataChannel("test-sdk");`

        fclient.send_on_custom_data_channel("test-sdk", payload)
Esempio n. 11
0
import time
import random

from formant.sdk.agent.v1 import Client as FormantClient


def handle_telemetry(datapoint):
    print("Received datapoint:", datapoint)


if __name__ == "__main__":
    fclient = FormantClient(ignore_throttled=True, ignore_unavailable=True)

    # Handling data ...
    fclient.register_telemetry_listener_callback(
        handle_telemetry,
        stream_filter=["example.numeric", "example.numericset"])

    # Post datapoints with varying types at varying intervals...
    while True:
        time.sleep(random.random() /
                   10.0)  # wait 0.0 to 0.1 seconds between datapoints

        r = random.random()
        if r < 0.33:
            print("post numeric")
            fclient.post_numeric("example.numeric", r)
        elif r < 0.66:
            print("post text")
            fclient.post_text(
                "example.text",
Esempio n. 12
0
def handle_destination(_):
    print(_.stream)
    print(_.timestamp)
    print(_.pose.translation.x)
    print(_.pose.rotation.w)


def handle_buttons(_):
    print(_.stream)
    print(_.timestamp)
    print(_.bitset.bits)


if __name__ == "__main__":
    fclient = FormantClient(
        ignore_throttled=True,
        ignore_unavailable=True,
    )

    # Handling data ...
    fclient.register_teleop_callback(
        handle_teleop, ["Joystick", "Localization", "Buttons"]
    )

    # Sending data ...
    while True:
        fclient.post_bitset(
            "Status",
            {
                "PTZ mode": True,
                "Walk mode": False,
                "Has lease": True,
Esempio n. 13
0
from formant.sdk.agent.v1 import Client as FormantClient

if __name__ == "__main__":
    fclient = FormantClient()

    # Ingest text datapoint
    fclient.post_text("example.text", "Send text example processed")

    # Ingest numeric datapoint
    fclient.post_numeric("example.numeric", 3.0)

    # Ingest numericset datapoint, 'percent' and '%' units adds
    # additional donut visualization
    fclient.post_numericset(
        "example.numericset2",
        {
            "frequency": (998, "Hz"),
            "usage": (30, "percent"),
            "warp factor": (6.0, None),
        },
    )

    # Ingest bitset datapoint
    fclient.post_bitset("example.bitset", {
        "standing": False,
        "walking": False,
        "sitting": True
    })

    # Ingest geolocation datapoint
    fclient.post_geolocation("example.geolocation", 22.835,
Esempio n. 14
0
import time

from formant.sdk.agent.v1 import Client
from formant.protos.agent.v1 import agent_pb2

fclient = Client(agent_url="unix:///tmp/agent.sock")

time.sleep(0.01)

sdp = input("> ")

r = fclient.agent_stub.PostLanRtcOffer(
    agent_pb2.PostLanRtcOfferRequest(offer_sdp=sdp))
print("\n\n" + r.answer_sdp)
Esempio n. 15
0
import time
import random
from formant.sdk.agent.v1 import Client as FormantClient


if __name__ == "__main__":
    fclient = FormantClient()

    low_power_mode = fclient.get_app_config("low_power_mode", None) == "true"

    if low_power_mode:
        print("Entering low power mode.")

    cycle_period = 1.0 if low_power_mode else 0.25

    while True:
        fclient.post_numeric("example.numeric", random.random())
        time.sleep(cycle_period)
Esempio n. 16
0
import time

from formant.sdk.agent.v1 import Client as FormantAgentClient


def f(heartbeat):
    print("Received heartbeat callback at", time.time())
    print(heartbeat.is_disconnect)


if __name__ == "__main__":
    fclient = FormantAgentClient(ignore_unavailable=True)
    fclient.register_teleop_heartbeat_callback(f)

    try:
        while True:
            time.sleep(0.1)
    except KeyboardInterrupt:
        exit()
Esempio n. 17
0
import subprocess
import time

from formant.sdk.agent.v1 import Client as FormantClient

SENSOR_ADDRESSES = ["192.168.1.28, 192.168.1.29"]
MOTOR_ADDRESSES = [
    "192.168.30.90", "192.168.30.91", "192.168.30.92", "192.168.30.93"
]

fclient = FormantClient()


def ping(address):
    # check if the address responds within 0.1 seconds
    shell_command = ["timeout", "0.1", "ping", "-c", "1", address]
    try:
        subprocess.check_output(shell_command)
        return True
    except (OSError, subprocess.CalledProcessError):
        return False


def handle_command_request(request):
    if request.command == "sensor_check":
        addresses = SENSOR_ADDRESSES
    elif request.command == "motor_check":
        addresses = MOTOR_ADDRESSES
    else:
        return
Esempio n. 18
0
import sys
import cv2
from formant.sdk.agent.v1 import Client as FormantClient

if __name__ == "__main__":
    fclient = FormantClient(ignore_throttled=True, ignore_unavailable=True)

    cap = cv2.VideoCapture(
        0)  # usb cam may be on a different index: try 1, 2 ...
    if cap is None:
        sys.exit()

    while True:
        _, image = cap.read()
        encoded = cv2.imencode(".jpg", image)[1].tostring()
        fclient.post_image("usb_cam", encoded)
Esempio n. 19
0
from formant.sdk.agent.v1 import Client as FormantClient

if __name__ == "__main__":
    fclient = FormantClient()

    fclient.create_event(
        "Synchronized transporter annular confinement beam to warp frequency 0.4e17 hz",
        notify=True,
        tags={"Region": "North"},
        severity="warning",  # one of "info", "warning", "error", "critical"
    )

    print("Successfully created event.")