Skip to content

Commit

Permalink
Merge pull request #45 from Lamprichauns/rich-main-sandbox
Browse files Browse the repository at this point in the history
Rich main sandbox
  • Loading branch information
chaffneue authored May 31, 2022
2 parents d70a4cd + d885750 commit b4a6803
Show file tree
Hide file tree
Showing 9 changed files with 286 additions and 51 deletions.
8 changes: 6 additions & 2 deletions src/behaviours/lamp_fade_in.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
from utils.fade import fade

class LampFadeIn(AnimatedBehaviour):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.immediate_control = True

async def draw(self):
for i in range(self.lamp.base.num_pixels):
self.lamp.base.buffer[i] = fade((0, 0, 0, 0), self.lamp.base.default_pixels[i], self.frames, self.frame)
Expand All @@ -14,8 +18,8 @@ async def draw(self):
await self.next_frame()

async def control(self):
self.lamp.behaviour(LampFadeIn).play()
self.lamp.behaviour(LampFadeIn).stop()
self.play()
self.stop()

while True:
#component will be in the stopped state twice: once on init and once above
Expand Down
18 changes: 18 additions & 0 deletions src/behaviours/lamp_fade_out.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Fade out and reboot
from machine import reset
from lamp_core.behaviour import AnimatedBehaviour
from utils.fade import fade

class LampFadeOut(AnimatedBehaviour):
async def draw(self):
for i in range(self.lamp.base.num_pixels):
self.lamp.base.buffer[i] = fade(self.lamp.base.buffer[i], (0, 0, 0, 0), self.frames, self.frame)

for i in range(self.lamp.shade.num_pixels):
self.lamp.shade.buffer[i] = fade(self.lamp.shade.buffer[i], (0, 0, 0, 0), self.frames, self.frame)

if self.is_last_frame():
self.stop()
reset()

await self.next_frame()
2 changes: 1 addition & 1 deletion src/components/network/bluetooth.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ def enable(self):
self.ble.gap_advertise(_GAP_ADV_INTERVAL_US, self.adv_payload, connectable=False)
asyncio.create_task(self.network.monitor())

print("BT enabled (scaning & advertising)")
print("BT enabled (scanning & advertising)")


# pylint: disable=too-many-arguments,unused-argument
Expand Down
39 changes: 32 additions & 7 deletions src/lamp_core/behaviour.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ class AnimationState:
STOPPED = 5

# An animation behavior that will loop indefinitely
# frames = animations run at 30fps max, so divide the time by that number
# frame = the current frame of the animation
# current_loop = a counter of the number of loops played
# chained_behaviors = play other behaviors when the controller is finished its work
# immediate_control = True to start the control block immediately. defaults to 5 seconds after wake
class AnimatedBehaviour(Behaviour):
def __init__(self, lamp, frames=60, chained_behaviors=None):
super().__init__(lamp)
Expand All @@ -41,6 +46,7 @@ def __init__(self, lamp, frames=60, chained_behaviors=None):
self.current_loop = 0
self.animation_state = AnimationState.STOPPED
self.chained_behaviors = chained_behaviors if isinstance(chained_behaviors, list) else []
self.immediate_control = False
gc.collect()

async def control(self):
Expand All @@ -57,9 +63,16 @@ async def animate(self):

await self.draw()

async def run_control(self):
if self.immediate_control:
await self.control()
else:
await asyncio.sleep(5)
await self.control()

async def run(self):
await asyncio.gather(
asyncio.create_task(self.control()),
asyncio.create_task(self.run_control()),
asyncio.create_task(self.animate())
)

Expand All @@ -74,6 +87,9 @@ def stop(self):
def play(self):
self.animation_state = AnimationState.PLAYING

def is_last_frame(self):
return self.frame == self.frames-1

async def next_frame(self):
if self.animation_state == AnimationState.PAUSING:
self.animation_state = AnimationState.PAUSED
Expand All @@ -98,20 +114,29 @@ async def run(self):
self.lamp.base.fill((0, 0, 0, 0))
self.lamp.shade.fill((0, 0, 0, 0))
ticks = 0
last_duration = 0
avg_duration = 0
while True:
t = utime.ticks_us()
t = utime.ticks_ms()
self.lamp.base.flush()
self.lamp.shade.flush()
gc.collect()

# Add a framerate cap to save power in light loads
wait_ms = 33-last_duration
await asyncio.sleep(0)
last_duration = utime.ticks_diff(utime.ticks_ms(), t)

if wait_ms > 0:
utime.sleep_ms(wait_ms)

avg_duration += utime.ticks_diff(utime.ticks_us(), t)
avg_duration += utime.ticks_diff(utime.ticks_ms(), t)
if ticks % 60 == 0:
if self.lamp.debug is True:
print('Average Draw Duration = {:6.3f}ms'.format(avg_duration/60000))
print('Framerate: {}Hz'.format(1000/(avg_duration/60000)))
# pylint: disable=no-member
print('Memory: {}, Free: {}'.format(gc.mem_alloc(), gc.mem_free()))
if avg_duration//60 > 0:
print('Average Draw Duration = {}ms'.format(avg_duration//60))
print('Framerate: {}Hz'.format(1000//(avg_duration//60)))
# pylint: disable=no-member
print('Memory: {}, Free: {}'.format(gc.mem_alloc(), gc.mem_free()))
avg_duration = 0
ticks += 1
10 changes: 3 additions & 7 deletions src/lamp_core/standard_lamp.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from lamp_core.lamp import Lamp
from lamp_core.frame_buffer import FrameBuffer
from utils.hex_to_rgbw import hex_to_rgbw
from utils.config import merge_configs

default_config = {
"base": { "pin": 12, "pixels": 40, "bpp": 4 },
Expand All @@ -21,13 +22,8 @@ def __init__(self, name, base_color, shade_color, config_opts = None, post_proce
super().__init__(name)

config = default_config.copy()
if isinstance(config_opts, dict):
for key in config_opts:
try:
if isinstance(config[key], dict):
config[key].update(config_opts[key])
except KeyError:
pass

merge_configs(config, config_opts)

if post_process_function is not None:
self.base = FrameBuffer(hex_to_rgbw(base_color), config["base"]["pixels"], NeoPixel(config["base"]["pin"], config["base"]["pixels"], config["base"]["bpp"]), post_process_function=post_process_function)
Expand Down
84 changes: 50 additions & 34 deletions src/lamps/century.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
# Century lamp
from time import sleep
from random import randrange, choice
from ujson import dump, load
import uasyncio as asyncio
from behaviours.lamp_fade_in import LampFadeIn
from behaviours.lamp_fade_out import LampFadeOut
from behaviours.social import SocialGreeting
from lamp_core.behaviour import AnimatedBehaviour, AnimationState, BackgroundBehavior
from lamp_core.standard_lamp import StandardLamp
Expand All @@ -14,59 +16,81 @@
from utils.fade import fade, pingpong_fade
from utils.temperature import get_temperature_index
from utils.easing import pingpong_ease
from utils.config import merge_configs
from vendor import tinyweb

# making flashing a little easier reboot->upload
sleep(1)

# for ease of use, you can define a config to flow into all the components
config = {
"shade": { "pin": 13, "pixels": 40, "bpp": 4 },
"base": { "pin": 12, "pixels": 60, "bpp": 4 },
"lamp": { "default_behaviours": False, "debug": True },
"motion": { "pin_sda": 21, "pin_scl": 22 },
"sunset": {"low": 30, "high": 40 },
"shade": { "pin": 13, "pixels": 40, "bpp": 4, "color":"#FFFFFF"},
"base": { "pin": 12, "pixels": 60, "bpp": 4, "color":"#931702"},
"lamp": { "default_behaviours": False, "debug": True, "name": "century" },
"motion": { "pin_sda": 21, "pin_scl": 22, "threshold": 3000 },
"sunset": {"low": 30, "high": 40, "current": 0 },
}

with open("/lamps/files/century/db", "r", encoding="utf8") as settings:
merge_configs(config, load(settings))

# knockout and brighten some pixels for all scenes
# add some gamma correction to save power
def post_process(ko_pixels):
for l in range(32,36):
for l in range(30,36):
ko_pixels[l] = darken(ko_pixels[l], percentage=85)
for l in range(49, 57):
ko_pixels[l] = (0, 0, 0, 0)
for l in range(0, 40):
ko_pixels[l] = darken(ko_pixels[l], percentage=20)

# Compose all the components
century = StandardLamp("century", "#931702", "#FFFFFF", config, post_process_function = post_process)
century = StandardLamp(config["lamp"]["name"], config["base"]["color"], config["shade"]["color"], config, post_process_function = post_process)
century.motion = MotionMPU6050(config["motion"]["pin_sda"], config["motion"]["pin_scl"])
century.temperature = TemperatureMPU6050(century.motion.accelerometer)
century.access_point = AccessPoint(ssid="century-lamp", password="123456789")
century.base.default_pixels = create_gradient((150, 10, 0, 0), (220, 100, 8, 0), steps=config["base"]["pixels"])
for j in range(20,25):
pixels = brighten(century.base.default_pixels[j], percentage=200)
century.base.default_pixels[j] = (pixels[0], pixels[1], pixels[2], 40)
century.shade.default_pixels = [(0,0,0,180)]*config["shade"]["pixels"]

# Web svc init
app = tinyweb.webserver()

# Handle new values
# Read and amend the config object
class Configurator():
def post(self, data):
#set as an override
config["sunset"]["low"] = int(data["temperature_low"])
config["sunset"]["high"] = int(data["temperature_high"])
def get(self, _):
config["sunset"]["current"] = century.temperature.get_temperature_value()
return config

def post(self, data):
print(data)
config["shade"]["color"] = data["shade"]
config["base"]["color"] = data["base"]
config["lamp"]["name"] = data["name"]
config["motion"]["threshold"] = int(data["threshold"])
config["sunset"]["low"] = int(data["low"])
config["sunset"]["high"] = int(data["high"])

try:
with open("/lamps/files/century/db", "w", encoding="utf8") as flash:
dump(config, flash)
except Exception as e:
print(e)

century.behaviour(LampFadeOut).play()

return {'message': 'OK'}, 200

@app.route('/')
async def index(_, resp):
await resp.send_file("/lamps/files/century/configurator.html")

#app.add_resource(Configurator, '/settings')
app.add_resource(Configurator, '/settings')

# Start listening for connections on port 80
class WebListener(BackgroundBehavior):
async def run(self):
await asyncio.sleep(5)
app.run(host='0.0.0.0', port=80)

# A very slow cooling/warming color change in reaction the ambient temperature
Expand All @@ -75,10 +99,10 @@ def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.sunset_stages = [
{ "start": (5, 33, 90, 0), "end": (30, 30, 12, 20) },
{ "start": (16, 7, 142, 0), "end": (85, 95, 16, 0) },
{ "start": (52, 4, 107, 0), "end": (104, 100, 15, 0) },
{ "start": (107, 5, 57, 0), "end": (155, 100, 30, 0) },
{ "start": (108, 4, 23, 0), "end": (181, 96, 14, 0) },
{ "start": (16, 7, 142, 0), "end": (85, 42, 16, 0) },
{ "start": (52, 4, 107, 0), "end": (104, 40, 15, 0) },
{ "start": (107, 5, 57, 0), "end": (155, 50, 30, 0) },
{ "start": (108, 4, 23, 0), "end": (181, 60, 14, 0) },
{ "start": (108, 13, 3, 0), "end": (217, 68, 30, 0) },
{ "start": (150, 10, 0, 0), "end": (220, 100, 8, 0) },
]
Expand All @@ -95,7 +119,7 @@ async def draw(self):
for i in range(config["base"]["pixels"]):
self.lamp.base.buffer[i] = fade(self.previous_scene_pixels[i], self.current_scene_pixels[i], self.frames, self.frame)

if self.frame == self.frames-1:
if self.is_last_frame():
self.scene_change = False
else:
self.lamp.base.buffer = self.current_scene_pixels.copy()
Expand All @@ -107,25 +131,20 @@ async def draw(self):
await self.next_frame()

async def control(self):
scene = -1
while True:
if (self.scene_change or
self.lamp.behaviour(LampFadeIn).animation_state in(AnimationState.PLAYING, AnimationState.STOPPING)):
if self.scene_change:
await asyncio.sleep(0)
continue

#scene = get_temperature_index(self.lamp.temperature.get_temperature_value(), config["sunset"]["low"], config["sunset"]["high"], 7)
scene += 1
if scene > 6:
scene = 0
scene = get_temperature_index(self.lamp.temperature.get_temperature_value(), config["sunset"]["low"], config["sunset"]["high"], 7)

if scene != self.current_scene:
print("Scene change {}: Temperature: {}".format(scene, self.lamp.temperature.get_temperature_value()))

self.previous_scene_pixels = self.create_scene(self.current_scene)
self.current_scene_pixels = self.create_scene(scene)

if scene < 3:
if scene < 2:
self.lamp.behaviour(StarShade).play()
else:
self.lamp.behaviour(StarShade).stop()
Expand All @@ -142,7 +161,7 @@ def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.last_accelerometer_value = 0
self.polling_interval = 100
self.dance_gesture_peak = 3000
self.dance_gesture_peak = config["motion"]["threshold"]
self.pixel_list = []

def within_range(self, value, baseline, threshold):
Expand All @@ -158,15 +177,13 @@ async def draw(self):

async def control(self):
while True:
if (self.animation_state in(AnimationState.PLAYING, AnimationState.STOPPING) or
self.lamp.behaviour(LampFadeIn).animation_state in(AnimationState.PLAYING, AnimationState.STOPPING)):
if self.animation_state in(AnimationState.PLAYING, AnimationState.STOPPING):
await asyncio.sleep_ms(self.polling_interval)
continue

value = self.lamp.motion.get_movement_intensity_value()

if (value >= self.dance_gesture_peak and value is not self.within_range(value, self.last_accelerometer_value, 100)):
print(value)
self.last_accelerometer_value = value

self.pixel_list = [randrange(0, self.lamp.base.num_pixels, 1) for i in range(int(self.lamp.base.num_pixels/2))]
Expand Down Expand Up @@ -222,8 +239,6 @@ async def control(self):
if self.frame == 0:
self.cloud_brightness = choice(range(145, 185))
self.cloud_positions = [randrange(22, 30, 1) for i in range(1)]
self.cloud_positions += [randrange(43, 57, 1) for i in range(2)]
self.cloud_positions += [randrange(48, 60, 1) for i in range(1)]
self.cloud_style = choice(range(0, 1))
await asyncio.sleep(0)

Expand Down Expand Up @@ -255,5 +270,6 @@ async def control(self):
century.add_behaviour(SocialGreeting(century, frames=300))
century.add_behaviour(DanceReaction(century, frames=16))
century.add_behaviour(WebListener(century))
century.add_behaviour(LampFadeOut(century, frames=30))

century.wake()
Loading

0 comments on commit b4a6803

Please sign in to comment.