Project
This commit is contained in:
164
.gitignore
vendored
Normal file
164
.gitignore
vendored
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
# Byte-compiled / optimized / DLL files
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
|
||||||
|
# C extensions
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Distribution / packaging
|
||||||
|
.Python
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
share/python-wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
MANIFEST
|
||||||
|
|
||||||
|
# PyInstaller
|
||||||
|
# Usually these files are written by a python script from a template
|
||||||
|
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||||
|
*.manifest
|
||||||
|
*.spec
|
||||||
|
|
||||||
|
# Installer logs
|
||||||
|
pip-log.txt
|
||||||
|
pip-delete-this-directory.txt
|
||||||
|
|
||||||
|
# Unit test / coverage reports
|
||||||
|
htmlcov/
|
||||||
|
.tox/
|
||||||
|
.nox/
|
||||||
|
.coverage
|
||||||
|
.coverage.*
|
||||||
|
.cache
|
||||||
|
nosetests.xml
|
||||||
|
coverage.xml
|
||||||
|
*.cover
|
||||||
|
*.py,cover
|
||||||
|
.hypothesis/
|
||||||
|
.pytest_cache/
|
||||||
|
cover/
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
*.mo
|
||||||
|
*.pot
|
||||||
|
|
||||||
|
# Django stuff:
|
||||||
|
*.log
|
||||||
|
local_settings.py
|
||||||
|
db.sqlite3
|
||||||
|
db.sqlite3-journal
|
||||||
|
|
||||||
|
# Flask stuff:
|
||||||
|
instance/
|
||||||
|
.webassets-cache
|
||||||
|
|
||||||
|
# Scrapy stuff:
|
||||||
|
.scrapy
|
||||||
|
|
||||||
|
# Sphinx documentation
|
||||||
|
docs/_build/
|
||||||
|
|
||||||
|
# PyBuilder
|
||||||
|
.pybuilder/
|
||||||
|
target/
|
||||||
|
|
||||||
|
# Jupyter Notebook
|
||||||
|
.ipynb_checkpoints
|
||||||
|
|
||||||
|
# IPython
|
||||||
|
profile_default/
|
||||||
|
ipython_config.py
|
||||||
|
|
||||||
|
# pyenv
|
||||||
|
# For a library or package, you might want to ignore these files since the code is
|
||||||
|
# intended to run in multiple environments; otherwise, check them in:
|
||||||
|
# .python-version
|
||||||
|
|
||||||
|
# pipenv
|
||||||
|
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||||
|
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||||
|
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||||
|
# install all needed dependencies.
|
||||||
|
#Pipfile.lock
|
||||||
|
|
||||||
|
# poetry
|
||||||
|
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||||
|
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||||
|
# commonly ignored for libraries.
|
||||||
|
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||||
|
#poetry.lock
|
||||||
|
|
||||||
|
# pdm
|
||||||
|
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||||
|
#pdm.lock
|
||||||
|
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
||||||
|
# in version control.
|
||||||
|
# https://pdm.fming.dev/#use-with-ide
|
||||||
|
.pdm.toml
|
||||||
|
|
||||||
|
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||||
|
__pypackages__/
|
||||||
|
|
||||||
|
# Celery stuff
|
||||||
|
celerybeat-schedule
|
||||||
|
celerybeat.pid
|
||||||
|
|
||||||
|
# SageMath parsed files
|
||||||
|
*.sage.py
|
||||||
|
|
||||||
|
# Environments
|
||||||
|
.env
|
||||||
|
.venv
|
||||||
|
env/
|
||||||
|
venv/
|
||||||
|
ENV/
|
||||||
|
env.bak/
|
||||||
|
venv.bak/
|
||||||
|
|
||||||
|
# Spyder project settings
|
||||||
|
.spyderproject
|
||||||
|
.spyproject
|
||||||
|
|
||||||
|
# Rope project settings
|
||||||
|
.ropeproject
|
||||||
|
|
||||||
|
# mkdocs documentation
|
||||||
|
/site
|
||||||
|
|
||||||
|
# mypy
|
||||||
|
.mypy_cache/
|
||||||
|
.dmypy.json
|
||||||
|
dmypy.json
|
||||||
|
|
||||||
|
# Pyre type checker
|
||||||
|
.pyre/
|
||||||
|
|
||||||
|
# pytype static type analyzer
|
||||||
|
.pytype/
|
||||||
|
|
||||||
|
# Cython debug symbols
|
||||||
|
cython_debug/
|
||||||
|
|
||||||
|
# PyCharm
|
||||||
|
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||||
|
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||||
|
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||||
|
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||||
|
#.idea/
|
||||||
|
secrets.py
|
||||||
|
settings.py
|
||||||
|
|
||||||
|
.vscode/Pico-W-Stub
|
||||||
3
.micropico
Normal file
3
.micropico
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"info": "This file is just used to identify a project folder."
|
||||||
|
}
|
||||||
8
.vscode/extensions.json
vendored
Normal file
8
.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"recommendations": [
|
||||||
|
"ms-python.python",
|
||||||
|
"visualstudioexptteam.vscodeintellicode",
|
||||||
|
"ms-python.vscode-pylance",
|
||||||
|
"paulober.pico-w-go"
|
||||||
|
]
|
||||||
|
}
|
||||||
22
.vscode/settings.json
vendored
Normal file
22
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"python.languageServer": "Pylance",
|
||||||
|
"python.analysis.diagnosticSeverityOverrides": {
|
||||||
|
"reportMissingModuleSource": "none"
|
||||||
|
},
|
||||||
|
"python.analysis.extraPaths": [
|
||||||
|
"c:\\Users\\adakovacs\\.vscode\\extensions\\joedevivo.vscode-circuitpython-0.1.20-win32-x64\\boards\\0x239A\\0x8120",
|
||||||
|
"c:\\Users\\adakovacs\\.vscode\\extensions\\joedevivo.vscode-circuitpython-0.1.20-win32-x64\\stubs",
|
||||||
|
"c:\\Users\\adakovacs\\AppData\\Roaming\\Code\\User\\globalStorage\\joedevivo.vscode-circuitpython\\bundle\\20231124\\adafruit-circuitpython-bundle-py-20231124\\lib",
|
||||||
|
".vscode\\Pico-W-Stub"
|
||||||
|
],
|
||||||
|
"circuitpython.board.version": null,
|
||||||
|
"circuitpython.board.vid": "0x239A",
|
||||||
|
"circuitpython.board.pid": "0x8120",
|
||||||
|
"python.linting.enabled": true,
|
||||||
|
"python.analysis.typeCheckingMode": "basic",
|
||||||
|
"micropico.syncFolder": "",
|
||||||
|
"micropico.openOnStart": true,
|
||||||
|
"python.analysis.typeshedPaths": [
|
||||||
|
".vscode\\Pico-W-Stub"
|
||||||
|
]
|
||||||
|
}
|
||||||
104
led.py
Normal file
104
led.py
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
import utime
|
||||||
|
import random
|
||||||
|
from machine import Pin
|
||||||
|
|
||||||
|
def time_ms() -> int:
|
||||||
|
return int(utime.ticks_ms())
|
||||||
|
|
||||||
|
""" global led1
|
||||||
|
global led2
|
||||||
|
global led3
|
||||||
|
global led4
|
||||||
|
global led1_state
|
||||||
|
global led2_state
|
||||||
|
global led3_state
|
||||||
|
global led4_state
|
||||||
|
global next_on_time_1
|
||||||
|
global next_on_time_2
|
||||||
|
global next_on_time_3
|
||||||
|
global next_on_time_4 """
|
||||||
|
|
||||||
|
led1 = Pin(2, Pin.OUT)
|
||||||
|
led2 = Pin(3, Pin.OUT)
|
||||||
|
led3 = Pin(4, Pin.OUT)
|
||||||
|
led4 = Pin(5, Pin.OUT)
|
||||||
|
|
||||||
|
current_time = time_ms()
|
||||||
|
led1_last_time = current_time
|
||||||
|
led2_last_time = current_time
|
||||||
|
led3_last_time = current_time
|
||||||
|
led4_last_time = current_time
|
||||||
|
|
||||||
|
led1_state = 0
|
||||||
|
led2_state = 0
|
||||||
|
led3_state = 0
|
||||||
|
led4_state = 0
|
||||||
|
|
||||||
|
next_on_time_1 = current_time + 1000
|
||||||
|
next_on_time_2 = current_time + 1000
|
||||||
|
next_on_time_3 = current_time + 1000
|
||||||
|
next_on_time_4 = current_time + 1000
|
||||||
|
|
||||||
|
def single_led_test(led):
|
||||||
|
led.value(0)
|
||||||
|
utime.sleep_ms(400)
|
||||||
|
led.value(1)
|
||||||
|
utime.sleep_ms(400)
|
||||||
|
|
||||||
|
def led_test():
|
||||||
|
single_led_test(led1)
|
||||||
|
single_led_test(led2)
|
||||||
|
single_led_test(led3)
|
||||||
|
single_led_test(led4)
|
||||||
|
|
||||||
|
def update_led(state, next_on_time, led):
|
||||||
|
current_time = time_ms()
|
||||||
|
|
||||||
|
try:
|
||||||
|
if state == 0:
|
||||||
|
if next_on_time <= current_time:
|
||||||
|
state = 1
|
||||||
|
next_on_time = current_time + random.randint(100, 400)
|
||||||
|
led.value(0)
|
||||||
|
else:
|
||||||
|
if next_on_time <= current_time:
|
||||||
|
state = 0
|
||||||
|
led.value(1)
|
||||||
|
next_on_time = current_time + random.randint(100, 2000)
|
||||||
|
except Exception as e:
|
||||||
|
print('Error in update_led: ' + str(e))
|
||||||
|
pass
|
||||||
|
|
||||||
|
return state, next_on_time
|
||||||
|
|
||||||
|
def update_leds():
|
||||||
|
global led1
|
||||||
|
global led2
|
||||||
|
global led3
|
||||||
|
global led4
|
||||||
|
global led1_state
|
||||||
|
global led2_state
|
||||||
|
global led3_state
|
||||||
|
global led4_state
|
||||||
|
global next_on_time_1
|
||||||
|
global next_on_time_2
|
||||||
|
global next_on_time_3
|
||||||
|
global next_on_time_4
|
||||||
|
|
||||||
|
(led1_state, next_on_time_1) = update_led(led1_state, next_on_time_1, led1)
|
||||||
|
(led2_state, next_on_time_2) = update_led(led2_state, next_on_time_2, led2)
|
||||||
|
(led3_state, next_on_time_3) = update_led(led3_state, next_on_time_3, led3)
|
||||||
|
(led4_state, next_on_time_4) = update_led(led4_state, next_on_time_4, led4)
|
||||||
|
|
||||||
|
def led_init():
|
||||||
|
global led1
|
||||||
|
global led2
|
||||||
|
global led3
|
||||||
|
global led4
|
||||||
|
|
||||||
|
print('Led init')
|
||||||
|
|
||||||
|
led1.value(1)
|
||||||
|
led2.value(1)
|
||||||
|
led3.value(1)
|
||||||
|
led4.value(1)
|
||||||
379
main1.py
Normal file
379
main1.py
Normal file
@@ -0,0 +1,379 @@
|
|||||||
|
# The MIT License (MIT)
|
||||||
|
# Copyright (c) 2022 Mike Teachman
|
||||||
|
# https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
|
# Purpose: Play a pure audio tone out of a speaker or headphones
|
||||||
|
#
|
||||||
|
# - write audio samples_0 containing a pure tone to an I2S amplifier or DAC module
|
||||||
|
# - tone will play continuously in a loop until
|
||||||
|
# a keyboard interrupt is detected or the board is reset
|
||||||
|
#
|
||||||
|
# Blocking version
|
||||||
|
# - the write() method blocks until the entire sample buffer is written to I2S
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import math
|
||||||
|
import struct
|
||||||
|
import utime
|
||||||
|
import micropython_ota
|
||||||
|
import machine
|
||||||
|
import requests
|
||||||
|
import ubinascii
|
||||||
|
import network
|
||||||
|
import random
|
||||||
|
import _thread
|
||||||
|
import gc
|
||||||
|
from led import led_init, led_test, time_ms, update_leds
|
||||||
|
from machine import I2S
|
||||||
|
from machine import Pin
|
||||||
|
from time import sleep
|
||||||
|
from settings import settings
|
||||||
|
from secrets import secrets
|
||||||
|
|
||||||
|
ota_project_name = 'pestrep'
|
||||||
|
ota_branch = settings['ota_branch']
|
||||||
|
ota_soft_reset_device=False
|
||||||
|
|
||||||
|
ntfy_topic = settings['ntfy_topic']
|
||||||
|
|
||||||
|
def make_tone(rate, bits, frequency):
|
||||||
|
# create a buffer containing the pure tone samples_0
|
||||||
|
samples_0_per_cycle = rate // frequency
|
||||||
|
sample_size_in_bytes = bits // 8
|
||||||
|
samples_0 = bytearray(int(samples_0_per_cycle * sample_size_in_bytes))
|
||||||
|
volume_reduction_factor = 32 # This sound be 32 while developing
|
||||||
|
range = pow(2, bits) // 2 // volume_reduction_factor
|
||||||
|
|
||||||
|
if bits == 16:
|
||||||
|
format = "<h"
|
||||||
|
else: # assume 32 bits
|
||||||
|
format = "<l"
|
||||||
|
|
||||||
|
for i in range(samples_0_per_cycle):
|
||||||
|
sample = range + int((range - 1) * math.cos(2 * math.pi * i / samples_0_per_cycle))
|
||||||
|
#sample = int((math.sin(math.pi * 2 * i / samples_0_per_cycle)) * 0.1 * (2 ** 15 - 1))
|
||||||
|
struct.pack_into(format, samples_0, i * sample_size_in_bytes, sample)
|
||||||
|
|
||||||
|
return samples_0
|
||||||
|
|
||||||
|
# Define blinking function for onboard LED to indicate error codes
|
||||||
|
def blink_onboard_led(num_blinks):
|
||||||
|
led = machine.Pin('LED', machine.Pin.OUT)
|
||||||
|
for i in range(num_blinks):
|
||||||
|
led.value(1)
|
||||||
|
utime.sleep_ms(200)
|
||||||
|
led.value(0)
|
||||||
|
utime.sleep_ms(200)
|
||||||
|
|
||||||
|
def connect_wifi():
|
||||||
|
wlan.active(True)
|
||||||
|
# If you need to disable powersaving mode
|
||||||
|
# wlan.config(pm = 0xa11140)
|
||||||
|
|
||||||
|
# See the MAC address in the wireless chip OTP
|
||||||
|
mac = ubinascii.hexlify(wlan.config('mac'),':').decode()
|
||||||
|
print('mac = ' + mac)
|
||||||
|
|
||||||
|
# Other things to query
|
||||||
|
# print(wlan.config('channel'))
|
||||||
|
# print(wlan.config('essid'))
|
||||||
|
# print(wlan.config('txpower'))
|
||||||
|
|
||||||
|
# Load login data from different file for safety reasons
|
||||||
|
pw = secrets['pw']
|
||||||
|
|
||||||
|
wlan.connect(ssid, pw)
|
||||||
|
|
||||||
|
# Wait for connection with 10 second timeout
|
||||||
|
timeout = 20
|
||||||
|
while timeout > 0:
|
||||||
|
if wlan.status() < 0 or wlan.status() >= 3:
|
||||||
|
break
|
||||||
|
timeout -= 1
|
||||||
|
print('Waiting for connection...')
|
||||||
|
blink_onboard_led(2)
|
||||||
|
utime.sleep(1)
|
||||||
|
# Handle connection error
|
||||||
|
# Error meanings
|
||||||
|
# 0 Link Down
|
||||||
|
# 1 Link Join
|
||||||
|
# 2 Link NoIp
|
||||||
|
# 3 Link Up
|
||||||
|
# -1 Link Fail
|
||||||
|
# -2 Link NoNet
|
||||||
|
# -3 Link BadAuth
|
||||||
|
|
||||||
|
wlan_status = wlan.status()
|
||||||
|
blink_onboard_led(wlan_status)
|
||||||
|
|
||||||
|
if wlan_status != 3:
|
||||||
|
blink_onboard_led(5)
|
||||||
|
raise RuntimeError('Wi-Fi connection failed')
|
||||||
|
else:
|
||||||
|
blink_onboard_led(1)
|
||||||
|
print('Connected')
|
||||||
|
status = wlan.ifconfig()
|
||||||
|
print('ip = ' + status[0])
|
||||||
|
|
||||||
|
def disconnect_wifi():
|
||||||
|
wlan.disconnect()
|
||||||
|
wlan.active(False)
|
||||||
|
wlan.deinit()
|
||||||
|
print('Disconnected')
|
||||||
|
|
||||||
|
def send_notification(title, tags):
|
||||||
|
try:
|
||||||
|
send_notification_to_server('https://ntfy.sh/' + ntfy_topic, title, tags)
|
||||||
|
except:
|
||||||
|
print('Error sending notification')
|
||||||
|
blink_onboard_led(3)
|
||||||
|
send_notification_to_server('https://ntfy.adix.link/' + ntfy_topic, title, tags)
|
||||||
|
|
||||||
|
def send_notification_to_server(notify_url, title, tags):
|
||||||
|
print('Sending notification to ' + notify_url + '...')
|
||||||
|
# Send notification
|
||||||
|
request = requests.post(notify_url, data="Csengo", headers={
|
||||||
|
'Title': title,
|
||||||
|
'Priority': '5',
|
||||||
|
'X-Tags': tags
|
||||||
|
})
|
||||||
|
print(request.content)
|
||||||
|
request.close()
|
||||||
|
|
||||||
|
# ======= I2S CONFIGURATION =======
|
||||||
|
SCK_PIN = 16 #BCLK
|
||||||
|
WS_PIN = 17 #WSEL/LRC
|
||||||
|
SD_PIN = 18 #DIN
|
||||||
|
I2S_ID = 0
|
||||||
|
BUFFER_LENGTH_IN_BYTES = 2000
|
||||||
|
|
||||||
|
# ======= AUDIO CONFIGURATION =======
|
||||||
|
SAMPLE_SIZE_IN_BITS = 16
|
||||||
|
FORMAT = I2S.MONO # only MONO supported in this example
|
||||||
|
# ======= AUDIO CONFIGURATION =======
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
tones = [
|
||||||
|
{
|
||||||
|
'frequency_start': int(22_000),
|
||||||
|
'frequency_end': int(32_000),
|
||||||
|
'sample_rate_multiplier': 2
|
||||||
|
}
|
||||||
|
]
|
||||||
|
"""
|
||||||
|
{
|
||||||
|
'frequency_start': int(4_000),
|
||||||
|
'frequency_end': int(8_000),
|
||||||
|
'sample_rate_multiplier': 4
|
||||||
|
} """
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
print('Starting up...')
|
||||||
|
|
||||||
|
led_init()
|
||||||
|
|
||||||
|
led_test()
|
||||||
|
|
||||||
|
wlan = network.WLAN(network.STA_IF)
|
||||||
|
ssid = secrets['ssid']
|
||||||
|
|
||||||
|
""" try:
|
||||||
|
connect_wifi()
|
||||||
|
print('Searching for update...')
|
||||||
|
micropython_ota.ota_update('https://iot-sw.adix.link', ota_project_name, ota_branch)
|
||||||
|
except:
|
||||||
|
print('Error while checking updates') """
|
||||||
|
|
||||||
|
# continuously write tone sample buffer to an I2S DAC
|
||||||
|
print("========== START PLAYBACK ==========")
|
||||||
|
last_update_time = time_ms()
|
||||||
|
last_led_update_time = time_ms()
|
||||||
|
|
||||||
|
animation_start_time = time_ms()
|
||||||
|
animation_duration_ms = 1000
|
||||||
|
audio_select = 0
|
||||||
|
|
||||||
|
sound = 0
|
||||||
|
|
||||||
|
def select_next_tone_range(next_tone_id):
|
||||||
|
global sound
|
||||||
|
global animation_duration_ms
|
||||||
|
global animation_start_time
|
||||||
|
global animation_direction
|
||||||
|
global TONE_START_FREQUENCY_IN_HZ
|
||||||
|
global TONE_END_FREQUENCY_IN_HZ
|
||||||
|
global sample_rate_multiplier
|
||||||
|
|
||||||
|
animation_start_time = time_ms()
|
||||||
|
animation_duration_ms = random.randint(1000, 3000)
|
||||||
|
|
||||||
|
if next_tone_id >= len(tones):
|
||||||
|
sound = 0
|
||||||
|
else:
|
||||||
|
sound = 1
|
||||||
|
animation_direction = random.randint(0,1)
|
||||||
|
|
||||||
|
next_tone = tones[next_tone_id]
|
||||||
|
|
||||||
|
tone_start_frequency_in_hz = int(next_tone['frequency_start'])
|
||||||
|
tone_end_frequency_in_hz = int(next_tone['frequency_end'])
|
||||||
|
|
||||||
|
tone_diff_half = int(tone_end_frequency_in_hz - tone_start_frequency_in_hz) / 2
|
||||||
|
|
||||||
|
start_frequency = random.randint(tone_start_frequency_in_hz, int(tone_start_frequency_in_hz + tone_diff_half))
|
||||||
|
end_frequency = random.randint(int(start_frequency + tone_diff_half), tone_end_frequency_in_hz)
|
||||||
|
|
||||||
|
if end_frequency < start_frequency + tone_diff_half / 3:
|
||||||
|
end_frequency = start_frequency + tone_diff_half / 3
|
||||||
|
|
||||||
|
TONE_START_FREQUENCY_IN_HZ = start_frequency
|
||||||
|
TONE_END_FREQUENCY_IN_HZ = end_frequency
|
||||||
|
|
||||||
|
sample_rate_multiplier = next_tone['sample_rate_multiplier']
|
||||||
|
|
||||||
|
def set_next_tone():
|
||||||
|
global audio_out_0
|
||||||
|
global samples_0
|
||||||
|
global audio_out_1
|
||||||
|
global samples_1
|
||||||
|
global audio_select
|
||||||
|
global animation_start_time
|
||||||
|
global animation_duration_ms
|
||||||
|
global animation_direction
|
||||||
|
global TONE_START_FREQUENCY_IN_HZ
|
||||||
|
global TONE_END_FREQUENCY_IN_HZ
|
||||||
|
global sample_rate_multiplier
|
||||||
|
|
||||||
|
global I2S_ID
|
||||||
|
global SCK_PIN
|
||||||
|
global WS_PIN
|
||||||
|
global SD_PIN
|
||||||
|
global BUFFER_LENGTH_IN_BYTES
|
||||||
|
global FORMAT
|
||||||
|
|
||||||
|
delta_frequency = TONE_END_FREQUENCY_IN_HZ - TONE_START_FREQUENCY_IN_HZ
|
||||||
|
current_delta_frequency = int(delta_frequency * (time_ms() - animation_start_time) / animation_duration_ms)
|
||||||
|
|
||||||
|
if animation_direction == 0:
|
||||||
|
current_frequency = TONE_START_FREQUENCY_IN_HZ + current_delta_frequency + random.randint(0, int(delta_frequency / 8))
|
||||||
|
else:
|
||||||
|
current_frequency = TONE_END_FREQUENCY_IN_HZ - current_delta_frequency - random.randint(0, int(delta_frequency / 8))
|
||||||
|
|
||||||
|
current_sample_rate_in_hz = int(current_frequency * sample_rate_multiplier)
|
||||||
|
|
||||||
|
audio_out = I2S(
|
||||||
|
I2S_ID, #I2S_ID,
|
||||||
|
sck=Pin(SCK_PIN),
|
||||||
|
ws=Pin(WS_PIN),
|
||||||
|
sd=Pin(SD_PIN),
|
||||||
|
mode=I2S.TX,
|
||||||
|
bits=SAMPLE_SIZE_IN_BITS,
|
||||||
|
format=FORMAT,
|
||||||
|
rate=current_sample_rate_in_hz,
|
||||||
|
ibuf=BUFFER_LENGTH_IN_BYTES,
|
||||||
|
)
|
||||||
|
samples = make_tone(current_sample_rate_in_hz, SAMPLE_SIZE_IN_BITS, current_frequency)
|
||||||
|
|
||||||
|
if audio_select == 1:
|
||||||
|
audio_out_0 = audio_out
|
||||||
|
samples_0 = samples
|
||||||
|
audio_select = 0
|
||||||
|
else:
|
||||||
|
audio_out_1 = audio_out
|
||||||
|
samples_1 = samples
|
||||||
|
audio_select = 1
|
||||||
|
|
||||||
|
|
||||||
|
select_next_tone_range(0)
|
||||||
|
|
||||||
|
def play_sound():
|
||||||
|
global sound
|
||||||
|
global audio_select
|
||||||
|
global audio_out_0
|
||||||
|
global samples_0
|
||||||
|
global audio_out_1
|
||||||
|
global samples_1
|
||||||
|
global run
|
||||||
|
|
||||||
|
while run:
|
||||||
|
try:
|
||||||
|
if sound > 0:
|
||||||
|
if audio_select == 0:
|
||||||
|
audio_out_0.write(samples_0)
|
||||||
|
else:
|
||||||
|
audio_out_1.write(samples_1)
|
||||||
|
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
print("caught exception in audio thread {} {}".format(type(e).__name__, e))
|
||||||
|
sleep(1)
|
||||||
|
|
||||||
|
_thread.exit()
|
||||||
|
|
||||||
|
select_next_tone_range(0)
|
||||||
|
set_next_tone()
|
||||||
|
|
||||||
|
mode = 0
|
||||||
|
run = True
|
||||||
|
second_thread = _thread.start_new_thread(play_sound, ())
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
current_time = time_ms()
|
||||||
|
|
||||||
|
if current_time - last_led_update_time > 20:
|
||||||
|
update_leds()
|
||||||
|
last_led_update_time = current_time
|
||||||
|
|
||||||
|
# Check Update
|
||||||
|
#try:
|
||||||
|
# if current_time - last_update_time > 36_000_000:
|
||||||
|
# version_changed, remote_version = micropython_ota.check_version('https://iot-sw.adix.link', ota_project_name, ota_branch)
|
||||||
|
# if version_changed:
|
||||||
|
# send_notification(title = 'Updating to ' + remote_version, tags = 'new')
|
||||||
|
# if ota_soft_reset_device:
|
||||||
|
# print(f'Found new version {remote_version}, soft-resetting device...')
|
||||||
|
# machine.soft_reset()
|
||||||
|
# else:
|
||||||
|
# print(f'Found new version {remote_version}, hard-resetting device...')
|
||||||
|
# machine.reset()
|
||||||
|
# else:
|
||||||
|
# print('No new version available')
|
||||||
|
# last_update_time = current_time
|
||||||
|
#except:
|
||||||
|
# pass
|
||||||
|
|
||||||
|
#print(str(current_time) + ' ' + str(last_update_time))
|
||||||
|
if current_time - last_update_time > 500:
|
||||||
|
if current_time >= animation_start_time + animation_duration_ms:
|
||||||
|
mode = random.randint(0,3)
|
||||||
|
|
||||||
|
if mode == 0:
|
||||||
|
sound = 0
|
||||||
|
else: #mode=1 is constant freq, mode=2 is changing freq
|
||||||
|
next_tone_id = random.randint(0, len(tones)-1)
|
||||||
|
select_next_tone_range(next_tone_id)
|
||||||
|
set_next_tone()
|
||||||
|
|
||||||
|
gc.collect()
|
||||||
|
elif sound > 0 and mode == 2:
|
||||||
|
set_next_tone()
|
||||||
|
|
||||||
|
last_update_time = current_time
|
||||||
|
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
print("caught exception in main loop {} {}".format(type(e).__name__, e))
|
||||||
|
sys.print_exception(e)
|
||||||
|
sleep(1)
|
||||||
|
|
||||||
|
# cleanup
|
||||||
|
run = False
|
||||||
|
audio_out_0.deinit()
|
||||||
|
audio_out_1.deinit()
|
||||||
|
print("Done")
|
||||||
115
micropython_ota.py
Normal file
115
micropython_ota.py
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
# Based on:
|
||||||
|
# https://raw.githubusercontent.com/olivergregorius/micropython_ota/main/micropython_ota.py
|
||||||
|
|
||||||
|
import machine
|
||||||
|
import ubinascii
|
||||||
|
import uos
|
||||||
|
import urequests
|
||||||
|
import os
|
||||||
|
|
||||||
|
def reset_version():
|
||||||
|
try:
|
||||||
|
os.remove('version')
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def check_version(host, project, branch, auth=None, timeout=5) -> (bool, str):
|
||||||
|
current_version = ''
|
||||||
|
try:
|
||||||
|
if 'version' in uos.listdir():
|
||||||
|
with open('version', 'r') as current_version_file:
|
||||||
|
current_version = current_version_file.readline().strip()
|
||||||
|
|
||||||
|
if auth:
|
||||||
|
response = urequests.get(f'{host}/{project}/version_{branch}', headers={'Authorization': f'Basic {auth}'}, timeout=timeout)
|
||||||
|
else:
|
||||||
|
response = urequests.get(f'{host}/{project}/version_{branch}', timeout=timeout)
|
||||||
|
response_status_code = response.status_code
|
||||||
|
response_text = response.text
|
||||||
|
response.close()
|
||||||
|
if response_status_code != 200:
|
||||||
|
print(f'Remote version file {host}/{project}/version_{branch} not found')
|
||||||
|
return False, current_version
|
||||||
|
remote_version = response_text.strip()
|
||||||
|
return current_version != remote_version, remote_version
|
||||||
|
except Exception as ex:
|
||||||
|
print(f'Something went wrong: {ex}')
|
||||||
|
return False, current_version
|
||||||
|
|
||||||
|
|
||||||
|
def generate_auth(user=None, passwd=None) -> str | None:
|
||||||
|
if not user and not passwd:
|
||||||
|
return None
|
||||||
|
if (user and not passwd) or (passwd and not user):
|
||||||
|
raise ValueError('Either only user or pass given. None or both are required.')
|
||||||
|
auth_bytes = ubinascii.b2a_base64(f'{user}:{passwd}'.encode())
|
||||||
|
return auth_bytes.decode().strip()
|
||||||
|
|
||||||
|
|
||||||
|
def ota_update(host, project, branch='stable', use_version_prefix=False, user=None, passwd=None, hard_reset_device=True, soft_reset_device=False, timeout=5) -> None:
|
||||||
|
all_files_found = True
|
||||||
|
auth = generate_auth(user, passwd)
|
||||||
|
prefix_or_path_separator = '_' if use_version_prefix else '/'
|
||||||
|
try:
|
||||||
|
version_changed, remote_version = check_version(host, project, branch, auth=auth, timeout=timeout)
|
||||||
|
if version_changed:
|
||||||
|
try:
|
||||||
|
uos.mkdir('tmp')
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if auth:
|
||||||
|
response = urequests.get(f'{host}/{project}/{remote_version}{prefix_or_path_separator}.files', headers={'Authorization': f'Basic {auth}'}, timeout=timeout)
|
||||||
|
else:
|
||||||
|
response = urequests.get(f'{host}/{project}/{remote_version}{prefix_or_path_separator}.files', timeout=timeout)
|
||||||
|
files = str.split(response.text, '\n')
|
||||||
|
|
||||||
|
while("" in files):
|
||||||
|
files.remove("")
|
||||||
|
|
||||||
|
for filename in files:
|
||||||
|
|
||||||
|
if auth:
|
||||||
|
response = urequests.get(f'{host}/{project}/{remote_version}{prefix_or_path_separator}{filename}', headers={'Authorization': f'Basic {auth}'}, timeout=timeout)
|
||||||
|
else:
|
||||||
|
response = urequests.get(f'{host}/{project}/{remote_version}{prefix_or_path_separator}{filename}', timeout=timeout)
|
||||||
|
response_status_code = response.status_code
|
||||||
|
response_text = response.text
|
||||||
|
response.close()
|
||||||
|
if response_status_code != 200:
|
||||||
|
print(f'Remote source file {host}/{project}/{remote_version}{prefix_or_path_separator}{filename} not found')
|
||||||
|
all_files_found = False
|
||||||
|
continue
|
||||||
|
with open(f'tmp/{filename}', 'w') as source_file:
|
||||||
|
source_file.write(response_text)
|
||||||
|
if all_files_found:
|
||||||
|
for filename in files:
|
||||||
|
with open(f'tmp/{filename}', 'r') as source_file, open(filename, 'w') as target_file:
|
||||||
|
target_file.write(source_file.read())
|
||||||
|
uos.remove(f'tmp/{filename}')
|
||||||
|
try:
|
||||||
|
uos.rmdir('tmp')
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
with open('version', 'w') as current_version_file:
|
||||||
|
current_version_file.write(remote_version)
|
||||||
|
if soft_reset_device:
|
||||||
|
print('Soft-resetting device...')
|
||||||
|
machine.soft_reset()
|
||||||
|
if hard_reset_device:
|
||||||
|
print('Hard-resetting device...')
|
||||||
|
machine.reset()
|
||||||
|
except Exception as ex:
|
||||||
|
print(f'Something went wrong: {ex}')
|
||||||
|
|
||||||
|
|
||||||
|
def check_for_ota_update(host, project, branch='stable', user=None, passwd=None, timeout=5, soft_reset_device=False):
|
||||||
|
auth = generate_auth(user, passwd)
|
||||||
|
version_changed, remote_version = check_version(host, project, branch, auth=auth, timeout=timeout)
|
||||||
|
if version_changed:
|
||||||
|
if soft_reset_device:
|
||||||
|
print(f'Found new version {remote_version}, soft-resetting device...')
|
||||||
|
#machine.soft_reset()
|
||||||
|
else:
|
||||||
|
print(f'Found new version {remote_version}, hard-resetting device...')
|
||||||
|
#machine.reset()
|
||||||
Reference in New Issue
Block a user