/**
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
*/
package org.thingml.compilers.c.arduino;
import org.sintef.thingml.*;
import org.thingml.compilers.Context;
import org.thingml.compilers.c.CCompilerContext;
import org.thingml.compilers.c.cepHelper.CCepHelper;
import org.thingml.compilers.thing.ThingCepCompiler;
import org.thingml.compilers.thing.ThingCepSourceDeclaration;
import org.thingml.compilers.thing.ThingCepViewCompiler;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static org.thingml.compilers.c.cepHelper.CCepHelper.isMessageUseOnce;
import static org.thingml.compilers.c.cepHelper.CCepHelper.shouldTriggerOnInputNumber;
import static org.thingml.compilers.c.cepHelper.CCepHelper.shouldTriggerOnTimer;
public class ArduinoThingCepCompiler extends ThingCepCompiler {
public ArduinoThingCepCompiler(ThingCepViewCompiler cepViewCompiler, ThingCepSourceDeclaration sourceDeclaration) {
super(cepViewCompiler, sourceDeclaration);
}
public static String getSlidingStep(Stream s, CCompilerContext ctx) {
String slidingImpl = "";
for (ViewSource vs : s.getInput().getOperators()) {
if (vs instanceof LengthWindow) {
StringBuilder b = new StringBuilder();
ctx.getCompiler().getThingActionCompiler().generate(((LengthWindow) vs).getStep(), b, ctx);
String step = b.toString();
slidingImpl += "int step = " + step + " * /*STREAM_NAME_UPPER*/_/*MESSAGE_NAME_UPPER*/_ELEMENT_SIZE;\n";
slidingImpl += "if (/*MESSAGE_NAME*/_length() < step )\n\tstep = /*STREAM_NAME_UPPER*/_/*MESSAGE_NAME_UPPER*/_FIFO_SIZE;\n";
slidingImpl += "/*MESSAGE_NAME*/_fifo_head = (/*MESSAGE_NAME*/_fifo_head + step) % /*STREAM_NAME_UPPER*/_/*MESSAGE_NAME_UPPER*/_FIFO_SIZE;";
break; // we stop at first match, a stream can have only one window right?
}
}
/* If no window is specified */
if (slidingImpl.equals(""))
slidingImpl += "/*MESSAGE_NAME*/_fifo_head = (/*MESSAGE_NAME*/_fifo_head + /*STREAM_NAME_UPPER*/_/*MESSAGE_NAME_UPPER*/_ELEMENT_SIZE) % /*STREAM_NAME_UPPER*/_/*MESSAGE_NAME_UPPER*/_FIFO_SIZE;";
return slidingImpl;
}
public static void generateCEPLibAPI(Thing thing, StringBuilder builder, CCompilerContext ctx) {
for (Stream s : CCepHelper.getStreamWithBuffer(thing)) {
String cepTemplate = ctx.getCEPLibTemplateClass();
String constants = ctx.getCEPLibTemplateStreamConstants();
constants = constants.replace("/*STREAM_NAME_UPPER*/", s.getName().toUpperCase());
constants = constants.replace("/*OUTPUT_TTL*/", CCepHelper.getOutputMessageStreamTTL(s, ctx));
constants = constants.replace("/*INPUT_TTL*/", CCepHelper.getInputMessagesStreamTTL(s, ctx));
String methodsSignatures = "";
String attributesSignatures = "";
Map<Message, SimpleSource> messagesFromStream = CCepHelper.getMessageFromStream(s);
for (Message msg : messagesFromStream.keySet()) {
/*
* Constants
*/
String constantTemplate = ctx.getCEPLibTemplateMessageConstants();
int messageSize = ctx.getMessageSerializationSize(msg) - 4; //subtract the ports size
constantTemplate = constantTemplate.replace("/*MESSAGE_TTL*/", CCepHelper.getInputMessagesStreamTTL(msg, s, ctx));
constantTemplate = constantTemplate.replace("/*MESSAGE_NAME_UPPER*/", msg.getName().toUpperCase());
constantTemplate = constantTemplate.replace("/*STREAM_NAME_UPPER*/", s.getName().toUpperCase());
constantTemplate = constantTemplate.replace("/*STRUCT_SIZE*/", Integer.toString(messageSize));
constantTemplate = constantTemplate.replace("/*NUMBER_MESSAGE*/", CCepHelper.getInputMessagesNumber(messagesFromStream.get(msg), s, ctx));
constants += constantTemplate;
constants += CCepHelper.getExposeMacros(msg, messagesFromStream.get(msg), s, ctx);
/*
* Methods Signatures
*/
String methodsTemplate = ctx.getCEPLibTemplateMethodsSignatures();
methodsTemplate = methodsTemplate.replace("/*MESSAGE_NAME*/", msg.getName());
List<String> popParameters = new ArrayList<>();
for (Parameter p : msg.getParameters())
popParameters.add(ctx.getCType(p.getType()) + "* " + p.getName());
String popParametersString = "";
if (!popParameters.isEmpty())
popParametersString = ", " + String.join(", ", popParameters);
methodsTemplate = methodsTemplate.replace("/*POP_PARAMETERS*/", "unsigned long* " + msg.getName() +
"Time" + popParametersString);
StringBuilder paramBuilder = new StringBuilder();
ctx.appendFormalParameters(thing, paramBuilder, msg);
methodsTemplate = methodsTemplate.replace("/*MESSAGE_PARAMETERS*/", paramBuilder);
methodsSignatures += methodsTemplate;
/*
* Export methods
*/
//TODO check for input buffer
for (Parameter p : msg.getParameters())
methodsSignatures += " " + ctx.getCType(p.getType()) + "* export_" + msg.getName() + "_" + p.getName() + "();\n";
/*
* Attributes
*/
String attributesTemplate = ctx.getCEPLibTemplateAttributesSignatures();
attributesTemplate = attributesTemplate.replace("/*MESSAGE_NAME*/", msg.getName());
attributesTemplate = attributesTemplate.replace("/*MESSAGE_NAME_UPPER*/", msg.getName().toUpperCase());
attributesTemplate = attributesTemplate.replace("/*STREAM_NAME_UPPER*/", s.getName().toUpperCase());
attributesSignatures += attributesTemplate;
for (Parameter p : msg.getParameters())
attributesSignatures += ctx.getCType(p.getType()) + " " + msg.getName() + p.getName() + " [" +s.getName().toUpperCase() + "_" + msg.getName().toUpperCase() + "_NUMBER_MSG];\n";
}
if (shouldTriggerOnTimer(s, ctx))
attributesSignatures += " uint32_t last" + s.getName() + "Trigger = millis();\n";
if (shouldTriggerOnInputNumber(s, ctx))
attributesSignatures += " uint8_t input" + s.getName() + "Trigger = 0;\n";
/*
* Trigger timer
*/
String triggerTimer = "";
if (shouldTriggerOnTimer(s, ctx)) {
String param = "struct " + ctx.getInstanceStructName(thing) + " *" + ctx.getInstanceVarName();
triggerTimer = "void checkTimer(" + param + ");\n";
}
constants += CCepHelper.getInputBufferMacros(s, ctx);
cepTemplate = cepTemplate.replace("/*STREAM_NAME*/", s.getName());
cepTemplate = cepTemplate.replace("/*METHOD_SIGNATURES*/", methodsSignatures);
cepTemplate = cepTemplate.replace("/*ATTRIBUTES_SIGNATURES*/", attributesSignatures);
cepTemplate = cepTemplate.replace("/*TRIGGER_INST_PARAM*/", "struct " + ctx.getInstanceStructName(thing) + " *" + ctx.getInstanceVarName());
cepTemplate = cepTemplate.replace("/*TRIGGER_TIMER_SIGNATURES*/", triggerTimer);
cepTemplate = cepTemplate.replace("/*STREAM_CONSTANTS*/", constants);
builder.append(cepTemplate);
}
}
public static void generateCEPLibImpl(Thing thing, StringBuilder builder, CCompilerContext ctx) {
for (Stream s : CCepHelper.getStreamWithBuffer(thing)) {
String msgsImpl = "";
for (Message msg : CCepHelper.getMessageFromStream(s).keySet()) {
String messageImpl = ctx.getCEPLibTemplatesMessageImpl();
messageImpl = messageImpl.replace("/*STREAM_NAME*/", s.getName());
messageImpl = messageImpl.replace("/*STREAM_NAME_UPPER*/", s.getName().toUpperCase());
messageImpl = messageImpl.replace("/*MESSAGE_NAME*/", msg.getName());
messageImpl = messageImpl.replace("/*MESSAGE_NAME_UPPER*/", msg.getName().toUpperCase());
StringBuilder paramBuilder = new StringBuilder();
ctx.appendFormalParameters(thing, paramBuilder, msg);
messageImpl = messageImpl.replace("/*MESSAGE_PARAMETERS*/", paramBuilder);
/*
* Sliding Window Impl
* Here we have basically 3 cases
* - the stream has no window specified, we only remove one message
* - the stream is a LenghtWindow, we remove `step` element(s)
* - the stream is a TimeWindow, we do other stuff
*/
String slidingImp = getSlidingStep(s, ctx);
slidingImp = slidingImp.replace("/*MESSAGE_NAME*/", msg.getName());
slidingImp = slidingImp.replace("/*MESSAGE_NAME_UPPER*/", msg.getName().toUpperCase());
slidingImp = slidingImp.replace("/*STREAM_NAME_UPPER*/", s.getName().toUpperCase());
messageImpl = messageImpl.replace("/*SLIDING_IMPL*/", slidingImp);
/*
* Queue Impl
*/
int fifo_buffer_index = 4; // just after the stamp
String queueImpl = "";
for (Parameter p : msg.getParameters()) {
queueImpl += "union u_" + msg.getName() + "_" + p.getName() + "_t {\n";
queueImpl += ctx.getCType(p.getType()) + " p;\n";
queueImpl += "byte bytebuffer[" + ctx.getCByteSize(p.getType(), 0) + "];\n";
queueImpl += "} u_" + msg.getName() + "_" + p.getName() + ";\n";
queueImpl += "u_" + msg.getName() + "_" + p.getName() + ".p = " + p.getName() + ";\n";
for (int i = ctx.getCByteSize(p.getType(), 0) - 1; i >= 0; i--) {
queueImpl += msg.getName() + "_fifo[(" + msg.getName() + "_fifo_tail + " + fifo_buffer_index + ") % " + s.getName().toUpperCase() + "_" + msg.getName().toUpperCase() + "_FIFO_SIZE]";
queueImpl += " = u_" + msg.getName() + "_" + p.getName() + ".bytebuffer[" + i + "];\n";
fifo_buffer_index++;
}
}
if (CCepHelper.handlerShouldTrigger(s, ctx))
messageImpl = messageImpl.replace("/*TRIGGER*/", "checkTrigger(_instance);\n");
else
messageImpl = messageImpl.replace("/*TRIGGER*/", "");
messageImpl = messageImpl.replace("/*QUEUE_IMPL*/", queueImpl);
/*
* Pop Impl
*/
List<String> popParameters = new ArrayList<>();
for (Parameter p : msg.getParameters())
popParameters.add(ctx.getCType(p.getType()) + "* " + p.getName());
String popParametersString = "";
if (!popParameters.isEmpty())
popParametersString = ", " + String.join(", ", popParameters);
messageImpl = messageImpl.replace("/*POP_PARAMETERS*/", popParametersString);
String popImpl = "";
fifo_buffer_index = 4; // reset the index after the stamp
for (Parameter p : msg.getParameters()) {
popImpl += "union u_" + msg.getName() + "_" + p.getName() + "_t {\n";
popImpl += ctx.getCType(p.getType()) + " p;\n";
popImpl += "byte bytebuffer[" + ctx.getCByteSize(p.getType(), 0) + "];\n";
popImpl += "} u_" + msg.getName() + "_" + p.getName() + ";\n";
for (int i = ctx.getCByteSize(p.getType(), 0) - 1; i >= 0; i--) {
popImpl += "u_" + msg.getName() + "_" + p.getName() + ".bytebuffer[" + i + "]";
popImpl += " = " + msg.getName() + "_fifo[(" + msg.getName() + "_fifo_head + " + fifo_buffer_index +
") % " + s.getName().toUpperCase() + "_" + msg.getName().toUpperCase() + "_FIFO_SIZE];\n";
fifo_buffer_index++;
}
popImpl += "*" + p.getName() + " = u_" + msg.getName() + "_" + p.getName() + ".p;\n";
}
messageImpl = messageImpl.replace("/*POP_IMPL*/", popImpl);
msgsImpl += messageImpl;
//TODO check if we want an input buffer
String exportImpl = "";
//TODO should be set to the offset
fifo_buffer_index = 4; // reset the index after the stamp
for (Parameter p : msg.getParameters()) {
exportImpl += ctx.getCType(p.getType()) + "* stream_" + s.getName() +
" ::export_" + msg.getName() + "_" + p.getName() + "()\n{\n";
exportImpl += " int size = " + msg.getName() + "_length() / " + s.getName().toUpperCase() + "_" +
msg.getName().toUpperCase() + "_ELEMENT_SIZE;\n";
exportImpl += " for (int i=0; i<size; i++) {\n";
//union de store the extracted value
exportImpl += " union u_" + msg.getName() + "_" + p.getName() + "_t {\n";
exportImpl += " " + ctx.getCType(p.getType()) + " p;\n";
exportImpl += " byte bytebuffer[" + ctx.getCByteSize(p.getType(), 0) + "];\n";
exportImpl += " } u_" + msg.getName() + "_" + p.getName() + ";\n";
for (int i = ctx.getCByteSize(p.getType(), 0) - 1; i >= 0; i--) {
exportImpl += "u_" + msg.getName() + "_" + p.getName() + ".bytebuffer[" + i + "]";
exportImpl += " = " + msg.getName() + "_fifo[(" + msg.getName() + "_fifo_head + " + fifo_buffer_index +
" + (i * " + s.getName().toUpperCase() + "_" + msg.getName().toUpperCase() + "_ELEMENT_SIZE)) % "
+ s.getName().toUpperCase() + "_" + msg.getName().toUpperCase() + "_FIFO_SIZE];\n";
fifo_buffer_index++;
}
exportImpl += " " + msg.getName() + p.getName() + "[i] = u_" + msg.getName() + "_" + p.getName() + ".p;\n";
exportImpl += " }\n";
exportImpl += " return " + msg.getName() + p.getName() + ";\n";
exportImpl += "}\n";
}
msgsImpl += exportImpl;
}
String classImpl = ctx.getCEPLibTemplateClassImpl();
classImpl = classImpl.replace("/*STREAM_NAME*/", s.getName());
classImpl = classImpl.replace("/*MESSAGE_IMPL*/", msgsImpl);
classImpl = classImpl.replace("/*TRIGGER_INST_PARAM*/", "struct " + ctx.getInstanceStructName(thing) + " *" + ctx.getInstanceVarName());
classImpl = classImpl.replace("/*TRIGGER_IMPL*/", generateTriggerImpl(s, ctx));
if (shouldTriggerOnTimer(s, ctx))
classImpl = classImpl.replace("/*TRIGGER_TIMER_IMPL*/", generateTriggerCallBack(s, ctx));
else
classImpl = classImpl.replace("/*TRIGGER_TIMER_IMPL*/", "");
builder.append(classImpl);
}
}
/**
* Generate the implementation of the function checkTrigger used to detect
* if a stream can output its actions.
*
* @param s A object stream, streams have exactly one checkTrigger function
* @param ctx Compiler context
* @return Code as a String object.
*/
private static String generateTriggerImpl(Stream s, CCompilerContext ctx) {
String triggerImpl = "";
if (s.getInput() instanceof JoinSources || shouldTriggerOnInputNumber(s, ctx) || shouldTriggerOnTimer(s, ctx)) {
Set<Message> msgs = CCepHelper.getMessageFromStream(s).keySet();
List<String> triggerCondition = new ArrayList<>();
for (Message m : msgs) {
triggerImpl += "check" + m.getName() + "TTL();\n";
triggerCondition.add("!" + m.getName() + "_isEmpty()");
}
if (shouldTriggerOnInputNumber(s, ctx)) {
triggerImpl += "input" + s.getName() + "Trigger++;\n";
triggerCondition.add("input" + s.getName() + "Trigger == " + CCepHelper.getStreamTriggerInputNumber(s, ctx));
}
List<String> guards = new ArrayList<>();
for (ViewSource vs : s.getInput().getOperators()) {
if (vs instanceof Filter) {
StringBuilder g = new StringBuilder();
ctx.getCompiler().getThingActionCompiler().generate(((Filter) vs).getGuard(), g, ctx);
guards.add(g.toString());
}
}
String guardsString = "";
if (guards.size() > 0) {
guardsString += " && ";
guardsString += String.join(" && ", guards);
}
triggerImpl += "if (" + String.join(" && ", triggerCondition) + guardsString + " )\n {\n";
// reset the trigger counter
if (shouldTriggerOnInputNumber(s, ctx))
triggerImpl += "input" + s.getName() + "Trigger = 0;\n";
// pop messages
for (Message m : msgs) {
triggerImpl += "//poping messages " + m.getName() + "\n";
triggerImpl += "unsigned long " + m.getName() + "Time;\n";
for (Parameter p : m.getParameters())
triggerImpl += ctx.getCType(p.getType()) + " " + p.getName() + ";\n";
triggerImpl += m.getName() + "_popEvent(&" + m.getName() + "Time";
for (Parameter p : m.getParameters())
triggerImpl += ", &" + p.getName();
triggerImpl += ");\n";
triggerImpl += "//done poping\n";
for (Parameter p : m.getParameters())
ctx.putCepMsgParam(m.getName(), p.getName(), s.getName());
}
StringBuilder outAction = new StringBuilder();
int resultMessageParameterIndex = 0;
if (s.getInput() instanceof JoinSources) {
for (Expression e : ((JoinSources) s.getInput()).getRules()) {
Parameter p = ((JoinSources) s.getInput()).getResultMessage().getParameters().get(resultMessageParameterIndex);
outAction.append(ctx.getCType(p.getType()) + " " + p.getName() + " = ");
ctx.getCompiler().getThingActionCompiler().generate(e, outAction, ctx);
outAction.append(";\n");
resultMessageParameterIndex++;
}
}
for (LocalVariable lv : s.getSelection()) {
lv.setName("local" + lv.getName());
ctx.getCompiler().getThingActionCompiler().generate(lv, outAction, ctx);
}
ctx.getCompiler().getThingActionCompiler().generate(s.getOutput(), outAction, ctx);
triggerImpl += outAction;
// effectively remove messages from buffer
for (Message m : msgs)
if (isMessageUseOnce(s, m))
triggerImpl += m.getName() + "_removeEvent();\n";
triggerImpl += "\n}\n";
ctx.resetCepMsgContext();
}
return triggerImpl;
}
/**
* Function checking if the timer has expired and if the checkTrigger should be called
*
* @param s Stream producing events with a timer
* @param ctx Compiler context
*/
private static String generateTriggerCallBack(Stream s, CCompilerContext ctx) {
String ret = "";
Thing t = ctx.getConcreteThing();
String param = "struct " + ctx.getInstanceStructName(t) + " *" + ctx.getInstanceVarName();
ret += "\nvoid stream_" + s.getName() + "::checkTimer(" + param + ")\n{\n";
ret += " if (millis() - last" + s.getName() + "Trigger > " + CCepHelper.getStreamTriggerTime(s, ctx) +
")\n {\n";
ret += " last" + s.getName() + "Trigger = millis();\n";
ret += " checkTrigger(_instance);\n";
ret += " }\n";
ret += "}\n";
return ret;
}
@Override
public void generateStream(Stream stream, StringBuilder builder, Context ctx) {
sourceDeclaration.generate(stream, stream.getInput(), builder, ctx);
}
}