Commit 099a4873 authored by alexandre burton's avatar alexandre burton
Browse files

Initial commit

parents
# Xcode
#
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
## User settings
xcuserdata/
## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
*.xcscmblueprint
*.xccheckout
## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
build/
DerivedData/
*.moved-aside
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
## Gcc Patch
/*.gcno
# MQTT Max Client
**MQTT client based on libmosquitto**
This object provides a Max interface to a treaded instance of the [libmosquitto](http://mosquitto.org/man/libmosquitto-3.html) client.
v20210101 was developped, tested and signed on macOS 10.14.6. It covers the features our application required; more work to follow (see below).
It bundles a static libmosquitto binary (TLS support was giving errors so it was simply disabled through CMake). You can probably swap the .a in the .mxo with your own library if you wish.
Signing seems to be OK; not sure how the Xcode project will present itself to someone else, but as long as the bundled libmosquitto.a gets signed (there a scripting step for that), it works fine on unrelated computers.
## TODO
Max interface:
- QOS, persistence and will
- complete MQTT callback support
- complete attribute support
- auto-connection
Bundled library:
- TLS support
- (maybe) auto-discovery of dynamic system-level dylib support, vs bundled as fallback (?)
- or should the MQTT CMake-based source be included / compiled with the object?
Other features:
- max package
- internal preprocessing of the received message (detect JSON dict, or list or ...?)
- optimize lifecycle of the mosquitto instance? (re-use vs re-create)
Windows version:
- compile a libmosquitto lib, and the .mxe
Linux version:
- as soon as Max4Linux comes out of alpha
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>BuildMachineOSBuild</key>
<string>18G6032</string>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleExecutable</key>
<string>mqtt</string>
<key>CFBundleIdentifier</key>
<string>org.artificiel.mqtt</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>8.0.2</string>
<key>CFBundleLongVersionString</key>
<string>mqtt 8.0.2, Copyright 2014 Cycling '74</string>
<key>CFBundlePackageType</key>
<string>iLaX</string>
<key>CFBundleShortVersionString</key>
<string>8.0.2</string>
<key>CFBundleSignature</key>
<string>max2</string>
<key>CFBundleSupportedPlatforms</key>
<array>
<string>MacOSX</string>
</array>
<key>CFBundleVersion</key>
<string>8.0.2</string>
<key>CSResourcesFileMapped</key>
<true/>
<key>DTCompiler</key>
<string>com.apple.compilers.llvm.clang.1_0</string>
<key>DTPlatformBuild</key>
<string>11C504</string>
<key>DTPlatformVersion</key>
<string>GM</string>
<key>DTSDKBuild</key>
<string>19B90</string>
<key>DTSDKName</key>
<string>macosx10.15</string>
<key>DTXcode</key>
<string>1130</string>
<key>DTXcodeBuild</key>
<string>11C504</string>
<key>LSMinimumSystemVersion</key>
<string>10.9</string>
</dict>
</plist>
iLaXmax2
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>files</key>
<dict/>
<key>files2</key>
<dict>
<key>Frameworks/libmosquitto_static.a</key>
<dict>
<key>cdhash</key>
<data>
LKXsQtk3PyDUOKqeiS6XcEOZdMM=
</data>
<key>requirement</key>
<string>identifier "libmosquitto_static" and anchor apple generic and certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.OU] = "2FS5KJDXKD"</string>
</dict>
</dict>
<key>rules</key>
<dict>
<key>^Resources/</key>
<true/>
<key>^Resources/.*\.lproj/</key>
<dict>
<key>optional</key>
<true/>
<key>weight</key>
<real>1000</real>
</dict>
<key>^Resources/.*\.lproj/locversion.plist$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>1100</real>
</dict>
<key>^Resources/Base\.lproj/</key>
<dict>
<key>weight</key>
<real>1010</real>
</dict>
<key>^version.plist$</key>
<true/>
</dict>
<key>rules2</key>
<dict>
<key>.*\.dSYM($|/)</key>
<dict>
<key>weight</key>
<real>11</real>
</dict>
<key>^(.*/)?\.DS_Store$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>2000</real>
</dict>
<key>^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/</key>
<dict>
<key>nested</key>
<true/>
<key>weight</key>
<real>10</real>
</dict>
<key>^.*</key>
<true/>
<key>^Info\.plist$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>20</real>
</dict>
<key>^PkgInfo$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>20</real>
</dict>
<key>^Resources/</key>
<dict>
<key>weight</key>
<real>20</real>
</dict>
<key>^Resources/.*\.lproj/</key>
<dict>
<key>optional</key>
<true/>
<key>weight</key>
<real>1000</real>
</dict>
<key>^Resources/.*\.lproj/locversion.plist$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>1100</real>
</dict>
<key>^Resources/Base\.lproj/</key>
<dict>
<key>weight</key>
<real>1010</real>
</dict>
<key>^[^/]+$</key>
<dict>
<key>nested</key>
<true/>
<key>weight</key>
<real>10</real>
</dict>
<key>^embedded\.provisionprofile$</key>
<dict>
<key>weight</key>
<real>20</real>
</dict>
<key>^version\.plist$</key>
<dict>
<key>weight</key>
<real>20</real>
</dict>
</dict>
</dict>
</plist>
// Xcode target configuration settings for the Max 6 SDK
// Used as the basis for Xcode projects to build Max externals.
//
// Changes to the settings in this file will be applied to all SDK examples
// To change settings for only one of the examples, override the settings using
// Xcode's target inspector.
//
// by Timothy Place
// Copyright © 2012, Cycling '74
// Name & Version
PRODUCT_NAME = $(PROJECT_NAME)
PRODUCT_VERSION = 8.0.2
ARCHS = x86_64
// Paths
C74SUPPORT = $(SRCROOT)/../../c74support
HEADER_SEARCH_PATHS = "$(C74SUPPORT)/max-includes" "$(C74SUPPORT)/msp-includes" "$(C74SUPPORT)/jit-includes"
FRAMEWORK_SEARCH_PATHS = "$(C74SUPPORT)/max-includes" "$(C74SUPPORT)/msp-includes" "$(C74SUPPORT)/jit-includes"
DSTROOT = $(SRCROOT)/../../../externals
// (This next path is relative to DSTROOT)
INSTALL_PATH = /
// Special Files
GCC_PREFIX_HEADER = $(C74SUPPORT)/max-includes/macho-prefix.pch
INFOPLIST_FILE = $(SRCROOT)/../../Info.plist
// Architecture and Deployment
ARCHS = x86_64
// The following section sets the Mac SDK version to be used.
// For most projects this has little to no impact because there are no direct dependencies on OS function calls.
// In those projects with OS function calls, it should be okay to use the most recent SDK version because the
// MACOSX_DEPLOYMENT_TARGET will disable functionality that is unavailable in the older target OS.
// For this reason, the SDKROOT variable is commented out, telling Xcode to use the default (which is the most recent SDK).
//
// SDKROOT = macosx10.6
MACOSX_DEPLOYMENT_TARGET = 10.9
// Compiler Version -- leave them all commented out to get the default version provided by Xcode
// GCC_VERSION = com.apple.compilers.llvmgcc42
// GCC_VERSION = com.apple.compilers.llvm.clang.1_0
// Preprocessor Defines
GCC_PREPROCESSOR_DEFINITIONS = "DENORM_WANT_FIX = 1" "NO_TRANSLATION_SUPPORT = 1"
// Static Configuration (don't change these)
WRAPPER_EXTENSION = mxo;
WARNING_CFLAGS = -Wmost -Wno-four-char-constants -Wno-unknown-pragmas
DEPLOYMENT_LOCATION = YES
GENERATE_PKGINFO_FILE = YES
// Flags to enforce some build-time checks for the symbols used while not actually performing a hard link
C74_SYM_LINKER_FLAGS = @$(C74SUPPORT)/max-includes/c74_linker_flags.txt
// hide all symbols by default
// mark a function to be exported with the C74_EXPORT macro
// most likely this will only apply to the ext_main() function which we've already prototyped for you
OTHER_CFLAGS = -fvisibility=hidden
OTHER_LDFLAGS = -framework MaxAudioAPI -framework JitterAPI $(C74_SYM_LINKER_FLAGS)
#include <stdio.h>
#include "/usr/local/include/mosquitto.h"
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "ext.h"
#include "ext_obex.h"
typedef struct _mqtt {
t_object p_ob;
void *p_status_outlet;
void *p_main_outlet;
struct mosquitto *mosq; // instance of mosquitto client
int a_state; // MQTT connection state
long a_port; // MQTT Broker port
char *a_verbose; // verbose chatter in Max windows
t_symbol *a_autoconnect; // auto-connect on object instanciation
t_symbol *a_user; // MQTT username
t_symbol *a_pw; // MQTT password
t_symbol *a_id; // Client ID
} t_mqtt;
#define MQTT_DEFAULT_PORT 1883
static long MOSQUITTO_CLIENT_ID = 0;
// download & compile static mosquitto lib (did not fight TLS/https; disabled in cmake)
// /usr/bin/codesign --force --sign 0EFF62FA1517300CC579B619A634293E5A5E7193 --timestamp=none/usr/bin/codesign --force --sign 0EFF62FA1517300CC579B619A634293E5A5E7193 --timestamp=none /Users/brtn/Downloads/max-sdk-8.0.3/externals/mqtt.mxo/Contents/Frameworks/libmosquitto_static.a
// add to max project
void mqtt_change_state(t_mqtt *x, int state) {
switch (state) {
case 1:
if (x->a_verbose) post("Mosquitto <%s> connection up",x->a_id->s_name);
x->a_state =1;
break;
default:
if (x->a_verbose) post("Mosquitto <%s> connecction down",x->a_id->s_name);
x->a_state =0;
}
outlet_int(x->p_status_outlet, x->a_state);
}
void connect_callback(struct mosquitto *mosq, void *obj, int result)
{
t_mqtt * x = obj;
switch (result) {
case 0:
mqtt_change_state(x, 1);
break;
default:
error("Mosquitto <%s> Connection Refused, [%s]", x->a_id->s_name, mosquitto_reason_string(result));
mqtt_change_state(x, 0);
mosquitto_disconnect(mosq);
}
}
void disconnect_callback(struct mosquitto *mosq, void *obj, int result)
{
t_mqtt * x = obj;
if (result ==0) {
if (x->a_verbose) post("Mosquitto <%s> disconnected as requested",x->a_id->s_name);
} else {
post("Mosquitto <%s> unexpectedly disconnected from broker",x->a_id->s_name);
}
mqtt_change_state(x, 0);
}
void message_callback(struct mosquitto *mosq, void *obj, const struct mosquitto_message *message)
{
t_mqtt * x = obj;
t_atom a[1];
atom_setsym(a+0, gensym((char*) message->payload));
outlet_anything(x->p_main_outlet, gensym(message->topic), 1,a);
}
void mqtt_assist(t_mqtt *x, void *b, long m, long a, char *s);
void mqtt_subscribe_method(t_mqtt *x, t_symbol *topic);
void mqtt_unsubscribe_method(t_mqtt *x, t_symbol *topic);
void mqtt_publish_method(t_mqtt *x, t_symbol *s, long argc, t_atom *argv);
void mqtt_connect_method(t_mqtt *x, t_symbol *s, long argc, t_atom *argv);
void mqtt_loop_method(t_mqtt *x);
void mqtt_set_id(t_mqtt *x, void *attr, long argc, t_atom *argv);
void *mqtt_new(t_symbol *s, long argc, t_atom *argv);
void mosquitto_free_client(t_mqtt *x) {
if (x->a_verbose) post("Mosquitto: freeing underlying client <%s>", x->a_id->s_name);
mosquitto_disconnect(x->mosq);
mosquitto_loop_stop(x->mosq, true);
mosquitto_destroy(x->mosq);
}
void mqtt_free(t_mqtt *x)
{
mosquitto_free_client(x);
}
t_class *mqtt_class;
void ext_main(void *r)
{
t_class *c;
c = class_new("mqtt", (method)mqtt_new, (method)mqtt_free, sizeof(t_mqtt), 0L, A_GIMME, 0);
class_addmethod(c, (method)mqtt_loop_method, "bang", 0);
class_addmethod(c, (method)mqtt_assist, "assist", A_CANT, 0);
class_addmethod(c, (method)mqtt_subscribe_method, "subscribe", A_DEFSYM, 0);
class_addmethod(c, (method)mqtt_unsubscribe_method, "unsubscribe",A_DEFSYM, 0);
class_addmethod(c, (method)mqtt_publish_method, "publish", A_GIMME, 0);
class_addmethod(c, (method)mqtt_connect_method, "connect", A_GIMME, 0);
CLASS_ATTR_SYM(c, "id", 0, t_mqtt, a_id);
CLASS_ATTR_ACCESSORS(c, "id", NULL, mqtt_set_id);
CLASS_ATTR_CHAR(c, "verbose", 0, t_mqtt, a_verbose);
CLASS_ATTR_STYLE_LABEL(c, "verbose",0,"onoff","Verbose");
// CLASS_ATTR_SYM(c, "autoconnect", 0, t_mqtt, a_autoconnect);
CLASS_ATTR_SYM(c, "user", 0, t_mqtt, a_user);
CLASS_ATTR_SYM(c, "password", 0, t_mqtt, a_pw);
CLASS_ATTR_LONG(c, "port", 0, t_mqtt, a_port);
class_register(CLASS_BOX, c);
mqtt_class = c;
int res = mosquitto_lib_init();
post("MQTT Client for Max ©2021 https://gitlab.artificiel.org/max/mqtt",0);
if (res==MOSQ_ERR_SUCCESS) {
int a,b,c;
mosquitto_lib_version(&a,&b,&c);
post("Mosquitto MQTT Library %i.%i.%i initialized.",a,b,c);
} else {
error("Mosquitto MQTT did not correctly initialize!",0);
}
}
void mqtt_set_id(t_mqtt *x, void *attr, long argc, t_atom *argv)
{
if (atom_getsym(argv)->s_name != x->a_id->s_name) {
x->a_id = atom_getsym(argv);
if (x->a_state == 1) {
post("TO IMPLEMENT: should reconnect with id %s", x->a_id->s_name);
}
}
}
void mqtt_connect_method(t_mqtt *x, t_symbol *s, long argc, t_atom *argv) {
if ((argc < 1 && argc > 4) || argc ==3) {
error("mqtt connect requires 1, 2 or 4 arguments: host (port (username password))");
} else {
if (argv[0].a_type==A_SYM) {
// mosquitto_reinitialise(x->mosq, x->clientid, false, x);
if (x->mosq != NULL) {
mosquitto_free_client(x);
}
x->mosq = mosquitto_new(x->a_id->s_name, false, x);
if (x->a_verbose) post("Mosquitto Instance id <%s> created",x->a_id->s_name);
char host[100];
int port = MQTT_DEFAULT_PORT;
sprintf(host, "%s", argv[0].a_w.w_sym->s_name);
if (argc > 1) {
if (argv[1].a_type==A_LONG) {
port = argv[1].a_w.w_long;
} else {
error("mqtt connect port (arg#2) must be int -- using default 1883");
}
if (argc ==4) {
if (argv[2].a_type==A_SYM && argv[3].a_type==A_SYM) {
mosquitto_username_pw_set(x->mosq, argv[2].a_w.w_sym->s_name, argv[3].a_w.w_sym->s_name);
} else {
error("mqtt connect username & password (arg#2-3) must be symbols -- skipping auth");
}
}
}
mosquitto_disconnect_callback_set(x->mosq, disconnect_callback);
mosquitto_connect_callback_set(x->mosq, connect_callback);
mosquitto_message_callback_set(x->mosq, message_callback);
mosquitto_user_data_set(x->mosq, x);
// rc = mosquitto_loop(x->mosq, -1, 1);
// if(rc){
// error ("Mosquitto loop error!\n");
// } else {
int res = mosquitto_connect(x->mosq, host, port, 60);
// post("Mosquitto Instance id %s starting loop",x->a_id->s_name );
// int rloop = mosquitto_loop_start(x->mosq);
// if (rloop==MOSQ_ERR_INVAL) {
// error("Mosquitto <%s> could not start thread [MOSQ_ERR_INVAL]", x->clientid);
// } else if (rloop==MOSQ_ERR_NOT_SUPPORTED) {
// error("Mosquitto <%s> could not start thread [MOSQ_ERR_NOT_SUPPORTED]", x->clientid);
// } else {
// post("Mosquitto <%s> started event thread",x->clientid);
// }
// // }
//
// mosquitto_disconnect(x->mosq);
// mosquitto_loop_stop(x->mosq, false);
int rloop;
switch (res) {
case MOSQ_ERR_SUCCESS:
// rc = mosquitto_loop(x->mosq, -1, 1);
// if(rc){
// error ("Mosquitto connection error!\n");
// } else {
rloop = mosquitto_loop_start(x->mosq);
if (rloop==MOSQ_ERR_INVAL) {
error("Mosquitto <%s> could not start thread [MOSQ_ERR_INVAL]", x->a_id->s_name);
} else if (rloop==MOSQ_ERR_NOT_SUPPORTED) {
error("Mosquitto <%s> could not start thread [MOSQ_ERR_NOT_SUPPORTED]", x->a_id->s_name);
} else {
if (x->a_verbose) post("Mosquitto <%s> started event thread",x->a_id->s_name);
}
// }
break;
case MOSQ_ERR_INVAL: error("Mosquitto::connect <%s> invalid parameters [MOSQ_ERR_INVAL]", x->a_id); break;
case MOSQ_ERR_ERRNO: error("Mosquitto::connect <%s> out of memory [MOSQ_ERR_NOMEM]", x->a_id); break;
}
} else {
error("mqtt connect host (arg#1) must be string");
}
}
}
void mqtt_loop_method(t_mqtt * x) {
post("loop...");
int res = mosquitto_loop(x->mosq, -1, 1);
post("loop %i", res);
}
void mqtt_publish_method(t_mqtt *x, t_symbol *s, long argc, t_atom *argv) {
t_symbol topic;
char value[100];
long qos = 0;
bool retain = false;
if (argc < 2 || argc > 4) {
error("mqtt publish requires 2, 3 or 4 arguments: topic valuee (qos (retain))");
} else {
if (argv[0].a_type==A_SYM) {
topic = *argv[0].a_w.w_sym;
if (argv[1].a_type==A_LONG) {
sprintf(value, "%lli", argv[1].a_w.w_long);
} else if (argv[1].a_type==A_FLOAT) {
sprintf(value, "%f", argv[1].a_w.w_float);
} else if (argv[1].a_type==A_SYM) {
sprintf(value, "%s", argv[1].a_w.w_sym->s_name);
}
if (argc>2) {
if (argv[2].a_type==A_LONG) {
long q = argv[2].a_w.w_long;
if (q <0) q = 0;
if (q > 2) q = 2;
qos = q;
}
if (argc==4) {
if (argv[4].a_type==A_LONG) {
long q = argv[2].a_w.w_long;
if (q <0) q = 0;
if (q > 1) q = 1;
retain = q;
}
}
}
int res = mosquitto_publish(x->mosq, NULL, topic.s_name, strlen(value), value, qos, retain);
switch (res) {
case MOSQ_ERR_SUCCESS: /* silent success */
break;
default:
error("Mosquitto::publish <%s> %s [%s]", x->a_id->s_name, topic, mosquitto_reason_string(res));
}
} else {
error("mqtt publish topic (arg#1) must be string");
}
}
}
void mqtt_subscribe_method(t_mqtt *x, t_symbol * topic) {
int sres = mosquitto_subscribe(x->mosq, NULL, topic->s_name, 0);
switch (sres) {
case MOSQ_ERR_SUCCESS:
if (x->a_verbose) post("Mosquitto <%s> susbscribed %s", x->a_id->s_name, topic->s_name);
break;
default: