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
Copyright (c) 2016 Dirtbags
Copyright © 2020 Neale Pickett
Permission is hereby granted, free of charge, to any person obtaining a copy
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
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
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
SOFTWARE.
The software is provided "as is", without warranty of any kind, express or
implied, including but not limited to the warranties of merchantability,
fitness for a particular purpose and noninfringement. In no event shall the
authors or copyright holders be liable for any claim, damages or other
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
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 "bounce2.h"
#include "durations.h"
#include "morse.h"
#include "pulse.h"
// Do you want it to run forever, or cycle every 24 hours?
#ifdef TINY
@ -14,8 +16,10 @@
#endif
// Which pin your LED strip is connected to
#ifdef TINY
#if defined(TINY)
#define NEOPIXEL_PIN 3
#elif defined(ADAFRUIT_TRINKET_M0)
#define NEOPIXEL_PIN 2
#else
#define NEOPIXEL_PIN 6
#endif
@ -35,42 +39,40 @@
// How many milliseconds between activity in one group
#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
#define ACTIVITY 40
// Which pixel to flash messages in morse code: -1 = disable
#define MORSE_PIXEL 8
#define MORSE_COLOR 0x880000
#define MORSE_COLOR CRGB::Red
// How long a dit lasts
#define DIT_DURATION_MS 100
#define DIT_DURATION_MS 150
// Color for all-white mode
#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.
// This means you lose time.
// How much time do you lose?
// I'm guessing 10 minutes a day.
#define SNOSSLOSS_DAY (DAY - (10 * MINUTE))
#define ON_FOR (5 * HOUR)
#define SNOSSLOSS_DAY (DURATION_DAY - (10 * DURATION_MINUTE))
#define ON_FOR (5 * DURATION_HOUR)
const char *messages[] = {
"seasons greetings",
"happy holiday",
"merry xmas",
"happy new year",
"CQ CQ OF9X",
};
const int nmessages = sizeof(messages) / sizeof(*messages);
#define ARK "\x03\x04"
const char *message = (
"seasons greetings" ARK
"happy holiday" ARK
"merry xmas" ARK
"happy new year" ARK
"CQ CQ OF9X" ARK
);
// These colors mirror pretty closely some cheapo LED lights we have
const uint32_t colors[] = {
@ -100,80 +102,67 @@ void setup() {
pinMode(LED_BUILTIN, OUTPUT);
}
bool strandUpdate(bool white) {
static unsigned long nextEventMillis = 0;
unsigned long now = millis();
static int group = 0;
Pulse mainPulse = Pulse(DELAY_MS);
if (now < nextEventMillis) {
bool strandUpdate(bool white) {
if (!mainPulse.Ticked()) {
return false;
}
int pos = (group * LEDS_PER_GROUP) + random(LEDS_PER_GROUP);
if (random(100) < ACTIVITY) {
uint32_t color;
for (int group = 0; group < NUM_GROUPS; ++group) {
int pos = (group * LEDS_PER_GROUP) + random(LEDS_PER_GROUP);
if (random(100) < GROUP_UPDATE_PROBABILITY) {
if (random(100) < ACTIVITY) {
uint32_t color;
if (white) {
color = WHITE;
} else {
color = colors[random(ncolors)];
if (white) {
color = WHITE;
} else {
color = colors[random(ncolors)];
}
leds[pos] = color;
} else {
leds[pos] = 0;
}
group = (group + 1) % NUM_GROUPS;
}
leds[pos] = color;
} else {
leds[pos] = 0;
}
group = (group + 1) % NUM_GROUPS;
nextEventMillis = now + (DELAY_MS / NUM_GROUPS);
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() {
static unsigned long nextEventMillis = 0;
unsigned long now = millis();
if (now < nextEventMillis) {
if (!mainPulse.Ticked()) {
return false;
}
FastLED.clear();
nextEventMillis = now + (DELAY_MS / NUM_GROUPS); // Keep timing consistent
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() {
static bool white = false;
bool update = false;
@ -188,6 +177,7 @@ void loop() {
update |= morseUpdate();
} else {
update |= black();
update |= millisUpdate();
}
if (update) {

212
morse.cpp
View File

@ -1,42 +1,122 @@
#include "morse.h"
// Each morse dit/dah is stored as a nybble
#define dit 1
#define dah 3
#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
struct MorseSign {
// Length of this sign. If Length is 0, Bits is the duration of a pause.
uint8_t Length;
uint8_t Bits;
};
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() {
SetText("");
}
@ -46,7 +126,7 @@ MorseEncoder::MorseEncoder(const char *s) {
void MorseEncoder::SetText(const char *s) {
p = s;
crumb = 0;
bit = 0;
ticksLeft = 0;
Transmitting = false;
}
@ -75,45 +155,29 @@ bool MorseEncoder::Tick() {
return true;
}
// If that was the end of the letter, we have to pause more
if (crumb == 4) {
crumb = 0;
++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;
ticksLeft = MORSE_PAUSE_WORD - 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;
const struct MorseSign sign = GetMorseSign(*p);
if (sign.Length == 0) {
// Pause
Transmitting = false;
ticksLeft = MORSE_PAUSE_LETTER - MORSE_DIT;
ticksLeft = sign.Bits - (MORSE_PAUSE_LETTER - MORSE_DIT);
++p;
bit = 0;
} else if (bit == sign.Length) {
// All done with that sign!
Transmitting = false;
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;

View File

@ -6,6 +6,7 @@
#define MORSE_DAH 3
#define MORSE_PAUSE_LETTER 3
#define MORSE_PAUSE_WORD 6
#define MORSE_PAUSE_TRANSMISSION 50
class MorseEncoder {
public:
@ -35,6 +36,6 @@ class MorseEncoder {
private:
const char *p;
uint8_t crumb;
uint8_t bit;
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
int test_main(int argc, char *argv[]) {
MorseEncoder enc = MorseEncoder("SOS ck ck ark");
MorseEncoder enc = MorseEncoder("SOS ck ck \x03");
while (enc.Tick()) {
if (enc.Transmitting) {
printf("#");