Skip to content

Add timestamps to pin histories (extensible to all settable items) #116

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Feb 20, 2019
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -7,14 +7,26 @@ and this project adheres to [Semantic Versioning](http://semver.org/).

## [Unreleased]
### Added
* `release-new-version.sh` script
* outputs for `PinHistory` can now report timestamps
* Fibonacci Clock for clock testing purposes (internal to this library)

### Changed
* Shortened `ArduinoQueue` push and pop operations
* `ci/Queue.h` is now `MockEventQueue.h`, with timing data
* `MockEventQueue::Node` now contains struct `MockEventQueue::Event`, which contains both the templated type `T` and a field for a timestamp.
* Construction of `MockEventQueue` now includes a constructor argument for the time-fetching function
* Construction of `PinHistory` now includes a constructor argument for the time-fetching function
* `PinHistory` can now return an array of timestamps for its events
* `GodmodeState` is now a singleton pattern, which is necessary to support the globality of Arduino functions
* `GodmodeState` now uses timestamped PinHistory for Analog and Digital

### Deprecated

### Removed

### Fixed
* `ArduinoQueue` no longer leaks memory

### Security

25 changes: 13 additions & 12 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -46,16 +46,17 @@ ARDUINO_CI_SKIP_RUBY_RSPEC_TESTS=1 bundle exec rspec
## Packaging the Gem

* Merge pull request with new features
* `git stash save` (at least before the gem build step, but easiest here).
* `git pull --rebase`
* Update the sections of `CHANGELOG.md` by running `bundle exec keepachangelog_manager.rb --increment-patch`
* Bump the version in lib/arduino_ci/version.rb and change it in README.md (since rubydoc.info doesn't always redirect to the latest version)
* `git add README.md CHANGELOG.md lib/arduino_ci/version.rb`
* `git commit -m "vVERSION bump"`
* `git tag -a vVERSION -m "Released version VERSION"`
* `gem build arduino_ci.gemspec`
* `git stash pop`
* `gem push arduino_ci-VERSION.gem`
* `git push upstream`
* `git push upstream --tags`
* Execute `release-new-version.sh` with the appropriate argument (e.g. `--increment-patch`), which does the following:
* `git stash save` (at least before the gem build step, but easiest here).
* `git pull --rebase`
* Update the sections of `CHANGELOG.md` by running `bundle exec keepachangelog_manager.rb --increment-patch`
* Bump the version in lib/arduino_ci/version.rb and change it in README.md (since rubydoc.info doesn't always redirect to the latest version)
* `git add README.md CHANGELOG.md lib/arduino_ci/version.rb`
* `git commit -m "vVERSION bump"`
* `git tag -a vVERSION -m "Released version VERSION"`
* `gem build arduino_ci.gemspec`
* `git stash pop`
* `gem push arduino_ci-VERSION.gem`
* `git push upstream`
* `git push upstream --tags`
* Visit http://www.rubydoc.info/gems/arduino_ci/VERSION to initiate the doc generation process
7 changes: 7 additions & 0 deletions SampleProjects/DoSomething/test/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Purpose

These files are designed to test the Ruby gem itself, such that its basic tasks of library installation and compilation can be verified. (i.e., use minimal C++ files -- feature tests for C++ unittest/arduino code belong in `../TestSomething/test/`).

## Naming convention

Files in this directory are expected to have names that either contains "bad" if it is expected to fail or "good" if it is expected to pass. This provides a signal to `rspec` for how the code is expected to perform.
17 changes: 17 additions & 0 deletions SampleProjects/TestSomething/test/fibonacciClock.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#pragma once

// fibbonacci clock
unsigned long lastFakeMicros = 1;
unsigned long fakeMicros = 0;

void resetFibClock() {
lastFakeMicros = 1;
fakeMicros = 0;
}

unsigned long fibMicros() {
unsigned long ret = lastFakeMicros + fakeMicros;
lastFakeMicros = fakeMicros;
fakeMicros = ret;
return ret;
}
25 changes: 25 additions & 0 deletions SampleProjects/TestSomething/test/fibonacciclock.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#include <ArduinoUnitTests.h>
#include <Arduino.h>
#include "fibonacciClock.h"

unittest(my_fib_clock)
{
resetFibClock();
assertEqual(1, fibMicros());
assertEqual(1, fibMicros());
assertEqual(2, fibMicros());
assertEqual(3, fibMicros());
assertEqual(5, fibMicros());
assertEqual(8, fibMicros());
assertEqual(13, fibMicros());
assertEqual(21, fibMicros());

// and again
resetFibClock();
assertEqual(1, fibMicros());
assertEqual(1, fibMicros());
assertEqual(2, fibMicros());
}


unittest_main()
67 changes: 37 additions & 30 deletions SampleProjects/TestSomething/test/godmode.cpp
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
#include <ArduinoUnitTests.h>
#include <Arduino.h>
#include "fibonacciClock.h"

GodmodeState* state = GODMODE();

unittest_setup()
{
unittest_setup() {
resetFibClock();
state->reset();
}

unittest(millis_micros_and_delay)
{
unittest(millis_micros_and_delay) {
assertEqual(0, millis());
assertEqual(0, micros());
delay(3);
@@ -20,8 +20,7 @@ unittest(millis_micros_and_delay)
assertEqual(14000, micros());
}

unittest(random)
{
unittest(random) {
randomSeed(1);
assertEqual(state->seed, 1);

@@ -37,8 +36,7 @@ unittest(random)
assertEqual(state->seed, 4294967282);
}

unittest(pins)
{
unittest(pins) {
pinMode(1, OUTPUT); // this is a no-op in unit tests. it's just here to prove compilation
digitalWrite(1, HIGH);
assertEqual(HIGH, state->digitalPin[1]);
@@ -64,8 +62,7 @@ unittest(pins)
assertEqual(56, analogRead(1));
}

unittest(pin_read_history)
{
unittest(pin_read_history) {
int future[6] = {33, 22, 55, 11, 44, 66};
state->analogPin[1].fromArray(future, 6);
for (int i = 0; i < 6; ++i)
@@ -88,20 +85,20 @@ unittest(pin_read_history)
}
}

unittest(pin_write_history)
{
unittest(digital_pin_write_history_with_timing) {
int numMoved;
bool expectedD[6] = {LOW, HIGH, LOW, LOW, HIGH, HIGH};
bool actualD[6];
unsigned long expectedT[6] = {0, 1, 1, 2, 3, 5};
unsigned long actualT[6];

// history for digital pin
digitalWrite(1, HIGH);
digitalWrite(1, LOW);
digitalWrite(1, LOW);
digitalWrite(1, HIGH);
digitalWrite(1, HIGH);
// history for digital pin. start from 1 since LOW is the initial value
for (int i = 1; i < 6; ++i) {
state->micros = fibMicros();
digitalWrite(1, expectedD[i]);
}

assertEqual(6, state->digitalPin[1].historySize());
bool expectedD[6] = {LOW, HIGH, LOW, LOW, HIGH, HIGH};
bool actualD[6];
numMoved = state->digitalPin[1].toArray(actualD, 6);
assertEqual(6, numMoved);
// assert non-destructive
@@ -113,6 +110,19 @@ unittest(pin_write_history)
assertEqual(expectedD[i], actualD[i]);
}

numMoved = state->digitalPin[1].toTimestampArray(actualT, 6);
assertEqual(6, numMoved);
for (int i = 0; i < numMoved; ++i)
{
assertEqual(expectedT[i], actualT[i]);
}
}

unittest(analog_pin_write_history) {
int numMoved;
int expectedA[6] = {0, 11, 22, 33, 44, 55};
int actualA[6];

// history for analog pin
analogWrite(1, 11);
analogWrite(1, 22);
@@ -121,8 +131,7 @@ unittest(pin_write_history)
analogWrite(1, 55);

assertEqual(6, state->analogPin[1].historySize());
int expectedA[6] = {0, 11, 22, 33, 44, 55};
int actualA[6];

numMoved = state->analogPin[1].toArray(actualA, 6);
assertEqual(6, numMoved);
// assert non-destructive
@@ -133,7 +142,9 @@ unittest(pin_write_history)
{
assertEqual(expectedA[i], actualA[i]);
}
}

unittest(ascii_pin_write_history) {
// digitial history as serial data, big-endian
bool binaryAscii[24] = {
0, 1, 0, 1, 1, 0, 0, 1,
@@ -207,8 +218,7 @@ unittest(spi) {
}
}

unittest(does_nothing_if_no_data)
{
unittest(does_nothing_if_no_data) {
int myPin = 3;
state->serialPort[0].dataIn = "";
state->serialPort[0].dataOut = "";
@@ -218,8 +228,7 @@ unittest(spi) {
assertEqual("", state->serialPort[0].dataOut);
}

unittest(keeps_pin_low_and_acks)
{
unittest(keeps_pin_low_and_acks) {
int myPin = 3;
state->serialPort[0].dataIn = "0";
state->serialPort[0].dataOut = "";
@@ -230,8 +239,7 @@ unittest(spi) {
assertEqual("Ack 3 0", state->serialPort[0].dataOut);
}

unittest(flips_pin_high_and_acks)
{
unittest(flips_pin_high_and_acks) {
int myPin = 3;
state->serialPort[0].dataIn = "1";
state->serialPort[0].dataOut = "";
@@ -242,8 +250,7 @@ unittest(spi) {
assertEqual("Ack 3 1", state->serialPort[0].dataOut);
}

unittest(two_flips)
{
unittest(two_flips) {
int myPin = 3;
state->serialPort[0].dataIn = "10junk";
state->serialPort[0].dataOut = "";
126 changes: 121 additions & 5 deletions SampleProjects/TestSomething/test/pinhistory.cpp
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
#include <ArduinoUnitTests.h>
#include <Arduino.h>
#include "fibonacciClock.h"


unittest(pin_read_history) {
PinHistory<int> phi;
unittest(pin_read_history_int) {
PinHistory<int> phi; // pin history int

const int future[6] = {33, 22, 55, 11, 44, 66};
assertEqual(0, phi.queueSize());
@@ -16,8 +16,10 @@ unittest(pin_read_history) {

// assert end of history works
assertEqual(future[5], phi.retrieve());
}

PinHistory<bool> phb;
unittest(pin_read_history_bool_to_ascii) {
PinHistory<bool> phb; // pin history bool
phb.fromAscii("Yo", true);

// digitial history as serial data, big-endian
@@ -32,7 +34,19 @@ unittest(pin_read_history) {
assertEqual("Yo", phb.toAscii(0, true));
}

unittest(ascii_stuff) {
unittest(assignment_dumps_queue) {
PinHistory<bool> phb; // pin history bool
assertEqual(0, phb.queueSize());
assertEqual(0, phb.historySize());
phb.fromAscii("Yo", true);
assertEqual(16, phb.queueSize());
assertEqual(0, phb.historySize());
phb = false;
assertEqual(0, phb.queueSize());
assertEqual(1, phb.historySize());
}

unittest(ascii_to_bool_and_back) {
PinHistory<bool> phb;
assertEqual(0, phb.historySize());
phb.reset(false);
@@ -50,4 +64,106 @@ unittest(ascii_stuff) {
assertEqual("hi", phb.toAscii(1, true));
}

unittest(write_history) {
PinHistory<int> phi; // pin history int
int expectedA[6] = {0, 11, 22, 33, 44, 55};
for (int i = 0; i < 6; ++i)
{
assertEqual(i, phi.historySize());
phi = expectedA[i];
assertEqual(i + 1, phi.historySize());
assertEqual(0, phi.queueSize());
assertEqual(phi, expectedA[i]);
}

int actualA[6];
int numMoved = phi.toArray(actualA, 6);
assertEqual(6, numMoved);
// assert non-destructive by repeating the operation
numMoved = phi.toArray(actualA, 6);
assertEqual(6, numMoved);
for (int i = 0; i < 6; ++i)
{
assertEqual(expectedA[i], actualA[i]);
}
}

unittest(null_timing) {
PinHistory<int> phi; // pin history int
int expectedA[6] = {0, 11, 22, 33, 44, 55};
for (int i = 0; i < 6; ++i)
{
phi = expectedA[i];
}

unsigned long tStamps[6];
int numMoved = phi.toTimestampArray(tStamps, 6);
assertEqual(6, numMoved);
// assert non-destructive by repeating the operation
numMoved = phi.toTimestampArray(tStamps, 6);
assertEqual(6, numMoved);
for (int i = 0; i < 6; ++i)
{
assertEqual(0, tStamps[i]);
}
}

unittest(actual_timing_set_in_constructor) {
resetFibClock();
PinHistory<int> phi(fibMicros); // pin history int
for (int i = 0; i < 6; ++i)
{
phi = 0;
}

int expectedT[6] = {1, 1, 2, 3, 5, 8};
unsigned long tStamps[6];
int numMoved = phi.toTimestampArray(tStamps, 6);
assertEqual(6, numMoved);
for (int i = 0; i < 6; ++i)
{
assertEqual(expectedT[i], tStamps[i]);
}
}

unittest(actual_timing_set_after_constructor) {
resetFibClock();
PinHistory<int> phi; // pin history int
phi.setMicrosRetriever(fibMicros);
for (int i = 0; i < 6; ++i)
{
phi = 0;
}

int expectedT[6] = {1, 1, 2, 3, 5, 8};
unsigned long tStamps[6];
int numMoved = phi.toTimestampArray(tStamps, 6);
assertEqual(6, numMoved);
for (int i = 0; i < 6; ++i)
{
assertEqual(expectedT[i], tStamps[i]);
}
}

unittest(event_history) {
resetFibClock();
PinHistory<int> phi(fibMicros); // pin history int
for (int i = 0; i < 6; ++i)
{
phi = i;
}

int expectedT[6] = {1, 1, 2, 3, 5, 8};
MockEventQueue<int>::Event event[6];
int numMoved = phi.toEventArray(event, 6);
assertEqual(6, numMoved);
for (int i = 0; i < 6; ++i)
{
assertEqual(i, event[i].data);
assertEqual(expectedT[i], event[i].micros);
}
}



unittest_main()
57 changes: 48 additions & 9 deletions SampleProjects/TestSomething/test/queue.cpp
Original file line number Diff line number Diff line change
@@ -1,23 +1,25 @@
#include <ArduinoUnitTests.h>
#include <ci/Queue.h>
#include <MockEventQueue.h>
#include "fibonacciClock.h"

unittest(basic_queue_dequeue_and_size)
{
ArduinoCIQueue<int> q;
MockEventQueue<int> q;
int data[5] = {11, 22, 33, 44, 55};

assertTrue(q.empty());

for (int i = 0; i < 5; ++i) {
assertEqual(i, q.size());
q.push(data[i]);
assertEqual(data[i], q.back());
assertEqual(data[i], q.backData());
assertEqual(0, q.backTime()); // we didn't provide a function, so it should default to 0
assertEqual(i + 1, q.size());
}

for (int i = 0; i < 5; ++i) {
assertEqual(5 - i, q.size());
assertEqual(data[i], q.front());
assertEqual(data[i], q.frontData());
q.pop();
assertEqual(5 - i - 1, q.size());
}
@@ -27,30 +29,30 @@ unittest(basic_queue_dequeue_and_size)

unittest(copy_constructor)
{
ArduinoCIQueue<int> q;
MockEventQueue<int> q;
int data[5] = {11, 22, 33, 44, 55};
for (int i = 0; i < 5; ++i) q.push(data[i]);

ArduinoCIQueue<int> q2(q);
MockEventQueue<int> q2(q);

for (int i = 0; i < 5; ++i) {
assertEqual(5 - i, q2.size());
assertEqual(data[i], q2.front());
assertEqual(data[i], q2.frontData());
q2.pop();
assertEqual(5 - i - 1, q2.size());
}

for (int i = 0; i < 5; ++i) {
assertEqual(5 - i, q.size());
assertEqual(data[i], q.front());
assertEqual(data[i], q.frontData());
q.pop();
assertEqual(5 - i - 1, q.size());
}
}

unittest(boundaries)
{
ArduinoCIQueue<int> q;
MockEventQueue<int> q;
int data[2] = {11, 22};
for (int i = 0; i < 2; ++i) q.push(data[i]);

@@ -64,4 +66,41 @@ unittest(boundaries)

}

unittest(timed_events)
{
MockEventQueue<int> q;
int data[7] = {4, 50, 600, 8555, 9000, 9001, 1000000000};
for (int i = 0; i < 7; ++i) {
q.push(data[i], data[i]);
assertEqual(data[i], q.backData());
assertEqual(data[i], q.backTime());
}

for (int i = 0; i < 7; ++i) {
assertEqual(data[i], q.frontData());
assertEqual(data[i], q.frontTime());
q.pop();
}

}

unittest(clocked_events)
{
resetFibClock();
MockEventQueue<int> q(fibMicros);
int data[7] = {1, 1, 2, 3, 5, 8, 13}; //eureka
for (int i = 0; i < 7; ++i) {
q.push(data[i]);
assertEqual(data[i], q.backData());
assertEqual(data[i], q.backTime());
}

for (int i = 0; i < 7; ++i) {
assertEqual(data[i], q.frontData());
assertEqual(data[i], q.frontTime());
q.pop();
}

}

unittest_main()
2 changes: 1 addition & 1 deletion arduino_ci.gemspec
Original file line number Diff line number Diff line change
@@ -29,7 +29,7 @@ Gem::Specification.new do |spec|
spec.add_dependency "rubyzip", "~> 1.2"

spec.add_development_dependency "bundler", "~> 1.15"
spec.add_development_dependency "keepachangelog_manager", "~> 0.0.1"
spec.add_development_dependency "keepachangelog_manager", "~> 0.0.2"
spec.add_development_dependency "rspec", "~> 3.0"
spec.add_development_dependency 'rubocop', '~>0.59.0'
spec.add_development_dependency 'yard', '~>0.9.11'
46 changes: 27 additions & 19 deletions cpp/arduino/Godmode.cpp
Original file line number Diff line number Diff line change
@@ -2,37 +2,45 @@
#include "HardwareSerial.h"
#include "SPI.h"

GodmodeState godmode = GodmodeState();

GodmodeState* GODMODE() {
return &godmode;
return GodmodeState::getInstance();
}

GodmodeState* GodmodeState::instance = nullptr;

GodmodeState* GodmodeState::getInstance()
{
if (instance == nullptr)
{
instance = new GodmodeState();
for (int i = 0; i < MOCK_PINS_COUNT; ++i) {
instance->digitalPin[i].setMicrosRetriever(&GodmodeState::getMicros);
instance->analogPin[i].setMicrosRetriever(&GodmodeState::getMicros);
}
}

return instance;
}

unsigned long millis() {
GodmodeState* godmode = GODMODE();
return godmode->micros / 1000;
return GODMODE()->micros / 1000;
}

unsigned long micros() {
GodmodeState* godmode = GODMODE();
return godmode->micros;
return GODMODE()->micros;
}

void delay(unsigned long millis) {
GodmodeState* godmode = GODMODE();
godmode->micros += millis * 1000;
GODMODE()->micros += millis * 1000;
}

void delayMicroseconds(unsigned long micros) {
GodmodeState* godmode = GODMODE();
godmode->micros += micros;
GODMODE()->micros += micros;
}


void randomSeed(unsigned long seed)
{
GodmodeState* godmode = GODMODE();
godmode->seed = seed;
GODMODE()->seed = seed;
}

long random(long vmax)
@@ -81,16 +89,16 @@ void detachInterrupt(uint8_t interrupt) {

// Serial ports
#if defined(HAVE_HWSERIAL0)
HardwareSerial Serial(&godmode.serialPort[0].dataIn, &godmode.serialPort[0].dataOut, &godmode.serialPort[0].readDelayMicros);
HardwareSerial Serial(&GODMODE()->serialPort[0].dataIn, &GODMODE()->serialPort[0].dataOut, &GODMODE()->serialPort[0].readDelayMicros);
#endif
#if defined(HAVE_HWSERIAL1)
HardwareSerial Serial1(&godmode.serialPort[1].dataIn, &godmode.serialPort[1].dataOut, &godmode.serialPort[1].readDelayMicros);
HardwareSerial Serial1(&GODMODE()->serialPort[1].dataIn, &GODMODE()->serialPort[1].dataOut, &GODMODE()->serialPort[1].readDelayMicros);
#endif
#if defined(HAVE_HWSERIAL2)
HardwareSerial Serial2(&godmode.serialPort[2].dataIn, &godmode.serialPort[2].dataOut, &godmode.serialPort[2].readDelayMicros);
HardwareSerial Serial2(&GODMODE()->serialPort[2].dataIn, &GODMODE()->serialPort[2].dataOut, &GODMODE()->serialPort[2].readDelayMicros);
#endif
#if defined(HAVE_HWSERIAL3)
HardwareSerial Serial3(&godmode.serialPort[3].dataIn, &godmode.serialPort[3].dataOut, &godmode.serialPort[3].readDelayMicros);
HardwareSerial Serial3(&GODMODE()->serialPort[3].dataIn, &GODMODE()->serialPort[3].dataOut, &GODMODE()->serialPort[3].readDelayMicros);
#endif

template <typename T>
@@ -100,4 +108,4 @@ inline std::ostream& operator << ( std::ostream& out, const PinHistory<T>& ph )
}

// defined in SPI.h
SPIClass SPI = SPIClass(&godmode.spi.dataIn, &godmode.spi.dataOut);
SPIClass SPI = SPIClass(&GODMODE()->spi.dataIn, &GODMODE()->spi.dataOut);
43 changes: 30 additions & 13 deletions cpp/arduino/Godmode.h
Original file line number Diff line number Diff line change
@@ -16,7 +16,6 @@ void delayMicroseconds(unsigned long micros);
unsigned long millis();
unsigned long micros();


#define MOCK_PINS_COUNT 256

#if defined(UBRR3H)
@@ -32,16 +31,19 @@ unsigned long micros();
#endif

class GodmodeState {
struct PortDef {
String dataIn;
String dataOut;
unsigned long readDelayMicros;
};
private:
struct PortDef {
String dataIn;
String dataOut;
unsigned long readDelayMicros;
};

struct InterruptDef {
bool attached;
uint8_t mode;
};

struct InterruptDef {
bool attached;
uint8_t mode;
};
static GodmodeState* instance;

public:
unsigned long micros;
@@ -98,8 +100,24 @@ class GodmodeState {
return NUM_SERIAL_PORTS;
}

GodmodeState()
{
// Using this for anything other than unit testing arduino_ci itself
// is unsupported at the moment
void overrideClockTruth(unsigned long (*getMicros)(void)) {
}

// singleton pattern
static GodmodeState* getInstance();

static unsigned long getMicros() {
return instance->micros;
}

// C++ 11, declare as public for better compiler error messages
GodmodeState(GodmodeState const&) = delete;
void operator=(GodmodeState const&) = delete;

private:
GodmodeState() {
reset();
}
};
@@ -123,4 +141,3 @@ inline void noTone(uint8_t _pin) {}


GodmodeState* GODMODE();

86 changes: 86 additions & 0 deletions cpp/arduino/MockEventQueue.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
#pragma once

template <typename T>
class MockEventQueue {
public:
struct Event {
T data;
unsigned long micros;

Event() : data(T()), micros(0) {}
Event(const T &d, unsigned long const t) : data(d), micros(t) { }
};

private:
struct Node {
Event event;
Node* next;

Node(const Event &e, Node* n) : event(e), next(n) { }
};

Node* mFront;
Node* mBack;
unsigned long mSize;
T mNil;
unsigned long (*mGetMicros)(void);

void init(unsigned long (*getMicros)(void)) {
mFront = mBack = nullptr;
mSize = 0;
mGetMicros = getMicros;
}

public:
MockEventQueue(unsigned long (*getMicros)(void)): mNil() { init(getMicros); }
MockEventQueue(): mNil() { init(nullptr); }

MockEventQueue(const MockEventQueue<T>& q) {
init(q.mGetMicros);
for (Node* n = q.mFront; n; n = n->next) push(n->event);
}

void setMicrosRetriever(unsigned long (*getMicros)(void)) { mGetMicros = getMicros; }

inline unsigned long size() const { return mSize; }
inline bool empty() const { return 0 == mSize; }
inline Event front() const { return empty() ? Event(mNil, 0) : mFront->event; }
inline Event back() const { return empty() ? Event(mNil, 0) : mBack->event; }
inline T frontData() const { return front().data; }
inline T backData() const { return back().data; }
inline unsigned long frontTime() const { return front().micros; }
inline unsigned long backTime() const { return back().micros; }


// fully formed event
bool push(const Event& e) {
Node *n = new Node(e, nullptr);
if (n == nullptr) return false;
mBack = (mFront == nullptr ? mFront : mBack->next) = n;
return ++mSize;
}

// fully specfied event
bool push(const T& v, unsigned long const time) {
Event e = {v, time};
return push(e);
}

// event needing timestamp
bool push(const T& v) {
unsigned long micros = mGetMicros == nullptr ? 0 : mGetMicros();
return push(v, micros);
}

void pop() {
if (empty()) return;
Node* n = mFront;
mFront = mFront->next;
delete n;
if (--mSize == 0) mBack = nullptr;
}

void clear() { while (!empty()) pop(); }

~MockEventQueue() { clear(); }
};
88 changes: 64 additions & 24 deletions cpp/arduino/PinHistory.h
Original file line number Diff line number Diff line change
@@ -1,40 +1,40 @@
#pragma once
#include "ci/Queue.h"
#include "MockEventQueue.h"
#include "ci/ObservableDataStream.h"
#include "WString.h"

// pins with history.
template <typename T>
class PinHistory : public ObservableDataStream {
private:
ArduinoCIQueue<T> qIn;
ArduinoCIQueue<T> qOut;
MockEventQueue<T> qIn;
MockEventQueue<T> qOut;

void clear() {
qOut.clear();
qIn.clear();
}

// enqueue ascii bits
void a2q(ArduinoCIQueue<T> &q, String input, bool bigEndian, bool advertise) {
void a2q(MockEventQueue<T> &q, String input, bool bigEndian, bool advertise) {
// 8 chars at a time, form up
for (int j = 0; j < input.length(); ++j) {
for (int i = 0; i < 8; ++i) {
int shift = bigEndian ? 7 - i : i;
unsigned char mask = (0x01 << shift);
q.push(mask & input[j]);
if (advertise) advertiseBit(q.back()); // not valid for all possible types but whatever
if (advertise) advertiseBit(q.backData()); // not valid for all possible types but whatever
}
}
}


// convert a queue to a string as if it was serial bits
// start from offset, consider endianness
String q2a(const ArduinoCIQueue<T> &q, unsigned int offset, bool bigEndian) const {
String q2a(const MockEventQueue<T> &q, unsigned int offset, bool bigEndian) const {
String ret = "";

ArduinoCIQueue<T> q2(q);
MockEventQueue<T> q2(q);

while (offset) {
q2.pop();
@@ -48,7 +48,7 @@ class PinHistory : public ObservableDataStream {
unsigned char acc = 0x00;
for (int i = 0; i < 8; ++i) {
int shift = bigEndian ? 7 - i : i;
T val = q2.front();
T val = q2.frontData();
unsigned char bit = val ? 0x1 : 0x0;
acc |= (bit << shift);
q2.pop();
@@ -59,15 +59,25 @@ class PinHistory : public ObservableDataStream {
return ret;
}

void init() {
asciiEncodingOffsetIn = 0; // default is sensible
asciiEncodingOffsetOut = 1; // default is sensible
}

public:
unsigned int asciiEncodingOffsetIn;
unsigned int asciiEncodingOffsetOut;

PinHistory(unsigned long (*getMicros)(void)) : ObservableDataStream(), qOut(getMicros) {
init();
}

PinHistory() : ObservableDataStream() {
asciiEncodingOffsetIn = 0; // default is sensible
asciiEncodingOffsetOut = 1; // default is sensible
init();
}

void setMicrosRetriever(unsigned long (*getMicros)(void)) { qOut.setMicrosRetriever(getMicros); }

void reset(T val) {
clear();
qOut.push(val);
@@ -79,8 +89,8 @@ class PinHistory : public ObservableDataStream {

// This returns the "value" of the pin in a raw sense
operator T() const {
if (!qIn.empty()) return qIn.front();
return qOut.back();
if (!qIn.empty()) return qIn.frontData();
return qOut.backData();
}

// this sets the value of the pin authoritatively
@@ -89,23 +99,23 @@ class PinHistory : public ObservableDataStream {
T operator=(const T& i) {
qIn.clear();
qOut.push(i);
advertiseBit(qOut.back()); // not valid for all possible types but whatever
return qOut.back();
advertiseBit(qOut.backData()); // not valid for all possible types but whatever
return qOut.backData();
}

// This returns the "value" of the pin according to the queued values
// if there is input, advance it to the output.
// then take the latest output.
T retrieve() {
if (!qIn.empty()) {
T hack_required_by_travis_ci = qIn.front();
T hack_required_by_travis_ci = qIn.frontData();
qIn.pop();
qOut.push(hack_required_by_travis_ci);
}
return qOut.back();
return qOut.backData();
}

// enqueue a set of elements
// enqueue a set of data elements
void fromArray(T const * const arr, unsigned int length) {
for (int i = 0; i < length; ++i) qIn.push(arr[i]);
}
@@ -124,18 +134,48 @@ class PinHistory : public ObservableDataStream {
// start from offset, consider endianness
String incomingToAscii(bool bigEndian) const { return incomingToAscii(asciiEncodingOffsetIn, bigEndian); }

// convert the pin history to a string as if it was Serial comms
// convert the pin history data to a string as if it was Serial comms
// start from offset, consider endianness
String toAscii(unsigned int offset, bool bigEndian) const { return q2a(qOut, offset, bigEndian); }

// convert the pin history to a string as if it was Serial comms
// convert the pin history data to a string as if it was Serial comms
// start from offset, consider endianness
String toAscii(bool bigEndian) const { return toAscii(asciiEncodingOffsetOut, bigEndian); }

// copy elements to an array, up to a given length
// copy data elements to an array, up to a given length
// return the number of elements moved
int toArray (T* arr, unsigned int length) const {
ArduinoCIQueue<T> q2(qOut);
MockEventQueue<T> q2(qOut); // preserve const by copying

int ret = 0;
for (int i = 0; i < length && q2.size(); ++i) {
arr[i] = q2.frontData();
q2.pop();
++ret;
}
return ret;
}

// copy pin history timing to an array, up to a given length.
// note that this records times between calls to the pin, not between transitions
// return the number of elements moved
int toTimestampArray(unsigned long* arr, unsigned int length) const {
MockEventQueue<T> q2(qOut); // preserve const by copying

int ret = 0;
for (int i = 0; i < length && q2.size(); ++i) {
arr[i] = q2.frontTime();
q2.pop();
++ret;
}
return ret;
}

// copy pin history timing to an array, up to a given length.
// note that this records times between calls to the pin, not between transitions
// return the number of elements moved
int toEventArray(typename MockEventQueue<T>::Event* arr, unsigned int length) const {
MockEventQueue<T> q2(qOut); // preserve const by copying

int ret = 0;
for (int i = 0; i < length && q2.size(); ++i) {
@@ -146,12 +186,12 @@ class PinHistory : public ObservableDataStream {
return ret;
}

// see if the array matches the elements in the queue
// see if the array matches the data of the elements in the queue
bool hasElements (T const * const arr, unsigned int length) const {
int i;
ArduinoCIQueue<T> q2(qOut);
MockEventQueue<T> q2(qOut); // preserve const by copying
for (i = 0; i < length && q2.size(); ++i) {
if (q2.front() != arr[i]) return false;
if (q2.frontData() != arr[i]) return false;
q2.pop();
}
return i == length;
73 changes: 0 additions & 73 deletions cpp/arduino/ci/Queue.h

This file was deleted.

73 changes: 73 additions & 0 deletions release-new-version.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
#!/bin/bash

# This script automates the gem release project for this repo.

GEM_NAME=arduino_ci
GEM_MODULE=ArduinoCI
PUSH_REMOTE=upstream

# test if we have an arguments on the command line
if [ $# -lt 1 ]
then
echo "You must pass an argument for KeepAChangelogManager:"
bundle exec keepachangelog_manager.rb
exit 1
fi

# set up a cleanup function for any errors, so that we git stash pop
cleanup () {
set +x +e
echo -e "\n### Reverting uncommitted changes"
git checkout README.md CHANGELOG.md lib/$GEM_NAME/version.rb
if [ $DID_STASH -eq 0 ]; then
echo -e "\n### Unstashing changes"
git stash pop
fi
exit $1
}

DIDNT_STASH="No local changes to save"
DID_STASH=1
echo -ne "\n### Stashing changes..."
STASH_OUTPUT=$(git stash save)
[ "$DIDNT_STASH" != "$STASH_OUTPUT" ]
DID_STASH=$?
echo DID_STASH=$DID_STASH

trap "cleanup 1" INT TERM ERR
set -xe

echo "### Checking existence of specified git push destination '$PUSH_REMOTE'"
git remote get-url $PUSH_REMOTE

# ensure latest master
git pull --rebase

# update version in changelog and save it
NEW_VERSION=$(bundle exec keepachangelog_manager.rb $@)

echo "Checking whether new version string is a semver"
echo $NEW_VERSION | grep -Eq ^[0-9]*\.[0-9]*\.[0-9]*$

# write version.rb with new version
cat << EOF > lib/$GEM_NAME/version.rb
module $GEM_MODULE
VERSION = "$NEW_VERSION".freeze
end
EOF

# update README with new version
sed -e "s/\/gems\/$GEM_NAME\/[0-9]*\.[0-9]*\.[0-9]*)/\/gems\/$GEM_NAME\/$NEW_VERSION)/" -i "" README.md

# mutation!
git add README.md CHANGELOG.md lib/$GEM_NAME/version.rb
git commit -m "v$NEW_VERSION bump"
git tag -a v$NEW_VERSION -m "Released version $NEW_VERSION"
gem build $GEM_NAME.gemspec
gem push $GEM_NAME-$NEW_VERSION.gem
git push $PUSH_REMOTE
git push $PUSH_REMOTE --tags
git fetch

# do normal cleanup
cleanup 0