Transcoding Ringtones with ffmpeg

Recently, my daughter wanted a ringtone for her new favorite song, “Soulmate” by Mac Miller. Here is what I used after poking around with ffmpeg.

ffmpeg -i Mac\ Miller\ \ Soulmate.mp4 -ss 139 -t 30 -vn -acodec aac -b:a 128k soulmate_ringtone_139s_30s.m4r

-i – input file
-ss – start time in seconds, can be other formats, i.e., 2:19
-t – length in seconds, ringtones have to be <= 30 seconds
-vn | -codec:v none | -vcodec none – no video processing
-acodec aac – output audio in aac format, needed for ringtones
-b:a | -b:audio | -audio_bitrate – output bitrate

Tagged with:
Posted in ffmpeg

School Picture Racket

Every parent knows Lifetouch sets its prices at tolerable levels. Every year it’s the same form, exact pricing. We give in. My daughter’s school switched to a company that attempted to charge $110 for one digital image. Thanks for the sample images, they worked great in my AI app.

Disclaimer: I bought the photos but it was an opportunity to experiment on real stuff

Posted in Uncategorized

Debugging Hell

I’ve dealt with a conundrum for the past 72 hours. My TouchID key had seemed to stopped working on my ’17 MacBook Pro. That meant a few difficult things:

  1. No powering off the machine
  2. No powering on the machine
  3. No forcing it into recovery mode
  4. No resetting of the SMC
  5. No resetting of the PVRAM

The only way of doing anything was waiting for the battery to die enough to fresh-boot and try something, whatever it was. Otherwise the login screen would deny every attempt. Also, every re-install attempt would result in the infamous The recovery server could not be contacted. Which from the logs seemed to be a legit timeout combined with an unexpected (but correct) disk format. I was about to throw it out the window, slamming on it on the desk. Too many people had seen the same errors before.

But after a trip to Lowe’s to retrieve the (almost) correct drivers I was able to disconnect the battery internally. So now, powering it on, just meant plugging it in. So finally, after a few attempts at making a bootable installer, it’s back up and running. TouchID and all working-fine. Pentalobe != Torx.

It took hours. But it’s back in its humble state; ready for Zooms and rudimentary Java experiments.

Posted in Uncategorized

Getting Sublime’s Package Control Working

I’m trying out Sublime Text after using Atom forever and ever; even after being sunsetted years ago. The only issue was ST’s Package Control wasn’t working, which was one of Atom’s greatest features. It looks like this has plagued users for a while. The root issue is MacOS’s heavily modified version of OpenSSL, LibreSSL, isn’t compatible. The console will show something like…

Traceback (most recent call last):
File "../Library/Application Support/Sublime Text/Installed Packages/Package Control.sublime-package/package_control/deps/oscrypto/_openssl/_libcrypto_ctypes.py", line 305, in <module>
File "./python3.3/ctypes/__init__.py", line 366, in __getattr__
File "./python3.3/ctypes/__init__.py", line 371, in __getitem__
AttributeError: dlsym(0x7f876fc44440, EVP_PKEY_size): symbol not found

There were a lot of incorrect solutions out there that were potentially dangerous 😬, like unlinking LibreSSL or replacing its libraries manually. It turns out the latest versions of Package Control fix it. They just haven’t been included in Sublime Text’s releases yet. It turns out the fix is rather easy.

  1. Download the latest release from https://github.com/wbond/package_control/releases
  2. Rename the package to Package Control.sublime-package1
  3. (Re)place the package in ~/Library/Application Support/Sublime Text/Installed Packages
  4. Restart Sublime Text and there shouldn’t be any more errors in the console.
It works!
  1. This was a huge gotcha for me. ST kept thinking it wasn’t installed therefore would download the original broken version. ↩︎
Posted in Uncategorized

Setting Version-Agnostic Environment Variables

A new job means a new development environment!  Our wiki had us setting environment variables for JAVA_HOME, etc. tied to specific versions. I poked around to see if there’s a way to do that version-agnostically.  Turns out there is! Especially if you use Homebrew to install your dependencies.

Need JAVA_HOME but don’t want a hardcoded location? /usr/libexec/java_home will give you the full path and the -v 1.(6|7|8) option will give it for whatever version you want. Adding it to your .bash_profile looks like export JAVA_HOME="$(/usr/libexec/java_home -v 1.8)".

Need (GRADLE|GROOVY|MAVEN)_HOME? Did you install and maintain it with Homebrew? They’re symlinked to /usr/local/opt/(gradle|groovy|maven)/libexec

Tagged with: , ,
Posted in Mac, Scripting

Commands To Install MSMQ By OS Version

Windows XP & Server 2003 (R2)


sysocmgr /i:"%SYSTEMROOT%\inf\sysoc.inf" /u:"msmq.ini"


[Components]
msmq_Core = ON
msmq_LocalStorage = ON

view raw

msmq.ini

hosted with ❤ by GitHub

Windows Vista & 7


ocsetup MSMQ-Container;MSMQ-Server /quiet /norestart

Windows Server 2008 (R2)


ocsetup MSMQ-Server /quiet /norestart

Windows Server 2012 (R2) & 8(.1)


dism /online /enable-feature /featurename:"MSMQ-Services" /featurename:"MSMQ-Server" /featurename:"MSMQ" /quiet /norestart

Posted in Uncategorized

Using Reusable ZeroMQ C Helpers

Now it’s really easy to throw data around using the 0MQ helpers written up before!

Subscribe


void SubscribeCallback(uint8_t *buf, int size)
{
//do whatever with the payload received
}
//set up the subscribe thread
SubscribeZeroMQDataThreadArgs *subscribeThread = StartSubscribe(zmqContext, "tcp://hostname:port", "topic", SubscribeCallback, "LogCategory");
//shutdown the subscribe thread later
StopSubscribe(subscribeThread);

Request Reply


static RequestReplyZeroMQReply* RequestReplyCallback(const char *request, const void *argument, const uint8_t *data, const int size)
{
RequestReplyZeroMQReply *reply = malloc(sizeof(RequestReplyZeroMQReply));
someData_t *someData;
int len = get_some_data_to_return(&someData, data, size);
reply->data = someData;
reply->size = len;
return reply;
}
//set up the request reply thread
//NULL could be a pointer to a variable you'd like passed to the callback
RequestReplyZeroMQThreadArgs* thread = StartRequestReply(zmqContext, "tcp://hostname:port", RequestReplyCallback, NULL, "logCategory");
StopRequestReply(thread);

Forwarding


ForwardZeroMQDataThreadArgs *forwardThread = StartForward(context, "tcp://forwardTransport", "logCategory", 2, "ipc://someSubTransport", "tcp://anotherSubTransport");
StopForward(forwardThread);

Posted in C, Messaging, ZeroMQ

Reusable ZeroMQ C Helpers

My most recent project has involved a few common 0MQ patterns like request-reply, forwarding, and topic subscription. I was using each of these enough to move them out to a shared library that could be improved as I encountered bugs and new, albiet small, improvements needed.

First was reliably getting a socket. For some reason, I have yet to investigate, the legacy application I’m building on seems to throw a number of interrupts on startup so I had to create a common retry method when creating sockets.


void *GetZMQSocketWithRetries(void *context, const char *transport, int socketOptions, bool bind, log4c_category_t *cat);

view raw

ZMQHelpers.h

hosted with ❤ by GitHub

The user provides the root 0MQ context, desired transport, any options, if a bind or connect should be used, and finally a logging category.

The second was subscribing to a transport and doing something whenever a specific topic (first part of a multi-part message) was received.


typedef void (* BYTE_ARRAY_CALLBACK)(uint8_t *data, int size);
typedef struct _SubscribeZeroMQDataThreadStruct
{
void *context;
const char *transport;
const char *topic;
BYTE_ARRAY_CALLBACK callback;
const char *logCategory;
bool running;
pthread_t *thread;
} SubscribeZeroMQDataThreadArgs;
SubscribeZeroMQDataThreadArgs *StartSubscribe(void *zmqContext, const char *zmqTransport, const char *topic, BYTE_ARRAY_CALLBACK callback, const char *logCategory);
void StopSubscribe(SubscribeZeroMQDataThreadArgs *args);

view raw

Subscribe.h

hosted with ❤ by GitHub

It follows the same pattern; provide the context, transport, desired topic, and logging category along with a callback that will receive the payload or second part of the multi-part message.

The above subscribe pattern was modified into a rough request-reply pattern allowing the user not only receive both the topic and data in a callback but also reply directly to the requester with another payload.


typedef struct _RequestReplyZeroMQReply
{
void *data;
int size;
} RequestReplyZeroMQReply;
typedef RequestReplyZeroMQReply* (*STRING_CALLBACK)(const char *request, const void *argument, const uint8_t *data, const int size);
typedef struct _RequestReplyZeroMQThreadStruct
{
void *context;
bool bind;
bool running;
const char *transport;
const char *logCategory;
const void *argument;
STRING_CALLBACK callback;
pthread_t *thread;
} RequestReplyZeroMQThreadArgs;
RequestReplyZeroMQThreadArgs *StartRequestReply(void *zmqContext, const char *zmqTransport, STRING_CALLBACK callback, void *callbackArg, const char *logCategory);
void StopRequestReply(RequestReplyZeroMQThreadArgs *args);

view raw

RequestReply.h

hosted with ❤ by GitHub

Lastly, was reliably forwarding messages from any number of transports to another transport.


typedef struct _ForwardZeroMQDataThreadStruct
{
void *context;
int numSubs;
const char *subTransports[10];
const char *pubTransport;
const char *logCategory;
bool running;
pthread_t *thread;
} ForwardZeroMQDataThreadArgs;
ForwardZeroMQDataThreadArgs *StartForward(void *zmqContext, const char *pubTransport, const char *logCategory, int numSubTransports, …);
void StopForward(ForwardZeroMQDataThreadArgs *args);

view raw

Forward.h

hosted with ❤ by GitHub

Below are some recent cuts of the code…


void *GetZMQSocketWithRetries(void *context, const char *transport, int socketOptions, bool bind, log4c_category_t *cat)
{
void *socket = zmq_socket(context, socketOptions);
if(socket == NULL)
{
log4c_category_log(cat, LOG4C_PRIORITY_ERROR, "unable to create socket for transport [%s] %s", transport, zmq_strerror(errno));
return NULL;
}
log4c_category_log(cat, LOG4C_PRIORITY_INFO, "created socket for transport [%s]", transport);
int rc;
int retryCount = 0;
do{
log4c_category_log(cat, LOG4C_PRIORITY_DEBUG, "attempted to connect to [%s] for the %d time", transport, retryCount);
if(!bind)
rc = zmq_connect(socket, transport);
else
rc = zmq_bind(socket, transport);
retryCount++;
if(rc != 0)
log4c_category_log(cat, LOG4C_PRIORITY_WARN, "unable to connect to [%s] during %d attempt: %s", transport, retryCount, zmq_strerror(errno));
}while(rc != 0 && retryCount < 5);
if(rc != 0)
{
log4c_category_log(cat, LOG4C_PRIORITY_ERROR, "unable to connect to [%s] after five retries", transport);
return NULL;
}
log4c_category_log(cat, LOG4C_PRIORITY_INFO, "connected socket for transport [%s]", transport);
return socket;
}

view raw

ZMQHelpers.c

hosted with ❤ by GitHub


static void *ZMQSubscribe(void *args)
{
SubscribeZeroMQDataThreadArgs *typedArgs = args;
log4c_category_t *cat = log4c_category_get(typedArgs->logCategory);
void *subscriber = GetZMQSocketWithRetries(typedArgs->context, typedArgs->transport, ZMQ_SUB, false, cat);
if(subscriber == NULL)
return NULL;
int rc = zmq_setsockopt(subscriber, ZMQ_SUBSCRIBE, typedArgs->topic, strlen(typedArgs->topic));
if(rc != 0)
{
log4c_category_log(cat, LOG4C_PRIORITY_ERROR, "unable to subscribe to topic [%s] on transport [%s] %s", typedArgs->topic, typedArgs->transport, zmq_strerror(errno));
return NULL;
}
log4c_category_log(cat, LOG4C_PRIORITY_INFO, "subscribed to topic [%s] on transport [%s]", typedArgs->topic, typedArgs->transport);
uint8_t buf[1024];
char topic[1024];
while(typedArgs->running)
{
int size = zmq_recv(subscriber, topic, sizeof(topic), ZMQ_NOBLOCK);
if(size == -1)
{
if(errno != EAGAIN && errno != EINTR)
log4c_category_log(cat, LOG4C_PRIORITY_WARN, "couldn't receive topic from transport [%s] %s", typedArgs->transport, zmq_strerror(errno));
//no messages available, sleep and go on
usleep(500000);
}
else
{
topic[size] = '\0';
log4c_category_log(cat, LOG4C_PRIORITY_DEBUG, "received a topic of size %d [%s] from transport [%s]", size, topic, typedArgs->transport);
size = zmq_recv(subscriber, buf, 1024, 0);
log4c_category_log(cat, LOG4C_PRIORITY_DEBUG, "received data of size %d from transport [%s]", size, typedArgs->transport);
typedArgs->callback(buf, size);
}
}
return NULL;
}
SubscribeZeroMQDataThreadArgs *StartSubscribe(void *zmqContext, const char *zmqTransport, const char *topic, BYTE_ARRAY_CALLBACK callback, const char *logCategory)
{
SubscribeZeroMQDataThreadArgs *threadArgs = malloc(sizeof(SubscribeZeroMQDataThreadArgs));
threadArgs->callback = callback;
threadArgs->context = zmqContext;
threadArgs->running = true;
char *topicCopy = malloc(1 + strlen(topic));
strcpy(topicCopy, topic);
threadArgs->topic = topicCopy;
char *logCategoryCopy = malloc(1 + strlen(logCategory));
strcpy(logCategoryCopy, logCategory);
threadArgs->logCategory = logCategoryCopy;
char *transportCopy = malloc(1 + strlen(zmqTransport));
strcpy(transportCopy, zmqTransport);
threadArgs->transport = transportCopy;
threadArgs->thread = malloc(sizeof(pthread_t));
pthread_create(threadArgs->thread, NULL, ZMQSubscribe, threadArgs);
return threadArgs;
}
void StopSubscribe(SubscribeZeroMQDataThreadArgs *args){
args->running = false;
pthread_join(*(args->thread), NULL);
free(args->thread);
free((char*)args->logCategory);
free((char*)args->transport);
free((char*)args->topic);
free(args);
}

view raw

Subscribe.c

hosted with ❤ by GitHub


static void *ZMQRequestReply(void *args)
{
RequestReplyZeroMQThreadArgs *typedArgs = args;
log4c_category_t *cat = log4c_category_get(typedArgs->logCategory);
log4c_category_log(cat, LOG4C_PRIORITY_INFO, "starting request reply thread");
void *server = GetZMQSocketWithRetries(typedArgs->context, typedArgs->transport, ZMQ_REP, true, cat);
if(server == NULL)
return NULL;
uint8_t buf[4096];
char request[4096];
while(typedArgs->running) {
int size = zmq_recv(server, request, 4096, ZMQ_NOBLOCK);
if(size == -1)
{
if(errno != EAGAIN && errno != EINTR)
log4c_category_log(cat, LOG4C_PRIORITY_WARN, "couldn't receive request from transport [%s] %s", typedArgs->transport, zmq_strerror(errno));
//no messages available, sleep and go on
usleep(500000);
}
else
{
request[size] = '\0';
log4c_category_log(cat, LOG4C_PRIORITY_DEBUG, "received a request of size %d [%s] from transport [%s]", size, request, typedArgs->transport);
size = zmq_recv(server, buf, 4096, 0);
log4c_category_log(cat, LOG4C_PRIORITY_DEBUG, "received a request payload of size %d from transport [%s]", size, typedArgs->transport);
RequestReplyZeroMQReply *toReturn = typedArgs->callback(request, typedArgs->argument, buf, size);
zmq_send(server, toReturn->data, toReturn->size, 0);
log4c_category_log(cat, LOG4C_PRIORITY_DEBUG, "sent reply");
free(toReturn->data);
free(toReturn);
}
}
log4c_category_log(cat, LOG4C_PRIORITY_DEBUG, "exiting request reply thread for %s", typedArgs->transport);
zmq_close(server);
return NULL;
}
RequestReplyZeroMQThreadArgs *StartRequestReply(void *zmqContext, const char *zmqTransport, STRING_CALLBACK callback, void *callbackArg, const char *logCategory)
{
RequestReplyZeroMQThreadArgs *threadArgs = malloc(sizeof(RequestReplyZeroMQThreadArgs));
threadArgs->bind = true;
threadArgs->callback = callback;
threadArgs->context = zmqContext;
threadArgs->running = true;
threadArgs->argument = callbackArg;
char *logCategoryCopy = malloc(1 + strlen(logCategory));
strcpy(logCategoryCopy, logCategory);
threadArgs->logCategory = logCategoryCopy;
char *transportCopy = malloc(1 + strlen(zmqTransport));
strcpy(transportCopy, zmqTransport);
threadArgs->transport = transportCopy;
threadArgs->thread = malloc(sizeof(pthread_t));
pthread_create(threadArgs->thread, NULL, ZMQRequestReply, threadArgs);
return threadArgs;
}
void StopRequestReply(RequestReplyZeroMQThreadArgs *args)
{
args->running = false;
pthread_join(*(args->thread), NULL);
free(args->thread);
free((char*)args->logCategory);
free((char*)args->transport);
free(args);
}

view raw

RequestReply.c

hosted with ❤ by GitHub


static void *ZMQForward(void *args)
{
ForwardZeroMQDataThreadArgs *typedArgs = args;
int rc, i, size;
zmq_pollitem_t items[typedArgs->numSubs];
log4c_category_t *mainLog = log4c_category_get(typedArgs->logCategory);
void *outgoingSocket = GetZMQSocketWithRetries(typedArgs->context, typedArgs->pubTransport, ZMQ_PUB, true, mainLog);
if(outgoingSocket == NULL)
return NULL;
for(i=0;i<(typedArgs->numSubs);i++)
{
items[i].socket = GetZMQSocketWithRetries(typedArgs->context, typedArgs->subTransports[i], ZMQ_SUB, false, mainLog);
items[i].events = ZMQ_POLLIN;
items[i].fd = 0;
items[i].revents = 0;
if(items[i].socket == NULL)
{
log4c_category_log(mainLog, LOG4C_PRIORITY_ERROR, "unable to create socket %s", zmq_strerror(errno));
}
else
{
rc = zmq_setsockopt(items[i].socket, ZMQ_SUBSCRIBE, NULL, 0);
if(rc != 0)
{
log4c_category_log(mainLog, LOG4C_PRIORITY_ERROR, "unable to subscribe to all messages on transport [%s]: %s", typedArgs->subTransports[i] , zmq_strerror(errno));
}
else
{
log4c_category_log(mainLog, LOG4C_PRIORITY_INFO, "subscribed to all messages on transport to [%s]", typedArgs->subTransports[i]);
}
}
}
while(typedArgs->running)
{
uint8_t buf[1024];
char topic[1024];
rc = zmq_poll(items, typedArgs->numSubs, -1);
if(rc == ETERM || rc == EFAULT)
{
log4c_category_log(mainLog, LOG4C_PRIORITY_ERROR, "couldn't poll all sockets: %s", zmq_strerror(errno));
break;
}
else if(rc == EINTR)
{
//Interrupted, no biggie
}
else
{
for(i=0;i<typedArgs->numSubs;i++)
{
if(items[i].revents & ZMQ_POLLIN)
{
size = zmq_recv(items[i].socket, topic, sizeof(topic), 0);
if(size > -1)
{
topic[size] = '\0';
log4c_category_log(mainLog, LOG4C_PRIORITY_DEBUG, "received topic of size %d [%s] from transport [%s]", size, topic, typedArgs->subTransports[i]);
size = zmq_send(outgoingSocket, topic, size, ZMQ_SNDMORE);
if(size > -1)
log4c_category_log(mainLog, LOG4C_PRIORITY_DEBUG, "forwarded topic of size %d from transport [%s] to [%s]", size, typedArgs->subTransports[i], typedArgs->pubTransport);
else
log4c_category_log(mainLog, LOG4C_PRIORITY_ERROR, "error forwarding topic of size on transport [%s] %s", typedArgs->pubTransport, zmq_strerror(errno));
size = zmq_recv(items[i].socket, buf, sizeof(buf), 0);
if(size > -1)
log4c_category_log(mainLog, LOG4C_PRIORITY_DEBUG, "received data of size %d from transport [%s]", size, typedArgs->subTransports[i]);
else
log4c_category_log(mainLog, LOG4C_PRIORITY_ERROR, "error receiving data on transport [%s] %s", typedArgs->subTransports[i], zmq_strerror(errno));
size = zmq_send(outgoingSocket, buf, size, 0);
if (size > -1)
log4c_category_log(mainLog, LOG4C_PRIORITY_DEBUG, "forwarded data of size %d from transport [%s] to [%s]", size, typedArgs->subTransports[i], typedArgs->pubTransport);
else
log4c_category_log(mainLog, LOG4C_PRIORITY_ERROR, "error forwarding data on transport [%s] %s", typedArgs->pubTransport, zmq_strerror(errno));
}
else
{
log4c_category_log(mainLog, LOG4C_PRIORITY_ERROR, "couldn't receive topic from transport [%s] %s", typedArgs->subTransports[i], zmq_strerror(errno));
}
}
}
}
usleep(500000);
}
return NULL;
}
ForwardZeroMQDataThreadArgs *StartForward(void *zmqContext, const char *pubTransport, const char *logCategory, int numSubTransports, …)
{
ForwardZeroMQDataThreadArgs *threadArgs = malloc(sizeof(ForwardZeroMQDataThreadArgs));
threadArgs->context = zmqContext;
threadArgs->running = true;
char *logCategoryCopy = malloc(1 + strlen(logCategory));
strcpy(logCategoryCopy, logCategory);
threadArgs->logCategory = logCategoryCopy;
char *pubTransportCopy = malloc(1 + strlen(pubTransport));
strcpy(pubTransportCopy, pubTransport);
threadArgs->pubTransport = pubTransportCopy;
threadArgs->numSubs = numSubTransports;
int i;
va_list subArgs;
va_start(subArgs, numSubTransports);
char *subTransport;
for(i=0;i<numSubTransports;i++)
{
subTransport = va_arg(subArgs, char*);
char *subTransportCopy = malloc(1 + strlen(subTransport));
strcpy(subTransportCopy, subTransport);
threadArgs->subTransports[i] = subTransportCopy;
}
va_end(subArgs);
threadArgs->thread = malloc(sizeof(pthread_t));
pthread_create(threadArgs->thread, NULL, ZMQForward, threadArgs);
return threadArgs;
}
void StopForward(ForwardZeroMQDataThreadArgs *args)
{
args->running = false;
pthread_join(*(args->thread), NULL);
free(args->thread);
free((char*)args->logCategory);
free((char*)args->pubTransport);
free(args);
}

view raw

Forward.c

hosted with ❤ by GitHub

Tagged with: , , ,
Posted in C, Messaging, ZeroMQ

Reactive Extensions, amazing

The Reactive Extensions library is amazing. It has turned what was sure to be a mess of crap code into a few lines that are easy to understand and very easy to test.

I had a requirement that was very easy to state but hard to implement on my latest project. The idea was that the app would stop processing if some sensor stopped going off either consistently for n seconds or bounced between states some configurable amount of times for the same n seconds. Easy right? Well, you’d have to cache the data, start some threads or at least some timers to check the history. Then start and stop those async things, clean up, test, etc. I didn’t want to do that. There had to be a better way. Enter Reactive Extensions.

Reactive Extensions allow a developer to query streams of events, timers, whatever async things you might want. Imagine turning a bunch of event handlers or whatever into something you could do Linq queries against?

So I met my requirements by using two main Rx tools, Buffer and DistinctUntilChanged. First I turned my already-written events providing the sensor data into Observables. Before you had to listen to the event and procedurally do what you wanted with the data. Now the data just streams in and you can query against it. Buffer will cache the output of the stream after something is observed until another thing is observed. DistinctUntilChanged is pretty simple, it just filters out values from the stream that aren’t successively unique.

Prep: make sure whatever events and properties required are observable

//make the sensor values observable
_sensorValuesSequence = Observable.FromEvent<EventHandler<SensorDataEventArgs>, bool>(
  handler =>
  {
      EventHandler<SensorDataEventArgs> dataHandler = (sender, e) => handler(e.Data.BooleanValue);
          return dataHandler;
  }, 
  asdfHandler => NewData += fsdfHandler, 
  asdfHandler => NewData -= asdfHandler);

//make the simple boolean sensor Connected property an observable
_connectedSequence = this.ObservePropertyChanged(x => x.Connected);

First requirement: stop processing if some sensor stops going off for n seconds consistently.

/*
 * Here's how the below code works...
 * time:                            n sec   2n sec  3n sec    4n sec    //time vs timeout period
 * sensor value stream:      F F F T|F F F F|F F F F|F F F T F|F F F F| //take the sensor values
 * distinctUntilChanged:     F     T|F      |       |      T F|       | //observe only the changes
 * buffer created #:         1     2|3      |       |      4 5|       | //Buffer the value changes until a timeout
 * buffer expires #:                |1     2|3      |3        |    4 5| //each buffer 'expires' at timeout and is eval'ed
 * evals to:                 buffer 1: no abort                         //1's first value is false, gets a true later
 *                           buffer 2: no abort                         //2 starts with true, doesn't count
 *                           buffer 3: abort                            //3 gets a false, no trues later, abort!
 *                           buffer 4: no abort                         //4 starts with true, doesn't count
 *                           buffer 5: abort                            //5 starts with false, no trues later, abort!
 */

//get some timespan, the n seconds above
var timeout = TimeSpan.FromSeconds(Config.SomeSecondsValue);

//create a buffer of unique sensor values that is n seconds long whenever the sensor starts or stops going off
var bufferedNotGoingOff =
  //get the sequence of sensor values, we only care about when they change though so use distinct
  _sensorValuesSequence.DistinctUntilChanged()
    //first param: create a buffer of same values whenever the sensor value changes
    //second param: stop buffering values when we hit the timeout, this could be any other observable
    .Buffer(_sensorValuesSequence.DistinctUntilChanged(), o => Observable.Interval(timeout))
    //log some more for debugging

//see if every value in each buffer is false, then the sensor stopped going off
var bufferedNotGoingOffSequence = bufferedNotGoingOff.Select(o => o.All(p => !p));

//create an observable that only streams when a n second window of not going off occurs
return from asdf in bufferedNotGoingOffSequence where asdf select new Unit();

Second requirement: only start processing when the sensor has started to go off consistently for n seconds and hasn’t gone offline in the same amount of time.

/* Here's how the code below works...
 * time:                            n sec   2n sec  3n sec    //time vs timeout period
 * sensor value stream:      T T T T|T T T T|T F T T|T T T T| //stream of sensor values
 * disconnect value stream:       F |       |       |       | //stream of when the sensor disconnects (just falses)
 * merged:                   T T TFT|T T T T|T F T T|T T T T| //merge the two streams, sensor values & false disconnects
 * distinct:                 T    FT|       |  F T  |       | //only care about the changes
 * buffer created #:         1    23|       |  4 5  |       | //Buffer the changes until timeout
 * buffer expires #:                |1    23|       |  4 5  | //each buffer 'expires' and is eval'ed
 * evals to:                 buffer 1: don't process          //1 sees the first sensor true but also the disconnect
 *                           buffer 2: don't process          //2 is started on the disconnect, doesn't count
 *                           buffer 3: process                //3 starts on a sensor true value, doesn't see anything bad
 *                           buffer 4: don't process          //4 is started on a sensor false, doesn't count
 *                           buffer 5: process                //5 sees just the sensor start
 */

//create a merged stream of distinct sensor values (bools) and disconnects (as false)
var distinctValuesAndDisconnects = _sensorValuesSequence.Merge(_connectedSequence.Where(o => !o)).DistinctUntilChanged();

//get some configurable time required
var timeout = TimeSpan.FromSeconds(Config.SomeSecondsValue);

//buffer the merged sensor values and disconnects for n seconds whenever there is a change
var bufferedDistinctValuesAndDisconnects = distinctValuesAndDisconnects
  .Buffer(distinctAlarmsAndDisconnects, o => Observable.Interval(timeout))

//see if every value in each buffer is true, then nothing disconnected and the sensor kept going off, yay!
var windowedSequence = bufferedDistinctValuesAndDisconnects.Select(o => o.All(p => p));

//create an observable that only streams when a n second window of sensor going off occurs with no disconnects
return from asdf in windowedSequence where asdf select new Unit();
Tagged with: ,
Posted in .Net

Order Checking in Rhino Mocks with AAA Syntax

Rhino Mocks recently switched to AAA syntax (Arrange, Act, Assert) from Record/Replay.  It’s fairly easy to make the switch.  I  did run into a problem checking the order of calls made on my mocked objects though.  It seemed my expected calls never got wired up at all.  I found you ¿shouldn’t really? mock things into an instance of a MockRepository but rather call it statically.  As soon as I switched everything worked.  You can get a reference to the MockRepository by called GetMockRepository on any of your mocked objects.

using (_mockObject1.GetMockRepository().Ordered())
using (_mockObject2.GetMockRepository().Ordered())
{
    _mockObject1.Expect(o => o.Func1(Arg<SomeType>.Is.TypeOf));
    _mockObject2.Expect(o => o.Func2());
    _mockObject1.Expect(o => o.Func3()).Return(true);
    _mockObject1.Expect(o => o.Func4()).Return(true);
    _mockObject2.Expect(o => o.Func5());
    _mockObject1.Expect(o => o.Func6());
    _mockObject1.Expect(o => o.Func7()).Return(true);
}
Tagged with:
Posted in .Net, Unit Testing
Is this your new site? Log in to activate admin features and dismiss this message
Log In