Rework with new classes, debugging 24-hour timer

This commit is contained in:
Neale Pickett 2020-12-15 19:32:50 -07:00
parent 67008e8502
commit e74850574c
11 changed files with 279 additions and 158 deletions

View File

@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2016 Dirtbags Copyright © 2020 Neale Pickett
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
@ -12,10 +12,10 @@ furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software. copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR The software is provided "as is", without warranty of any kind, express or
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, implied, including but not limited to the warranties of merchantability,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE fitness for a particular purpose and noninfringement. In no event shall the
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER authors or copyright holders be liable for any claim, damages or other
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, liability, whether in an action of contract, tort or otherwise, arising from,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE out of or in connection with the software or the use or other dealings in the
SOFTWARE. software.

4
Makefile Normal file
View File

@ -0,0 +1,4 @@
FQBN = adafruit:samd:adafruit_trinket_m0
install: holiday-lights.ino
arduino --upload --board $(FQBN) $@

12
NOTES.md Normal file
View File

@ -0,0 +1,12 @@
TIME FOR SOME INFORMATION THEORY!
We need to pack morse code. The most efficient packing I can think of is a stream of base 3 ints:
0 = END, 1 = DIT, 2 = DAH
We have to encode into 8-bit bytes, so the smallest atomic (quick to index) encoded chunk is:
int('22222', 3) = 242
Given the last int must be 0, we can encode anything up to length 4.
But I need to encode the prosign ARK = .-.-.-.- length 8.
So I need two bytes.
Well, if I'm going to use two bytes, I may as well just encode a length and a bitmask.

7
durations.h Normal file
View File

@ -0,0 +1,7 @@
#pragma once
#define DURATION_MILLISECOND 1L
#define DURATION_SECOND (1000 * DURATION_MILLISECOND)
#define DURATION_MINUTE (60 * DURATION_SECOND)
#define DURATION_HOUR (60 * DURATION_MINUTE)
#define DURATION_DAY (24 * DURATION_HOUR)

View File

@ -4,7 +4,9 @@
#include <FastLED.h> #include <FastLED.h>
#include "bounce2.h" #include "bounce2.h"
#include "durations.h"
#include "morse.h" #include "morse.h"
#include "pulse.h"
// Do you want it to run forever, or cycle every 24 hours? // Do you want it to run forever, or cycle every 24 hours?
#ifdef TINY #ifdef TINY
@ -14,8 +16,10 @@
#endif #endif
// Which pin your LED strip is connected to // Which pin your LED strip is connected to
#ifdef TINY #if defined(TINY)
#define NEOPIXEL_PIN 3 #define NEOPIXEL_PIN 3
#elif defined(ADAFRUIT_TRINKET_M0)
#define NEOPIXEL_PIN 2
#else #else
#define NEOPIXEL_PIN 6 #define NEOPIXEL_PIN 6
#endif #endif
@ -35,42 +39,40 @@
// How many milliseconds between activity in one group // How many milliseconds between activity in one group
#define DELAY_MS 600 #define DELAY_MS 600
// Each group has this percentage chance of
#define GROUP_UPDATE_PROBABILITY 25
// What percentage chance a chosen light has of being on // What percentage chance a chosen light has of being on
#define ACTIVITY 40 #define ACTIVITY 40
// Which pixel to flash messages in morse code: -1 = disable // Which pixel to flash messages in morse code: -1 = disable
#define MORSE_PIXEL 8 #define MORSE_PIXEL 8
#define MORSE_COLOR 0x880000 #define MORSE_COLOR CRGB::Red
// How long a dit lasts // How long a dit lasts
#define DIT_DURATION_MS 100 #define DIT_DURATION_MS 150
// Color for all-white mode // Color for all-white mode
#define WHITE 0x886655 #define WHITE 0x886655
// Some units of time
#define MILLISECOND 1L
#define SECOND (1000 * MILLISECOND)
#define MINUTE (60 * SECOND)
#define HOUR (60 * MINUTE)
#define DAY (24 * HOUR)
// The Neopixel library masks interrupts while writing. // The Neopixel library masks interrupts while writing.
// This means you lose time. // This means you lose time.
// How much time do you lose? // How much time do you lose?
// I'm guessing 10 minutes a day. // I'm guessing 10 minutes a day.
#define SNOSSLOSS_DAY (DAY - (10 * MINUTE)) #define SNOSSLOSS_DAY (DURATION_DAY - (10 * DURATION_MINUTE))
#define ON_FOR (5 * HOUR) #define ON_FOR (5 * DURATION_HOUR)
const char *messages[] = { #define ARK "\x03\x04"
"seasons greetings", const char *message = (
"happy holiday",
"merry xmas", "seasons greetings" ARK
"happy new year", "happy holiday" ARK
"CQ CQ OF9X", "merry xmas" ARK
}; "happy new year" ARK
const int nmessages = sizeof(messages) / sizeof(*messages); "CQ CQ OF9X" ARK
);
// These colors mirror pretty closely some cheapo LED lights we have // These colors mirror pretty closely some cheapo LED lights we have
const uint32_t colors[] = { const uint32_t colors[] = {
@ -100,16 +102,16 @@ void setup() {
pinMode(LED_BUILTIN, OUTPUT); pinMode(LED_BUILTIN, OUTPUT);
} }
bool strandUpdate(bool white) { Pulse mainPulse = Pulse(DELAY_MS);
static unsigned long nextEventMillis = 0;
unsigned long now = millis();
static int group = 0;
if (now < nextEventMillis) { bool strandUpdate(bool white) {
if (!mainPulse.Ticked()) {
return false; return false;
} }
for (int group = 0; group < NUM_GROUPS; ++group) {
int pos = (group * LEDS_PER_GROUP) + random(LEDS_PER_GROUP); int pos = (group * LEDS_PER_GROUP) + random(LEDS_PER_GROUP);
if (random(100) < GROUP_UPDATE_PROBABILITY) {
if (random(100) < ACTIVITY) { if (random(100) < ACTIVITY) {
uint32_t color; uint32_t color;
@ -123,57 +125,44 @@ bool strandUpdate(bool white) {
leds[pos] = 0; leds[pos] = 0;
} }
group = (group + 1) % NUM_GROUPS; group = (group + 1) % NUM_GROUPS;
}
}
nextEventMillis = now + (DELAY_MS / NUM_GROUPS);
return true; return true;
} }
bool morseUpdate() {
static MorseEncoder enc;
static unsigned long nextEventMillis = 0;
static bool ARK = true;
unsigned long now = millis();
bool ret = false;
if (now >= nextEventMillis) {
nextEventMillis = now + DIT_DURATION_MS;
if (!enc.Tick()) {
ARK = !ARK;
if (ARK) {
enc.SetText("ARK");
enc.Quiet(MORSE_PAUSE_WORD);
} else {
#ifdef TINY
// I have tried twenty different ways to make this work on the ATTINY,
// including a big switch statement. It always freezes the program.
// I give up. Maybe it's a compiler bug having to do with a modulo.
enc.SetText(messages[0]);
#else
enc.SetText(messages[now % nmessages]);
#endif
enc.Quiet(200);
}
}
ret = true;
}
leds[MORSE_PIXEL] = enc.Transmitting ? MORSE_COLOR : 0;
return ret;
}
bool black() { bool black() {
static unsigned long nextEventMillis = 0; if (!mainPulse.Ticked()) {
unsigned long now = millis();
if (now < nextEventMillis) {
return false; return false;
} }
FastLED.clear(); FastLED.clear();
nextEventMillis = now + (DELAY_MS / NUM_GROUPS); // Keep timing consistent
return true; return true;
} }
bool morseUpdate() {
static MorseEncoder enc;
static Pulse pulse = Pulse(DIT_DURATION_MS);
leds[MORSE_PIXEL] = enc.Transmitting ? MORSE_COLOR : CRGB::Black;
if (pulse.Ticked()) {
if (!enc.Tick()) {
enc.SetText(message);
}
return true;
}
return false;
}
bool millisUpdate() {
unsigned long now = millis();
for (int i = 0; i < sizeof(unsigned long) * 8; ++i) {
leds[i] = CHSV(0, 255, bitRead(now, i) ? 32 : 0);
}
return false;
}
void loop() { void loop() {
static bool white = false; static bool white = false;
bool update = false; bool update = false;
@ -188,6 +177,7 @@ void loop() {
update |= morseUpdate(); update |= morseUpdate();
} else { } else {
update |= black(); update |= black();
update |= millisUpdate();
} }
if (update) { if (update) {

208
morse.cpp
View File

@ -1,42 +1,122 @@
#include "morse.h" #include "morse.h"
// Each morse dit/dah is stored as a nybble struct MorseSign {
// Length of this sign. If Length is 0, Bits is the duration of a pause.
#define dit 1 uint8_t Length;
#define dah 3 uint8_t Bits;
#define Pack(a, b, c, d) ((a << 0) | (b << 2) | (c << 4) | (d << 6))
#define Unpack(m, pos) ((m >> (pos * 2)) & 0b11)
const uint8_t MorseLetters[] = {
Pack(dit, dah, 0, 0), // A
Pack(dah, dit, dit, dit), // B
Pack(dah, dit, dah, dit), // C
Pack(dah, dit, dit, 0), // D
Pack(dit, 0, 0, 0), // E
Pack(dit, dit, dah, dit), // F
Pack(dah, dah, dit, 0), // G
Pack(dit, dit, dit, dit), // H
Pack(dit, dit, 0, 0), // I
Pack(dit, dah, dah, dah), // J
Pack(dah, dit, dah, 0), // K
Pack(dit, dah, dit, dit), // L
Pack(dah, dah, 0, 0), // M
Pack(dah, dit, 0, 0), // N
Pack(dah, dah, dah, 0), // O
Pack(dit, dah, dah, dit), // P
Pack(dah, dah, dit, dah), // Q
Pack(dit, dah, dit, 0), // R
Pack(dit, dit, dit, 0), // S
Pack(dah, 0, 0, 0), // T
Pack(dit, dit, dah, 0), // U
Pack(dit, dit, dit, dah), // V
Pack(dit, dah, dah, 0), // W
Pack(dah, dit, dit, dah), // X
Pack(dah, dit, dah, dah), // Y
Pack(dah, dah, dit, dit), // Z
}; };
const struct MorseSign GetMorseSign(char c) {
switch (c) {
case 3: // ETX - End of Text
return (struct MorseSign){8, 0b01010101};
case 4: // EOT - End of Transmission
return (struct MorseSign){0, MORSE_PAUSE_TRANSMISSION};
case ' ':
return (struct MorseSign){0, MORSE_PAUSE_WORD};
case '0':
return (struct MorseSign){5, 0b11111};
case '1':
return (struct MorseSign){5, 0b01111};
case '2':
return (struct MorseSign){5, 0b00111};
case '3':
return (struct MorseSign){5, 0b00011};
case '4':
return (struct MorseSign){5, 0b00001};
case 5:
return (struct MorseSign){5, 0b00000};
case 6:
return (struct MorseSign){5, 0b10000};
case 7:
return (struct MorseSign){5, 0b11000};
case 8:
return (struct MorseSign){5, 0b11100};
case 9:
return (struct MorseSign){5, 0b11110};
case 'a':
case 'A':
return (struct MorseSign){2, 0b01};
case 'b':
case 'B':
return (struct MorseSign){4, 0b1000};
case 'c':
case 'C':
return (struct MorseSign){4, 0b1010};
case 'd':
case 'D':
return (struct MorseSign){3, 0b100};
case 'e':
case 'E':
return (struct MorseSign){1, 0b0};
case 'f':
case 'F':
return (struct MorseSign){4, 0b0010};
case 'g':
case 'G':
return (struct MorseSign){3, 0b110};
case 'h':
case 'H':
return (struct MorseSign){4, 0b0000};
case 'i':
case 'I':
return (struct MorseSign){2, 0b00};
case 'j':
case 'J':
return (struct MorseSign){4, 0b0111};
case 'k':
case 'K':
return (struct MorseSign){3, 0b101};
case 'l':
case 'L':
return (struct MorseSign){4, 0b0100};
case 'm':
case 'M':
return (struct MorseSign){2, 0b11};
case 'n':
case 'N':
return (struct MorseSign){2, 0b10};
case 'o':
case 'O':
return (struct MorseSign){3, 0b111};
case 'p':
case 'P':
return (struct MorseSign){4, 0b0110};
case 'q':
case 'Q':
return (struct MorseSign){4, 0b1101};
case 'r':
case 'R':
return (struct MorseSign){3, 0b010};
case 's':
case 'S':
return (struct MorseSign){3, 0b000};
case 't':
case 'T':
return (struct MorseSign){1, 0b1};
case 'u':
case 'U':
return (struct MorseSign){3, 0b001};
case 'v':
case 'V':
return (struct MorseSign){4, 0b0001};
case 'w':
case 'W':
return (struct MorseSign){3, 0b011};
case 'x':
case 'X':
return (struct MorseSign){4, 0b1001};
case 'y':
case 'Y':
return (struct MorseSign){4, 0b1011};
case 'z':
case 'Z':
return (struct MorseSign){4, 0b1100};
default:
return (struct MorseSign){0, 0};
}
}
MorseEncoder::MorseEncoder() { MorseEncoder::MorseEncoder() {
SetText(""); SetText("");
} }
@ -46,7 +126,7 @@ MorseEncoder::MorseEncoder(const char *s) {
void MorseEncoder::SetText(const char *s) { void MorseEncoder::SetText(const char *s) {
p = s; p = s;
crumb = 0; bit = 0;
ticksLeft = 0; ticksLeft = 0;
Transmitting = false; Transmitting = false;
} }
@ -75,45 +155,29 @@ bool MorseEncoder::Tick() {
return true; return true;
} }
// If that was the end of the letter, we have to pause more const struct MorseSign sign = GetMorseSign(*p);
if (crumb == 4) { if (sign.Length == 0) {
crumb = 0; // Pause
++p;
ticksLeft = MORSE_PAUSE_LETTER - MORSE_DIT;
return true;
}
switch (*p) {
case '\0':
return false;
case 'a' ... 'z':
Transmitting = true;
ticksLeft = Unpack(MorseLetters[*p - 'a'], crumb++);
break;
case 'A' ... 'Z':
Transmitting = true;
ticksLeft = Unpack(MorseLetters[*p - 'A'], crumb++);
break;
case ' ':
crumb = 0;
++p;
Transmitting = false; Transmitting = false;
ticksLeft = MORSE_PAUSE_WORD - MORSE_DIT; ticksLeft = sign.Bits - (MORSE_PAUSE_LETTER - MORSE_DIT);
break;
default: // this should never happen! Transmit for a word pause to indicate weirdness.
crumb = 0;
++p;
Transmitting = true;
ticksLeft = MORSE_PAUSE_WORD;
break;
}
if (0 == ticksLeft) {
// Unpack can return 0 if there are fewer than 4 emissions for a letter.
// In that case, we 're done with the letter.
crumb = 0;
++p; ++p;
bit = 0;
} else if (bit == sign.Length) {
// All done with that sign!
Transmitting = false; Transmitting = false;
ticksLeft = MORSE_PAUSE_LETTER - MORSE_DIT; ticksLeft = MORSE_PAUSE_LETTER;
++p;
bit = 0;
} else {
// Sign
uint8_t bitMask = 1 << (sign.Length - bit - 1);
Transmitting = true;
if (sign.Bits & bitMask) {
ticksLeft = MORSE_DAH;
} else {
ticksLeft = MORSE_DIT;
}
++bit;
} }
return true; return true;

View File

@ -6,6 +6,7 @@
#define MORSE_DAH 3 #define MORSE_DAH 3
#define MORSE_PAUSE_LETTER 3 #define MORSE_PAUSE_LETTER 3
#define MORSE_PAUSE_WORD 6 #define MORSE_PAUSE_WORD 6
#define MORSE_PAUSE_TRANSMISSION 50
class MorseEncoder { class MorseEncoder {
public: public:
@ -35,6 +36,6 @@ class MorseEncoder {
private: private:
const char *p; const char *p;
uint8_t crumb; uint8_t bit;
int ticksLeft; int ticksLeft;
}; };

26
pulse.cpp Normal file
View File

@ -0,0 +1,26 @@
#include "pulse.h"
#include <Arduino.h>
Pulse::Pulse(unsigned long period) {
this->period = period;
this->nextEventMillis = 0;
}
bool Pulse::Ticked() {
unsigned long now = millis();
if (now >= nextEventMillis) {
Until(period, now);
return true;
}
return false;
}
void Pulse::Until(unsigned long next, unsigned long now) {
nextEventMillis = now + next;
}
void Pulse::Until(unsigned long next) {
Until(next, millis());
}

17
pulse.h Normal file
View File

@ -0,0 +1,17 @@
#pragma once
#include "durations.h"
class Pulse {
public:
Pulse(unsigned long period);
/** Ticked tells you if a period has elapsed. */
bool Ticked();
/** Until sets the duration of the next period. */
void Until(unsigned long next);
void Until(unsigned long next, unsigned long now);
unsigned long period;
unsigned long nextEventMillis;
};

BIN
test

Binary file not shown.

View File

@ -4,7 +4,7 @@
// gcc -D test_main=main -o test morse.cpp test.cpp && ./test // gcc -D test_main=main -o test morse.cpp test.cpp && ./test
int test_main(int argc, char *argv[]) { int test_main(int argc, char *argv[]) {
MorseEncoder enc = MorseEncoder("SOS ck ck ark"); MorseEncoder enc = MorseEncoder("SOS ck ck \x03");
while (enc.Tick()) { while (enc.Tick()) {
if (enc.Transmitting) { if (enc.Transmitting) {
printf("#"); printf("#");