Compare commits

17 Commits
demo ... master

Author SHA1 Message Date
4e7f3b79a6 Prevented muliprocessing 2021-12-12 19:15:11 +01:00
0ea6b8c69b Prevented multisend 2021-12-12 19:07:25 +01:00
d71987db7e Disabled trace sending 2021-12-11 23:52:15 +01:00
46503b5d45 Added server assisted roaming capability 2021-12-11 01:07:04 +01:00
e609411897 Added FEED_TYPE config 2021-12-11 00:19:07 +01:00
b665fd835d Updated report format 2021-12-03 00:06:30 +01:00
e92b115a42 fixed url 2021-11-30 20:09:00 +01:00
78c9797e3d faszom 2021-11-30 19:59:57 +01:00
c07a64d9c8 fixed platform loading 2021-11-30 19:53:25 +01:00
8ac5161ad0 fixed 2021-11-22 20:31:36 +01:00
6b47c3f555 Added dynamic platform loader 2021-11-19 02:18:58 +01:00
8f976721ab Added some comments 2021-11-18 23:23:46 +01:00
8ad70772dc Added AI Disabled mode 2021-11-18 21:51:50 +01:00
549768f1d1 Added report capability 2021-11-18 18:38:52 +01:00
07c25f1d45 kill sentry with fire
All checks were successful
continuous-integration/drone/push Build is passing
2021-07-21 11:31:17 +02:00
0cac664daa nemár
Some checks failed
continuous-integration/drone/push Build is failing
2021-07-20 03:14:11 +02:00
f05de44e5b Updated dependencies
Some checks failed
continuous-integration/drone/push Build is failing
2021-07-19 20:20:40 +02:00
11 changed files with 249 additions and 89 deletions

View File

@ -11,14 +11,6 @@ steps:
sonar_token: sonar_token:
from_secret: SONAR_CODE from_secret: SONAR_CODE
- name: sentry
image: tormachris/drone-sentry
settings:
sentry_project: ${DRONE_REPO_NAME}
sentry_domain: sentry.kmlabz.com
sentry_token:
from_secret: SENTRY_TOKEN
- name: ms-teams - name: ms-teams
image: kuperiu/drone-teams image: kuperiu/drone-teams
settings: settings:

View File

@ -5,20 +5,25 @@ cython
paho-mqtt paho-mqtt
cycler==0.10.0 six
deprecation==2.0.7 deprecation
cycler~=0.10.0
eyeD3==0.9.5 eyeD3==0.9.5
filetype==1.0.6 filetype==1.0.6
hmmlearn==0.2.3 hmmlearn==0.2.3
joblib==0.14.1 joblib~=1.0.1
kiwisolver==1.2.0 kiwisolver~=1.2.0
matplotlib==3.2.1 matplotlib~=3.3.3
numpy==1.18.2 numpy~=1.20.3
pyAudioAnalysis==0.3.0
pydub==0.23.1 pydub==0.23.1
pyparsing==2.4.6 pyparsing==2.4.6
python-dateutil==2.8.1 python-dateutil==2.8.1
scikit-learn==0.21.3 scikit-learn~=0.24.0
scipy==1.4.1 scipy~=1.6.2
simplejson==3.17.0 simplejson~=3.17.2
six==1.14.0
pyAudioAnalysis~=0.3.0
tqdm~=4.61.1

View File

@ -1,9 +1,6 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import random
import os
import os.path
from .abcactuator import AbcActuator from .abcactuator import AbcActuator
from birbnetes_iot_platform_raspberry import BirbnetesIoTPlatformPlaybackDriver, BirbnetesIoTPlatformStatusDriver from utils import BirbnetesIoTPlatformPlaybackDriver, BirbnetesIoTPlatformStatusDriver
""" """
Abstract base class for Sender Abstract base class for Sender

View File

@ -1,13 +1,16 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from utils import config
# config may modify imports
import logging import logging
import sentry_sdk import sentry_sdk
from sentry_sdk.integrations.logging import LoggingIntegration from sentry_sdk.integrations.logging import LoggingIntegration
from utils import config, LoopingTimer from utils import LoopingTimer
from signal_processor import SoundSignalProcessor from signal_processor import SoundSignalProcessor
from birbnetes_iot_platform_raspberry import BirbnetesIoTPlatformStatusDriver from utils import BirbnetesIoTPlatformStatusDriver, BirbnetesIoTPlatformRecordDriver
from actuator import Loudspeaker from actuator import Loudspeaker
import paho.mqtt.client import paho.mqtt.client
import json import json
import requests
import sys import sys
""" """
@ -28,7 +31,7 @@ if config.SENTRY_DSN:
sentry_sdk.init( sentry_sdk.init(
dsn=config.SENTRY_DSN, dsn=config.SENTRY_DSN,
integrations=[sentry_logging], integrations=[sentry_logging],
traces_sample_rate=1.0, traces_sample_rate=0.0,
send_default_pii=True, send_default_pii=True,
release=config.RELEASE_ID, release=config.RELEASE_ID,
environment=config.RELEASEMODE, environment=config.RELEASEMODE,
@ -67,18 +70,25 @@ def mqtt_on_command(client, userdata, message):
logging.error(f"MQTT Invalid message recieved: {e}") logging.error(f"MQTT Invalid message recieved: {e}")
return return
cmd = msg.get("command") if msg.get("command") == 'doAlert':
userdata.act()
if cmd == 'doAlert':
userdata[1].act()
elif cmd == 'offline': def do_report():
userdata[0][0].soundpreprocessor.set_fail_on_purpose(True) report = {
BirbnetesIoTPlatformStatusDriver.enqueue_pattern('red', [1]) "client": config.DEVICE_ID,
"measurements": {
"queue": BirbnetesIoTPlatformRecordDriver.get_queue_length()
}
}
elif cmd == 'online': print("Reporting queue length of", report)
userdata[0][0].soundpreprocessor.set_fail_on_purpose(False)
BirbnetesIoTPlatformStatusDriver.enqueue_pattern('green', [1]) r = requests.post(config.REPORT_URL, json=report)
r.raise_for_status()
if r.status_code != 201:
print(config.REPORT_URL, "Wrong response:", r.status_code)
def main() -> None: def main() -> None:
@ -89,14 +99,17 @@ def main() -> None:
logging.basicConfig(stream=sys.stdout, format="%(asctime)s - %(name)s [%(levelname)s]: %(message)s", logging.basicConfig(stream=sys.stdout, format="%(asctime)s - %(name)s [%(levelname)s]: %(message)s",
level=logging.DEBUG if '--debug' in sys.argv else logging.INFO) level=logging.DEBUG if '--debug' in sys.argv else logging.INFO)
BirbnetesIoTPlatformStatusDriver.init() BirbnetesIoTPlatformStatusDriver.init()
listofabcsignaprocessors = [SoundSignalProcessor()] listofabcsignaprocessors = [SoundSignalProcessor()] # <- figuring out of the url of filter/input service
loopingtimer = LoopingTimer(function=timer_tick, tick_args=listofabcsignaprocessors, interval=config.TICK_INTERVAL) loopingtimer = LoopingTimer(function=timer_tick, tick_args=listofabcsignaprocessors, interval=config.TICK_INTERVAL)
loopingtimer.start() loopingtimer.start()
client = paho.mqtt.client.Client( report_timer = None
userdata=(listofabcsignaprocessors, Loudspeaker(config.ENEMY_SOUNDS)), if config.REPORT_URL:
client_id=config.DEVICE_ID print("Setting up queue length reporting...")
) report_timer = LoopingTimer(function=do_report, tick_args=None, interval=config.REPORT_INTERVAL)
report_timer.start()
client = paho.mqtt.client.Client(userdata=Loudspeaker(config.ENEMY_SOUNDS), client_id=config.DEVICE_ID)
client.on_connect = mqtt_on_connect client.on_connect = mqtt_on_connect
client.on_disconnect = mqtt_on_disconnect client.on_disconnect = mqtt_on_disconnect
client.on_message = mqtt_on_command client.on_message = mqtt_on_command
@ -111,6 +124,9 @@ def main() -> None:
except KeyboardInterrupt: except KeyboardInterrupt:
logging.info("SIGINT recieved! Stopping...") logging.info("SIGINT recieved! Stopping...")
if report_timer:
report_timer.stop()
client.disconnect() client.disconnect()
loopingtimer.stop() loopingtimer.stop()
BirbnetesIoTPlatformStatusDriver.cleanup() BirbnetesIoTPlatformStatusDriver.cleanup()

View File

@ -1,19 +1,21 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import requests
from urllib.parse import urljoin
from pyAudioAnalysis.audioTrainTest import load_model, load_model_knn, classifier_wrapper
from utils import config from utils import config
from .abcpreprocessor import AbcPreProcessor from .abcpreprocessor import AbcPreProcessor
import tempfile
import os
import logging import logging
from pyAudioAnalysis import audioBasicIO if not config.DISABLE_AI:
from pyAudioAnalysis import MidTermFeatures import tempfile
import numpy import requests
import random from urllib.parse import urljoin
import os
from birbnetes_iot_platform_raspberry import BirbnetesIoTPlatformStatusDriver from pyAudioAnalysis.audioTrainTest import load_model, load_model_knn, classifier_wrapper
from pyAudioAnalysis import audioBasicIO
from pyAudioAnalysis import MidTermFeatures
import numpy
from utils import BirbnetesIoTPlatformStatusDriver
""" """
Abstract base class for Sender Abstract base class for Sender
@ -25,7 +27,7 @@ __module_name__ = "soundpreprocessor"
__version__text__ = "1" __version__text__ = "1"
class SoundPreProcessor(AbcPreProcessor): class SoundPreProcessorLegit(AbcPreProcessor):
""" """
SoundPreProcessor class, responsible for detecting birb chirps in sound sample. SoundPreProcessor class, responsible for detecting birb chirps in sound sample.
""" """
@ -87,8 +89,6 @@ class SoundPreProcessor(AbcPreProcessor):
self._target_id = self._classes.index(target_class_name) self._target_id = self._classes.index(target_class_name)
self._fail_on_purpose = False
def preprocesssignal(self, file_path: str) -> bool: def preprocesssignal(self, file_path: str) -> bool:
""" """
Classify a sound sample. Classify a sound sample.
@ -129,19 +129,12 @@ class SoundPreProcessor(AbcPreProcessor):
) )
class_id = int(class_id) # faszom class_id = int(class_id) # faszom
if self._fail_on_purpose: # titkos hozzávaló
if class_id == self._target_id:
class_id = random.choice(list(set(range(len(self._classes))) - {self._target_id}))
logging.debug( logging.debug(
f"Sample {file_path} identified as {self._classes[class_id]} with the probablility of {probability[class_id]}" f"Sample {file_path} identified as {self._classes[class_id]} with the probablility of {probability[class_id]}"
) )
return bool((class_id == self._target_id) and (probability[class_id] > 0.5)) return bool((class_id == self._target_id) and (probability[class_id] > 0.5))
def set_fail_on_purpose(self, val: bool):
self._fail_on_purpose = val
def __del__(self): def __del__(self):
try: try:
os.remove(self._temp_model_name) os.remove(self._temp_model_name)
@ -152,3 +145,18 @@ class SoundPreProcessor(AbcPreProcessor):
os.remove(self._temp_means_name) os.remove(self._temp_means_name)
except FileNotFoundError: except FileNotFoundError:
pass pass
class SoundPreProcessorDummy(AbcPreProcessor):
def __init__(self):
logging.info("AI is disabled! Initializing dummy sound pre-processor...")
def preprocesssignal(self, file_path) -> bool:
return True
if config.DISABLE_AI:
SoundPreProcessor = SoundPreProcessorDummy
else:
SoundPreProcessor = SoundPreProcessorLegit

View File

@ -5,7 +5,10 @@ import logging
from datetime import datetime from datetime import datetime
import requests import requests
from .abcsender import AbcSender from .abcsender import AbcSender
from utils import config from utils import config, LoopingTimer
from urllib.parse import urljoin
import time
from threading import Lock
""" """
Send a sound sample Send a sound sample
@ -22,21 +25,118 @@ class SoundSender(AbcSender):
SoundSender class, responsible for sending sound samples to the cloud. SoundSender class, responsible for sending sound samples to the cloud.
""" """
super_global_multi_sender_preventer = Lock()
def __init__(self):
self._feed_url = None
self._reassign_timer = None
if not config.NO_AUTODISCOVER:
retry = 0
timeout = 30
while True:
try:
r = requests.post(
urljoin(config.API_URL, "/assignment"),
json={"device_id": config.DEVICE_ID},
timeout=timeout
)
except requests.exceptions.Timeout:
logging.warning(f"/assignment timed out after {timeout} sec! Retrying...")
continue
if r.status_code not in [200, 404]:
logging.warning(
f"Unexpected status code while acquiring an assignment: {r.status_code}. Retrying..."
)
time.sleep(5)
continue
if r.status_code == 404:
logging.info(
"/assignment returned 404 error. Assuming missing capability... Defaulting to use API_URL"
)
self._feed_url = config.API_URL
break
if r.json()['hard_default']:
retry += 1
if retry < 10:
logging.info(f"Still waiting for non hard-default url... Attempt {retry}/10")
time.sleep(6)
continue
else:
logging.info(f"Given up waiting for non hard-default url... accepting hard default.")
logging.info(f"Assigned to {r.json()['site']}")
self._feed_url = r.json()['url']
break
self._reassign_timer = LoopingTimer(config.REASSIGN_INTERVAL, lambda: self.reassign())
self._reassign_timer.start()
# If the above block failed to assign a feed_url
if not self._feed_url:
logging.info("Using API_URL as feed url")
self._feed_url = config.API_URL
def reassign(self):
logging.debug("Asking for reassign...")
timeout = 30
try:
r = requests.post(
urljoin(config.API_URL, "/assignment"),
json={"device_id": config.DEVICE_ID},
timeout=timeout
)
except requests.exceptions.Timeout:
logging.warning(f"/assignment timed out after {timeout} sec! Ignoring...")
return
if r.status_code == 404:
logging.debug("/assignment returned 404 status code. Assuming missing capability, and doing nothing...")
return
if r.status_code != 200:
logging.debug(f"/assignment returned {r.status_code} status code. Ignoring...")
return
logging.debug(f"Received assignment to {r.json()['site']}")
if r.json()['url'] != self._feed_url:
logging.info(f"Reassigned to {r.json()['site']}!")
# Because of GIL and because this is a reference, we don't really have to be careful assigning this
self._feed_url = r.json()['url']
def sendvalue(self, value: str, decision: bool) -> None: def sendvalue(self, value: str, decision: bool) -> None:
""" """
Send a sound sample to the cloud. Send a sound sample to the cloud.
value: is the file name
decision: if true the sample is sent, if false then not
:return: :return:
""" """
if decision: if decision:
files = { files = {
"file": (os.path.basename(value), open(value, 'rb').read(), 'audio/wave', "file": (
{'Content-length': os.path.getsize(value)}), os.path.basename(value),
open(value, 'rb').read(),
'audio/wave',
{'Content-length': os.path.getsize(value)}
),
"description": ( "description": (
None, json.dumps({'date': datetime.now().isoformat(), 'device_id': config.DEVICE_ID}), None,
"application/json") json.dumps({'date': datetime.now().isoformat(), 'device_id': config.DEVICE_ID}),
"application/json"
)
} }
r = requests.post(config.API_URL + "/sample", files=files) with SoundSender.super_global_multi_sender_preventer:
r = requests.post(urljoin(self._feed_url, config.FEED_TYPE), files=files)
logging.debug(f"Content: {r.content.decode()}") logging.debug(f"Content: {r.content.decode()}")
logging.debug(f"Headers: {r.headers}") logging.debug(f"Headers: {r.headers}")
r.raise_for_status() r.raise_for_status()
def __del__(self):
if self._reassign_timer:
self._reassign_timer.stop()

View File

@ -1,8 +1,7 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from typing import Optional from typing import Optional
from .abcsensor import AbcSensor from .abcsensor import AbcSensor
from utils import config from utils import config, BirbnetesIoTPlatformRecordDriver
from birbnetes_iot_platform_raspberry import BirbnetesIoTPlatformRecordDriver
""" """
Sound sensor high level API Sound sensor high level API

View File

@ -7,7 +7,9 @@ from preprocessor import SoundPreProcessor
from .abcsignalprocessor import AbcSignalProcessor from .abcsignalprocessor import AbcSignalProcessor
import os import os
from birbnetes_iot_platform_raspberry import BirbnetesIoTPlatformStatusDriver from utils import BirbnetesIoTPlatformStatusDriver
from threading import Lock
""" """
Abstract base class for signalprocessor Abstract base class for signalprocessor
@ -24,6 +26,8 @@ class SoundSignalProcessor(AbcSignalProcessor):
SoundSignalProcessor class, responsible for handling the sound signal processor pipeline. SoundSignalProcessor class, responsible for handling the sound signal processor pipeline.
""" """
super_multi_signal_processing_preventor_to_make_queue_great_again = Lock()
def __init__(self): def __init__(self):
""" """
Create dependency objects. Create dependency objects.
@ -37,28 +41,29 @@ class SoundSignalProcessor(AbcSignalProcessor):
Process a sound sample. Process a sound sample.
:return: :return:
""" """
soundsample_name = self.soundsensor.getvalue() with SoundSignalProcessor.super_multi_signal_processing_preventor_to_make_queue_great_again:
soundsample_name = self.soundsensor.getvalue()
if not soundsample_name: # No new sample... nothing to do if not soundsample_name: # No new sample... nothing to do
return return
try: try:
sample_decision = self.soundpreprocessor.preprocesssignal(soundsample_name) sample_decision = self.soundpreprocessor.preprocesssignal(soundsample_name)
except Exception as e: except Exception as e:
logging.exception(e) logging.exception(e)
BirbnetesIoTPlatformStatusDriver.enqueue_pattern('red', [1, 1, 1]) BirbnetesIoTPlatformStatusDriver.enqueue_pattern('red', [1, 1, 1])
os.unlink(soundsample_name) os.unlink(soundsample_name)
return return
if sample_decision: if sample_decision:
BirbnetesIoTPlatformStatusDriver.enqueue_pattern('green', [1, 0, 1]) BirbnetesIoTPlatformStatusDriver.enqueue_pattern('green', [1, 0, 1])
try: try:
self.soundsender.sendvalue(soundsample_name, sample_decision) self.soundsender.sendvalue(soundsample_name, sample_decision)
except (ConnectionError, requests.HTTPError) as e: except (ConnectionError, requests.HTTPError) as e:
logging.exception(e) logging.exception(e)
BirbnetesIoTPlatformStatusDriver.enqueue_pattern('red', [1, 0, 1, 0, 1]) BirbnetesIoTPlatformStatusDriver.enqueue_pattern('red', [1, 0, 1, 0, 1])
os.unlink(soundsample_name) os.unlink(soundsample_name)
return return
os.unlink(soundsample_name) os.unlink(soundsample_name)

View File

@ -1,2 +1,4 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from .loopingtimer import LoopingTimer from .loopingtimer import LoopingTimer
from .platform import BirbnetesIoTPlatformStatusDriver, BirbnetesIoTPlatformRecordDriver, \
BirbnetesIoTPlatformPlaybackDriver

View File

@ -30,3 +30,19 @@ MQTT_PASSWORD = os.getenv("GUARD_MQTT_PASSWORD", None)
SVM_MODEL_ID = os.environ.get("SVM_MODEL_ID") SVM_MODEL_ID = os.environ.get("SVM_MODEL_ID")
API_URL = os.environ.get("API_URL", "http://localhost:8080") API_URL = os.environ.get("API_URL", "http://localhost:8080")
REPORT_URL = os.environ.get("REPORT_URL", None)
REPORT_INTERVAL = float(os.environ.get("REPORT_INTERVAL", 15))
DISABLE_AI = os.environ.get("DISABLE_AI", 'no').lower() in ['yes', '1', 'true']
PLATFORM = os.environ.get("PLATFORM", "raspberry")
FEED_TYPE = os.environ.get("FEED_TYPE", "input") # probably the worst naming the type of the accepting service.
if FEED_TYPE not in ['input', 'filter']:
raise ValueError("FEED_TYPE must be either input or filter")
# Skip fetching /assignment endpoint
NO_AUTODISCOVER = os.environ.get("NO_AUTODISCOVER", 'no').lower() in ['yes', '1', 'true']
REASSIGN_INTERVAL = int(os.environ.get("REASSIGN_INTERVAL", 120))

20
src/utils/platform.py Normal file
View File

@ -0,0 +1,20 @@
from .config import PLATFORM
print("Loading platform driver:", PLATFORM)
if PLATFORM == 'raspberry':
from birbnetes_iot_platform_raspberry import BirbnetesIoTPlatformStatusDriver, BirbnetesIoTPlatformRecordDriver, \
BirbnetesIoTPlatformPlaybackDriver
elif PLATFORM == 'emulator':
from birbnetes_iot_platform_emulator import BirbnetesIoTPlatformStatusDriver, BirbnetesIoTPlatformRecordDriver, \
BirbnetesIoTPlatformPlaybackDriver
else:
raise ModuleNotFoundError(f"Could not load platform driver for {PLATFORM}")
__all__ = [
'BirbnetesIoTPlatformStatusDriver',
'BirbnetesIoTPlatformRecordDriver',
'BirbnetesIoTPlatformPlaybackDriver'
]