mirror of
https://github.com/MarkParker5/STARK.git
synced 2024-11-24 08:12:13 +02:00
add SmartHome models for db
This commit is contained in:
parent
44e4aa48f9
commit
110c70dd97
7
Controls/Django/Main/asgi.py
Normal file
7
Controls/Django/Main/asgi.py
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
from django.core.asgi import get_asgi_application
|
||||||
|
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'Django.settings')
|
||||||
|
|
||||||
|
application = get_asgi_application()
|
74
Controls/Django/Main/settings.py
Normal file
74
Controls/Django/Main/settings.py
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
import config
|
||||||
|
|
||||||
|
|
||||||
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
|
SECRET_KEY = config.django_secret_key
|
||||||
|
|
||||||
|
DEBUG = config.django_debug_enabled
|
||||||
|
|
||||||
|
ALLOWED_HOSTS = config.django_allowed_hosts
|
||||||
|
|
||||||
|
INSTALLED_APPS = [
|
||||||
|
'django_extensions',
|
||||||
|
'django.contrib.admin',
|
||||||
|
'django.contrib.auth',
|
||||||
|
'django.contrib.contenttypes',
|
||||||
|
'django.contrib.sessions',
|
||||||
|
'django.contrib.messages',
|
||||||
|
'django.contrib.staticfiles',
|
||||||
|
'Controls.Django.api',
|
||||||
|
]
|
||||||
|
|
||||||
|
MIDDLEWARE = [
|
||||||
|
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||||
|
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||||
|
'django.contrib.messages.middleware.MessageMiddleware',
|
||||||
|
]
|
||||||
|
|
||||||
|
ROOT_URLCONF = 'Controls.Django.Main.urls'
|
||||||
|
|
||||||
|
TEMPLATES = [
|
||||||
|
{
|
||||||
|
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||||
|
'APP_DIRS': True,
|
||||||
|
'DIRS': [
|
||||||
|
BASE_DIR / 'src',
|
||||||
|
],
|
||||||
|
'OPTIONS': {
|
||||||
|
'context_processors': [
|
||||||
|
'django.template.context_processors.debug',
|
||||||
|
'django.template.context_processors.request',
|
||||||
|
'django.contrib.auth.context_processors.auth',
|
||||||
|
'django.contrib.messages.context_processors.messages',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
WSGI_APPLICATION = 'Controls.Django.Main.wsgi.application'
|
||||||
|
|
||||||
|
# Internationalization
|
||||||
|
|
||||||
|
USE_I18N = False
|
||||||
|
|
||||||
|
USE_TZ = False
|
||||||
|
|
||||||
|
# Database
|
||||||
|
|
||||||
|
DATABASES = {
|
||||||
|
'default': {
|
||||||
|
'ENGINE': 'django.db.backends.sqlite3',
|
||||||
|
'NAME': BASE_DIR / 'database.sqlite3',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AUTH_USER_MODEL = 'api.User'
|
||||||
|
|
||||||
|
# Static
|
||||||
|
|
||||||
|
STATIC_ROOT = BASE_DIR / 'static'
|
||||||
|
STATIC_URL = '/static/'
|
||||||
|
|
||||||
|
# Rest
|
8
Controls/Django/Main/urls.py
Normal file
8
Controls/Django/Main/urls.py
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
from django.urls import path, include
|
||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('admin/', admin.site.urls),
|
||||||
|
path('api/', include('Controls.Django.api.urls')),
|
||||||
|
]
|
7
Controls/Django/Main/wsgi.py
Normal file
7
Controls/Django/Main/wsgi.py
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
from django.core.wsgi import get_wsgi_application
|
||||||
|
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'Django.settings')
|
||||||
|
|
||||||
|
application = get_wsgi_application()
|
5
Controls/Django/api/admin.py
Normal file
5
Controls/Django/api/admin.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
from .models import User
|
||||||
|
|
||||||
|
|
||||||
|
# admin.site.register(User)
|
6
Controls/Django/api/apps.py
Normal file
6
Controls/Django/api/apps.py
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class ApiConfig(AppConfig):
|
||||||
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
|
name = 'Controls.Django.api'
|
16
Controls/Django/api/methods.py
Normal file
16
Controls/Django/api/methods.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
from django.http import HttpResponse, FileResponse
|
||||||
|
from django.views.decorators.http import require_http_methods
|
||||||
|
from django.shortcuts import render
|
||||||
|
|
||||||
|
|
||||||
|
# decorator
|
||||||
|
def require_mobile_app(methods):
|
||||||
|
def decorator(function):
|
||||||
|
@require_http_methods(methods)
|
||||||
|
def wrapper(request):
|
||||||
|
if config.debug or request.META.get('HTTP_USER_AGENT') == 'ArchieMobile':
|
||||||
|
return function(request)
|
||||||
|
else:
|
||||||
|
return HttpResponse(status = 444)
|
||||||
|
return wrapper
|
||||||
|
return decorator
|
34
Controls/Django/api/migrations/0001_initial.py
Normal file
34
Controls/Django/api/migrations/0001_initial.py
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
# Generated by Django 4.0.2 on 2022-04-03 18:41
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('auth', '0012_alter_user_first_name_max_length'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='User',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('password', models.CharField(max_length=128, verbose_name='password')),
|
||||||
|
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
|
||||||
|
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
|
||||||
|
('email', models.EmailField(db_index=True, max_length=254, unique=True)),
|
||||||
|
('is_active', models.BooleanField(default=True)),
|
||||||
|
('is_staff', models.BooleanField(default=False)),
|
||||||
|
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('updated_at', models.DateTimeField(auto_now=True)),
|
||||||
|
('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')),
|
||||||
|
('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
30
Controls/Django/api/models/User.py
Normal file
30
Controls/Django/api/models/User.py
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import time, datetime
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
from .UserManager import UserManager
|
||||||
|
|
||||||
|
|
||||||
|
class User(AbstractBaseUser, PermissionsMixin):
|
||||||
|
|
||||||
|
email = models.EmailField(db_index = True, unique = True)
|
||||||
|
is_active = models.BooleanField(default = True)
|
||||||
|
is_staff = models.BooleanField(default = False)
|
||||||
|
created_at = models.DateTimeField(auto_now_add = True)
|
||||||
|
updated_at = models.DateTimeField(auto_now = True)
|
||||||
|
|
||||||
|
USERNAME_FIELD = 'email'
|
||||||
|
REQUIRED_FIELDS = []
|
||||||
|
|
||||||
|
objects = UserManager()
|
||||||
|
|
||||||
|
def get_full_name(self):
|
||||||
|
return self.email
|
||||||
|
|
||||||
|
def get_short_name(self):
|
||||||
|
return self.email
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.email
|
27
Controls/Django/api/models/UserManager.py
Normal file
27
Controls/Django/api/models/UserManager.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
from django.contrib.auth.models import BaseUserManager
|
||||||
|
|
||||||
|
|
||||||
|
class UserManager(BaseUserManager):
|
||||||
|
|
||||||
|
def create_user(self, email, password = None):
|
||||||
|
|
||||||
|
if email is None:
|
||||||
|
raise TypeError('Users must have an email address.')
|
||||||
|
|
||||||
|
user = self.model(email = self.normalize_email(email))
|
||||||
|
user.set_password(password)
|
||||||
|
user.save()
|
||||||
|
|
||||||
|
return user
|
||||||
|
|
||||||
|
def create_superuser(self, email, password):
|
||||||
|
|
||||||
|
if password is None:
|
||||||
|
raise TypeError('Superusers must have a password.')
|
||||||
|
|
||||||
|
user = self.create_user(email, password)
|
||||||
|
user.is_superuser = True
|
||||||
|
user.is_staff = True
|
||||||
|
user.save()
|
||||||
|
|
||||||
|
return user
|
2
Controls/Django/api/models/__init__.py
Normal file
2
Controls/Django/api/models/__init__.py
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
from .UserManager import UserManager
|
||||||
|
from .User import User
|
8
Controls/Django/api/urls.py
Normal file
8
Controls/Django/api/urls.py
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
from django.urls import path
|
||||||
|
|
||||||
|
# from . import methods
|
||||||
|
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
|
||||||
|
]
|
1
Controls/Django/api/views.py
Normal file
1
Controls/Django/api/views.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
from rest_framework_simplejwt.authentication import JWTAuthentication
|
@ -1,3 +0,0 @@
|
|||||||
from django.contrib import admin
|
|
||||||
|
|
||||||
# Register your models here.
|
|
@ -1,5 +0,0 @@
|
|||||||
from django.apps import AppConfig
|
|
||||||
|
|
||||||
|
|
||||||
class ApiConfig(AppConfig):
|
|
||||||
name = 'api'
|
|
@ -1,3 +0,0 @@
|
|||||||
from django.db import models
|
|
||||||
|
|
||||||
# Create your models here.
|
|
@ -1,3 +0,0 @@
|
|||||||
from django.test import TestCase
|
|
||||||
|
|
||||||
# Create your tests here.
|
|
@ -1,8 +0,0 @@
|
|||||||
from django.urls import path
|
|
||||||
|
|
||||||
from . import views
|
|
||||||
|
|
||||||
urlpatterns = [
|
|
||||||
path('text', views.text, name = 'text'),
|
|
||||||
path('command', views.command, name = 'command'),
|
|
||||||
]
|
|
@ -1,27 +0,0 @@
|
|||||||
from django.shortcuts import render
|
|
||||||
from django.http import HttpResponse
|
|
||||||
import os
|
|
||||||
# Archie
|
|
||||||
from Command import Command
|
|
||||||
import modules
|
|
||||||
import json
|
|
||||||
|
|
||||||
def text(request):
|
|
||||||
text = request.GET.get("text")
|
|
||||||
if not text: return HttpResponse("")
|
|
||||||
cmd, params = Command.reg_find(text).values()
|
|
||||||
try: response = cmd.start(params)
|
|
||||||
except: {'text': f'Ошибка в модуле {cmd.getName()}', 'voice': 'Произошла ошибка'}
|
|
||||||
json_string = json.dumps(response)
|
|
||||||
return HttpResponse(json_string)
|
|
||||||
|
|
||||||
def command(request):
|
|
||||||
name = request.GET.get("name")
|
|
||||||
params = request.GET.get("params") or {}
|
|
||||||
if not name: return HttpResponse('')
|
|
||||||
cmd = Command.getCommand(name)
|
|
||||||
if not cmd: return HttpResponse('')
|
|
||||||
try: response = cmd.start(params)
|
|
||||||
except: return HttpResponse('')
|
|
||||||
json_string = json.dumps(response)
|
|
||||||
return HttpResponse(json_string)
|
|
@ -1,16 +0,0 @@
|
|||||||
"""
|
|
||||||
ASGI config for archieapi project.
|
|
||||||
|
|
||||||
It exposes the ASGI callable as a module-level variable named ``application``.
|
|
||||||
|
|
||||||
For more information on this file, see
|
|
||||||
https://docs.djangoproject.com/en/3.1/howto/deployment/asgi/
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
|
|
||||||
from django.core.asgi import get_asgi_application
|
|
||||||
|
|
||||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'archieapi.settings')
|
|
||||||
|
|
||||||
application = get_asgi_application()
|
|
@ -1,123 +0,0 @@
|
|||||||
"""
|
|
||||||
Django settings for archieapi project.
|
|
||||||
|
|
||||||
Generated by 'django-admin startproject' using Django 3.1.2.
|
|
||||||
|
|
||||||
For more information on this file, see
|
|
||||||
https://docs.djangoproject.com/en/3.1/topics/settings/
|
|
||||||
|
|
||||||
For the full list of settings and their values, see
|
|
||||||
https://docs.djangoproject.com/en/3.1/ref/settings/
|
|
||||||
"""
|
|
||||||
|
|
||||||
from pathlib import Path
|
|
||||||
import os, sys
|
|
||||||
import config
|
|
||||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
|
||||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
|
||||||
|
|
||||||
PROJECT_ROOT = os.path.dirname(__file__)
|
|
||||||
sys.path.insert(0, os.path.join(PROJECT_ROOT, 'apps'))
|
|
||||||
|
|
||||||
# Quick-start development settings - unsuitable for production
|
|
||||||
# See https://docs.djangoproject.com/en/3.1/howto/deployment/checklist/
|
|
||||||
|
|
||||||
# SECURITY WARNING: keep the secret key used in production secret!
|
|
||||||
SECRET_KEY = config.django_secret_key
|
|
||||||
|
|
||||||
# SECURITY WARNING: don't run with debug turned on in production!
|
|
||||||
DEBUG = config.django_debug
|
|
||||||
|
|
||||||
ALLOWED_HOSTS = config.django_allowed_hosts
|
|
||||||
|
|
||||||
|
|
||||||
# Application definition
|
|
||||||
|
|
||||||
INSTALLED_APPS = [
|
|
||||||
'django.contrib.admin',
|
|
||||||
'django.contrib.auth',
|
|
||||||
'django.contrib.contenttypes',
|
|
||||||
'django.contrib.sessions',
|
|
||||||
'django.contrib.messages',
|
|
||||||
'django.contrib.staticfiles',
|
|
||||||
]
|
|
||||||
|
|
||||||
MIDDLEWARE = [
|
|
||||||
'django.middleware.security.SecurityMiddleware',
|
|
||||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
|
||||||
'django.middleware.common.CommonMiddleware',
|
|
||||||
'django.middleware.csrf.CsrfViewMiddleware',
|
|
||||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
|
||||||
'django.contrib.messages.middleware.MessageMiddleware',
|
|
||||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
|
||||||
]
|
|
||||||
|
|
||||||
ROOT_URLCONF = 'archieapi.urls'
|
|
||||||
|
|
||||||
TEMPLATES = [
|
|
||||||
{
|
|
||||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
|
||||||
'DIRS': [],
|
|
||||||
'APP_DIRS': True,
|
|
||||||
'OPTIONS': {
|
|
||||||
'context_processors': [
|
|
||||||
'django.template.context_processors.debug',
|
|
||||||
'django.template.context_processors.request',
|
|
||||||
'django.contrib.auth.context_processors.auth',
|
|
||||||
'django.contrib.messages.context_processors.messages',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
WSGI_APPLICATION = 'archieapi.wsgi.application'
|
|
||||||
|
|
||||||
|
|
||||||
# Database
|
|
||||||
# https://docs.djangoproject.com/en/3.1/ref/settings/#databases
|
|
||||||
|
|
||||||
DATABASES = {
|
|
||||||
'default': {
|
|
||||||
'ENGINE': 'django.db.backends.sqlite3',
|
|
||||||
'NAME': BASE_DIR / 'db.sqlite3',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# Password validation
|
|
||||||
# https://docs.djangoproject.com/en/3.1/ref/settings/#auth-password-validators
|
|
||||||
|
|
||||||
AUTH_PASSWORD_VALIDATORS = [
|
|
||||||
{
|
|
||||||
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
# Internationalization
|
|
||||||
# https://docs.djangoproject.com/en/3.1/topics/i18n/
|
|
||||||
|
|
||||||
LANGUAGE_CODE = config.language_code
|
|
||||||
|
|
||||||
TIME_ZONE = 'UTC'
|
|
||||||
|
|
||||||
USE_I18N = True
|
|
||||||
|
|
||||||
USE_L10N = True
|
|
||||||
|
|
||||||
USE_TZ = True
|
|
||||||
|
|
||||||
|
|
||||||
# Static files (CSS, JavaScript, Images)
|
|
||||||
# https://docs.djangoproject.com/en/3.1/howto/static-files/
|
|
||||||
|
|
||||||
STATIC_URL = '/static/'
|
|
@ -1,22 +0,0 @@
|
|||||||
"""archieapi URL Configuration
|
|
||||||
|
|
||||||
The `urlpatterns` list routes URLs to views. For more information please see:
|
|
||||||
https://docs.djangoproject.com/en/3.1/topics/http/urls/
|
|
||||||
Examples:
|
|
||||||
Function views
|
|
||||||
1. Add an import: from my_app import views
|
|
||||||
2. Add a URL to urlpatterns: path('', views.home, name='home')
|
|
||||||
Class-based views
|
|
||||||
1. Add an import: from other_app.views import Home
|
|
||||||
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
|
|
||||||
Including another URLconf
|
|
||||||
1. Import the include() function: from django.urls import include, path
|
|
||||||
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
|
||||||
"""
|
|
||||||
from django.contrib import admin
|
|
||||||
from django.urls import path, include
|
|
||||||
|
|
||||||
urlpatterns = [
|
|
||||||
path('api/', include('api.urls')),
|
|
||||||
path('admin/', admin.site.urls),
|
|
||||||
]
|
|
@ -1,16 +0,0 @@
|
|||||||
"""
|
|
||||||
WSGI config for archieapi project.
|
|
||||||
|
|
||||||
It exposes the WSGI callable as a module-level variable named ``application``.
|
|
||||||
|
|
||||||
For more information on this file, see
|
|
||||||
https://docs.djangoproject.com/en/3.1/howto/deployment/wsgi/
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
|
|
||||||
from django.core.wsgi import get_wsgi_application
|
|
||||||
|
|
||||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'archieapi.settings')
|
|
||||||
|
|
||||||
application = get_wsgi_application()
|
|
@ -1,4 +0,0 @@
|
|||||||
from . import alarmclock
|
|
||||||
from . import light
|
|
||||||
from . import main_light
|
|
||||||
from . import window
|
|
@ -1,22 +0,0 @@
|
|||||||
from .SmartHome import SmartHome
|
|
||||||
from ArchieCore import Response, Command
|
|
||||||
import Text2Speech
|
|
||||||
import os
|
|
||||||
|
|
||||||
################################################################################
|
|
||||||
|
|
||||||
@Command.new(primary = False)
|
|
||||||
def alarmclock(params):
|
|
||||||
text = voice = ''
|
|
||||||
Command.getCommand('tv on').start({})
|
|
||||||
Command.getCommand('window_open').start({})
|
|
||||||
shedule = Command.getCommand('Todays Shedule').start({}).voice
|
|
||||||
time = Command.getCommand('Current Time').start({}).voice
|
|
||||||
voice = f'Доброе утро! {time}.\n'
|
|
||||||
if shedule:
|
|
||||||
voice = voice + 'Сегодня у вас: \n' + shedule
|
|
||||||
while True:
|
|
||||||
if os.popen('echo \'pow 0.0.0.0\' | cec-client -s -d 1 |grep power').read() == 'power status: on\n':
|
|
||||||
break
|
|
||||||
Text2Speech.Engine().generate(voice).speak()
|
|
||||||
return Response(text = text, voice = voice)
|
|
@ -1,788 +0,0 @@
|
|||||||
#!/usr/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
|
|
||||||
|
|
||||||
# This file lib_nrf24.py is a slightly tweaked version of Barraca's "pynrf24".
|
|
||||||
|
|
||||||
# So this is my tweak for Raspberry Pi and "Virtual GPIO" ...
|
|
||||||
# ... of Barraca's port to BeagleBone python ... (Joao Paulo Barraca <jpbarraca@gmail.com>)
|
|
||||||
# ... of maniacbug's NRF24L01 C++ library for Arduino.
|
|
||||||
# Brian Lavery Oct 2014
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import time
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
print (sys.argv[0], 'is an importable module:')
|
|
||||||
print ("... from", sys.argv[0], "import lib_nrf24")
|
|
||||||
print ("")
|
|
||||||
|
|
||||||
exit()
|
|
||||||
|
|
||||||
def _BV(x):
|
|
||||||
return 1 << x
|
|
||||||
|
|
||||||
|
|
||||||
class NRF24:
|
|
||||||
MAX_CHANNEL = 127
|
|
||||||
MAX_PAYLOAD_SIZE = 32
|
|
||||||
|
|
||||||
# PA Levels
|
|
||||||
PA_MIN = 0
|
|
||||||
PA_LOW = 1
|
|
||||||
PA_HIGH = 2
|
|
||||||
PA_MAX = 3
|
|
||||||
PA_ERROR = 4
|
|
||||||
|
|
||||||
# Bit rates
|
|
||||||
BR_1MBPS = 0
|
|
||||||
BR_2MBPS = 1
|
|
||||||
BR_250KBPS = 2
|
|
||||||
|
|
||||||
# CRC
|
|
||||||
CRC_DISABLED = 0
|
|
||||||
CRC_8 = 1
|
|
||||||
CRC_16 = 2
|
|
||||||
CRC_ENABLED = 3
|
|
||||||
|
|
||||||
# Registers
|
|
||||||
CONFIG = 0x00
|
|
||||||
EN_AA = 0x01
|
|
||||||
EN_RXADDR = 0x02
|
|
||||||
SETUP_AW = 0x03
|
|
||||||
SETUP_RETR = 0x04
|
|
||||||
RF_CH = 0x05
|
|
||||||
RF_SETUP = 0x06
|
|
||||||
STATUS = 0x07
|
|
||||||
OBSERVE_TX = 0x08
|
|
||||||
CD = 0x09
|
|
||||||
RX_ADDR_P0 = 0x0A
|
|
||||||
RX_ADDR_P1 = 0x0B
|
|
||||||
RX_ADDR_P2 = 0x0C
|
|
||||||
RX_ADDR_P3 = 0x0D
|
|
||||||
RX_ADDR_P4 = 0x0E
|
|
||||||
RX_ADDR_P5 = 0x0F
|
|
||||||
TX_ADDR = 0x10
|
|
||||||
RX_PW_P0 = 0x11
|
|
||||||
RX_PW_P1 = 0x12
|
|
||||||
RX_PW_P2 = 0x13
|
|
||||||
RX_PW_P3 = 0x14
|
|
||||||
RX_PW_P4 = 0x15
|
|
||||||
RX_PW_P5 = 0x16
|
|
||||||
FIFO_STATUS = 0x17
|
|
||||||
DYNPD = 0x1C
|
|
||||||
FEATURE = 0x1D
|
|
||||||
|
|
||||||
|
|
||||||
# Bit Mnemonics */
|
|
||||||
MASK_RX_DR = 6
|
|
||||||
MASK_TX_DS = 5
|
|
||||||
MASK_MAX_RT = 4
|
|
||||||
EN_CRC = 3
|
|
||||||
CRCO = 2
|
|
||||||
PWR_UP = 1
|
|
||||||
PRIM_RX = 0
|
|
||||||
ENAA_P5 = 5
|
|
||||||
ENAA_P4 = 4
|
|
||||||
ENAA_P3 = 3
|
|
||||||
ENAA_P2 = 2
|
|
||||||
ENAA_P1 = 1
|
|
||||||
ENAA_P0 = 0
|
|
||||||
ERX_P5 = 5
|
|
||||||
ERX_P4 = 4
|
|
||||||
ERX_P3 = 3
|
|
||||||
ERX_P2 = 2
|
|
||||||
ERX_P1 = 1
|
|
||||||
ERX_P0 = 0
|
|
||||||
AW = 0
|
|
||||||
ARD = 4
|
|
||||||
ARC = 0
|
|
||||||
PLL_LOCK = 4
|
|
||||||
RF_DR = 3
|
|
||||||
RF_PWR = 6
|
|
||||||
RX_DR = 6
|
|
||||||
TX_DS = 5
|
|
||||||
MAX_RT = 4
|
|
||||||
RX_P_NO = 1
|
|
||||||
TX_FULL = 0
|
|
||||||
PLOS_CNT = 4
|
|
||||||
ARC_CNT = 0
|
|
||||||
TX_REUSE = 6
|
|
||||||
FIFO_FULL = 5
|
|
||||||
TX_EMPTY = 4
|
|
||||||
RX_FULL = 1
|
|
||||||
RX_EMPTY = 0
|
|
||||||
DPL_P5 = 5
|
|
||||||
DPL_P4 = 4
|
|
||||||
DPL_P3 = 3
|
|
||||||
DPL_P2 = 2
|
|
||||||
DPL_P1 = 1
|
|
||||||
DPL_P0 = 0
|
|
||||||
EN_DPL = 2
|
|
||||||
EN_ACK_PAY = 1
|
|
||||||
EN_DYN_ACK = 0
|
|
||||||
|
|
||||||
# Instruction Mnemonics
|
|
||||||
R_REGISTER = 0x00
|
|
||||||
W_REGISTER = 0x20
|
|
||||||
REGISTER_MASK = 0x1F
|
|
||||||
ACTIVATE = 0x50
|
|
||||||
R_RX_PL_WID = 0x60
|
|
||||||
R_RX_PAYLOAD = 0x61
|
|
||||||
W_TX_PAYLOAD = 0xA0
|
|
||||||
W_ACK_PAYLOAD = 0xA8
|
|
||||||
FLUSH_TX = 0xE1
|
|
||||||
FLUSH_RX = 0xE2
|
|
||||||
REUSE_TX_PL = 0xE3
|
|
||||||
NOP = 0xFF
|
|
||||||
|
|
||||||
|
|
||||||
# Non-P omissions
|
|
||||||
LNA_HCURR = 0x00
|
|
||||||
|
|
||||||
# P model memory Map
|
|
||||||
RPD = 0x09
|
|
||||||
|
|
||||||
# P model bit Mnemonics
|
|
||||||
RF_DR_LOW = 5
|
|
||||||
RF_DR_HIGH = 3
|
|
||||||
RF_PWR_LOW = 1
|
|
||||||
RF_PWR_HIGH = 2
|
|
||||||
|
|
||||||
# Signal Mnemonics
|
|
||||||
LOW = 0
|
|
||||||
HIGH = 1
|
|
||||||
|
|
||||||
datarate_e_str_P = ["1MBPS", "2MBPS", "250KBPS"]
|
|
||||||
model_e_str_P = ["nRF24L01", "nRF24l01+"]
|
|
||||||
crclength_e_str_P = ["Disabled", "8 bits", "16 bits"]
|
|
||||||
pa_dbm_e_str_P = ["PA_MIN", "PA_LOW", "PA_MED", "PA_HIGH"]
|
|
||||||
child_pipe = [RX_ADDR_P0, RX_ADDR_P1, RX_ADDR_P2, RX_ADDR_P3, RX_ADDR_P4, RX_ADDR_P5]
|
|
||||||
|
|
||||||
child_payload_size = [RX_PW_P0, RX_PW_P1, RX_PW_P2, RX_PW_P3, RX_PW_P4, RX_PW_P5]
|
|
||||||
child_pipe_enable = [ERX_P0, ERX_P1, ERX_P2, ERX_P3, ERX_P4, ERX_P5]
|
|
||||||
|
|
||||||
GPIO = None
|
|
||||||
spidev = None
|
|
||||||
|
|
||||||
def __init__(self, gpio, spidev):
|
|
||||||
# It should be possible to instantiate multiple objects, with different GPIO / spidev
|
|
||||||
# EG on Raspberry, one could be RPI GPIO & spidev module, other could be virtual-GPIO
|
|
||||||
# On rpi, only bus 0 is supported here, not bus 1 of the model B plus
|
|
||||||
self.GPIO = gpio # the GPIO module
|
|
||||||
self.spidev = spidev # the spidev object/instance
|
|
||||||
self.channel = 76
|
|
||||||
self.data_rate = NRF24.BR_1MBPS
|
|
||||||
self.wide_band = False # 2Mbs data rate in use?
|
|
||||||
self.p_variant = False # False for RF24L01 and true for RF24L01P (nrf24l01+)
|
|
||||||
self.payload_size = 5 #*< Fixed size of payloads
|
|
||||||
self.ack_payload_available = False #*< Whether there is an ack payload waiting
|
|
||||||
self.dynamic_payloads_enabled = False #*< Whether dynamic payloads are enabled.
|
|
||||||
self.ack_payload_length = 5 #*< Dynamic size of pending ack payload.
|
|
||||||
self.pipe0_reading_address = None #*< Last address set on pipe 0 for reading.
|
|
||||||
|
|
||||||
def ce(self, level):
|
|
||||||
if self.ce_pin == 0:
|
|
||||||
return
|
|
||||||
# rf24-CE is optional. Tie to HIGH if not used. (Altho, left floating seems to read HIGH anyway??? - risky!)
|
|
||||||
# Some RF24 modes may NEED control over CE.
|
|
||||||
# non-powerdown, fixed PTX or RTX role, dynamic payload size & ack-payload: does NOT need CE.
|
|
||||||
if level == NRF24.HIGH:
|
|
||||||
self.GPIO.output(self.ce_pin, self.GPIO.HIGH)
|
|
||||||
else:
|
|
||||||
self.GPIO.output(self.ce_pin, self.GPIO.LOW)
|
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def read_register(self, reg, blen=1):
|
|
||||||
buf = [NRF24.R_REGISTER | ( NRF24.REGISTER_MASK & reg )]
|
|
||||||
for col in range(blen):
|
|
||||||
buf.append(NRF24.NOP)
|
|
||||||
|
|
||||||
resp = self.spidev.xfer2(buf)
|
|
||||||
if blen == 1:
|
|
||||||
return resp[1]
|
|
||||||
|
|
||||||
return resp[1:blen + 1]
|
|
||||||
|
|
||||||
def write_register(self, reg, value, length=-1):
|
|
||||||
buf = [NRF24.W_REGISTER | ( NRF24.REGISTER_MASK & reg )]
|
|
||||||
###if isinstance(value, (int, long)): # ng for python3. but value should never be long anyway
|
|
||||||
if isinstance(value, int):
|
|
||||||
if length < 0:
|
|
||||||
length = 1
|
|
||||||
|
|
||||||
length = min(4, length)
|
|
||||||
for i in range(length):
|
|
||||||
buf.insert(1, int(value & 0xff))
|
|
||||||
value >>= 8
|
|
||||||
|
|
||||||
elif isinstance(value, list):
|
|
||||||
if length < 0:
|
|
||||||
length = len(value)
|
|
||||||
|
|
||||||
for i in range(min(len(value), length)):
|
|
||||||
buf.append(int(value[len(value) - i - 1] & 0xff))
|
|
||||||
else:
|
|
||||||
raise Exception("Value must be int or list")
|
|
||||||
|
|
||||||
return self.spidev.xfer2(buf)[0]
|
|
||||||
|
|
||||||
|
|
||||||
def write_payload(self, buf):
|
|
||||||
data_len = min(self.payload_size, len(buf))
|
|
||||||
blank_len = 0
|
|
||||||
if not self.dynamic_payloads_enabled:
|
|
||||||
blank_len = self.payload_size - data_len
|
|
||||||
|
|
||||||
txbuffer = [NRF24.W_TX_PAYLOAD]
|
|
||||||
for n in buf:
|
|
||||||
t = type(n)
|
|
||||||
if t is str:
|
|
||||||
txbuffer.append(ord(n))
|
|
||||||
elif t is int:
|
|
||||||
txbuffer.append(n)
|
|
||||||
else:
|
|
||||||
raise Exception("Only ints and chars are supported: Found " + str(t))
|
|
||||||
|
|
||||||
if blank_len != 0:
|
|
||||||
blank = [0x00 for i in range(blank_len)]
|
|
||||||
txbuffer.extend(blank)
|
|
||||||
|
|
||||||
return self.spidev.xfer2(txbuffer)
|
|
||||||
|
|
||||||
def read_payload(self, buf, buf_len=-1):
|
|
||||||
if buf_len < 0:
|
|
||||||
buf_len = self.payload_size
|
|
||||||
data_len = min(self.payload_size, buf_len)
|
|
||||||
blank_len = 0
|
|
||||||
if not self.dynamic_payloads_enabled:
|
|
||||||
blank_len = self.payload_size - data_len
|
|
||||||
|
|
||||||
txbuffer = [NRF24.NOP for i in range(0, blank_len + data_len + 1)]
|
|
||||||
txbuffer[0] = NRF24.R_RX_PAYLOAD
|
|
||||||
|
|
||||||
payload = self.spidev.xfer2(txbuffer)
|
|
||||||
del buf[:]
|
|
||||||
buf.extend(payload[1:data_len + 1])
|
|
||||||
return data_len
|
|
||||||
|
|
||||||
def flush_rx(self):
|
|
||||||
return self.spidev.xfer2([NRF24.FLUSH_RX])[0]
|
|
||||||
|
|
||||||
def flush_tx(self):
|
|
||||||
return self.spidev.xfer2([NRF24.FLUSH_TX])[0]
|
|
||||||
|
|
||||||
def get_status(self):
|
|
||||||
return self.spidev.xfer2([NRF24.NOP])[0]
|
|
||||||
|
|
||||||
def print_status(self, status):
|
|
||||||
status_str = "STATUS\t = 0x{0:02x} RX_DR={1:x} TX_DS={2:x} MAX_RT={3:x} RX_P_NO={4:x} TX_FULL={5:x}".format(
|
|
||||||
status,
|
|
||||||
1 if status & _BV(NRF24.RX_DR) else 0,
|
|
||||||
1 if status & _BV(NRF24.TX_DS) else 0,
|
|
||||||
1 if status & _BV(NRF24.MAX_RT) else 0,
|
|
||||||
((status >> NRF24.RX_P_NO) & 7),
|
|
||||||
1 if status & _BV(NRF24.TX_FULL) else 0)
|
|
||||||
|
|
||||||
print (status_str)
|
|
||||||
|
|
||||||
def print_observe_tx(self, value):
|
|
||||||
print ("Observe Tx: %02x Lost Pkts: %d Retries: %d" % (value, value >> NRF24.PLOS_CNT, value & 15))
|
|
||||||
|
|
||||||
|
|
||||||
def print_byte_register(self, name, reg, qty=1):
|
|
||||||
extra_tab = '\t' if len(name) < 8 else 0
|
|
||||||
print ("%s\t%c =" % (name, extra_tab)),
|
|
||||||
while qty > 0:
|
|
||||||
print ("0x%02x" % (self.read_register(reg))),
|
|
||||||
qty -= 1
|
|
||||||
reg += 1
|
|
||||||
|
|
||||||
print ("")
|
|
||||||
|
|
||||||
def print_address_register(self, name, reg, qty=1):
|
|
||||||
extra_tab = '\t' if len(name) < 8 else 0
|
|
||||||
print ("%s\t%c =" % (name, extra_tab)),
|
|
||||||
|
|
||||||
while qty > 0:
|
|
||||||
qty -= 1
|
|
||||||
buf = reversed(self.read_register(reg, 5))
|
|
||||||
reg += 1
|
|
||||||
sys.stdout.write(" 0x"),
|
|
||||||
for i in buf:
|
|
||||||
sys.stdout.write("%02x" % i)
|
|
||||||
|
|
||||||
print ("")
|
|
||||||
|
|
||||||
|
|
||||||
def setChannel(self, channel):
|
|
||||||
self.channel = min(max(0, channel), NRF24.MAX_CHANNEL)
|
|
||||||
self.write_register(NRF24.RF_CH, self.channel)
|
|
||||||
|
|
||||||
def getChannel(self):
|
|
||||||
return self.read_register(NRF24.RF_CH)
|
|
||||||
|
|
||||||
def setPayloadSize(self, size):
|
|
||||||
self.payload_size = min(max(size, 1), NRF24.MAX_PAYLOAD_SIZE)
|
|
||||||
|
|
||||||
def getPayloadSize(self):
|
|
||||||
return self.payload_size
|
|
||||||
|
|
||||||
def printDetails(self):
|
|
||||||
self.print_status(self.get_status())
|
|
||||||
self.print_address_register("RX_ADDR_P0-1", NRF24.RX_ADDR_P0, 2)
|
|
||||||
self.print_byte_register("RX_ADDR_P2-5", NRF24.RX_ADDR_P2, 4)
|
|
||||||
self.print_address_register("TX_ADDR", NRF24.TX_ADDR)
|
|
||||||
|
|
||||||
self.print_byte_register("RX_PW_P0-6", NRF24.RX_PW_P0, 6)
|
|
||||||
self.print_byte_register("EN_AA", NRF24.EN_AA)
|
|
||||||
self.print_byte_register("EN_RXADDR", NRF24.EN_RXADDR)
|
|
||||||
self.print_byte_register("RF_CH", NRF24.RF_CH)
|
|
||||||
self.print_byte_register("RF_SETUP", NRF24.RF_SETUP)
|
|
||||||
self.print_byte_register("CONFIG", NRF24.CONFIG)
|
|
||||||
self.print_byte_register("DYNPD/FEATURE", NRF24.DYNPD, 2)
|
|
||||||
|
|
||||||
#
|
|
||||||
print ("Data Rate\t = %s" % NRF24.datarate_e_str_P[self.getDataRate()])
|
|
||||||
print ("Model\t\t = %s" % NRF24.model_e_str_P[self.isPVariant()])
|
|
||||||
print ("CRC Length\t = %s" % NRF24.crclength_e_str_P[self.getCRCLength()])
|
|
||||||
print ("PA Power\t = %s" % NRF24.pa_dbm_e_str_P[self.getPALevel()])
|
|
||||||
|
|
||||||
def begin(self, csn_pin, ce_pin=0): # csn & ce are RF24 terminology. csn = SPI's CE!
|
|
||||||
# Initialize SPI bus..
|
|
||||||
# ce_pin is for the rx=listen or tx=trigger pin on RF24 (they call that ce !!!)
|
|
||||||
# CE optional (at least in some circumstances, eg fixed PTX PRX roles, no powerdown)
|
|
||||||
# CE seems to hold itself as (sufficiently) HIGH, but tie HIGH is safer!
|
|
||||||
self.spidev.open(0, csn_pin)
|
|
||||||
self.spidev.max_speed_hz = 4000000
|
|
||||||
self.ce_pin = ce_pin
|
|
||||||
|
|
||||||
if ce_pin:
|
|
||||||
self.GPIO.setup(self.ce_pin, self.GPIO.OUT)
|
|
||||||
|
|
||||||
time.sleep(5 / 1000000.0)
|
|
||||||
|
|
||||||
# Set 1500uS (minimum for 32B payload in ESB@250KBPS) timeouts, to make testing a little easier
|
|
||||||
# WARNING: If this is ever lowered, either 250KBS mode with AA is broken or maximum packet
|
|
||||||
# sizes must never be used. See documentation for a more complete explanation.
|
|
||||||
self.write_register(NRF24.SETUP_RETR, (0b0100 << NRF24.ARD) | 0b1111)
|
|
||||||
|
|
||||||
# Restore our default PA level
|
|
||||||
self.setPALevel(NRF24.PA_MAX)
|
|
||||||
|
|
||||||
# Determine if this is a p or non-p RF24 module and then
|
|
||||||
# reset our data rate back to default value. This works
|
|
||||||
# because a non-P variant won't allow the data rate to
|
|
||||||
# be set to 250Kbps.
|
|
||||||
if self.setDataRate(NRF24.BR_250KBPS):
|
|
||||||
self.p_variant = True
|
|
||||||
|
|
||||||
# Then set the data rate to the slowest (and most reliable) speed supported by all
|
|
||||||
# hardware.
|
|
||||||
self.setDataRate(NRF24.BR_1MBPS)
|
|
||||||
|
|
||||||
# Initialize CRC and request 2-byte (16bit) CRC
|
|
||||||
self.setCRCLength(NRF24.CRC_16)
|
|
||||||
|
|
||||||
# Disable dynamic payloads, to match dynamic_payloads_enabled setting
|
|
||||||
self.write_register(NRF24.DYNPD, 0)
|
|
||||||
|
|
||||||
# Reset current status
|
|
||||||
# Notice reset and flush is the last thing we do
|
|
||||||
self.write_register(NRF24.STATUS, _BV(NRF24.RX_DR) | _BV(NRF24.TX_DS) | _BV(NRF24.MAX_RT))
|
|
||||||
|
|
||||||
# Set up default configuration. Callers can always change it later.
|
|
||||||
# This channel should be universally safe and not bleed over into adjacent
|
|
||||||
# spectrum.
|
|
||||||
self.setChannel(self.channel)
|
|
||||||
|
|
||||||
# Flush buffers
|
|
||||||
self.flush_rx()
|
|
||||||
self.flush_tx()
|
|
||||||
|
|
||||||
def end(self):
|
|
||||||
if self.spidev:
|
|
||||||
self.spidev.close()
|
|
||||||
self.spidev = None
|
|
||||||
|
|
||||||
def startListening(self):
|
|
||||||
self.write_register(NRF24.CONFIG, self.read_register(NRF24.CONFIG) | _BV(NRF24.PWR_UP) | _BV(NRF24.PRIM_RX))
|
|
||||||
self.write_register(NRF24.STATUS, _BV(NRF24.RX_DR) | _BV(NRF24.TX_DS) | _BV(NRF24.MAX_RT))
|
|
||||||
|
|
||||||
# Restore the pipe0 address, if exists
|
|
||||||
if self.pipe0_reading_address:
|
|
||||||
self.write_register(self.RX_ADDR_P0, self.pipe0_reading_address, 5)
|
|
||||||
|
|
||||||
# Go!
|
|
||||||
self.ce(NRF24.HIGH)
|
|
||||||
|
|
||||||
# wait for the radio to come up (130us actually only needed)
|
|
||||||
time.sleep(130 / 1000000.0)
|
|
||||||
|
|
||||||
def stopListening(self):
|
|
||||||
self.ce(NRF24.LOW)
|
|
||||||
self.flush_tx()
|
|
||||||
self.flush_rx()
|
|
||||||
|
|
||||||
def powerDown(self):
|
|
||||||
self.write_register(NRF24.CONFIG, self.read_register(NRF24.CONFIG) & ~_BV(NRF24.PWR_UP))
|
|
||||||
|
|
||||||
def powerUp(self):
|
|
||||||
self.write_register(NRF24.CONFIG, self.read_register(NRF24.CONFIG) | _BV(NRF24.PWR_UP))
|
|
||||||
time.sleep(150 / 1000000.0)
|
|
||||||
|
|
||||||
def write(self, buf):
|
|
||||||
# Begin the write
|
|
||||||
self.startWrite(buf)
|
|
||||||
|
|
||||||
timeout = self.getMaxTimeout() #s to wait for timeout
|
|
||||||
sent_at = time.time()
|
|
||||||
|
|
||||||
while True:
|
|
||||||
#status = self.read_register(NRF24.OBSERVE_TX, 1)
|
|
||||||
status = self.get_status()
|
|
||||||
if (status & (_BV(NRF24.TX_DS) | _BV(NRF24.MAX_RT))) or (time.time() - sent_at > timeout ):
|
|
||||||
break
|
|
||||||
time.sleep(10 / 1000000.0)
|
|
||||||
#obs = self.read_register(NRF24.OBSERVE_TX)
|
|
||||||
#self.print_observe_tx(obs)
|
|
||||||
#self.print_status(status)
|
|
||||||
# (for debugging)
|
|
||||||
|
|
||||||
what = self.whatHappened()
|
|
||||||
|
|
||||||
result = what['tx_ok']
|
|
||||||
if what['tx_fail']:
|
|
||||||
self.flush_tx(); # bl - dont jam up the fifo
|
|
||||||
# Handle the ack packet
|
|
||||||
if what['rx_ready']:
|
|
||||||
self.ack_payload_length = self.getDynamicPayloadSize()
|
|
||||||
self.ack_payload_available = True ## bl
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
def startWrite(self, buf):
|
|
||||||
# Transmitter power-up
|
|
||||||
self.write_register(NRF24.CONFIG, (self.read_register(NRF24.CONFIG) | _BV(NRF24.PWR_UP) ) & ~_BV(NRF24.PRIM_RX))
|
|
||||||
|
|
||||||
# Send the payload
|
|
||||||
self.write_payload(buf)
|
|
||||||
|
|
||||||
# Allons!
|
|
||||||
if self.ce_pin:
|
|
||||||
if self.GPIO.RPI_REVISION > 0:
|
|
||||||
self.ce(self.GPIO.HIGH)
|
|
||||||
time.sleep(10 / 1000000.0)
|
|
||||||
self.ce(self.GPIO.LOW)
|
|
||||||
else:
|
|
||||||
# virtGPIO is slower. A 10 uSec pulse is better done with pulseOut():
|
|
||||||
self.GPIO.pulseOut(self.ce_pin, self.GPIO.HIGH, 10)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def getDynamicPayloadSize(self):
|
|
||||||
return self.spidev.xfer2([NRF24.R_RX_PL_WID, NRF24.NOP])[1]
|
|
||||||
|
|
||||||
def available(self, pipe_num=None):
|
|
||||||
if not pipe_num:
|
|
||||||
pipe_num = []
|
|
||||||
|
|
||||||
status = self.get_status()
|
|
||||||
result = False
|
|
||||||
|
|
||||||
# Sometimes the radio specifies that there is data in one pipe but
|
|
||||||
# doesn't set the RX flag...
|
|
||||||
if status & _BV(NRF24.RX_DR) or (status & 0b00001110 != 0b00001110):
|
|
||||||
result = True
|
|
||||||
|
|
||||||
if result:
|
|
||||||
# If the caller wants the pipe number, include that
|
|
||||||
if len(pipe_num) >= 1:
|
|
||||||
pipe_num[0] = ( status >> NRF24.RX_P_NO ) & 0b00000111
|
|
||||||
|
|
||||||
# Clear the status bit
|
|
||||||
|
|
||||||
# ??? Should this REALLY be cleared now? Or wait until we
|
|
||||||
# actually READ the payload?
|
|
||||||
self.write_register(NRF24.STATUS, _BV(NRF24.RX_DR))
|
|
||||||
|
|
||||||
# Handle ack payload receipt
|
|
||||||
if status & _BV(NRF24.TX_DS):
|
|
||||||
self.write_register(NRF24.STATUS, _BV(NRF24.TX_DS))
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
def read(self, buf, buf_len=-1):
|
|
||||||
# Fetch the payload
|
|
||||||
self.read_payload(buf, buf_len)
|
|
||||||
|
|
||||||
# was this the last of the data available?
|
|
||||||
return self.read_register(NRF24.FIFO_STATUS) & _BV(NRF24.RX_EMPTY)
|
|
||||||
|
|
||||||
def whatHappened(self):
|
|
||||||
# Read the status & reset the status in one easy call
|
|
||||||
# Or is that such a good idea?
|
|
||||||
status = self.write_register(NRF24.STATUS, _BV(NRF24.RX_DR) | _BV(NRF24.TX_DS) | _BV(NRF24.MAX_RT))
|
|
||||||
|
|
||||||
# Report to the user what happened
|
|
||||||
tx_ok = status & _BV(NRF24.TX_DS)
|
|
||||||
tx_fail = status & _BV(NRF24.MAX_RT)
|
|
||||||
rx_ready = status & _BV(NRF24.RX_DR)
|
|
||||||
return {'tx_ok': tx_ok, "tx_fail": tx_fail, "rx_ready": rx_ready}
|
|
||||||
|
|
||||||
def openWritingPipe(self, value):
|
|
||||||
# Note that the NRF24L01(+)
|
|
||||||
# expects it LSB first.
|
|
||||||
|
|
||||||
self.write_register(NRF24.RX_ADDR_P0, value, 5)
|
|
||||||
self.write_register(NRF24.TX_ADDR, value, 5)
|
|
||||||
|
|
||||||
max_payload_size = 32
|
|
||||||
self.write_register(NRF24.RX_PW_P0, min(self.payload_size, max_payload_size))
|
|
||||||
|
|
||||||
def openReadingPipe(self, child, address):
|
|
||||||
# If this is pipe 0, cache the address. This is needed because
|
|
||||||
# openWritingPipe() will overwrite the pipe 0 address, so
|
|
||||||
# startListening() will have to restore it.
|
|
||||||
if child == 0:
|
|
||||||
self.pipe0_reading_address = address
|
|
||||||
|
|
||||||
if child <= 6:
|
|
||||||
# For pipes 2-5, only write the LSB
|
|
||||||
if child < 2:
|
|
||||||
self.write_register(NRF24.child_pipe[child], address, 5)
|
|
||||||
else:
|
|
||||||
self.write_register(NRF24.child_pipe[child], address, 1)
|
|
||||||
|
|
||||||
self.write_register(NRF24.child_payload_size[child], self.payload_size)
|
|
||||||
|
|
||||||
# Note it would be more efficient to set all of the bits for all open
|
|
||||||
# pipes at once. However, I thought it would make the calling code
|
|
||||||
# more simple to do it this way.
|
|
||||||
self.write_register(NRF24.EN_RXADDR,
|
|
||||||
self.read_register(NRF24.EN_RXADDR) | _BV(NRF24.child_pipe_enable[child]))
|
|
||||||
|
|
||||||
|
|
||||||
def closeReadingPipe(self, pipe):
|
|
||||||
self.write_register(NRF24.EN_RXADDR,
|
|
||||||
self.read_register(EN_RXADDR) & ~_BV(NRF24.child_pipe_enable[pipe]))
|
|
||||||
|
|
||||||
|
|
||||||
def toggle_features(self):
|
|
||||||
buf = [NRF24.ACTIVATE, 0x73]
|
|
||||||
self.spidev.xfer2(buf)
|
|
||||||
|
|
||||||
def enableDynamicPayloads(self):
|
|
||||||
# Enable dynamic payload throughout the system
|
|
||||||
self.write_register(NRF24.FEATURE, self.read_register(NRF24.FEATURE) | _BV(NRF24.EN_DPL))
|
|
||||||
|
|
||||||
# If it didn't work, the features are not enabled
|
|
||||||
if not self.read_register(NRF24.FEATURE):
|
|
||||||
# So enable them and try again
|
|
||||||
self.toggle_features()
|
|
||||||
self.write_register(NRF24.FEATURE, self.read_register(NRF24.FEATURE) | _BV(NRF24.EN_DPL))
|
|
||||||
|
|
||||||
# Enable dynamic payload on all pipes
|
|
||||||
|
|
||||||
# Not sure the use case of only having dynamic payload on certain
|
|
||||||
# pipes, so the library does not support it.
|
|
||||||
self.write_register(NRF24.DYNPD, self.read_register(NRF24.DYNPD) | _BV(NRF24.DPL_P5) | _BV(NRF24.DPL_P4) | _BV(
|
|
||||||
NRF24.DPL_P3) | _BV(NRF24.DPL_P2) | _BV(NRF24.DPL_P1) | _BV(NRF24.DPL_P0))
|
|
||||||
|
|
||||||
self.dynamic_payloads_enabled = True
|
|
||||||
|
|
||||||
|
|
||||||
def enableAckPayload(self):
|
|
||||||
# enable ack payload and dynamic payload features
|
|
||||||
self.write_register(NRF24.FEATURE,
|
|
||||||
self.read_register(NRF24.FEATURE) | _BV(NRF24.EN_ACK_PAY) | _BV(NRF24.EN_DPL))
|
|
||||||
|
|
||||||
# If it didn't work, the features are not enabled
|
|
||||||
if not self.read_register(NRF24.FEATURE):
|
|
||||||
# So enable them and try again
|
|
||||||
self.toggle_features()
|
|
||||||
self.write_register(NRF24.FEATURE,
|
|
||||||
self.read_register(NRF24.FEATURE) | _BV(NRF24.EN_ACK_PAY) | _BV(NRF24.EN_DPL))
|
|
||||||
|
|
||||||
# Enable dynamic payload on pipes 0 & 1
|
|
||||||
self.write_register(NRF24.DYNPD, self.read_register(NRF24.DYNPD) | _BV(NRF24.DPL_P1) | _BV(NRF24.DPL_P0))
|
|
||||||
|
|
||||||
def writeAckPayload(self, pipe, buf, buf_len):
|
|
||||||
txbuffer = [NRF24.W_ACK_PAYLOAD | ( pipe & 0x7 )]
|
|
||||||
|
|
||||||
max_payload_size = 32
|
|
||||||
data_len = min(buf_len, max_payload_size)
|
|
||||||
txbuffer.extend(buf[0:data_len])
|
|
||||||
|
|
||||||
self.spidev.xfer2(txbuffer)
|
|
||||||
|
|
||||||
def isAckPayloadAvailable(self):
|
|
||||||
result = self.ack_payload_available
|
|
||||||
self.ack_payload_available = False
|
|
||||||
return result
|
|
||||||
|
|
||||||
def isPVariant(self):
|
|
||||||
return self.p_variant
|
|
||||||
|
|
||||||
def setAutoAck(self, enable):
|
|
||||||
if enable:
|
|
||||||
self.write_register(NRF24.EN_AA, 0b111111)
|
|
||||||
else:
|
|
||||||
self.write_register(NRF24.EN_AA, 0)
|
|
||||||
|
|
||||||
def setAutoAckPipe(self, pipe, enable):
|
|
||||||
if pipe <= 6:
|
|
||||||
en_aa = self.read_register(NRF24.EN_AA)
|
|
||||||
if enable:
|
|
||||||
en_aa |= _BV(pipe)
|
|
||||||
else:
|
|
||||||
en_aa &= ~_BV(pipe)
|
|
||||||
|
|
||||||
self.write_register(NRF24.EN_AA, en_aa)
|
|
||||||
|
|
||||||
def testCarrier(self):
|
|
||||||
return self.read_register(NRF24.CD) & 1
|
|
||||||
|
|
||||||
def testRPD(self):
|
|
||||||
return self.read_register(NRF24.RPD) & 1
|
|
||||||
|
|
||||||
def setPALevel(self, level):
|
|
||||||
setup = self.read_register(NRF24.RF_SETUP)
|
|
||||||
setup &= ~( _BV(NRF24.RF_PWR_LOW) | _BV(NRF24.RF_PWR_HIGH))
|
|
||||||
# switch uses RAM (evil!)
|
|
||||||
if level == NRF24.PA_MAX:
|
|
||||||
setup |= (_BV(NRF24.RF_PWR_LOW) | _BV(NRF24.RF_PWR_HIGH))
|
|
||||||
elif level == NRF24.PA_HIGH:
|
|
||||||
setup |= _BV(NRF24.RF_PWR_HIGH)
|
|
||||||
elif level == NRF24.PA_LOW:
|
|
||||||
setup |= _BV(NRF24.RF_PWR_LOW)
|
|
||||||
elif level == NRF24.PA_MIN:
|
|
||||||
nop = 0
|
|
||||||
elif level == NRF24.PA_ERROR:
|
|
||||||
# On error, go to maximum PA
|
|
||||||
setup |= (_BV(NRF24.RF_PWR_LOW) | _BV(NRF24.RF_PWR_HIGH))
|
|
||||||
|
|
||||||
self.write_register(NRF24.RF_SETUP, setup)
|
|
||||||
|
|
||||||
|
|
||||||
def getPALevel(self):
|
|
||||||
power = self.read_register(NRF24.RF_SETUP) & (_BV(NRF24.RF_PWR_LOW) | _BV(NRF24.RF_PWR_HIGH))
|
|
||||||
|
|
||||||
if power == (_BV(NRF24.RF_PWR_LOW) | _BV(NRF24.RF_PWR_HIGH)):
|
|
||||||
return NRF24.PA_MAX
|
|
||||||
elif power == _BV(NRF24.RF_PWR_HIGH):
|
|
||||||
return NRF24.PA_HIGH
|
|
||||||
elif power == _BV(NRF24.RF_PWR_LOW):
|
|
||||||
return NRF24.PA_LOW
|
|
||||||
else:
|
|
||||||
return NRF24.PA_MIN
|
|
||||||
|
|
||||||
def setDataRate(self, speed):
|
|
||||||
result = False
|
|
||||||
setup = self.read_register(NRF24.RF_SETUP)
|
|
||||||
|
|
||||||
# HIGH and LOW '00' is 1Mbs - our default
|
|
||||||
self.wide_band = False
|
|
||||||
setup &= ~(_BV(NRF24.RF_DR_LOW) | _BV(NRF24.RF_DR_HIGH))
|
|
||||||
|
|
||||||
if speed == NRF24.BR_250KBPS:
|
|
||||||
# Must set the RF_DR_LOW to 1 RF_DR_HIGH (used to be RF_DR) is already 0
|
|
||||||
# Making it '10'.
|
|
||||||
self.wide_band = False
|
|
||||||
setup |= _BV(NRF24.RF_DR_LOW)
|
|
||||||
else:
|
|
||||||
# Set 2Mbs, RF_DR (RF_DR_HIGH) is set 1
|
|
||||||
# Making it '01'
|
|
||||||
if speed == NRF24.BR_2MBPS:
|
|
||||||
self.wide_band = True
|
|
||||||
setup |= _BV(NRF24.RF_DR_HIGH)
|
|
||||||
else:
|
|
||||||
# 1Mbs
|
|
||||||
self.wide_band = False
|
|
||||||
|
|
||||||
self.write_register(NRF24.RF_SETUP, setup)
|
|
||||||
|
|
||||||
# Verify our result
|
|
||||||
if self.read_register(NRF24.RF_SETUP) == setup:
|
|
||||||
result = True
|
|
||||||
else:
|
|
||||||
self.wide_band = False
|
|
||||||
return result
|
|
||||||
|
|
||||||
def getDataRate(self):
|
|
||||||
dr = self.read_register(NRF24.RF_SETUP) & (_BV(NRF24.RF_DR_LOW) | _BV(NRF24.RF_DR_HIGH))
|
|
||||||
# Order matters in our case below
|
|
||||||
if dr == _BV(NRF24.RF_DR_LOW):
|
|
||||||
# '10' = 250KBPS
|
|
||||||
return NRF24.BR_250KBPS
|
|
||||||
elif dr == _BV(NRF24.RF_DR_HIGH):
|
|
||||||
# '01' = 2MBPS
|
|
||||||
return NRF24.BR_2MBPS
|
|
||||||
else:
|
|
||||||
# '00' = 1MBPS
|
|
||||||
return NRF24.BR_1MBPS
|
|
||||||
|
|
||||||
|
|
||||||
def setCRCLength(self, length):
|
|
||||||
config = self.read_register(NRF24.CONFIG) & ~( _BV(NRF24.CRC_16) | _BV(NRF24.CRC_ENABLED))
|
|
||||||
|
|
||||||
if length == NRF24.CRC_DISABLED:
|
|
||||||
# Do nothing, we turned it off above.
|
|
||||||
self.write_register(NRF24.CONFIG, config)
|
|
||||||
return
|
|
||||||
elif length == NRF24.CRC_8:
|
|
||||||
config |= _BV(NRF24.CRC_ENABLED)
|
|
||||||
config |= _BV(NRF24.CRC_8)
|
|
||||||
else:
|
|
||||||
config |= _BV(NRF24.CRC_ENABLED)
|
|
||||||
config |= _BV(NRF24.CRC_16)
|
|
||||||
|
|
||||||
self.write_register(NRF24.CONFIG, config)
|
|
||||||
|
|
||||||
def getCRCLength(self):
|
|
||||||
result = NRF24.CRC_DISABLED
|
|
||||||
config = self.read_register(NRF24.CONFIG) & ( _BV(NRF24.CRCO) | _BV(NRF24.EN_CRC))
|
|
||||||
|
|
||||||
if config & _BV(NRF24.EN_CRC):
|
|
||||||
if config & _BV(NRF24.CRCO):
|
|
||||||
result = NRF24.CRC_16
|
|
||||||
else:
|
|
||||||
result = NRF24.CRC_8
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
def disableCRC(self):
|
|
||||||
disable = self.read_register(NRF24.CONFIG) & ~_BV(NRF24.EN_CRC)
|
|
||||||
self.write_register(NRF24.CONFIG, disable)
|
|
||||||
|
|
||||||
def setRetries(self, delay, count):
|
|
||||||
# see specs. Delay code below 5 can conflict with some ACK lengths
|
|
||||||
# and count should be set = 0 for non-ACK modes
|
|
||||||
self.write_register(NRF24.SETUP_RETR, (delay & 0xf) << NRF24.ARD | (count & 0xf))
|
|
||||||
|
|
||||||
def getRetries(self):
|
|
||||||
return self.read_register(NRF24.SETUP_RETR)
|
|
||||||
|
|
||||||
def getMaxTimeout(self): # seconds
|
|
||||||
retries = self.getRetries()
|
|
||||||
tout = (((250+(250*((retries& 0xf0)>>4 ))) * (retries & 0x0f)) / 1000000.0 * 2) + 0.008
|
|
||||||
# Fudged up to about double Barraca's calculation
|
|
||||||
# Was too short & was timeing out wrongly. BL
|
|
||||||
return tout
|
|
@ -1,35 +0,0 @@
|
|||||||
from .SmartHome import *
|
|
||||||
from ArchieCore import Response
|
|
||||||
|
|
||||||
################################################################################
|
|
||||||
|
|
||||||
@Command.new(['(открыть|открой) (окно|окна)', '(подними|поднять) (шторы|роллеты)'])
|
|
||||||
def mainLight(params):
|
|
||||||
SmartHome.send({
|
|
||||||
'target': 'main_light',
|
|
||||||
'cmd': 'light_on',
|
|
||||||
})
|
|
||||||
voice = text = ''
|
|
||||||
return Response(text = text, voice = voice)
|
|
||||||
|
|
||||||
################################################################################
|
|
||||||
|
|
||||||
@Command.new(['включи* подсветку'])
|
|
||||||
def ledOn(params):
|
|
||||||
SmartHome.send({
|
|
||||||
'target': 'led',
|
|
||||||
'cmd': 'led_on',
|
|
||||||
})
|
|
||||||
voice = text = ''
|
|
||||||
return Response(text = text, voice = voice)
|
|
||||||
|
|
||||||
################################################################################
|
|
||||||
|
|
||||||
@Command.new(['выключи* подсветку'])
|
|
||||||
def ledOff(params):
|
|
||||||
SmartHome.send({
|
|
||||||
'target': 'led',
|
|
||||||
'cmd': 'led_off',
|
|
||||||
})
|
|
||||||
voice = text = ''
|
|
||||||
return Response(text = text, voice = voice)
|
|
@ -1,24 +0,0 @@
|
|||||||
from .SmartHome import SmartHome
|
|
||||||
from Command import Command, Response
|
|
||||||
|
|
||||||
################################################################################
|
|
||||||
|
|
||||||
@Command.new(['(открыть|открой) (окно|окна)', ' подними|поднять) (шторы|роллеты)'])
|
|
||||||
def windowOpen(params):
|
|
||||||
SmartHome.send({
|
|
||||||
'target': 'window',
|
|
||||||
'cmd': 'window_open',
|
|
||||||
})
|
|
||||||
voice = text = ''
|
|
||||||
return Response(text = text, voice = voice)
|
|
||||||
|
|
||||||
################################################################################
|
|
||||||
|
|
||||||
@Command.new(['(закрыть|закрой) (окно|окна)', '(опусти|опустить) (шторы|роллеты)'])
|
|
||||||
def windowClose(params):
|
|
||||||
SmartHome.send({
|
|
||||||
'target': 'window',
|
|
||||||
'cmd': 'window_close',
|
|
||||||
})
|
|
||||||
voice = text = ''
|
|
||||||
return Response(text = text, voice = voice)
|
|
@ -1,66 +0,0 @@
|
|||||||
import sqlite3
|
|
||||||
from sqlite3 import Error
|
|
||||||
import config
|
|
||||||
|
|
||||||
def sqlite(func):
|
|
||||||
def wrapper(self, *args):
|
|
||||||
result = None
|
|
||||||
try:
|
|
||||||
connect = sqlite3.connect(config.db_name)
|
|
||||||
cursor = connect.cursor()
|
|
||||||
result = func(self, cursor, *args)
|
|
||||||
connect.commit()
|
|
||||||
except Error as er:
|
|
||||||
print('SQLite error: %s' % (' '.join(er.args)))
|
|
||||||
print("Exception class is: ", er.__class__)
|
|
||||||
print('SQLite traceback: ')
|
|
||||||
finally:
|
|
||||||
connect.close()
|
|
||||||
return result
|
|
||||||
|
|
||||||
return wrapper
|
|
||||||
|
|
||||||
class DataBase:
|
|
||||||
@sqlite
|
|
||||||
def __init__(self, cursor, table_name, columns = None):
|
|
||||||
self.table_name = table_name
|
|
||||||
if not cursor.execute(f'select * from sqlite_master WHERE type = "table" AND name = "{self.table_name}"').fetchall():
|
|
||||||
if columns:
|
|
||||||
cursor.execute(f'create table if not exists {self.table_name}(id integer PRIMARY KEY, {", ".join(columns)})')
|
|
||||||
self.columns = ['id',]+columns
|
|
||||||
else:
|
|
||||||
self.columns = [properties[1] for properties in cursor.execute(f'pragma table_info({self.table_name})').fetchall()]
|
|
||||||
|
|
||||||
|
|
||||||
@sqlite
|
|
||||||
def all(self, cursor):
|
|
||||||
rows = cursor.execute(f'select * from {self.table_name}').fetchall()
|
|
||||||
data = []
|
|
||||||
for row in rows:
|
|
||||||
dict = {}
|
|
||||||
for i, value in enumerate(row):
|
|
||||||
dict[self.columns[i]] = value
|
|
||||||
data.append(dict)
|
|
||||||
return data
|
|
||||||
|
|
||||||
@sqlite
|
|
||||||
def where(self, cursor, condition):
|
|
||||||
return cursor.execute(f'select * from {self.table_name} where {condition}')
|
|
||||||
|
|
||||||
@sqlite
|
|
||||||
def count(self, count):
|
|
||||||
return len(self.all())
|
|
||||||
|
|
||||||
@sqlite
|
|
||||||
def update(self, cursor, values, where):
|
|
||||||
updates = " ".join([f'{key} = "{value}"' for key, value in values.items()])
|
|
||||||
cursor.execute(f'update {self.table_name} set {updates} where {where}')
|
|
||||||
|
|
||||||
@sqlite
|
|
||||||
def insert(self, cursor, values):
|
|
||||||
values = ['"'+v+'"' for v in values]
|
|
||||||
cursor.execute(f'insert into {self.table_name}({", ".join(self.columns[1:])}) values({", ".join(values)})')
|
|
||||||
|
|
||||||
@sqlite
|
|
||||||
def drop(self, cursor):
|
|
||||||
cursor.execute(f'drop table if exists {self.table_name}')
|
|
@ -1 +0,0 @@
|
|||||||
from .DataBase import *
|
|
@ -1 +1,13 @@
|
|||||||
from uuid import uuid4 as UUID
|
from typing import Optional
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
class UUID(uuid.UUID):
|
||||||
|
|
||||||
|
def __new__(cls, string: Optional[str] = None):
|
||||||
|
if string:
|
||||||
|
return super().__new__(cls)
|
||||||
|
else:
|
||||||
|
return uuid.uuid1()
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return self.hex
|
||||||
|
@ -3,10 +3,10 @@ from threading import Thread
|
|||||||
import RPi.GPIO as GPIO
|
import RPi.GPIO as GPIO
|
||||||
import spidev
|
import spidev
|
||||||
|
|
||||||
# from ArchieCore import Command
|
|
||||||
from .MerlinMessage import MerlinMessage
|
from .MerlinMessage import MerlinMessage
|
||||||
from .lib_nrf24 import NRF24
|
from .lib_nrf24 import NRF24
|
||||||
|
|
||||||
|
|
||||||
GPIO.setmode(GPIO.BCM)
|
GPIO.setmode(GPIO.BCM)
|
||||||
|
|
||||||
class Merlin():
|
class Merlin():
|
||||||
@ -33,8 +33,8 @@ class Merlin():
|
|||||||
|
|
||||||
self.radio = radio
|
self.radio = radio
|
||||||
|
|
||||||
def send(self, data):
|
def send(self, message: MerlinMessage):
|
||||||
self.send_queue.append(data)
|
self.send_queue.append(message)
|
||||||
|
|
||||||
def _send(self, message: MerlinMessage):
|
def _send(self, message: MerlinMessage):
|
||||||
self.radio.stopListening()
|
self.radio.stopListening()
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
class MerlinMessage:
|
class MerlinMessage:
|
||||||
urdi: bytearray
|
urdi: bytes
|
||||||
func: int # unsigned 1 byte int 0...255
|
func: int # unsigned 1 byte int 0...255
|
||||||
arg: int # unsigned 1 byte int 0...255
|
arg: int # unsigned 1 byte int 0...255
|
||||||
|
|
||||||
|
@ -2,7 +2,6 @@ from .Singleton import *
|
|||||||
from .Identifable import *
|
from .Identifable import *
|
||||||
from .ThreadingFunction import threadingFunction
|
from .ThreadingFunction import threadingFunction
|
||||||
#from .StringProcesser import *
|
#from .StringProcesser import *
|
||||||
from .DataBase import *
|
|
||||||
from .DispatchQueue import *
|
from .DispatchQueue import *
|
||||||
from .Notifications import *
|
from .Notifications import *
|
||||||
from .SpeechRecognition import *
|
from .SpeechRecognition import *
|
||||||
|
154
SmartHome/DBTable.py
Normal file
154
SmartHome/DBTable.py
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
from typing import List, Dict, Any, Optional
|
||||||
|
import sqlite3
|
||||||
|
from sqlite3 import Error
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
try: from . import config
|
||||||
|
except ImportError: import config
|
||||||
|
|
||||||
|
|
||||||
|
def eprint(*args, **kwargs):
|
||||||
|
if config.sql_exceptions_enabled:
|
||||||
|
print(*args, **kwargs)
|
||||||
|
|
||||||
|
def lprint(*args, **kwargs):
|
||||||
|
if config.sql_logs_enabled:
|
||||||
|
print(*args, **kwargs)
|
||||||
|
|
||||||
|
def sqlite(func):
|
||||||
|
def wrapper(self, *args, **kwargs):
|
||||||
|
result = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
connect = sqlite3.connect(f'{config.db_name}.sqlite')
|
||||||
|
cursor = connect.cursor()
|
||||||
|
|
||||||
|
cursor.execute('pragma foreign_keys = on')
|
||||||
|
connect.commit()
|
||||||
|
|
||||||
|
sql_list = []
|
||||||
|
|
||||||
|
def execute(sql: str):
|
||||||
|
lprint(f'Execute: "{sql}"')
|
||||||
|
sql_list.append(sql)
|
||||||
|
return cursor.execute(sql)
|
||||||
|
|
||||||
|
result = func(self, execute, *args, **kwargs)
|
||||||
|
connect.commit()
|
||||||
|
|
||||||
|
except Error as e:
|
||||||
|
eprint('-'*25, ' ERROR ', '-'*26)
|
||||||
|
eprint(f'SQLite error: {" ".join(e.args)}')
|
||||||
|
eprint(f'Exception class is: {e.__class__}')
|
||||||
|
eprint(f'SQL Request is: "{sql_list.pop()}"')
|
||||||
|
eprint(f'\nTraceback: {traceback.format_exc()}')
|
||||||
|
eprint('-'*60)
|
||||||
|
except Exception as e:
|
||||||
|
eprint('-'*23, ' EXCEPTION ', '-'*24)
|
||||||
|
eprint(f'Exception args: {" ".join(e.args)}')
|
||||||
|
eprint(f'Exception class is {e.__class__}')
|
||||||
|
eprint(f'\nTraceback: {traceback.format_exc()}')
|
||||||
|
eprint('-'*60)
|
||||||
|
finally:
|
||||||
|
connect.close()
|
||||||
|
return result
|
||||||
|
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
|
class DBTable:
|
||||||
|
table_name: str
|
||||||
|
columns: List[str]
|
||||||
|
id_type = 'text'
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
@sqlite
|
||||||
|
def sql_request(cls, execute, string: str) -> Any:
|
||||||
|
return execute(string).fetchall()
|
||||||
|
|
||||||
|
# -------------- Create ---------------
|
||||||
|
|
||||||
|
@sqlite
|
||||||
|
def __init__(self, execute, table_name: str, columns: Dict[str, str]):
|
||||||
|
self.table_name = table_name
|
||||||
|
if not execute(f'select * from sqlite_master WHERE type = "table" AND name = "{self.table_name}"').fetchall():
|
||||||
|
if columns:
|
||||||
|
columns_types = ', '.join([f'{name} {args}' for name, args in columns.items()])
|
||||||
|
execute(f'create table if not exists {self.table_name}(id {self.id_type} primary key, {columns_types})')
|
||||||
|
self.columns = ['id', *columns.keys()]
|
||||||
|
else:
|
||||||
|
self.columns = [properties[1] for properties in execute(f'pragma table_info({self.table_name})').fetchall()]
|
||||||
|
|
||||||
|
@sqlite
|
||||||
|
def create(self, execute, dict: Dict[str, Any]):
|
||||||
|
values = [f'"{v}"' for v in dict.values()]
|
||||||
|
execute(f'insert into {self.table_name}({", ".join(self.columns)}) values({", ".join(values)})')
|
||||||
|
|
||||||
|
# -------------- Read ---------------
|
||||||
|
|
||||||
|
def get(self, id: str) -> Optional[Dict[str, Any]]:
|
||||||
|
return self.first(where = f'id = "{id}"')
|
||||||
|
|
||||||
|
@sqlite
|
||||||
|
def first(self, execute, where: str) -> Optional[Dict[str, Any]]:
|
||||||
|
return self._parsedone(execute(f'select * from {self.table_name} where {where} limit 1').fetchone())
|
||||||
|
|
||||||
|
@sqlite
|
||||||
|
def where(self, execute, condition: str) -> List[Any]:
|
||||||
|
return self._parsed(execute(f'select * from {self.table_name} where {condition}').fetchall())
|
||||||
|
|
||||||
|
@sqlite
|
||||||
|
def all(self, execute) -> Dict[str, Any]:
|
||||||
|
return self._parsed(execute(f'select * from {self.table_name}').fetchall())
|
||||||
|
|
||||||
|
@sqlite
|
||||||
|
def count(self, execute) -> int:
|
||||||
|
return execute(f'select count(*) from {self.table_name}')
|
||||||
|
|
||||||
|
@sqlite
|
||||||
|
def countWhere(self, execute, condition: str) -> int:
|
||||||
|
return execute(f'select count(*) from {self.table_name} where {condition}')
|
||||||
|
|
||||||
|
# -------------- Update ---------------
|
||||||
|
|
||||||
|
@sqlite
|
||||||
|
def update(self, execute, values: Dict[str, Any], id: Optional[Any] = None):
|
||||||
|
id = values.get('id') or id
|
||||||
|
assert id != None
|
||||||
|
updates = ', '.join([f'{key} = "{value}"' for key, value in values.items() if key != 'id'])
|
||||||
|
execute(f'update {self.table_name} set {updates} where id = "{id}"')
|
||||||
|
|
||||||
|
@sqlite
|
||||||
|
def updateWhere(self, execute, values: Dict[str, Any], where: str):
|
||||||
|
updates = " ".join([f'{key} = "{value}"' for key, value in values.items()])
|
||||||
|
execute(f'update {self.table_name} set {updates} where {where}')
|
||||||
|
|
||||||
|
# -------------- Delete ---------------
|
||||||
|
|
||||||
|
@sqlite
|
||||||
|
def drop(self, execute):
|
||||||
|
execute(f'drop table if exists {self.table_name}')
|
||||||
|
|
||||||
|
@sqlite
|
||||||
|
def delete(self, execute, where: str):
|
||||||
|
execute(f'delete from {self.table_name} where {where}')
|
||||||
|
|
||||||
|
# -------------- Advanced ---------------
|
||||||
|
|
||||||
|
@sqlite
|
||||||
|
def alter(self, execute, to: DBModel, foreignKey: str, onDelete: str, onUpdate: str):
|
||||||
|
execute(f'alter table {self.table_name} add foreign key ({foreignKey}) references {to.table_name}(id) on delete {onDelete} on update {onUpdate}')
|
||||||
|
|
||||||
|
# -------------- Private -----------------
|
||||||
|
|
||||||
|
def _parsed(self, rows) -> List[Dict[str, Any]]:
|
||||||
|
return [self._parsedone(row) for row in rows]
|
||||||
|
|
||||||
|
def _parsedone(self, row) -> Dict[str, Any]:
|
||||||
|
dict = {}
|
||||||
|
if not row:
|
||||||
|
return dict
|
||||||
|
for i, value in enumerate(row):
|
||||||
|
dict[self.columns[i]] = value
|
||||||
|
return dict
|
18
SmartHome/__init__.py
Normal file
18
SmartHome/__init__.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
from .models import (
|
||||||
|
User,
|
||||||
|
House,
|
||||||
|
Room,
|
||||||
|
Device,
|
||||||
|
DeviceModel,
|
||||||
|
Parameter,
|
||||||
|
ParameterType
|
||||||
|
)
|
||||||
|
|
||||||
|
from .managers import (
|
||||||
|
UsersManager,
|
||||||
|
HousesManager,
|
||||||
|
RoomsManager,
|
||||||
|
DevicesManager,
|
||||||
|
DeviceModelsManager,
|
||||||
|
ParametersManager
|
||||||
|
)
|
168
SmartHome/managers.py
Normal file
168
SmartHome/managers.py
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
from .DBTable import DBTable
|
||||||
|
from . import tables
|
||||||
|
from .models import *
|
||||||
|
|
||||||
|
class UsersManager:
|
||||||
|
user: User
|
||||||
|
|
||||||
|
def __init__(self, user: User):
|
||||||
|
self.user = user
|
||||||
|
|
||||||
|
@property
|
||||||
|
@classmethod
|
||||||
|
def all(cls) -> List[Room]:
|
||||||
|
return [cls.fromDict(dict) for dict in tables.houses.all()]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get(cls, id: UUID) -> Optional[House]:
|
||||||
|
return cls.fromDict(tables.houses.get(id))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def fromDict(cls, dict) -> User:
|
||||||
|
user = User()
|
||||||
|
user.id = UUID(dict['id'])
|
||||||
|
user.name = dict['name']
|
||||||
|
return user
|
||||||
|
|
||||||
|
@property
|
||||||
|
def houses() -> [Room]:
|
||||||
|
return [cls.fromDict(dict) for dict in tables.houses.where(f'id = {self.id}')]
|
||||||
|
|
||||||
|
class HousesManager:
|
||||||
|
house: House
|
||||||
|
|
||||||
|
def __init__(self, house: House):
|
||||||
|
self.house = house
|
||||||
|
|
||||||
|
@property
|
||||||
|
@classmethod
|
||||||
|
def all(cls) -> List[Room]:
|
||||||
|
return [cls.fromDict(dict) for dict in tables.houses.all()]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get(cls, id: UUID) -> Optional[House]:
|
||||||
|
return cls.fromDict(tables.houses.get(id))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def fromDict(cls, dict) -> House:
|
||||||
|
house = House()
|
||||||
|
house.id = UUID(dict['id'])
|
||||||
|
house.name = dict['name']
|
||||||
|
house.owner_id = UsersManager.get(dict['owner_id'])
|
||||||
|
return house
|
||||||
|
|
||||||
|
@property
|
||||||
|
def rooms() -> [Room]:
|
||||||
|
return [cls.fromDict(dict) for dict in tables.rooms.where(f'id = {self.id}')]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def owner(self) -> User:
|
||||||
|
return UsersManager.get(self.owner_id)
|
||||||
|
|
||||||
|
class RoomsManager:
|
||||||
|
room: Room
|
||||||
|
|
||||||
|
def __init__(self, room: Room):
|
||||||
|
self.room = room
|
||||||
|
|
||||||
|
@property
|
||||||
|
@classmethod
|
||||||
|
def all(cls) -> List[Room]:
|
||||||
|
return [cls.fromDict(dict) for dict in tables.rooms.all()]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get(cls, id: UUID) -> Room:
|
||||||
|
return cls.fromDict(tables.rooms.get(id))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def fromDict(cls, dict) -> Room:
|
||||||
|
room = Room()
|
||||||
|
room.id = UUID(dict['id'])
|
||||||
|
room.name = dict['name']
|
||||||
|
return room
|
||||||
|
|
||||||
|
@property
|
||||||
|
def devices() -> List[Device]:
|
||||||
|
[cls.fromDict(dict) for dict in tables.devices.where(f'id = {self.id}')]
|
||||||
|
|
||||||
|
class DevicesManager:
|
||||||
|
device: Device
|
||||||
|
|
||||||
|
def __init__(self, device: Device):
|
||||||
|
self.device = device
|
||||||
|
|
||||||
|
@property
|
||||||
|
@classmethod
|
||||||
|
def all(cls) -> List[Device]:
|
||||||
|
return [cls.fromDict(dict) for dict in tables.devices.all()]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get(cls, id: UUID) -> Optional[Device]:
|
||||||
|
return cls.fromDict(tables.devices.get(id))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def fromDict(cls, dict) -> Device:
|
||||||
|
device = Device()
|
||||||
|
device.id = UUID(dict['id'])
|
||||||
|
device.name = dict['name']
|
||||||
|
device.urdi = dict['urdi']
|
||||||
|
device.room_id = UUID(dict['room_id'])
|
||||||
|
device.model_id = UUID(dict['model_id'])
|
||||||
|
return device
|
||||||
|
|
||||||
|
@property
|
||||||
|
def room(self) -> Room:
|
||||||
|
return RoomsManager.get(self.room_id)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def model(self) -> DeviceModel:
|
||||||
|
return DeviceModelsManager.get(self.model_id)
|
||||||
|
|
||||||
|
class DeviceModelsManager:
|
||||||
|
deviceModel: DeviceModel
|
||||||
|
|
||||||
|
def __init__(self, deviceModel: DeviceModel):
|
||||||
|
self.deviceModel = deviceModel
|
||||||
|
|
||||||
|
@property
|
||||||
|
@classmethod
|
||||||
|
def all(cls) -> List[DeviceModel]:
|
||||||
|
return [cls.fromDict(dict) for dict in tables.deviceModels.all()]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get(cls, id: UUID) -> Optional[DeviceModel]:
|
||||||
|
return cls.fromDict(tables.deviceModels.get(id))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def fromDict(cls, dict) -> DeviceModel:
|
||||||
|
deviceModel = DeviceModel()
|
||||||
|
deviceModel.id = UUID(dict['id'])
|
||||||
|
deviceModel.name = dict['name']
|
||||||
|
return deviceModel
|
||||||
|
|
||||||
|
@property
|
||||||
|
def parameters() -> List[Parameter]:
|
||||||
|
[cls.fromDict(dict) for dict in tables.parameters.where(f'id = {self.id}')]
|
||||||
|
|
||||||
|
class ParametersManager:
|
||||||
|
parameter: Parameter
|
||||||
|
|
||||||
|
def __init__(self, parameter: Parameter):
|
||||||
|
self.parameter = parameter
|
||||||
|
|
||||||
|
@property
|
||||||
|
@classmethod
|
||||||
|
def all(cls) -> List[Parameter]:
|
||||||
|
return [cls.fromDict(dict) for dict in tables.parameters.all()]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get(cls, id: UUID) -> Optional[Parameter]:
|
||||||
|
return cls.fromDict(tables.parameters.get(id))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def fromDict(cls, dict) -> Parameter:
|
||||||
|
parameter = Parameter()
|
||||||
|
parameter.id = UUID(dict['id'])
|
||||||
|
parameter.name = dict['name']
|
||||||
|
parameter.type = ParameterType(dict['type'])
|
||||||
|
return deviceModel
|
48
SmartHome/models.py
Normal file
48
SmartHome/models.py
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
from __future__ import typing
|
||||||
|
from ABC import ABC
|
||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
from General import Identifable, UUID
|
||||||
|
|
||||||
|
class NamedIdentifable(Identifable, ABC):
|
||||||
|
name: str
|
||||||
|
|
||||||
|
class User(NamedIdentifable):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class House(NamedIdentifable):
|
||||||
|
owner_id: UUID
|
||||||
|
|
||||||
|
class Room(NamedIdentifable):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class Device(NamedIdentifable):
|
||||||
|
urdi: bytes
|
||||||
|
room_id: UUID
|
||||||
|
model_id: UUID
|
||||||
|
|
||||||
|
class DeviceModel(NamedIdentifable):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class Parameter(NamedIdentifable):
|
||||||
|
type: ParameterType
|
||||||
|
|
||||||
|
class ParameterType(enum):
|
||||||
|
|
||||||
|
bool = 'bool'
|
||||||
|
percentage = 'percentage'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def valueType(self) -> class:
|
||||||
|
match self:
|
||||||
|
case ParameterType.bool:
|
||||||
|
return bool
|
||||||
|
case ParameterType.percentage:
|
||||||
|
return float
|
||||||
|
|
||||||
|
def toByte(value: Parameter.type.valueType) -> byte:
|
||||||
|
match self:
|
||||||
|
case ParameterType.bool:
|
||||||
|
return 0x01 if value else 0x00
|
||||||
|
case ParameterType.percentage:
|
||||||
|
return int(value * 255).to_bytes(1, byteorder = 'big')
|
23
SmartHome/tables.py
Normal file
23
SmartHome/tables.py
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
houses = DBTable('houses', {
|
||||||
|
'name': 'text',
|
||||||
|
'owner_id': 'text'
|
||||||
|
})
|
||||||
|
|
||||||
|
rooms = DBTable('rooms', {
|
||||||
|
'name': 'text',
|
||||||
|
})
|
||||||
|
|
||||||
|
devices = DBTable('devices', {
|
||||||
|
'name': 'text',
|
||||||
|
'urdi': 'blob',
|
||||||
|
'room_id': 'text',
|
||||||
|
'model_id': 'text'
|
||||||
|
})
|
||||||
|
|
||||||
|
deviceModels = DBTable('device_models', {
|
||||||
|
'name': 'text',
|
||||||
|
})
|
||||||
|
|
||||||
|
parameters = DBTable('parameters', {
|
||||||
|
'name': 'text',
|
||||||
|
})
|
@ -16,5 +16,5 @@ names = ['арчи', 'archie']
|
|||||||
#################################################
|
#################################################
|
||||||
# Django
|
# Django
|
||||||
django_secret_key = '-----123456789-----'
|
django_secret_key = '-----123456789-----'
|
||||||
django_debug = True
|
django_debug_enabled = True
|
||||||
django_allowed_hosts = []
|
django_allowed_hosts = []
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
Python 3.10
|
Python 3.10
|
||||||
|
|
||||||
|
|
||||||
# general
|
# general
|
||||||
requests
|
requests
|
||||||
aiohttp
|
aiohttp
|
||||||
|
|
||||||
|
|
||||||
# voice assistant
|
# voice assistant
|
||||||
pip install sounddevice
|
pip install sounddevice
|
||||||
pip install soundfile
|
pip install soundfile
|
||||||
@ -11,23 +13,34 @@ pip install numpy
|
|||||||
pip install vosk
|
pip install vosk
|
||||||
# download model from https://alphacephei.com/vosk/models
|
# download model from https://alphacephei.com/vosk/models
|
||||||
|
|
||||||
|
|
||||||
# tts
|
# tts
|
||||||
pip install google-cloud-texttospeech
|
pip install google-cloud-texttospeech
|
||||||
|
|
||||||
|
|
||||||
|
# Django
|
||||||
|
pip install django
|
||||||
|
pip install django-rest-framework
|
||||||
|
pip install djangorestframework-simplejwt
|
||||||
|
|
||||||
# telegram
|
# telegram
|
||||||
pip install PyTelegramBotApi
|
pip install PyTelegramBotApi
|
||||||
|
|
||||||
|
|
||||||
# QA
|
# QA
|
||||||
pip install bs4
|
pip install bs4
|
||||||
pip install wikipedia
|
pip install wikipedia
|
||||||
|
|
||||||
|
|
||||||
# Zieit
|
# Zieit
|
||||||
pip install xlrd
|
pip install xlrd
|
||||||
pip install xlwt
|
pip install xlwt
|
||||||
pip install xlutils
|
pip install xlutils
|
||||||
|
|
||||||
|
|
||||||
# Media
|
# Media
|
||||||
|
pip install aiohttp
|
||||||
pip install pafy
|
pip install pafy
|
||||||
pip install screeninfo
|
pip install screeninfo
|
||||||
pip install psutil
|
pip install psutil
|
||||||
pip install youtube-dl
|
pip install yt_dlp #pip install youtube-dl
|
||||||
|
@ -6,7 +6,7 @@ import sys
|
|||||||
|
|
||||||
def main():
|
def main():
|
||||||
"""Run administrative tasks."""
|
"""Run administrative tasks."""
|
||||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'archieapi.settings')
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'Controls.Django.Main.settings')
|
||||||
try:
|
try:
|
||||||
from django.core.management import execute_from_command_line
|
from django.core.management import execute_from_command_line
|
||||||
except ImportError as exc:
|
except ImportError as exc:
|
Loading…
Reference in New Issue
Block a user