gRPC C++ and Self Signed Certificates

Playing around with gRPC with a C++ server caused an issue that took longer to solve than it should. Once the linker and other issues were solved, the following error started to follow:

7562 ssl_transport_security.cc:1238] Handshake failed with fatal error SSL_ERROR_SSL: error:100000c0:SSL routines:OPENSSL_internal:PEER_DID_NOT_RETURN_A_CERTIFICATE.

After searching, it lead me to this file where the different enumeration values for the SSL handling could be set.


/** Server does not request client certificate. A client can present a self
signed or signed certificates if it wishes to do so and they would be
accepted. */
GRPC_SSL_DONT_REQUEST_CLIENT_CERTIFICATE,
/** Server requests client certificate but does not enforce that the client
presents a certificate.

If the client presents a certificate, the client authentication is left to
the application based on the metadata like certificate etc.

The key cert pair should still be valid for the SSL connection to be
established. */
GRPC_SSL_REQUEST_CLIENT_CERTIFICATE_BUT_DONT_VERIFY,
/** Server requests client certificate but does not enforce that the client
presents a certificate.

If the client presents a certificate, the client authentication is done by
grpc framework (The client needs to either present a signed cert or skip no
certificate for a successful connection).

The key cert pair should still be valid for the SSL connection to be
established. */
GRPC_SSL_REQUEST_CLIENT_CERTIFICATE_AND_VERIFY,
/** Server requests client certificate but enforces that the client presents a
certificate.

If the client presents a certificate, the client authentication is left to
the application based on the metadata like certificate etc.

The key cert pair should still be valid for the SSL connection to be
established. */
GRPC_SSL_REQUEST_AND_REQUIRE_CLIENT_CERTIFICATE_BUT_DONT_VERIFY,
/** Server requests client certificate but enforces that the client presents a
certificate.

The cerificate presented by the client is verified by grpc framework (The
client needs to present signed certs for a successful connection).

The key cert pair should still be valid for the SSL connection to be
established. */
GRPC_SSL_REQUEST_AND_REQUIRE_CLIENT_CERTIFICATE_AND_VERIFY

That lead me to find a more through breakout of the use cases for each enumeration here.

  1. With GRPC_SSL_DONT_REQUEST_CLIENT_CERTIFICATE: Server does not request for a client certificate. So the client can choose to present a self-signed or a signed certificate or not present a certificate at all and all of these should be okay.
    With GRPC_SSL_REQUEST_CLIENT_CERTIFICATE_BUT_DONT_VERIFY: Server requests the client for a certificate but the signature enforcement is not done by grpc server framework but left to the app. The app can use metadata like the certificate hash to verify a certificate (essentially provides the server a
    way to verify self signed certificates, provided they have an out of band mechanism to register the certificate with the app)
  2. By “client authentication done by grpc framework”, I meant certificate signature verification is done using the ssl protocol itself by the grpc server framework (SSL_VERIFY_PEER option is being used in ssl options). The client has to provide a signed certificate which can be verified by the server (using the SSL roots file).
  3. “don’t request”/ “request”/ “require” / “verify”
    – Server has the option to either request or not-request for client cert.
    – Client can choose to either present a certificate or not.
    – Server can choose to verify the client certificate or not
    Each of these three options are independent of each other and contribute to multiple options presented.
    “require” for instance is the case server request for client cert, client has to present a certificate for the ssl handshake to continue but the server will not verify the client certificate using signature but can do so if needed based on certificate metadata.
    “verify” – SSL_VERIFY_PEER option is being used in ssl options and the client signature is verified/trusted by the server using the SSL roots file.
  4. All of the above pretty much expected that the private key and the public key files were all in okay and the only question was whether they were self signed or signed by a mutually trusted CA. If the public key and private keys don’t match up then the connection fails.
  5. It is a typo. It should have been “The client needs to either present a signed cert or not present a
    certificate at all for a successful connection”
  6. grpc_auth_context has various properties of the peer like GRPC_X509_CN_PROPERTY_NAME, GRPC_X509_PEM_CERT_PROPERTY_NAME, GRPC_X509_SAN_PROPERTY_NAME that can be used.

Finally, that lead me to understand that for self-signed certificates in development GRPC_SSL_REQUEST_CLIENT_CERTIFICATE_BUT_DONT_VERIFY was the right enumeration.

Creating .proto definitions from existing types at runtime

There was a need to create .proto definition files from the definitions of a reverse engineered database first project. The approach taken was that of using System.Emit to generate the type definitions and feed those to protobuf-net and use its ability to generate the .proto files.

There are only three classes needed:

  • ContextFinder
  • ClassGenerator
  • Program

The ContextFinder is pretty straight forward. It uses reflection to get all the generic parameters of DbSet<> properties within a DbContext. Then, ClassGenerator is used to copy the properties of the Types we harvested into a new type with the addition of adding ProtoContract and ProtoMember appropriately. Then, the Program class just loads the assembly from the file specified and runs the previously two mentioned classes.


public class ClassGenerator
{
private readonly ModuleBuilder _moduleBuilder;
public ClassGenerator()
{
var an = new AssemblyName("DynamicProtoAssembly");
AssemblyBuilder assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(an,AssemblyBuilderAccess.Run);
_moduleBuilder = assemblyBuilder.DefineDynamicModule("DynamicProtoModule");
}
public Type CreateType(Type typeToCopy)
{
TypeBuilder tb = _moduleBuilder.DefineType(typeToCopy.Name + "Proto",
TypeAttributes.Public |
TypeAttributes.Class |
TypeAttributes.AutoClass |
TypeAttributes.AnsiClass |
TypeAttributes.BeforeFieldInit |
TypeAttributes.AutoLayout,
null);
ConstructorBuilder constructor = tb.DefineDefaultConstructor(MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName);
var ci = typeof(ProtoContractAttribute).GetConstructor(new Type[0]);
var builder = new CustomAttributeBuilder(ci,new object[0]);
tb.SetCustomAttribute(builder);
var propertiesToCopy = typeToCopy.GetProperties();
for (int i = 0; i < propertiesToCopy.Length; i++)
{
var propertyInfo = propertiesToCopy[i];
CreateProperty(tb,propertyInfo.Name,propertyInfo.PropertyType,i);
}
return tb.CreateType();
}
private static void CreateProperty(TypeBuilder tb, string propertyName, Type propertyType, int i)
{
FieldBuilder fieldBuilder = tb.DefineField("_" + propertyName, propertyType, FieldAttributes.Private);
PropertyBuilder propertyBuilder = tb.DefineProperty(propertyName, PropertyAttributes.HasDefault, propertyType, null);
MethodBuilder getPropMthdBldr = tb.DefineMethod("get_" + propertyName, MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig, propertyType, Type.EmptyTypes);
ILGenerator getIl = getPropMthdBldr.GetILGenerator();
getIl.Emit(OpCodes.Ldarg_0);
getIl.Emit(OpCodes.Ldfld, fieldBuilder);
getIl.Emit(OpCodes.Ret);
MethodBuilder setPropMthdBldr =
tb.DefineMethod("set_" + propertyName,
MethodAttributes.Public |
MethodAttributes.SpecialName |
MethodAttributes.HideBySig,
null, new[] { propertyType });
ILGenerator setIl = setPropMthdBldr.GetILGenerator();
Label modifyProperty = setIl.DefineLabel();
Label exitSet = setIl.DefineLabel();
setIl.MarkLabel(modifyProperty);
setIl.Emit(OpCodes.Ldarg_0);
setIl.Emit(OpCodes.Ldarg_1);
setIl.Emit(OpCodes.Stfld, fieldBuilder);
setIl.Emit(OpCodes.Nop);
setIl.MarkLabel(exitSet);
setIl.Emit(OpCodes.Ret);
propertyBuilder.SetGetMethod(getPropMthdBldr);
propertyBuilder.SetSetMethod(setPropMthdBldr);
var ci = typeof(ProtoMemberAttribute).GetConstructor(new [] { typeof(int) });
var builder = new CustomAttributeBuilder(ci, new object[] { i + 1 });
propertyBuilder.SetCustomAttribute(builder);
}
}


public class ContextFinder
{
public IEnumerable<Type> GetAllTypesInContextDbSets(Assembly assembly)
{
return GetContextTypes(assembly)
.Select(x => GetDataSetTypes(x))
.SelectMany(x => x)
.Select(x => x.GetGenericArguments()[0]);
}
private IEnumerable<Type> GetContextTypes(Assembly assembly)
{
return assembly.GetTypes()
.Where(myType => myType.IsClass && !myType.IsAbstract && myType.IsSubclassOf(typeof(DbContext)));
}
private IEnumerable<Type> GetDataSetTypes(Type context)
{
return context.GetProperties()
.Select(x => x.PropertyType)
.Where(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(DbSet<>));
}
}


class Program
{
static void Main(string[] args)
{
if (args.Length < 1)
{
Console.WriteLine("The first argument should be a path to the assembly");
return;
}
Assembly assembly = Assembly.LoadFile(args[0]);
ContextFinder finder = new ContextFinder();
var types = finder.GetAllTypesInContextDbSets(assembly);
ClassGenerator generator = new ClassGenerator();
var protoTypes = types.Select(x => generator.CreateType(x));
foreach (var protoType in protoTypes)
{
Console.WriteLine(GenerateProtoFile(protoType));
}
}
private static string GenerateProtoFile(Type protoType)
{
MethodInfo methodInfo = typeof(Serializer).GetMethod(nameof(Serializer.GetProto),new [] {typeof(ProtoSyntax)});
MethodInfo genericMethod = methodInfo.MakeGenericMethod(protoType);
return (string) genericMethod.Invoke(null, new object[] { ProtoSyntax.Proto3 });
}
}

view raw

Program.cs

hosted with ❤ by GitHub

Communicating between Python and .NET Core with Boost Interprocess

To see if I could, I put together a cross communication library for .Net Core and Python applications using Boost.Interprocess, Boost.Python, and Boost.Signals2. The goal was simple, expose the same interface for cross communication to C# and Python. The approach taken was to use the condition example and edit it to expose to the different languages.

Shared Definitions

First I need to create the objects to make the interface. There are four files making up these objects:

  • shm_remove.hpp – just a lifecycle object to clear the shared buffer when it is destructed
  • TraceQueue.hpp – The shared memory object
  • SharedMemoryConsumer.hpp – The subscriber to the shared memory data
  • SharedMemoryProducer.hpp – The publisher for the shared memory data


template<size_t BufferSize>
class SharedMemoryConsumer
{
public:
explicit SharedMemoryConsumer(const std::string& memory_name)
: name_length_(memory_name.size()),
shm_(boost::interprocess::open_or_create,memory_name.c_str(),boost::interprocess::read_write)
{
shm_.truncate(sizeof(TraceQueue<BufferSize>));
region_ = boost::interprocess::mapped_region(shm_,boost::interprocess::read_write);
//Get the address of the mapped region
void * addr = region_.get_address();
//Obtain a pointer to the shared structure
data_ = static_cast<TraceQueue<BufferSize>*>(addr);
if (data_->name_length != name_length_)
{
data_ = new (addr)TraceQueue<BufferSize>;
data_->name_length = name_length_;
}
}
~SharedMemoryConsumer()
{
keep_listening_ = false;
background_listener_.join();
}
void start()
{
keep_listening_ = true;
background_listener_ = std::thread([=](){
do {
boost::interprocess::scoped_lock<boost::interprocess::interprocess_mutex> lock(data_->mutex);
if (!data_->message_in) {
data_->cond_empty.wait(lock);
}
sig_(data_->message);
//Notify the other process that the buffer is empty
data_->message_in = false;
data_->cond_full.notify_one();
} while (!data_->closing_stream && keep_listening_);
});
}
void set_callable(const std::function<void(const InterprocessMessage<BufferSize>& memory_block)>& callback)
{
conn_ = sig_.connect([=](const InterprocessMessage<BufferSize>& message)
{
callback(message);
});
}
void wait()
{
background_listener_.join();
}
private:
int name_length_;
boost::signals2::signal<void(const InterprocessMessage<BufferSize>&)> sig_;
boost::signals2::connection conn_;
boost::interprocess::shared_memory_object shm_;
boost::interprocess::mapped_region region_;
std::thread background_listener_;
bool keep_listening_;
TraceQueue<BufferSize> * data_;
};


template <size_t BufferSize>
class SharedMemoryProducer
{
public:
explicit SharedMemoryProducer(const std::string& memory_name)
: name_length_(memory_name.size())
,remove_guard_(memory_name)
, shm_(boost::interprocess::open_or_create,memory_name.c_str(),boost::interprocess::read_write)
{
shm_.truncate(sizeof(TraceQueue<BufferSize>));
region_ = boost::interprocess::mapped_region(shm_,boost::interprocess::read_write);
//Get the address of the mapped region
void * addr = region_.get_address();
//Construct the shared structure in memory
data_ = static_cast<TraceQueue<BufferSize>*>(addr);
if (data_->name_length != name_length_)
{
data_ = new (addr)TraceQueue<BufferSize>;
data_->name_length = name_length_;
}
}
~SharedMemoryProducer()
{
data_->name_length = 0;
data_->closing_stream = true;
}
void write_data(const std::string& buffer)
{
if (buffer.size() > BufferSize)
{
throw std::invalid_argument("Message size is larger than buffer size");
}
boost::interprocess::scoped_lock<boost::interprocess::interprocess_mutex> lock(data_->mutex);
if(data_->message_in){
data_->cond_full.wait(lock);
}
memcpy(data_->message.message,buffer.data(),buffer.size());
data_->message.message_length = buffer.size();
//Notify to the other process that there is a message
data_->cond_empty.notify_one();
//Mark message buffer as full
data_->message_in = true;
}
private:
int name_length_;
shm_remove remove_guard_;
boost::interprocess::shared_memory_object shm_;
boost::interprocess::mapped_region region_;
TraceQueue<BufferSize> * data_;
};


#include <boost/interprocess/shared_memory_object.hpp>
struct shm_remove
{
public:
explicit shm_remove(const std::string& shm_name)
: shm_name_(shm_name)
{
// boost::interprocess::shared_memory_object::remove(shm_name.c_str());
}
~shm_remove(){ boost::interprocess::shared_memory_object::remove(shm_name_.c_str()); }
private:
std::string shm_name_;
};

view raw

shm_remove.hpp

hosted with ❤ by GitHub


template <size_t MESSAGE_SIZE>
struct InterprocessMessage
{
//Items to fill
char message[MESSAGE_SIZE];
//Message size
size_t message_length;
std::string to_string() const
{
return std::string(message,message_length);
}
};
template <size_t MESSAGE_SIZE>
struct TraceQueue
{
TraceQueue()
: message_in(false)
{}
//Mutex to protect access to the queue
boost::interprocess::interprocess_mutex mutex;
//Condition to wait when the queue is empty
boost::interprocess::interprocess_condition cond_empty;
//Condition to wait when the queue is full
boost::interprocess::interprocess_condition cond_full;
//Message
InterprocessMessage<MESSAGE_SIZE> message;
//Is there any message
bool message_in;
//Notify closing shop
bool closing_stream = false;
//Check to see if memory has been created
int name_length;
};

view raw

TraceQueue.hpp

hosted with ❤ by GitHub

These objects comprise the core interface of the shared memory provider. Now, the memory providers need to be exposed to multiple languages. There are different ways to do this and I decided to do it by hand. I should point out SWIG is my usual approach to this task, however, in this instance it seemed easy enough to do it by hand.

Boost Python

To expose the python code, I needed to create a few classes to expose the interface definitions to Boost.Python. The two classes are:

  • PythonSharedMemoryConsumer.hpp – The python interface for the SharedMemoryConsumer
  • PythonModule.cpp  – The file that exposes the module to python


BOOST_PYTHON_MODULE(ScryUnlimitedCommunicator)
{
Py_Initialize();
class_<SeralizedPythonSharedMemoryConsumer, boost::noncopyable>("SeralizedSharedMemoryConsumer",init<std::string>())
.def("set_callable", &SeralizedPythonSharedMemoryConsumer::set_callable)
.def("start",&SeralizedPythonSharedMemoryConsumer::start)
.def("wait",&SeralizedPythonSharedMemoryConsumer::wait);
class_<ImagePythonSharedMemoryConsumer, boost::noncopyable>("ImageSharedMemoryConsumer",init<std::string>())
.def("set_callable", &ImagePythonSharedMemoryConsumer::set_callable)
.def("start",&ImagePythonSharedMemoryConsumer::start)
.def("wait",&ImagePythonSharedMemoryConsumer::wait);
class_<SeralizedSharedMemoryProducer, boost::noncopyable>("SeralizedSharedMemoryProducer",init<std::string>())
.def("write_data", &SeralizedSharedMemoryProducer::write_data);
class_<ImageSharedMemoryProducer, boost::noncopyable>("ImageSharedMemoryProducer",init<std::string>())
.def("write_data", &ImageSharedMemoryProducer::write_data);
}


template <size_t BufferSize>
class PythonSharedMemoryConsumer
{
public:
explicit PythonSharedMemoryConsumer(const std::string& memory_name)
: sm_(memory_name) {}
void set_callable(boost::python::object callback)
{
if (!PyCallable_Check(callback.ptr()))
{
PyErr_SetString(PyExc_TypeError, "Callback must be a callable object");
boost::python::throw_error_already_set();
}
sm_.set_callable([=](const InterprocessMessage<BufferSize>& buffer)
{
try
{
callback(boost::python::object(
boost::python::handle<>(
PyBytes_FromStringAndSize(buffer.message,buffer.message_length))));
//callback(buffer.to_string());
}
catch (boost::python::error_already_set)
{
PyErr_Print();
}
});
}
void start()
{
sm_.start();
}
void wait()
{
sm_.wait();
}
private:
ScryUnlimited::SharedMemory::SharedMemoryConsumer<BufferSize> sm_;
};

These two classes combine to expose the files to python and can be used in a python script by just importing the shared library.

.NET Core

With the python portion complete, I needed to expose the shared memory objects to CSharp. This is easy enough to do by hand if you expose the classes to be used by PInvoke. To accomplish this, I only needed three files:

  • NetCoreSharedMemoryProducer.hpp – The .NET Core version of the publisher
  • NetCoreSharedMemoryConsumer.hpp – The .NET Core version of the consumer
  • NetCoreModule.cpp – The source file exposing the interfaces for PInvoke

 


extern "C" ImageNetCoreSharedMemoryConsumer* GetImageConsumer(char * name)
{
return new ImageNetCoreSharedMemoryConsumer(std::string(name));
}
extern "C" void DeleteImageNetCoreSharedMemoryConsumer(ImageNetCoreSharedMemoryConsumer* consumer)
{
delete consumer;
}
extern "C" void ImageConsumerSetCallback(ImageNetCoreSharedMemoryConsumer* consumer,
void(*callback)(const unsigned char *,size_t))
{
consumer->set_callback(callback);
}
extern "C" void StartImageConsumer(ImageNetCoreSharedMemoryConsumer* consumer)
{
consumer->start();
}
extern "C" void ImageWait(ImageNetCoreSharedMemoryConsumer* consumer)
{
consumer->wait();
}
extern "C" SeralizedNetCoreSharedMemoryConsumer * GetSerializedConsumer(const char * name)
{
return new SeralizedNetCoreSharedMemoryConsumer(name);
}
extern "C" void DeleteSeralizedNetCoreSharedMemoryConsumer(SeralizedNetCoreSharedMemoryConsumer* consumer)
{
delete consumer;
}
extern "C" void SeralizedConsumerSetCallback(SeralizedNetCoreSharedMemoryConsumer* consumer,
void(*callback)(const unsigned char *,size_t))
{
consumer->set_callback(callback);
}
extern "C" void StartSeralizedConsumer(SeralizedNetCoreSharedMemoryConsumer* consumer)
{
consumer->start();
}
extern "C" void SeralizedWait(SeralizedNetCoreSharedMemoryConsumer* consumer)
{
consumer->wait();
}
extern "C" ImageNetCoreSharedMemoryProducer * GetImageProducer(const char * name)
{
return new ImageNetCoreSharedMemoryProducer(name);
}
extern "C" void DeleteImageProducer(ImageNetCoreSharedMemoryProducer * producer)
{
delete producer;
}
extern "C" void SendImageData(ImageNetCoreSharedMemoryProducer * producer,void * data_ptr,size_t size)
{
producer->write_data(std::string(static_cast<char*>(data_ptr),size));
}
extern "C" SeralizedNetCoreSharedMemoryProducer * GetSeralizedProducer(const char * name)
{
return new SeralizedNetCoreSharedMemoryProducer(name);
}
extern "C" void DeleteSeralizedProducer(SeralizedNetCoreSharedMemoryProducer * producer)
{
delete producer;
}
extern "C" void SendSeralizedData(SeralizedNetCoreSharedMemoryProducer * producer,void * data_ptr,size_t size)
{
producer->write_data(std::string(static_cast<char*>(data_ptr),size));
}


template <size_t BufferSize>
class NetCoreSharedMemoryConsumer
{
public:
explicit NetCoreSharedMemoryConsumer(const std::string& name)
: consumer_(name){}
void set_callback(void (*callback_method)(const unsigned char *,size_t))
{
consumer_.set_callable([=](const InterprocessMessage<BufferSize>& message)
{
callback_method(reinterpret_cast<const unsigned char *>(message.message),message.message_length);
});
}
void start()
{
consumer_.start();
}
void wait()
{
consumer_.wait();
}
private:
SharedMemoryConsumer<BufferSize> consumer_;
};


template <size_t BufferSize>
class NetCoreSharedMemoryProducer
{
public:
explicit NetCoreSharedMemoryProducer(const std::string& name)
: producer_(name){}
void write_data(const std::string& data)
{
producer_.write_data(data);
}
private:
SharedMemoryProducer<BufferSize> producer_;
};


extern "C" ImageNetCoreSharedMemoryConsumer* GetImageConsumer(char * name)
{
return new ImageNetCoreSharedMemoryConsumer(std::string(name));
}
extern "C" void DeleteImageNetCoreSharedMemoryConsumer(ImageNetCoreSharedMemoryConsumer* consumer)
{
delete consumer;
}
extern "C" void ImageConsumerSetCallback(ImageNetCoreSharedMemoryConsumer* consumer,
void(*callback)(const unsigned char *,size_t))
{
consumer->set_callback(callback);
}
extern "C" void StartImageConsumer(ImageNetCoreSharedMemoryConsumer* consumer)
{
consumer->start();
}
extern "C" void ImageWait(ImageNetCoreSharedMemoryConsumer* consumer)
{
consumer->wait();
}
extern "C" SeralizedNetCoreSharedMemoryConsumer * GetSerializedConsumer(const char * name)
{
return new SeralizedNetCoreSharedMemoryConsumer(name);
}
extern "C" void DeleteSeralizedNetCoreSharedMemoryConsumer(SeralizedNetCoreSharedMemoryConsumer* consumer)
{
delete consumer;
}
extern "C" void SeralizedConsumerSetCallback(SeralizedNetCoreSharedMemoryConsumer* consumer,
void(*callback)(const unsigned char *,size_t))
{
consumer->set_callback(callback);
}
extern "C" void StartSeralizedConsumer(SeralizedNetCoreSharedMemoryConsumer* consumer)
{
consumer->start();
}
extern "C" void SeralizedWait(SeralizedNetCoreSharedMemoryConsumer* consumer)
{
consumer->wait();
}
extern "C" ImageNetCoreSharedMemoryProducer * GetImageProducer(const char * name)
{
return new ImageNetCoreSharedMemoryProducer(name);
}
extern "C" void DeleteImageProducer(ImageNetCoreSharedMemoryProducer * producer)
{
delete producer;
}
extern "C" void SendImageData(ImageNetCoreSharedMemoryProducer * producer,void * data_ptr,size_t size)
{
producer->write_data(std::string(static_cast<char*>(data_ptr),size));
}
extern "C" SeralizedNetCoreSharedMemoryProducer * GetSeralizedProducer(const char * name)
{
return new SeralizedNetCoreSharedMemoryProducer(name);
}
extern "C" void DeleteSeralizedProducer(SeralizedNetCoreSharedMemoryProducer * producer)
{
delete producer;
}
extern "C" void SendSeralizedData(SeralizedNetCoreSharedMemoryProducer * producer,void * data_ptr,size_t size)
{
producer->write_data(std::string(static_cast<char*>(data_ptr),size));
}


template <size_t BufferSize>
class NetCoreSharedMemoryConsumer
{
public:
explicit NetCoreSharedMemoryConsumer(const std::string& name)
: consumer_(name){}
void set_callback(void (*callback_method)(const unsigned char *,size_t))
{
consumer_.set_callable([=](const InterprocessMessage<BufferSize>& message)
{
callback_method(reinterpret_cast<const unsigned char *>(message.message),message.message_length);
});
}
void start()
{
consumer_.start();
}
void wait()
{
consumer_.wait();
}
private:
SharedMemoryConsumer<BufferSize> consumer_;
};


template <size_t BufferSize>
class NetCoreSharedMemoryProducer
{
public:
explicit NetCoreSharedMemoryProducer(const std::string& name)
: producer_(name){}
void write_data(const std::string& data)
{
producer_.write_data(data);
}
private:
SharedMemoryProducer<BufferSize> producer_;
};

Now we need to call that code from C# using PInvoke Interop


class ImageSharedMemoryConsumer
: ISharedMemoryConsumer
{
[DllImport("ScryUnlimitedCommunicator.so")]
private static extern IntPtr GetImageConsumer(string name);
[DllImport("ScryUnlimitedCommunicator.so")]
private static extern void DeleteImageNetCoreSharedMemoryConsumer(IntPtr instance);
[DllImport("ScryUnlimitedCommunicator.so")]
private static extern void ImageConsumerSetCallback(IntPtr instance, IntPtr callback);
[DllImport("ScryUnlimitedCommunicator.so")]
private static extern void StartImageConsumer(IntPtr instance);
[DllImport("ScryUnlimitedCommunicator.so")]
private static extern void SeralizedWait(IntPtr instance);
private readonly IntPtr _nativePointer;
private IntPtr _callbackPtr;
private GCHandle _callbackHandle;
public ImageSharedMemoryConsumer(string name)
{
_nativePointer = GetImageConsumer(name);
}
~ImageSharedMemoryConsumer()
{
DeleteImageNetCoreSharedMemoryConsumer(_nativePointer);
}
public long Size()
{
return 3000000;
}
public void SetCallback(HandleMessage handler)
{
void Callback(IntPtr x, int y)
{
byte[] managedArray = new byte[y];
Marshal.Copy(x, managedArray, 0, y);
handler(managedArray);
}
_callbackPtr = Marshal.GetFunctionPointerForDelegate(new InternalHandleMessage(Callback));
_callbackHandle = GCHandle.Alloc((Action<IntPtr, int>) Callback);
ImageConsumerSetCallback(_nativePointer,_callbackPtr);
}
public void Start()
{
StartImageConsumer(_nativePointer);
}
public void Wait()
{
SeralizedWait(_nativePointer);
}
}


class ImageSharedMemoryProducer
: ISharedMemoryProducer
{
[DllImport("ScryUnlimitedCommunicator.so")]
private static extern IntPtr GetImageProducer(string name);
[DllImport("ScryUnlimitedCommunicator.so")]
private static extern void DeleteImageProducer(IntPtr handle);
[DllImport("ScryUnlimitedCommunicator.so")]
private static extern void SendImageData(IntPtr handle, byte[] data,long length);
private IntPtr _nativeHandle;
public ImageSharedMemoryProducer(string name)
{
_nativeHandle = GetImageProducer(name);
}
~ImageSharedMemoryProducer()
{
DeleteImageProducer(_nativeHandle);
}
public long Size()
{
return 3000000;
}
public void Write(byte[] message)
{
SendImageData(_nativeHandle,message,message.LongLength);
}
}


using System;
public delegate void HandleMessage(byte[] message);
internal delegate void InternalHandleMessage(IntPtr array, int length);
public interface ISharedMemoryConsumer
{
long Size();
void SetCallback(HandleMessage handler);
void Start();
void Wait();
}


public interface ISharedMemoryProducer
{
long Size();
void Write(byte[] message);
}

 

Using Open CV C++ with Azure IoT Edge

If you are looking for a guide on creating an Open CV module in Python, check out a guide here. This guide will focus on creating an Azure IoT Edge module in C++. To accomplish this we need to take the following steps:

Create the Azure IoT Edge Module

Prerequisites

This article assumes that you use a computer or virtual machine running Windows or Linux as your development machine. And you simulate your IoT Edge device on your development machine.

Needs:

To create a module, you need Docker to build the module image, and a container registry to hold the module image:

Create a new solution template

Take these steps to create an IoT Edge module based on Azure IoT C SDK using Visual Studio Code and the Azure IoT Edge extension. First you create a solution, and then you generate the first module in that solution. Each solution can contain more than one module.

  1. In Visual Studio Code, select View > Integrated Terminal.
  2. Select View > Command Palette.
  3. In the command palette, enter and run the command Azure IoT Edge: New IoT Edge Solution.Run New IoT Edge Solution
  4. Browse to the folder where you want to create the new solution. Choose Select folder.
  5. Enter a name for your solution.
  6. Select C Module as the template for the first module in the solution.
  7. Enter a name for your module. Choose a name that’s unique within your container registry.
  8. Provide the name of the module’s image repository. VS Code autopopulates the module name with localhost:5000. Replace it with your own registry information. If you use a local Docker registry for testing, then localhost is fine. If you use Azure Container Registry, then use the login server from your registry’s settings. The login server looks like .azurecr.io.

VS Code takes the information you provided, creates an IoT Edge solution, and then loads it in a new window.

View IoT Edge solution

There are four items within the solution:

  • A .vscode folder contains debug configurations.
  • A modules folder has subfolders for each module. At this point, you only have one. But you can add more in the command palette with the command Azure IoT Edge: Add IoT Edge Module.
  • An .env file lists your environment variables. If Azure Container Registry is your registry, you’ll have an Azure Container Registry username and password in it.

    Note

    The environment file is only created if you provide an image repository for the module. If you accepted the localhost defaults to test and debug locally, then you don’t need to declare environment variables.

  • A deployment.template.json file lists your new module along with a sample tempSensor module that simulates data you can use for testing. For more information about how deployment manifests work, see Learn how to use deployment manifests to deploy modules and establish routes.

Develop your module

The default C module code that comes with the solution is located at modules >  > main.c. The module and the deployment.template.json file are set up so that you can build the solution, push it to your container registry, and deploy it to a device to start testing without touching any code. The module is built to simply take input from a source (in this case, the tempSensor module that simulates data) and pipe it to IoT Hub.

When you’re ready to customize the C template with your own code, use the Azure IoT Hub SDKs to build modules that address the key needs for IoT solutions such as security, device management, and reliability.

Build and deploy your module for debugging

In each module folder, there are several Docker files for different container types. Use any of these files that end with the extension .debug to build your module for testing. Currently, C modules support debugging only in Linux amd64 containers.

  1. In VS Code, navigate to the deployment.template.json file. Update your module image URL by adding .debug to the end.Add **.debug** to your image name
  2. Replace the Node.js module createOptions in deployment.template.json with below content and save this file:
    "createOptions": "{\"HostConfig\": {\"Privileged\": true}}"
    
  3. In the VS Code command palette, enter and run the command Edge: Build IoT Edge solution.
  4. Select the deployment.template.json file for your solution from the command palette.
  5. In Azure IoT Hub Device Explorer, right-click an IoT Edge device ID. Then select Create deployment for IoT Edge device.
  6. Open your solution’s config folder. Then select the deployment.json file. Choose Select Edge Deployment Manifest.

You’ll see the deployment successfully created with a deployment ID in a VS Code-integrated terminal.

Check your container status in the VS Code Docker explorer or by running the docker ps command in the terminal.

Start debugging C module in VS Code

VS Code keeps debugging configuration information in a launch.json file located in a .vscode folder in your workspace. This launch.json file was generated when you created a new IoT Edge solution. It updates each time you add a new module that supports debugging.

  1. Navigate to the VS Code debug view. Select the debug configuration file for your module. The debug option name should be similar to ModuleName Remote Debug (C)Select debug configuration.
  2. Navigate to main.c. Add a breakpoint in this file.
  3. Select Start Debugging or select F5. Select the process to attach to.
  4. In VS Code Debug view, you’ll see the variables in the left panel.

The preceding example shows how to debug C IoT Edge modules on containers. It added exposed ports in your module container createOptions. After you finish debugging your Node.js modules, we recommend you remove these exposed ports for production-ready IoT Edge modules.

Create a working Open CV Build

The working environment is an Ubuntu 18.04 64 bit Desktop OS running Clion using an embedded version of CMake 3.10. Open CV is added via source as a submodule to the project and added as a package in the CMakeLists.txt with the following line:

FIND_PACKAGE (OpenCV REQUIRED)

Once that was added to the CMakeLists.txt, the main.cpp file was changed to the following code:


#include <opencv2/opencv.hpp>
int main( int argc, char** argv )
{
VideoCapture cap;
if(!cap.open(0))
return 0;
Mat frame;
cap >> frame;
//Do something with the frame
}

view raw

main.cpp

hosted with ❤ by GitHub

Deploy the Azure IoT Edge Module

Once you create IoT Edge modules with your business logic, you want to deploy them to your devices to operate at the edge. If you have multiple modules that work together to collect and process data, you can deploy them all at once and declare the routing rules that connect them.

This article shows how to create a JSON deployment manifest, then use that file to push the deployment to an IoT Edge device. For information about creating a deployment that targets multiple devices based on their shared tags, see Deploy and monitor IoT Edge modules at scale

Prerequisites

Configure a deployment manifest

A deployment manifest is a JSON document that describes which modules to deploy, how data flows between the modules, and desired properties of the module twins. For more information about how deployment manifests work and how to create them, see Understand how IoT Edge modules can be used, configured, and reused.

To deploy modules using Visual Studio Code, save the deployment manifest locally as a .JSON file. You will use the file path in the next section when you run the command to apply the configuration to your device.

Here’s a basic deployment manifest with one module as an example:

{
  "modulesContent": {
    "$edgeAgent": {
      "properties.desired": {
        "schemaVersion": "1.0",
        "runtime": {
          "type": "docker",
          "settings": {
            "minDockerVersion": "v1.25",
            "loggingOptions": "",
            "registryCredentials": {}
          }
        },
        "systemModules": {
          "edgeAgent": {
            "type": "docker",
            "settings": {
              "image": "mcr.microsoft.com/azureiotedge-agent:1.0",
              "createOptions": "{}"
            }
          },
          "edgeHub": {
            "type": "docker",
            "status": "running",
            "restartPolicy": "always",
            "settings": {
              "image": "mcr.microsoft.com/azureiotedge-hub:1.0",
              "createOptions": "{}"
            }
          }
        },
        "modules": {
          "tempSensor": {
            "version": "1.0",
            "type": "docker",
            "status": "running",
            "restartPolicy": "always",
            "settings": {
              "image": "mcr.microsoft.com/azureiotedge-simulated-temperature-sensor:1.0",
              "createOptions": "{}"
            }
          }
        }
      }
    },
    "$edgeHub": {
      "properties.desired": {
        "schemaVersion": "1.0",
        "routes": {
            "route": "FROM /* INTO $upstream"
        },
        "storeAndForwardConfiguration": {
          "timeToLiveSecs": 7200
        }
      }
    },
    "tempSensor": {
      "properties.desired": {}
    }
  }
}

Sign in to access your IoT hub

You can use the Azure IoT extensions for Visual Studio Code to perform operations with your IoT hub. For these operations to work, you need to sign in to your Azure account and select the IoT hub that you are working on.

  1. In Visual Studio Code, open the Explorer view.
  2. At the bottom of the Explorer, expand the Azure IoT Hub Devices section.Expand Azure IoT Hub Devices
  3. Click on the  in the Azure IoT Hub Devices section header. If you don’t see the ellipsis, hover over the header.
  4. Choose Select IoT Hub.
  5. If you are not signed in to your Azure account, follow the prompts to do so.
  6. Select your Azure subscription.
  7. Select your IoT hub.

Deploy to your device

You deploy modules to your device by applying the deployment manifest that you configured with the module information.

  1. In the Visual Studio Code explorer view, expand the Azure IoT Hub Devices section.
  2. Right-click on the device that you want to configure with the deployment manifest.
  3. Select Create Deployment for IoT Edge Device.
  4. Navigate to the deployment manifest JSON file that you want to use, and click Select Edge Deployment Manifest.Select Edge Deployment Manifest

The results of your deployment are printed in the VS Code output. Successful deployments are applied within a few minutes if the target device is running and connected to the internet.

View modules on your device

Once you’ve deployed modules to your device, you can view all of them in the Azure IoT Hub Devices section. Select the arrow next to your IoT Edge device to expand it. All the currently running modules are displayed.

If you recently deployed new modules to a device, hover over the Azure IoT Hub Devices section header and select the refresh icon to update the view.

Right-click the name of a module to view and edit the module twin.

Using Protocol Buffers with Azure IoT Edge

Google’s Protocol Buffers are a perfect fit with the multilingual approach of Azure IoT Edge. Using ProtoBuf, a message format can be written once and used across multiple frameworks and languages while benefiting from the speed and message size intrinsic to ProtoBuf. For this Azure IoT Edge use case, we will generate a message in C++ and send it to a module written in Python to filter out the values that are sent to IoT Hub.

Steps

  • Create the message format
  • Create a C Azure IoT Edge Module
    • Add ProtoBuffers to build
    • Create C models
  • Create a Python Azure IoT Edge Module
    • Add ProtoBuffers to project
    • Create Python Module

Create the message format

Creating the message format is trivial. Following the language guide, there are two message types to create.

  1. A temperature reading, consisting of a float and a string
  2. An array of the previous reading with a string
syntax = "proto3";

message TemperatureReading {
  int reading = 1;
  string timestamp = 2;
}

message TemperatureReadingUpload {
  string uploaded_timestamp = 1;
  repeated TemperatureReading readings = 2;
}

The above is all that is needed to create the model for ProtoBuf. Creating the language specific code for each module is covered in their module sections.

Create a C Azure IoT Edge Module

Prerequisites

This article assumes that you use a computer or virtual machine running Windows or Linux as your development machine. And you simulate your IoT Edge device on your development machine.

Needs:

To create a module, you need Docker to build the module image, and a container registry to hold the module image:

Create a new solution template

Take these steps to create an IoT Edge module based on Azure IoT C SDK using Visual Studio Code and the Azure IoT Edge extension. First you create a solution, and then you generate the first module in that solution. Each solution can contain more than one module.

  1. In Visual Studio Code, select View > Integrated Terminal.
  2. Select View > Command Palette.
  3. In the command palette, enter and run the command Azure IoT Edge: New IoT Edge Solution.Run New IoT Edge Solution
  4. Browse to the folder where you want to create the new solution. Choose Select folder.
  5. Enter a name for your solution.
  6. Select C Module as the template for the first module in the solution.
  7. Enter a name for your module. Choose a name that’s unique within your container registry.
  8. Provide the name of the module’s image repository. VS Code autopopulates the module name with localhost:5000. Replace it with your own registry information. If you use a local Docker registry for testing, then localhost is fine. If you use Azure Container Registry, then use the login server from your registry’s settings. The login server looks like .azurecr.io.

VS Code takes the information you provided, creates an IoT Edge solution, and then loads it in a new window.

View IoT Edge solution

There are four items within the solution:

  • A .vscode folder contains debug configurations.
  • A modules folder has subfolders for each module. At this point, you only have one. But you can add more in the command palette with the command Azure IoT Edge: Add IoT Edge Module.
  • An .env file lists your environment variables. If Azure Container Registry is your registry, you’ll have an Azure Container Registry username and password in it.

    Note

    The environment file is only created if you provide an image repository for the module. If you accepted the localhost defaults to test and debug locally, then you don’t need to declare environment variables.

  • A deployment.template.json file lists your new module along with a sample tempSensor module that simulates data you can use for testing. For more information about how deployment manifests work, see Learn how to use deployment manifests to deploy modules and establish routes.

Develop your module

The default C module code that comes with the solution is located at modules >> main.c. The module and the deployment.template.json file are set up so that you can build the solution, push it to your container registry, and deploy it to a device to start testing without touching any code. The module is built to simply take input from a source (in this case, the tempSensor module that simulates data) and pipe it to IoT Hub.

When you’re ready to customize the C template with your own code, use the Azure IoT Hub SDKs to build modules that address the key needs for IoT solutions such as security, device management, and reliability.

Compile the Protocol Buffer file

To compile the Protocol Buffer file, use the command line compiler protoc. For more information on how to use protoc for each platform, check out the Protocol Buffer documentation. For the C module, we will use the C++ compiler options:

protoc --proto_path=src --cpp_out=model src/temp.proto

To create and serialize the object, use the following code:

TemperatureReading reading;
reading.set_reading(get_temperature_reading()); //get_temperature_reading is your function on generating the temperature reading value
auto message_body = reading.SerializeAsString();

 

Build and deploy your module for debugging

In each module folder, there are several Docker files for different container types. Use any of these files that end with the extension .debug to build your module for testing. Currently, C modules support debugging only in Linux amd64 containers.

  1. In VS Code, navigate to the deployment.template.json file. Update your module image URL by adding .debug to the end.Add **.debug** to your image name
  2. Replace the C module createOptions in deployment.template.json with below content and save this file:
    "createOptions": "{\"HostConfig\": {\"Privileged\": true}}"
    
  3. In the VS Code command palette, enter and run the command Edge: Build IoT Edge solution.
  4. Select the deployment.template.json file for your solution from the command palette.
  5. In Azure IoT Hub Device Explorer, right-click an IoT Edge device ID. Then select Create deployment for Single device.
  6. Open your solution’s config folder. Then select the deployment.json file. Choose Select Edge Deployment Manifest.

You’ll see the deployment successfully created with a deployment ID in a VS Code-integrated terminal.

Check your container status in the VS Code Docker explorer or by running the docker ps command in the terminal.

Start debugging C module in VS Code

VS Code keeps debugging configuration information in a launch.json file located in a .vscode folder in your workspace. This launch.json file was generated when you created a new IoT Edge solution. It updates each time you add a new module that supports debugging.

  1. Navigate to the VS Code debug view. Select the debug configuration file for your module. The debug option name should be similar to ModuleName Remote Debug (C)Select debug configuration.
  2. Navigate to main.c. Add a breakpoint in this file.
  3. Select Start Debugging or select F5. Select the process to attach to.
  4. In VS Code Debug view, you’ll see the variables in the left panel.

The preceding example shows how to debug C IoT Edge modules on containers. It added exposed ports in your module container createOptions. After you finish debugging your C modules, we recommend you remove these exposed ports for production-ready IoT Edge modules.

Create a Python Azure IoT Edge Module

Create an IoT Edge module project

The following steps create an IoT Edge Python module by using Visual Studio Code and the Azure IoT Edge extension.

Create a new solution

Use the Python package cookiecutter to create a Python solution template that you can build on top of.

  1. In Visual Studio Code, select View > Integrated Terminal to open the VS Code integrated terminal.
  2. In the integrated terminal, enter the following command to install (or update) cookiecutter, which you use to create the IoT Edge solution template in VS Code:
    pip install --upgrade --user cookiecutter

    Ensure the directory where cookiecutter will be installed is in your environment’s Path in order to make it possible to invoke it from a command prompt.

  3. Select View > Command Palette to open the VS Code command palette.
  4. In the command palette, enter and run the command Azure: Sign in and follow the instructions to sign in your Azure account. If you’re already signed in, you can skip this step.
  5. In the command palette, enter and run the command Azure IoT Edge: New IoT Edge solution. In the command palette, provide the following information to create your solution:
    1. Select the folder where you want to create the solution.
    2. Provide a name for your solution or accept the default EdgeSolution.
    3. Choose Python Module as the module template.
    4. Name your module PythonModule.
    5. Specify the Azure container registry that you created in the previous section as the image repository for your first module. Replace localhost:5000 with the login server value that you copied. The final string looks like <registry name>.azurecr.io/pythonmodule.

The VS Code window loads your IoT Edge solution workspace: the modules folder, a deployment manifest template file, and a .env file.

Add your registry credentials

The environment file stores the credentials for your container repository and shares them with the IoT Edge runtime. The runtime needs these credentials to pull your private images onto the IoT Edge device.

  1. In the VS Code explorer, open the .env file.
  2. Update the fields with the username and password values that you copied from your Azure container registry.
  3. Save this file.

Compile the Protocol Buffer file

To compile the Protocol Buffer file, use the command line compiler protoc. For more information on how to use protoc for each platform, check out the Protocol Buffer documentation. For the Python module, we will use the Python compiler options:

protoc --proto_path=src --python_out=model src/temp.proto

Update the module with custom code

Each template includes sample code, which takes simulated sensor data from the tempSensor module and routes it to the IoT hub. In this section, add the code that expands the PythonModule to analyze the messages before sending them.

  1. In the VS Code explorer, open modules > PythonModule > main.py.
  2. At the top of the main.py file, import the temp_pb3 library that was created by protoc:
    import temp_pb3
    
  3. Add the TEMPERATURE_THRESHOLD and TWIN_CALLBACKS variables under the global counters. The temperature threshold sets the value that the measured machine temperature must exceed for the data to be sent to the IoT hub.
    TEMPERATURE_THRESHOLD = 25
    TWIN_CALLBACKS = 0
    
  4. Replace the receive_message_callback function with the following code:
    # receive_message_callback is invoked when an incoming message arrives on the specified 
    # input queue (in the case of this sample, "input1").  Because this is a filter module, 
    # we forward this message to the "output1" queue.
    def receive_message_callback(message, hubManager):
        global RECEIVE_CALLBACKS
        global TEMPERATURE_THRESHOLD
        message_buffer = message.get_bytearray()
        map_properties = message.properties()
        key_value_pair = map_properties.get_internals()
        print ( "    Properties: %s" % key_value_pair )
        RECEIVE_CALLBACKS += 1
        print ( "    Total calls received: %d" % RECEIVE_CALLBACKS )
        data = TemperatureReading.ParseFromString(message_buffer)
        if data.reading > TEMPERATURE_THRESHOLD:
            map_properties.add("MessageType", "Alert")
            print("Machine temperature %s exceeds threshold %s" % (data["machine"]["temperature"], TEMPERATURE_THRESHOLD))
        hubManager.forward_event_to_output("output1", message, 0)
        return IoTHubMessageDispositionResult.ACCEPTED
    
  5. Add a new function called module_twin_callback. This function is invoked when the desired properties are updated.
    # module_twin_callback is invoked when the module twin's desired properties are updated.
    def module_twin_callback(update_state, payload, user_context):
        global TWIN_CALLBACKS
        global TEMPERATURE_THRESHOLD
        print ( "\nTwin callback called with:\nupdateStatus = %s\npayload = %s\ncontext = %s" % (update_state, payload, user_context) )
        data = json.loads(payload)
        if "desired" in data and "TemperatureThreshold" in data["desired"]:
            TEMPERATURE_THRESHOLD = data["desired"]["TemperatureThreshold"]
        if "TemperatureThreshold" in data:
            TEMPERATURE_THRESHOLD = data["TemperatureThreshold"]
        TWIN_CALLBACKS += 1
        print ( "Total calls confirmed: %d\n" % TWIN_CALLBACKS )
    
  6. In the HubManager class, add a new line to the init method to initialize the module_twin_callback function that you just added:
    # Sets the callback when a module twin's desired properties are updated.
    self.client.set_module_twin_callback(module_twin_callback, self)
    
  7. Save this file.

Build your IoT Edge solution

In the previous section, you created an IoT Edge solution and added code to the PythonModule to filter out messages where the reported machine temperature is below the acceptable threshold. Now you need to build the solution as a container image and push it to your container registry.

  1. Sign in to Docker by entering the following command in the Visual Studio Code integrated terminal. Then you can push your module image to your Azure container registry:
    docker login -u <ACR username> -p <ACR password> <ACR login server>
    

    Use the username, password, and login server that you copied from your Azure container registry in the first section. You can also retrieve these values from the Access keys section of your registry in the Azure portal.

  2. In the VS Code explorer, open the deployment.template.json file in your IoT Edge solution workspace.This file tells the $edgeAgent to deploy two modules: tempSensor, which simulates device data, and PythonModule. The PythonModule.image value is set to a Linux amd64 version of the image. To learn more about deployment manifests, see Understand how IoT Edge modules can be used, configured, and reused.This file also contains your registry credentials. In the template file, your user name and password are filled in with placeholders. When you generate the deployment manifest, the fields are updated with the values that you added to the .env file.
  3. Add the PythonModule module twin to the deployment manifest. Insert the following JSON content at the bottom of the moduleContent section, after the $edgeHub module twin:
        "PythonModule": {
            "properties.desired":{
                "TemperatureThreshold":25
            }
        }
    
  4. Save this file.
  5. In the VS Code explorer, right-click the deployment.template.json file and select Build and Push IoT Edge solution.

When you tell Visual Studio Code to build your solution, it first takes the information in the deployment template and generates a deployment.json file in a new folder named config. Then it runs two commands in the integrated terminal: docker build and docker push. These two commands build your code, containerize the Python code, and then push the code to the container registry that you specified when you initialized the solution.

You can see the full container image address with tag in the docker build command that runs in the VS Code integrated terminal. The image address is built from information in the module.json file with the format <repository>:<version>-<platform>. For this tutorial, it should look like registryname.azurecr.io/pythonmodule:0.0.1-amd64.

Deploy and run the solution

You can use the Azure portal to deploy your Python module to an IoT Edge device like you did in the quickstarts. You can also deploy and monitor modules from within Visual Studio Code. The following sections use the Azure IoT Edge extension for VS Code that was listed in the prerequisites. Install the extension now, if you didn’t already.

  1. Open the VS Code command palette by selecting View > Command Palette.
  2. Search for and run the command Azure: Sign in. Follow the instructions to sign in your Azure account.
  3. In the command palette, search for and run the command Azure IoT Hub: Select IoT Hub.
  4. Select the subscription that contains your IoT hub, and then select the IoT hub that you want to access.
  5. In the VS Code explorer, expand the Azure IoT Hub Devices section.
  6. Right-click the name of your IoT Edge device, and then select Create Deployment for IoT Edge device.
  7. Browse to the solution folder that contains the PythonModule. Open the config folder, select the deployment.json file, and then choose Select Edge Deployment Manifest.
  8. Refresh the Azure IoT Hub Devices section. You should see the new PythonModule running along with the TempSensor module and the $edgeAgent and $edgeHub.

Generate Protocol Buffers on build with CMake

Just to see if it was possible on my current project, I tried to generate C++ code files from their .proto definitions whenever CMake ran. To do this, I added a few lines to the CMakeLists.txt file of the project. The idea is to use execute_process to call protoc and generate the files in the appropriate folder in the solution.

First, file(GLOB …) is used to set all of the .proto files into an iterable variable. Then, variables are setup for the proto_path and cpp_out variables.

After that, the files variable is looped and for each of the files we use execute_process to invoke protoc and generate the .pb.h and .pb.cc files.


file(GLOB PROTOBUF_DEFINITION_FILES "*.proto")
set(PROTOBUF_INPUT_DIRECTORY "${PROJECT_SOURCE_DIR}")
set(PROTOBUF_OUTPUT_DIRECTORY "${PROJECT_SOURCE_DIR}/Models/Proto/")
foreach(file ${PROTOBUF_DEFINITION_FILES})
set(PROTOBUF_ARGUMENTS "protoc –proto_path=\"${PROTOBUF_INPUT_DIRECTORY}\" –cpp_out=\"${PROTOBUF_OUTPUT_DIRECTORY}\" \"${file}\"")
execute_process(COMMAND ${PROTOBUF_OUTPUT_DIRECTORY}
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
RESULT_VARIABLE PROTOBUF_RESULT
OUTPUT_VARIABLE PROTOBUF_OUTPUT_VARIABLE)
endforeach()
file(GLOB PROTOBUF_MODELS_INCLUDES "Models/Proto/*.pb.cc" "Models/Proto/*.hpp")

view raw

CMakeLists.txt

hosted with ❤ by GitHub

Finally, we want to add the .pb.h and .pb.cc files to a variable for the final build. To do so, use file(GLOB …) again to search for all appropriate files.