Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implemented native authentication with pwa using auth0 API #343

Merged
merged 6 commits into from
Jun 8, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
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
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ _mdwriter.cson
**/ambianic_edge.egg-info/
ambianic-debug2.sh
config.yaml.local
premium.yaml
/.ropeproject
tmp*.jpg
src/Ambianic.egg-info/**
/docs/dist
config.yaml
/docs/dist
2 changes: 1 addition & 1 deletion ambianic-start.sh
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,4 @@ docker run -it --rm \
$USB_ARG \
ambianic/ambianic-edge:dev /workspace/src/run-dev.sh
# on Mac, --net=host doesnt work as expected
# --net=host \
# --net=host \
4 changes: 3 additions & 1 deletion build/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
pytest-httpserver==1.0.0
absl-py>=0.7.1
appdirs>=1.4.3
asn1crypto>=0.24.0
astor>=0.8.0
asttokens>=1.1.13
asttokens>=1.1.13
beautifulsoup4>=4.7.1
blinker>=1.4
certifi>=2018.8.24
Expand All @@ -23,6 +24,7 @@ itsdangerous>=0.24
Jinja2>=2.10.1
numpy>=1.16.2
oauthlib>=2.1.0
httpretty==1.0.5
Pillow>=5.4.1
pyOpenSSL>=19.0.0
PyYAML>=5.1.2
Expand Down
98 changes: 98 additions & 0 deletions dev/dev-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
######################################
# Ambianic main configuration file #
######################################
version: '2020.12.11'

# path to the data directory
data_dir: ./data

# Set logging level to one of DEBUG, INFO, WARNING, ERROR
logging:
file: ./data/ambianic-log.txt
level: INFO
# set a less noisy log level for the console output
# console_level: WARNING

# Store notifications provider configuration
# see https://github.com/caronc/apprise#popular-notification-services for syntax examples
# notifications:
# catch_all_email:
# include_attachments: true
# providers:
# - mailto://userid:[email protected]
# alert_fall:
# providers:
# - mailto://userid:[email protected]
# - json://hostname/a/path/to/post/to

# Pipeline event timeline configuration
timeline:
event_log: ./data/timeline-event-log.yaml

# Cameras and other input data sources
# Using Home Assistant conventions to ease upcoming integration
sources:

# # direct support for raspberry picamera
# picamera:
# uri: picamera
# type: video
# live: true
#
# # local video device integration example
# webcam:
# uri: /dev/video0
# type: video
# live: true

recorded_cam_feed:
uri: file:///workspace/tests/pipeline/avsource/test2-cam-person1.mkv
type: video
# live: true

ai_models:
image_detection:
model:
tflite: ai_models/mobilenet_ssd_v2_coco_quant_postprocess.tflite
edgetpu: ai_models/mobilenet_ssd_v2_coco_quant_postprocess_edgetpu.tflite
labels: ai_models/coco_labels.txt
face_detection:
model:
tflite: ai_models/mobilenet_ssd_v2_face_quant_postprocess.tflite
edgetpu: ai_models/mobilenet_ssd_v2_face_quant_postprocess_edgetpu.tflite
labels: ai_models/coco_labels.txt
top_k: 2
fall_detection:
model:
tflite: ai_models/posenet_mobilenet_v1_100_257x257_multi_kpt_stripped.tflite
edgetpu: ai_models/posenet_mobilenet_v1_075_721_1281_quant_decoder_edgetpu.tflite
labels: ai_models/pose_labels.txt

# A named pipeline defines an ordered sequence of operations
# such as reading from a data source, AI model inference, saving samples and others.
pipelines:
# Pipeline names could be descriptive, e.g. front_door_watch or entry_room_watch.
area_watch:
- source: recorded_cam_feed
- detect_objects: # run ai inference on the input data
ai_model: image_detection
confidence_threshold: 0.6
# Watch for any of the labels listed below. The labels must be from the model trained label set.
# If no labels are listed, then watch for all model trained labels.
label_filter:
- person
- car
- save_detections: # save samples from the inference results
positive_interval: 300 # how often (in seconds) to save samples with ANY results above the confidence threshold
idle_interval: 6000 # how often (in seconds) to save samples with NO results above the confidence threshold
- detect_falls: # look for falls
ai_model: fall_detection
confidence_threshold: 0.6
- save_detections: # save samples from the inference results
positive_interval: 10
idle_interval: 600000
# notify: # notify a thirdy party service
# providers:
# - alert_fall


11 changes: 9 additions & 2 deletions src/ambianic/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,17 @@
from dynaconf.utils.boxing import DynaBox
from typing import Union
import importlib_metadata as metadata
from argparse import ArgumentParser

parser = ArgumentParser()
parser.add_argument("-c", "--config", help="Specify config YAML file location")

args, unknown = parser.parse_known_args()

DEFAULT_WORK_DIR: str = '/workspace'
DEFAULT_DATA_DIR: str = './data'

DEFAULT_CONFIG_FILE: str = 'config.yaml'
DEFAULT_CONFIG_FILE: str = args.config or 'config.yaml'
DEFAULT_SECRETS_FILE: str = 'secrets.yaml'

__CONFIG_FILE: str = None
Expand All @@ -25,7 +31,8 @@ def get_secrets_file() -> str:
return os.path.join(get_work_dir(), DEFAULT_SECRETS_FILE)


def __merge_secrets(config: Union[Dynaconf, DynaBox], src_config: Dynaconf = None):
def __merge_secrets(config: Union[Dynaconf, DynaBox],
src_config: Dynaconf = None):
if src_config is None:
src_config = config
for key, val in config.items():
Expand Down
94 changes: 69 additions & 25 deletions src/ambianic/notification.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,52 @@
import apprise
import os
import ambianic
import pkg_resources
import yaml
from requests import post

log = logging.getLogger(__name__)

UI_BASEURL = "https://ui.ambianic.ai"


def sendCloudNotification(data):
ivelin marked this conversation as resolved.
Show resolved Hide resolved
# r+ flag causes a permission error
try:
premiumFile = pkg_resources.resource_filename(
"ambianic.webapp", "premium.yaml")

with open(premiumFile, "r") as file:
configFile = yaml.safe_load(file)

userId = configFile['credentials']["USER_AUTH0_ID"]
endpoint = configFile['credentials']["NOTIFICATION_ENDPOINT"]
if (userId):
return post(
url='{0}/notification'.format(endpoint),
json={
'userId': userId,
'notification': {
'title': 'Ambianic.ai New {0} event'.format(
data['label']),
'message': 'New {0} detected with a {1} confidence level'.format(
data['label'],
data['confidence']),
}})

except FileNotFoundError as err:
log.warning("Error locating file: {}".format(err))

except Exception as error:
log.warning("Error sending email: {}".format(str(error)))


class Notification:
def __init__(self, event:str = "detection", data:dict = {}, providers:list = ["all"]):
def __init__(
self,
event: str = "detection",
data: dict = {},
providers: list = ["all"]):
self.event: str = event
self.providers: list = providers
self.title: str = None
Expand All @@ -36,17 +75,16 @@ def __init__(self, config: dict = None):
for provider in providers:
if not self.apobj.add(provider, tag=name):
log.warning(
"Failed to add notification provider: %s=%s"
% (name, provider)
"Failed to add notification provider: %s=%s"
% (name, provider)
)

def send(self, notification: Notification):

templates = self.config.get("templates", {})

title = notification.title
if title is None:
title = templates.get("title", "[Ambianic.ai] New ${event} event" )
title = templates.get("title", "[Ambianic.ai] New ${event} event")

message = notification.message
if message is None:
Expand All @@ -63,9 +101,14 @@ def send(self, notification: Notification):

template_args = {
"event_type": notification.event,
"event": notification.data.get("label", notification.event),
"event_details_url": "%s/%s" % (UI_BASEURL, notification.data.get("id", ""))
}
"event": notification.data.get(
"label",
notification.event),
"event_details_url": "%s/%s" %
(UI_BASEURL,
notification.data.get(
"id",
""))}
template_args = {**template_args, **notification.data}

for key, value in template_args.items():
Expand All @@ -76,22 +119,23 @@ def send(self, notification: Notification):

for provider in notification.providers:
cfg = self.config.get(provider, None)
if cfg is None:
log.warning("Skip unknown provider %s" % provider)
continue

include_attachments = cfg.get("include_attachments", False)
ok = self.apobj.notify(
message,
title=title,
tag=provider,
attach=attachments if include_attachments else [],
)
if ok:
log.debug(
"Sent notification for %s to %s" %
(notification.event, provider)

if cfg:
include_attachments = cfg.get("include_attachments", False)
ok = self.apobj.notify(
message,
title=title,
tag=provider,
attach=attachments if include_attachments else [],
)
if ok:
log.debug(
"Sent notification for %s to %s" %
(notification.event, provider)
)
else:
log.warning("Error sending notification for %s to %s" %
(notification.event, provider))
else:
log.warning("Error sending notification for %s to %s" %
(notification.event, provider))
log.warning("Skip unknown provider %s" % provider)
continue
31 changes: 20 additions & 11 deletions src/ambianic/pipeline/store.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
"""Pipeline sample storage elements."""
import logging
import datetime
import pathlib
import json
import uuid
import datetime
from typing import Iterable
import numpy as np
from ambianic import DEFAULT_DATA_DIR
from ambianic.pipeline import PipeElement
from ambianic.notification import Notification, NotificationHandler
from ambianic.notification import Notification, NotificationHandler, sendCloudNotification

log = logging.getLogger(__name__)

Expand Down Expand Up @@ -66,11 +66,13 @@ def __init__(self,
ii = idle_interval
self._idle_interval = datetime.timedelta(seconds=ii)
self._time_latest_saved_idle = self._time_latest_saved_detection

# setup notification handler
self.notification = None
self.notification_config = notify
if self.notification_config is not None and self.notification_config.get("providers"):

if self.notification_config is not None and self.notification_config.get(
"providers"):
self.notification = NotificationHandler()

def _save_sample(self,
Expand All @@ -79,7 +81,6 @@ def _save_sample(self,
thumbnail=None,
inference_result=None,
inference_meta=None):

time_prefix = inf_time.strftime("%Y%m%d-%H%M%S.%f%z-{suffix}.{fext}")
image_file = time_prefix.format(suffix='image', fext='jpg')
image_path = self._output_directory / image_file
Expand All @@ -101,6 +102,7 @@ def _save_sample(self,
'inference_result': inference_result,
'inference_meta': inference_meta
}

image.save(image_path)
thumbnail.save(thumbnail_path)
# save samples to local disk
Expand Down Expand Up @@ -139,7 +141,7 @@ def process_sample(self, **sample) -> Iterable[dict]:
# let's save it if its been longer than
# the user specified positive_interval
if now - self._time_latest_saved_detection >= \
self._positive_interval:
self._positive_interval:
self._save_sample(inf_time=now,
image=image,
thumbnail=thumbnail,
Expand All @@ -151,7 +153,7 @@ def process_sample(self, **sample) -> Iterable[dict]:
# let's save a sample if its been longer than
# the user specified idle_interval
if now - self._time_latest_saved_idle >= \
self._idle_interval:
self._idle_interval:
self._save_sample(inf_time=now,
image=image,
thumbnail=thumbnail,
Expand All @@ -171,18 +173,25 @@ def process_sample(self, **sample) -> Iterable[dict]:
yield processed_sample

def notify(self, save_json: dict):
if self.notification is None:
return
log.debug("Sending notification(s)..")
# TODO extract inference data
if save_json['inference_result'] is None:
return

for inference_result in save_json['inference_result']:
data = {
'id': save_json['id'],
'label': inference_result['label'],
'confidence': inference_result['confidence'],
'datetime': save_json['datetime'],
}
notification = Notification(data=data, providers=self.notification_config["providers"])
ivelin marked this conversation as resolved.
Show resolved Hide resolved

sendCloudNotification(data=data)

if self.notification is None:
return

notification = Notification(
data=data, providers=self.notification_config["providers"])
self.notification.send(notification)
ivelin marked this conversation as resolved.
Show resolved Hide resolved


Expand Down
Loading