-
Notifications
You must be signed in to change notification settings - Fork 0
/
SolarbirdTiny.ino
169 lines (137 loc) · 4.3 KB
/
SolarbirdTiny.ino
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
// [email protected], Kazoosh, 2021
// For ATtiny85 @ 8 MHz
// I/O pins //
#define PIN_PIEZO_A 3
#define PIN_PIEZO_B 4
#define PIN_LED_1_ANODE 0
#define PIN_LED_2_ANODE 1
#define PIN_LED_CATHODES 2
// High-level access to I/O and system features //
#include "system/leds.h"
#include "system/piezo.h"
#include "system/lowpower.h"
// Bird voice //
#include "voices/blackbird.h"
Blackbird Bird;
// Behaviour settings //
const bool runStartupTest = false;
const bool powerDownAfterStartIfDark = false;
const unsigned long powerDownAfterStartIfDarkTimeoutMillis = 5*1000;
const bool lightLEDsWhenPlaying = true;
const int activityMaximum = 8; // should be divisible by the difference of activeSoundInterval*
const int activeSoundIntervalMinimum = 1;
const int activeSoundIntervalMaximum = 5; // should be a power of 2 greater than the minimum
const int idleSoundIntervalMinimum = 60*10; // all intervals must be smaller than 32768
const int idleSoundIntervalMaximum = 60*60;
const int nightModeSleepSeconds = 2;
const bool silentAtNight = false;
// Main logic //
void setup()
{
// wait to make sure programmer is high-Z
// and be gentle with the power source
delay( 100 );
if ( runStartupTest )
{
// startup test: flash LEDs
LEDs::on();
delay( 50 );
LEDs::off();
delay( 500 );
// startup test: play all melodies once with fixed delay in between
for ( int i = 0; i < Bird.numMelodies; i++ ) {
Bird.playSingleMelody( i, lightLEDsWhenPlaying );
delay( 1000 );
}
delay( 1000 );
}
if ( powerDownAfterStartIfDark )
{
// check if environment is not dark
bool dark = true;
unsigned long startTime = millis();
while ( millis()-startTime <= powerDownAfterStartIfDarkTimeoutMillis ) {
if ( LEDs::senseRawBrightness( false ) > 0 ) {
dark = false;
break;
}
}
if ( dark ) {
// no brightness sensed within defined time window, power down and don't wake up until external reset
Piezo::on();
Piezo::tone( 1E6/220, 1E6 );
Piezo::off();
LowPower::powerDown();
}
}
// initialize random number generator with external source of randomness
pinMode( PIN_LED_1_ANODE, INPUT ); pinMode( PIN_LED_2_ANODE, INPUT );
randomSeed( analogRead( PIN_LED_1_ANODE ) ^ analogRead( PIN_LED_2_ANODE ) );
}
void loop()
{
static int lastBrightness = 0;
static int activity = 0;
static bool active = false;
static int cyclesBeforeNextSound = 0;
// measure current brightness (logarithmic value, proportional to order of magnitude)
int brightness = LEDs::senseBrightness();
// determine if in darkness (i.e., night mode)
bool dark = lastBrightness == 0 && brightness == 0;
// compare with previous brightness (don't care for sign) and save for next cycle
int brightnessActivity = abs( lastBrightness - brightness );
lastBrightness = brightness;
// accumulate activity within limit
activity = activity + brightnessActivity;
activity = min( activityMaximum, activity );
// true if just became active in current cycle
bool justBecameActive = false;
// sensed activity?
if ( activity > 0 )
{
// flash LEDs, the more activity the longer
LEDs::on();
delay( activity * 2 );
LEDs::off();
// just became active?
if ( !active )
{
// set flag and remember active state
justBecameActive = true;
active = true;
}
}
else
{
// clear active state
active = false;
}
// time to make noise?
// either after scheduled interval or on transition from idle to active
if ( cyclesBeforeNextSound == 0 || justBecameActive )
{
// play sound
Bird.play( lightLEDsWhenPlaying );
// determine waiting time before next sound (both for active and idle case)
if ( activity > 0 )
{
// active case = mapping of max activity to min interval plus one cycle of randomness
cyclesBeforeNextSound = (activityMaximum-activity) * (activeSoundIntervalMaximum-activeSoundIntervalMinimum) / activityMaximum + activeSoundIntervalMinimum + random( 0, 1+1 );
}
else
{
cyclesBeforeNextSound = random( idleSoundIntervalMinimum, idleSoundIntervalMaximum+1 );
}
}
else
{
// stop counter when in night mode, i.e., remain silent at night
if ( !dark || !silentAtNight ) {
cyclesBeforeNextSound--;
}
}
// decrease activity level
if ( activity > 0 ) { activity--; }
// wait 1 second by day (2 seconds by night) until next cycle
LowPower::sleepSeconds( dark && !active ? nightModeSleepSeconds : 1 );
}