# 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 = " 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")