diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 0000000..e69de29 diff --git a/404.html b/404.html new file mode 100644 index 0000000..eece76e --- /dev/null +++ b/404.html @@ -0,0 +1,407 @@ + + + + + + + + + + + + + + + + + + A Short Course on IoT + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ +

404 - Not found

+ +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/assets/ashotcouresoniot.pdf b/assets/ashotcouresoniot.pdf new file mode 100644 index 0000000..eba38fb Binary files /dev/null and b/assets/ashotcouresoniot.pdf differ diff --git a/assets/images/2023-06-12-12-18-54.png b/assets/images/2023-06-12-12-18-54.png new file mode 100644 index 0000000..b29b1f7 Binary files /dev/null and b/assets/images/2023-06-12-12-18-54.png differ diff --git a/assets/images/2023-07-04-16-31-57.png b/assets/images/2023-07-04-16-31-57.png new file mode 100644 index 0000000..6511103 Binary files /dev/null and b/assets/images/2023-07-04-16-31-57.png differ diff --git a/assets/images/2023-07-04-16-33-07.png b/assets/images/2023-07-04-16-33-07.png new file mode 100644 index 0000000..10dd9e2 Binary files /dev/null and b/assets/images/2023-07-04-16-33-07.png differ diff --git a/assets/images/2023-07-17-12-28-04.png b/assets/images/2023-07-17-12-28-04.png new file mode 100644 index 0000000..5d5986b Binary files /dev/null and b/assets/images/2023-07-17-12-28-04.png differ diff --git a/assets/images/2023-07-17-12-28-17.png b/assets/images/2023-07-17-12-28-17.png new file mode 100644 index 0000000..5d5986b Binary files /dev/null and b/assets/images/2023-07-17-12-28-17.png differ diff --git a/assets/images/2023-07-18-13-48-40.png b/assets/images/2023-07-18-13-48-40.png new file mode 100644 index 0000000..028fe6e Binary files /dev/null and b/assets/images/2023-07-18-13-48-40.png differ diff --git a/assets/images/2023-07-18-14-16-39.png b/assets/images/2023-07-18-14-16-39.png new file mode 100644 index 0000000..5ba0176 Binary files /dev/null and b/assets/images/2023-07-18-14-16-39.png differ diff --git a/assets/images/2023-08-22-09-46-59.png b/assets/images/2023-08-22-09-46-59.png new file mode 100644 index 0000000..e447acd Binary files /dev/null and b/assets/images/2023-08-22-09-46-59.png differ diff --git a/assets/images/2023-08-22-09-50-26.png b/assets/images/2023-08-22-09-50-26.png new file mode 100644 index 0000000..12933ce Binary files /dev/null and b/assets/images/2023-08-22-09-50-26.png differ diff --git a/assets/images/2024-01-04-11-26-18.png b/assets/images/2024-01-04-11-26-18.png new file mode 100644 index 0000000..d0cf4c0 Binary files /dev/null and b/assets/images/2024-01-04-11-26-18.png differ diff --git a/assets/images/2024-03-29-12-34-57.png b/assets/images/2024-03-29-12-34-57.png new file mode 100644 index 0000000..a7ff932 Binary files /dev/null and b/assets/images/2024-03-29-12-34-57.png differ diff --git a/assets/images/com.gif b/assets/images/com.gif new file mode 100644 index 0000000..426a7f9 Binary files /dev/null and b/assets/images/com.gif differ diff --git a/assets/images/evolution.gif b/assets/images/evolution.gif new file mode 100644 index 0000000..1b70585 Binary files /dev/null and b/assets/images/evolution.gif differ diff --git a/assets/images/favicon.png b/assets/images/favicon.png new file mode 100644 index 0000000..ec8048d Binary files /dev/null and b/assets/images/favicon.png differ diff --git a/assets/images/logo.png b/assets/images/logo.png new file mode 100644 index 0000000..7dade16 Binary files /dev/null and b/assets/images/logo.png differ diff --git a/assets/images/logo.svg b/assets/images/logo.svg new file mode 100644 index 0000000..b516e5c --- /dev/null +++ b/assets/images/logo.svg @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/images/logo1.svg b/assets/images/logo1.svg new file mode 100644 index 0000000..ab34e65 --- /dev/null +++ b/assets/images/logo1.svg @@ -0,0 +1,122 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/mail.png b/assets/images/mail.png new file mode 100644 index 0000000..c4f82b2 Binary files /dev/null and b/assets/images/mail.png differ diff --git a/assets/images/mail.xcf b/assets/images/mail.xcf new file mode 100644 index 0000000..ccfd7b9 Binary files /dev/null and b/assets/images/mail.xcf differ diff --git a/assets/images/small_stud1.jpg b/assets/images/small_stud1.jpg new file mode 100644 index 0000000..4121836 Binary files /dev/null and b/assets/images/small_stud1.jpg differ diff --git a/assets/images/small_stud2.jpg b/assets/images/small_stud2.jpg new file mode 100644 index 0000000..02876bb Binary files /dev/null and b/assets/images/small_stud2.jpg differ diff --git a/assets/images/with_herman.jpg b/assets/images/with_herman.jpg new file mode 100644 index 0000000..132a146 Binary files /dev/null and b/assets/images/with_herman.jpg differ diff --git a/assets/javascripts/bundle.b4d07000.min.js b/assets/javascripts/bundle.b4d07000.min.js new file mode 100644 index 0000000..3c0bdad --- /dev/null +++ b/assets/javascripts/bundle.b4d07000.min.js @@ -0,0 +1,29 @@ +"use strict";(()=>{var Ci=Object.create;var gr=Object.defineProperty;var Ri=Object.getOwnPropertyDescriptor;var ki=Object.getOwnPropertyNames,Ht=Object.getOwnPropertySymbols,Hi=Object.getPrototypeOf,yr=Object.prototype.hasOwnProperty,nn=Object.prototype.propertyIsEnumerable;var rn=(e,t,r)=>t in e?gr(e,t,{enumerable:!0,configurable:!0,writable:!0,value:r}):e[t]=r,P=(e,t)=>{for(var r in t||(t={}))yr.call(t,r)&&rn(e,r,t[r]);if(Ht)for(var r of Ht(t))nn.call(t,r)&&rn(e,r,t[r]);return e};var on=(e,t)=>{var r={};for(var n in e)yr.call(e,n)&&t.indexOf(n)<0&&(r[n]=e[n]);if(e!=null&&Ht)for(var n of Ht(e))t.indexOf(n)<0&&nn.call(e,n)&&(r[n]=e[n]);return r};var Pt=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports);var Pi=(e,t,r,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of ki(t))!yr.call(e,o)&&o!==r&&gr(e,o,{get:()=>t[o],enumerable:!(n=Ri(t,o))||n.enumerable});return e};var yt=(e,t,r)=>(r=e!=null?Ci(Hi(e)):{},Pi(t||!e||!e.__esModule?gr(r,"default",{value:e,enumerable:!0}):r,e));var sn=Pt((xr,an)=>{(function(e,t){typeof xr=="object"&&typeof an!="undefined"?t():typeof define=="function"&&define.amd?define(t):t()})(xr,function(){"use strict";function e(r){var n=!0,o=!1,i=null,s={text:!0,search:!0,url:!0,tel:!0,email:!0,password:!0,number:!0,date:!0,month:!0,week:!0,time:!0,datetime:!0,"datetime-local":!0};function a(O){return!!(O&&O!==document&&O.nodeName!=="HTML"&&O.nodeName!=="BODY"&&"classList"in O&&"contains"in O.classList)}function f(O){var Qe=O.type,De=O.tagName;return!!(De==="INPUT"&&s[Qe]&&!O.readOnly||De==="TEXTAREA"&&!O.readOnly||O.isContentEditable)}function c(O){O.classList.contains("focus-visible")||(O.classList.add("focus-visible"),O.setAttribute("data-focus-visible-added",""))}function u(O){O.hasAttribute("data-focus-visible-added")&&(O.classList.remove("focus-visible"),O.removeAttribute("data-focus-visible-added"))}function p(O){O.metaKey||O.altKey||O.ctrlKey||(a(r.activeElement)&&c(r.activeElement),n=!0)}function m(O){n=!1}function d(O){a(O.target)&&(n||f(O.target))&&c(O.target)}function h(O){a(O.target)&&(O.target.classList.contains("focus-visible")||O.target.hasAttribute("data-focus-visible-added"))&&(o=!0,window.clearTimeout(i),i=window.setTimeout(function(){o=!1},100),u(O.target))}function v(O){document.visibilityState==="hidden"&&(o&&(n=!0),Y())}function Y(){document.addEventListener("mousemove",N),document.addEventListener("mousedown",N),document.addEventListener("mouseup",N),document.addEventListener("pointermove",N),document.addEventListener("pointerdown",N),document.addEventListener("pointerup",N),document.addEventListener("touchmove",N),document.addEventListener("touchstart",N),document.addEventListener("touchend",N)}function B(){document.removeEventListener("mousemove",N),document.removeEventListener("mousedown",N),document.removeEventListener("mouseup",N),document.removeEventListener("pointermove",N),document.removeEventListener("pointerdown",N),document.removeEventListener("pointerup",N),document.removeEventListener("touchmove",N),document.removeEventListener("touchstart",N),document.removeEventListener("touchend",N)}function N(O){O.target.nodeName&&O.target.nodeName.toLowerCase()==="html"||(n=!1,B())}document.addEventListener("keydown",p,!0),document.addEventListener("mousedown",m,!0),document.addEventListener("pointerdown",m,!0),document.addEventListener("touchstart",m,!0),document.addEventListener("visibilitychange",v,!0),Y(),r.addEventListener("focus",d,!0),r.addEventListener("blur",h,!0),r.nodeType===Node.DOCUMENT_FRAGMENT_NODE&&r.host?r.host.setAttribute("data-js-focus-visible",""):r.nodeType===Node.DOCUMENT_NODE&&(document.documentElement.classList.add("js-focus-visible"),document.documentElement.setAttribute("data-js-focus-visible",""))}if(typeof window!="undefined"&&typeof document!="undefined"){window.applyFocusVisiblePolyfill=e;var t;try{t=new CustomEvent("focus-visible-polyfill-ready")}catch(r){t=document.createEvent("CustomEvent"),t.initCustomEvent("focus-visible-polyfill-ready",!1,!1,{})}window.dispatchEvent(t)}typeof document!="undefined"&&e(document)})});var cn=Pt(Er=>{(function(e){var t=function(){try{return!!Symbol.iterator}catch(c){return!1}},r=t(),n=function(c){var u={next:function(){var p=c.shift();return{done:p===void 0,value:p}}};return r&&(u[Symbol.iterator]=function(){return u}),u},o=function(c){return encodeURIComponent(c).replace(/%20/g,"+")},i=function(c){return decodeURIComponent(String(c).replace(/\+/g," "))},s=function(){var c=function(p){Object.defineProperty(this,"_entries",{writable:!0,value:{}});var m=typeof p;if(m!=="undefined")if(m==="string")p!==""&&this._fromString(p);else if(p instanceof c){var d=this;p.forEach(function(B,N){d.append(N,B)})}else if(p!==null&&m==="object")if(Object.prototype.toString.call(p)==="[object Array]")for(var h=0;hd[0]?1:0}),c._entries&&(c._entries={});for(var p=0;p1?i(d[1]):"")}})})(typeof global!="undefined"?global:typeof window!="undefined"?window:typeof self!="undefined"?self:Er);(function(e){var t=function(){try{var o=new e.URL("b","http://a");return o.pathname="c d",o.href==="http://a/c%20d"&&o.searchParams}catch(i){return!1}},r=function(){var o=e.URL,i=function(f,c){typeof f!="string"&&(f=String(f)),c&&typeof c!="string"&&(c=String(c));var u=document,p;if(c&&(e.location===void 0||c!==e.location.href)){c=c.toLowerCase(),u=document.implementation.createHTMLDocument(""),p=u.createElement("base"),p.href=c,u.head.appendChild(p);try{if(p.href.indexOf(c)!==0)throw new Error(p.href)}catch(O){throw new Error("URL unable to set base "+c+" due to "+O)}}var m=u.createElement("a");m.href=f,p&&(u.body.appendChild(m),m.href=m.href);var d=u.createElement("input");if(d.type="url",d.value=f,m.protocol===":"||!/:/.test(m.href)||!d.checkValidity()&&!c)throw new TypeError("Invalid URL");Object.defineProperty(this,"_anchorElement",{value:m});var h=new e.URLSearchParams(this.search),v=!0,Y=!0,B=this;["append","delete","set"].forEach(function(O){var Qe=h[O];h[O]=function(){Qe.apply(h,arguments),v&&(Y=!1,B.search=h.toString(),Y=!0)}}),Object.defineProperty(this,"searchParams",{value:h,enumerable:!0});var N=void 0;Object.defineProperty(this,"_updateSearchParams",{enumerable:!1,configurable:!1,writable:!1,value:function(){this.search!==N&&(N=this.search,Y&&(v=!1,this.searchParams._fromString(this.search),v=!0))}})},s=i.prototype,a=function(f){Object.defineProperty(s,f,{get:function(){return this._anchorElement[f]},set:function(c){this._anchorElement[f]=c},enumerable:!0})};["hash","host","hostname","port","protocol"].forEach(function(f){a(f)}),Object.defineProperty(s,"search",{get:function(){return this._anchorElement.search},set:function(f){this._anchorElement.search=f,this._updateSearchParams()},enumerable:!0}),Object.defineProperties(s,{toString:{get:function(){var f=this;return function(){return f.href}}},href:{get:function(){return this._anchorElement.href.replace(/\?$/,"")},set:function(f){this._anchorElement.href=f,this._updateSearchParams()},enumerable:!0},pathname:{get:function(){return this._anchorElement.pathname.replace(/(^\/?)/,"/")},set:function(f){this._anchorElement.pathname=f},enumerable:!0},origin:{get:function(){var f={"http:":80,"https:":443,"ftp:":21}[this._anchorElement.protocol],c=this._anchorElement.port!=f&&this._anchorElement.port!=="";return this._anchorElement.protocol+"//"+this._anchorElement.hostname+(c?":"+this._anchorElement.port:"")},enumerable:!0},password:{get:function(){return""},set:function(f){},enumerable:!0},username:{get:function(){return""},set:function(f){},enumerable:!0}}),i.createObjectURL=function(f){return o.createObjectURL.apply(o,arguments)},i.revokeObjectURL=function(f){return o.revokeObjectURL.apply(o,arguments)},e.URL=i};if(t()||r(),e.location!==void 0&&!("origin"in e.location)){var n=function(){return e.location.protocol+"//"+e.location.hostname+(e.location.port?":"+e.location.port:"")};try{Object.defineProperty(e.location,"origin",{get:n,enumerable:!0})}catch(o){setInterval(function(){e.location.origin=n()},100)}}})(typeof global!="undefined"?global:typeof window!="undefined"?window:typeof self!="undefined"?self:Er)});var qr=Pt((Mt,Nr)=>{/*! + * clipboard.js v2.0.11 + * https://clipboardjs.com/ + * + * Licensed MIT © Zeno Rocha + */(function(t,r){typeof Mt=="object"&&typeof Nr=="object"?Nr.exports=r():typeof define=="function"&&define.amd?define([],r):typeof Mt=="object"?Mt.ClipboardJS=r():t.ClipboardJS=r()})(Mt,function(){return function(){var e={686:function(n,o,i){"use strict";i.d(o,{default:function(){return Ai}});var s=i(279),a=i.n(s),f=i(370),c=i.n(f),u=i(817),p=i.n(u);function m(j){try{return document.execCommand(j)}catch(T){return!1}}var d=function(T){var E=p()(T);return m("cut"),E},h=d;function v(j){var T=document.documentElement.getAttribute("dir")==="rtl",E=document.createElement("textarea");E.style.fontSize="12pt",E.style.border="0",E.style.padding="0",E.style.margin="0",E.style.position="absolute",E.style[T?"right":"left"]="-9999px";var H=window.pageYOffset||document.documentElement.scrollTop;return E.style.top="".concat(H,"px"),E.setAttribute("readonly",""),E.value=j,E}var Y=function(T,E){var H=v(T);E.container.appendChild(H);var I=p()(H);return m("copy"),H.remove(),I},B=function(T){var E=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{container:document.body},H="";return typeof T=="string"?H=Y(T,E):T instanceof HTMLInputElement&&!["text","search","url","tel","password"].includes(T==null?void 0:T.type)?H=Y(T.value,E):(H=p()(T),m("copy")),H},N=B;function O(j){"@babel/helpers - typeof";return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?O=function(E){return typeof E}:O=function(E){return E&&typeof Symbol=="function"&&E.constructor===Symbol&&E!==Symbol.prototype?"symbol":typeof E},O(j)}var Qe=function(){var T=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{},E=T.action,H=E===void 0?"copy":E,I=T.container,q=T.target,Me=T.text;if(H!=="copy"&&H!=="cut")throw new Error('Invalid "action" value, use either "copy" or "cut"');if(q!==void 0)if(q&&O(q)==="object"&&q.nodeType===1){if(H==="copy"&&q.hasAttribute("disabled"))throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');if(H==="cut"&&(q.hasAttribute("readonly")||q.hasAttribute("disabled")))throw new Error(`Invalid "target" attribute. You can't cut text from elements with "readonly" or "disabled" attributes`)}else throw new Error('Invalid "target" value, use a valid Element');if(Me)return N(Me,{container:I});if(q)return H==="cut"?h(q):N(q,{container:I})},De=Qe;function $e(j){"@babel/helpers - typeof";return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?$e=function(E){return typeof E}:$e=function(E){return E&&typeof Symbol=="function"&&E.constructor===Symbol&&E!==Symbol.prototype?"symbol":typeof E},$e(j)}function Ei(j,T){if(!(j instanceof T))throw new TypeError("Cannot call a class as a function")}function tn(j,T){for(var E=0;E0&&arguments[0]!==void 0?arguments[0]:{};this.action=typeof I.action=="function"?I.action:this.defaultAction,this.target=typeof I.target=="function"?I.target:this.defaultTarget,this.text=typeof I.text=="function"?I.text:this.defaultText,this.container=$e(I.container)==="object"?I.container:document.body}},{key:"listenClick",value:function(I){var q=this;this.listener=c()(I,"click",function(Me){return q.onClick(Me)})}},{key:"onClick",value:function(I){var q=I.delegateTarget||I.currentTarget,Me=this.action(q)||"copy",kt=De({action:Me,container:this.container,target:this.target(q),text:this.text(q)});this.emit(kt?"success":"error",{action:Me,text:kt,trigger:q,clearSelection:function(){q&&q.focus(),window.getSelection().removeAllRanges()}})}},{key:"defaultAction",value:function(I){return vr("action",I)}},{key:"defaultTarget",value:function(I){var q=vr("target",I);if(q)return document.querySelector(q)}},{key:"defaultText",value:function(I){return vr("text",I)}},{key:"destroy",value:function(){this.listener.destroy()}}],[{key:"copy",value:function(I){var q=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{container:document.body};return N(I,q)}},{key:"cut",value:function(I){return h(I)}},{key:"isSupported",value:function(){var I=arguments.length>0&&arguments[0]!==void 0?arguments[0]:["copy","cut"],q=typeof I=="string"?[I]:I,Me=!!document.queryCommandSupported;return q.forEach(function(kt){Me=Me&&!!document.queryCommandSupported(kt)}),Me}}]),E}(a()),Ai=Li},828:function(n){var o=9;if(typeof Element!="undefined"&&!Element.prototype.matches){var i=Element.prototype;i.matches=i.matchesSelector||i.mozMatchesSelector||i.msMatchesSelector||i.oMatchesSelector||i.webkitMatchesSelector}function s(a,f){for(;a&&a.nodeType!==o;){if(typeof a.matches=="function"&&a.matches(f))return a;a=a.parentNode}}n.exports=s},438:function(n,o,i){var s=i(828);function a(u,p,m,d,h){var v=c.apply(this,arguments);return u.addEventListener(m,v,h),{destroy:function(){u.removeEventListener(m,v,h)}}}function f(u,p,m,d,h){return typeof u.addEventListener=="function"?a.apply(null,arguments):typeof m=="function"?a.bind(null,document).apply(null,arguments):(typeof u=="string"&&(u=document.querySelectorAll(u)),Array.prototype.map.call(u,function(v){return a(v,p,m,d,h)}))}function c(u,p,m,d){return function(h){h.delegateTarget=s(h.target,p),h.delegateTarget&&d.call(u,h)}}n.exports=f},879:function(n,o){o.node=function(i){return i!==void 0&&i instanceof HTMLElement&&i.nodeType===1},o.nodeList=function(i){var s=Object.prototype.toString.call(i);return i!==void 0&&(s==="[object NodeList]"||s==="[object HTMLCollection]")&&"length"in i&&(i.length===0||o.node(i[0]))},o.string=function(i){return typeof i=="string"||i instanceof String},o.fn=function(i){var s=Object.prototype.toString.call(i);return s==="[object Function]"}},370:function(n,o,i){var s=i(879),a=i(438);function f(m,d,h){if(!m&&!d&&!h)throw new Error("Missing required arguments");if(!s.string(d))throw new TypeError("Second argument must be a String");if(!s.fn(h))throw new TypeError("Third argument must be a Function");if(s.node(m))return c(m,d,h);if(s.nodeList(m))return u(m,d,h);if(s.string(m))return p(m,d,h);throw new TypeError("First argument must be a String, HTMLElement, HTMLCollection, or NodeList")}function c(m,d,h){return m.addEventListener(d,h),{destroy:function(){m.removeEventListener(d,h)}}}function u(m,d,h){return Array.prototype.forEach.call(m,function(v){v.addEventListener(d,h)}),{destroy:function(){Array.prototype.forEach.call(m,function(v){v.removeEventListener(d,h)})}}}function p(m,d,h){return a(document.body,m,d,h)}n.exports=f},817:function(n){function o(i){var s;if(i.nodeName==="SELECT")i.focus(),s=i.value;else if(i.nodeName==="INPUT"||i.nodeName==="TEXTAREA"){var a=i.hasAttribute("readonly");a||i.setAttribute("readonly",""),i.select(),i.setSelectionRange(0,i.value.length),a||i.removeAttribute("readonly"),s=i.value}else{i.hasAttribute("contenteditable")&&i.focus();var f=window.getSelection(),c=document.createRange();c.selectNodeContents(i),f.removeAllRanges(),f.addRange(c),s=f.toString()}return s}n.exports=o},279:function(n){function o(){}o.prototype={on:function(i,s,a){var f=this.e||(this.e={});return(f[i]||(f[i]=[])).push({fn:s,ctx:a}),this},once:function(i,s,a){var f=this;function c(){f.off(i,c),s.apply(a,arguments)}return c._=s,this.on(i,c,a)},emit:function(i){var s=[].slice.call(arguments,1),a=((this.e||(this.e={}))[i]||[]).slice(),f=0,c=a.length;for(f;f{"use strict";/*! + * escape-html + * Copyright(c) 2012-2013 TJ Holowaychuk + * Copyright(c) 2015 Andreas Lubbe + * Copyright(c) 2015 Tiancheng "Timothy" Gu + * MIT Licensed + */var rs=/["'&<>]/;Yo.exports=ns;function ns(e){var t=""+e,r=rs.exec(t);if(!r)return t;var n,o="",i=0,s=0;for(i=r.index;i0&&i[i.length-1])&&(c[0]===6||c[0]===2)){r=0;continue}if(c[0]===3&&(!i||c[1]>i[0]&&c[1]=e.length&&(e=void 0),{value:e&&e[n++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")}function W(e,t){var r=typeof Symbol=="function"&&e[Symbol.iterator];if(!r)return e;var n=r.call(e),o,i=[],s;try{for(;(t===void 0||t-- >0)&&!(o=n.next()).done;)i.push(o.value)}catch(a){s={error:a}}finally{try{o&&!o.done&&(r=n.return)&&r.call(n)}finally{if(s)throw s.error}}return i}function D(e,t,r){if(r||arguments.length===2)for(var n=0,o=t.length,i;n1||a(m,d)})})}function a(m,d){try{f(n[m](d))}catch(h){p(i[0][3],h)}}function f(m){m.value instanceof et?Promise.resolve(m.value.v).then(c,u):p(i[0][2],m)}function c(m){a("next",m)}function u(m){a("throw",m)}function p(m,d){m(d),i.shift(),i.length&&a(i[0][0],i[0][1])}}function pn(e){if(!Symbol.asyncIterator)throw new TypeError("Symbol.asyncIterator is not defined.");var t=e[Symbol.asyncIterator],r;return t?t.call(e):(e=typeof Ee=="function"?Ee(e):e[Symbol.iterator](),r={},n("next"),n("throw"),n("return"),r[Symbol.asyncIterator]=function(){return this},r);function n(i){r[i]=e[i]&&function(s){return new Promise(function(a,f){s=e[i](s),o(a,f,s.done,s.value)})}}function o(i,s,a,f){Promise.resolve(f).then(function(c){i({value:c,done:a})},s)}}function C(e){return typeof e=="function"}function at(e){var t=function(n){Error.call(n),n.stack=new Error().stack},r=e(t);return r.prototype=Object.create(Error.prototype),r.prototype.constructor=r,r}var It=at(function(e){return function(r){e(this),this.message=r?r.length+` errors occurred during unsubscription: +`+r.map(function(n,o){return o+1+") "+n.toString()}).join(` + `):"",this.name="UnsubscriptionError",this.errors=r}});function Ve(e,t){if(e){var r=e.indexOf(t);0<=r&&e.splice(r,1)}}var Ie=function(){function e(t){this.initialTeardown=t,this.closed=!1,this._parentage=null,this._finalizers=null}return e.prototype.unsubscribe=function(){var t,r,n,o,i;if(!this.closed){this.closed=!0;var s=this._parentage;if(s)if(this._parentage=null,Array.isArray(s))try{for(var a=Ee(s),f=a.next();!f.done;f=a.next()){var c=f.value;c.remove(this)}}catch(v){t={error:v}}finally{try{f&&!f.done&&(r=a.return)&&r.call(a)}finally{if(t)throw t.error}}else s.remove(this);var u=this.initialTeardown;if(C(u))try{u()}catch(v){i=v instanceof It?v.errors:[v]}var p=this._finalizers;if(p){this._finalizers=null;try{for(var m=Ee(p),d=m.next();!d.done;d=m.next()){var h=d.value;try{ln(h)}catch(v){i=i!=null?i:[],v instanceof It?i=D(D([],W(i)),W(v.errors)):i.push(v)}}}catch(v){n={error:v}}finally{try{d&&!d.done&&(o=m.return)&&o.call(m)}finally{if(n)throw n.error}}}if(i)throw new It(i)}},e.prototype.add=function(t){var r;if(t&&t!==this)if(this.closed)ln(t);else{if(t instanceof e){if(t.closed||t._hasParent(this))return;t._addParent(this)}(this._finalizers=(r=this._finalizers)!==null&&r!==void 0?r:[]).push(t)}},e.prototype._hasParent=function(t){var r=this._parentage;return r===t||Array.isArray(r)&&r.includes(t)},e.prototype._addParent=function(t){var r=this._parentage;this._parentage=Array.isArray(r)?(r.push(t),r):r?[r,t]:t},e.prototype._removeParent=function(t){var r=this._parentage;r===t?this._parentage=null:Array.isArray(r)&&Ve(r,t)},e.prototype.remove=function(t){var r=this._finalizers;r&&Ve(r,t),t instanceof e&&t._removeParent(this)},e.EMPTY=function(){var t=new e;return t.closed=!0,t}(),e}();var Sr=Ie.EMPTY;function jt(e){return e instanceof Ie||e&&"closed"in e&&C(e.remove)&&C(e.add)&&C(e.unsubscribe)}function ln(e){C(e)?e():e.unsubscribe()}var Le={onUnhandledError:null,onStoppedNotification:null,Promise:void 0,useDeprecatedSynchronousErrorHandling:!1,useDeprecatedNextContext:!1};var st={setTimeout:function(e,t){for(var r=[],n=2;n0},enumerable:!1,configurable:!0}),t.prototype._trySubscribe=function(r){return this._throwIfClosed(),e.prototype._trySubscribe.call(this,r)},t.prototype._subscribe=function(r){return this._throwIfClosed(),this._checkFinalizedStatuses(r),this._innerSubscribe(r)},t.prototype._innerSubscribe=function(r){var n=this,o=this,i=o.hasError,s=o.isStopped,a=o.observers;return i||s?Sr:(this.currentObservers=null,a.push(r),new Ie(function(){n.currentObservers=null,Ve(a,r)}))},t.prototype._checkFinalizedStatuses=function(r){var n=this,o=n.hasError,i=n.thrownError,s=n.isStopped;o?r.error(i):s&&r.complete()},t.prototype.asObservable=function(){var r=new F;return r.source=this,r},t.create=function(r,n){return new xn(r,n)},t}(F);var xn=function(e){ie(t,e);function t(r,n){var o=e.call(this)||this;return o.destination=r,o.source=n,o}return t.prototype.next=function(r){var n,o;(o=(n=this.destination)===null||n===void 0?void 0:n.next)===null||o===void 0||o.call(n,r)},t.prototype.error=function(r){var n,o;(o=(n=this.destination)===null||n===void 0?void 0:n.error)===null||o===void 0||o.call(n,r)},t.prototype.complete=function(){var r,n;(n=(r=this.destination)===null||r===void 0?void 0:r.complete)===null||n===void 0||n.call(r)},t.prototype._subscribe=function(r){var n,o;return(o=(n=this.source)===null||n===void 0?void 0:n.subscribe(r))!==null&&o!==void 0?o:Sr},t}(x);var Et={now:function(){return(Et.delegate||Date).now()},delegate:void 0};var wt=function(e){ie(t,e);function t(r,n,o){r===void 0&&(r=1/0),n===void 0&&(n=1/0),o===void 0&&(o=Et);var i=e.call(this)||this;return i._bufferSize=r,i._windowTime=n,i._timestampProvider=o,i._buffer=[],i._infiniteTimeWindow=!0,i._infiniteTimeWindow=n===1/0,i._bufferSize=Math.max(1,r),i._windowTime=Math.max(1,n),i}return t.prototype.next=function(r){var n=this,o=n.isStopped,i=n._buffer,s=n._infiniteTimeWindow,a=n._timestampProvider,f=n._windowTime;o||(i.push(r),!s&&i.push(a.now()+f)),this._trimBuffer(),e.prototype.next.call(this,r)},t.prototype._subscribe=function(r){this._throwIfClosed(),this._trimBuffer();for(var n=this._innerSubscribe(r),o=this,i=o._infiniteTimeWindow,s=o._buffer,a=s.slice(),f=0;f0?e.prototype.requestAsyncId.call(this,r,n,o):(r.actions.push(this),r._scheduled||(r._scheduled=ut.requestAnimationFrame(function(){return r.flush(void 0)})))},t.prototype.recycleAsyncId=function(r,n,o){var i;if(o===void 0&&(o=0),o!=null?o>0:this.delay>0)return e.prototype.recycleAsyncId.call(this,r,n,o);var s=r.actions;n!=null&&((i=s[s.length-1])===null||i===void 0?void 0:i.id)!==n&&(ut.cancelAnimationFrame(n),r._scheduled=void 0)},t}(Wt);var Sn=function(e){ie(t,e);function t(){return e!==null&&e.apply(this,arguments)||this}return t.prototype.flush=function(r){this._active=!0;var n=this._scheduled;this._scheduled=void 0;var o=this.actions,i;r=r||o.shift();do if(i=r.execute(r.state,r.delay))break;while((r=o[0])&&r.id===n&&o.shift());if(this._active=!1,i){for(;(r=o[0])&&r.id===n&&o.shift();)r.unsubscribe();throw i}},t}(Dt);var Oe=new Sn(wn);var _=new F(function(e){return e.complete()});function Vt(e){return e&&C(e.schedule)}function Cr(e){return e[e.length-1]}function Ye(e){return C(Cr(e))?e.pop():void 0}function Te(e){return Vt(Cr(e))?e.pop():void 0}function zt(e,t){return typeof Cr(e)=="number"?e.pop():t}var pt=function(e){return e&&typeof e.length=="number"&&typeof e!="function"};function Nt(e){return C(e==null?void 0:e.then)}function qt(e){return C(e[ft])}function Kt(e){return Symbol.asyncIterator&&C(e==null?void 0:e[Symbol.asyncIterator])}function Qt(e){return new TypeError("You provided "+(e!==null&&typeof e=="object"?"an invalid object":"'"+e+"'")+" where a stream was expected. You can provide an Observable, Promise, ReadableStream, Array, AsyncIterable, or Iterable.")}function zi(){return typeof Symbol!="function"||!Symbol.iterator?"@@iterator":Symbol.iterator}var Yt=zi();function Gt(e){return C(e==null?void 0:e[Yt])}function Bt(e){return un(this,arguments,function(){var r,n,o,i;return $t(this,function(s){switch(s.label){case 0:r=e.getReader(),s.label=1;case 1:s.trys.push([1,,9,10]),s.label=2;case 2:return[4,et(r.read())];case 3:return n=s.sent(),o=n.value,i=n.done,i?[4,et(void 0)]:[3,5];case 4:return[2,s.sent()];case 5:return[4,et(o)];case 6:return[4,s.sent()];case 7:return s.sent(),[3,2];case 8:return[3,10];case 9:return r.releaseLock(),[7];case 10:return[2]}})})}function Jt(e){return C(e==null?void 0:e.getReader)}function U(e){if(e instanceof F)return e;if(e!=null){if(qt(e))return Ni(e);if(pt(e))return qi(e);if(Nt(e))return Ki(e);if(Kt(e))return On(e);if(Gt(e))return Qi(e);if(Jt(e))return Yi(e)}throw Qt(e)}function Ni(e){return new F(function(t){var r=e[ft]();if(C(r.subscribe))return r.subscribe(t);throw new TypeError("Provided object does not correctly implement Symbol.observable")})}function qi(e){return new F(function(t){for(var r=0;r=2;return function(n){return n.pipe(e?A(function(o,i){return e(o,i,n)}):de,ge(1),r?He(t):Dn(function(){return new Zt}))}}function Vn(){for(var e=[],t=0;t=2,!0))}function pe(e){e===void 0&&(e={});var t=e.connector,r=t===void 0?function(){return new x}:t,n=e.resetOnError,o=n===void 0?!0:n,i=e.resetOnComplete,s=i===void 0?!0:i,a=e.resetOnRefCountZero,f=a===void 0?!0:a;return function(c){var u,p,m,d=0,h=!1,v=!1,Y=function(){p==null||p.unsubscribe(),p=void 0},B=function(){Y(),u=m=void 0,h=v=!1},N=function(){var O=u;B(),O==null||O.unsubscribe()};return y(function(O,Qe){d++,!v&&!h&&Y();var De=m=m!=null?m:r();Qe.add(function(){d--,d===0&&!v&&!h&&(p=$r(N,f))}),De.subscribe(Qe),!u&&d>0&&(u=new rt({next:function($e){return De.next($e)},error:function($e){v=!0,Y(),p=$r(B,o,$e),De.error($e)},complete:function(){h=!0,Y(),p=$r(B,s),De.complete()}}),U(O).subscribe(u))})(c)}}function $r(e,t){for(var r=[],n=2;ne.next(document)),e}function K(e,t=document){return Array.from(t.querySelectorAll(e))}function z(e,t=document){let r=ce(e,t);if(typeof r=="undefined")throw new ReferenceError(`Missing element: expected "${e}" to be present`);return r}function ce(e,t=document){return t.querySelector(e)||void 0}function _e(){return document.activeElement instanceof HTMLElement&&document.activeElement||void 0}function tr(e){return L(b(document.body,"focusin"),b(document.body,"focusout")).pipe(ke(1),l(()=>{let t=_e();return typeof t!="undefined"?e.contains(t):!1}),V(e===_e()),J())}function Xe(e){return{x:e.offsetLeft,y:e.offsetTop}}function Kn(e){return L(b(window,"load"),b(window,"resize")).pipe(Ce(0,Oe),l(()=>Xe(e)),V(Xe(e)))}function rr(e){return{x:e.scrollLeft,y:e.scrollTop}}function dt(e){return L(b(e,"scroll"),b(window,"resize")).pipe(Ce(0,Oe),l(()=>rr(e)),V(rr(e)))}var Yn=function(){if(typeof Map!="undefined")return Map;function e(t,r){var n=-1;return t.some(function(o,i){return o[0]===r?(n=i,!0):!1}),n}return function(){function t(){this.__entries__=[]}return Object.defineProperty(t.prototype,"size",{get:function(){return this.__entries__.length},enumerable:!0,configurable:!0}),t.prototype.get=function(r){var n=e(this.__entries__,r),o=this.__entries__[n];return o&&o[1]},t.prototype.set=function(r,n){var o=e(this.__entries__,r);~o?this.__entries__[o][1]=n:this.__entries__.push([r,n])},t.prototype.delete=function(r){var n=this.__entries__,o=e(n,r);~o&&n.splice(o,1)},t.prototype.has=function(r){return!!~e(this.__entries__,r)},t.prototype.clear=function(){this.__entries__.splice(0)},t.prototype.forEach=function(r,n){n===void 0&&(n=null);for(var o=0,i=this.__entries__;o0},e.prototype.connect_=function(){!Wr||this.connected_||(document.addEventListener("transitionend",this.onTransitionEnd_),window.addEventListener("resize",this.refresh),va?(this.mutationsObserver_=new MutationObserver(this.refresh),this.mutationsObserver_.observe(document,{attributes:!0,childList:!0,characterData:!0,subtree:!0})):(document.addEventListener("DOMSubtreeModified",this.refresh),this.mutationEventsAdded_=!0),this.connected_=!0)},e.prototype.disconnect_=function(){!Wr||!this.connected_||(document.removeEventListener("transitionend",this.onTransitionEnd_),window.removeEventListener("resize",this.refresh),this.mutationsObserver_&&this.mutationsObserver_.disconnect(),this.mutationEventsAdded_&&document.removeEventListener("DOMSubtreeModified",this.refresh),this.mutationsObserver_=null,this.mutationEventsAdded_=!1,this.connected_=!1)},e.prototype.onTransitionEnd_=function(t){var r=t.propertyName,n=r===void 0?"":r,o=ba.some(function(i){return!!~n.indexOf(i)});o&&this.refresh()},e.getInstance=function(){return this.instance_||(this.instance_=new e),this.instance_},e.instance_=null,e}(),Gn=function(e,t){for(var r=0,n=Object.keys(t);r0},e}(),Jn=typeof WeakMap!="undefined"?new WeakMap:new Yn,Xn=function(){function e(t){if(!(this instanceof e))throw new TypeError("Cannot call a class as a function.");if(!arguments.length)throw new TypeError("1 argument required, but only 0 present.");var r=ga.getInstance(),n=new La(t,r,this);Jn.set(this,n)}return e}();["observe","unobserve","disconnect"].forEach(function(e){Xn.prototype[e]=function(){var t;return(t=Jn.get(this))[e].apply(t,arguments)}});var Aa=function(){return typeof nr.ResizeObserver!="undefined"?nr.ResizeObserver:Xn}(),Zn=Aa;var eo=new x,Ca=$(()=>k(new Zn(e=>{for(let t of e)eo.next(t)}))).pipe(g(e=>L(ze,k(e)).pipe(R(()=>e.disconnect()))),X(1));function he(e){return{width:e.offsetWidth,height:e.offsetHeight}}function ye(e){return Ca.pipe(S(t=>t.observe(e)),g(t=>eo.pipe(A(({target:r})=>r===e),R(()=>t.unobserve(e)),l(()=>he(e)))),V(he(e)))}function bt(e){return{width:e.scrollWidth,height:e.scrollHeight}}function ar(e){let t=e.parentElement;for(;t&&(e.scrollWidth<=t.scrollWidth&&e.scrollHeight<=t.scrollHeight);)t=(e=t).parentElement;return t?e:void 0}var to=new x,Ra=$(()=>k(new IntersectionObserver(e=>{for(let t of e)to.next(t)},{threshold:0}))).pipe(g(e=>L(ze,k(e)).pipe(R(()=>e.disconnect()))),X(1));function sr(e){return Ra.pipe(S(t=>t.observe(e)),g(t=>to.pipe(A(({target:r})=>r===e),R(()=>t.unobserve(e)),l(({isIntersecting:r})=>r))))}function ro(e,t=16){return dt(e).pipe(l(({y:r})=>{let n=he(e),o=bt(e);return r>=o.height-n.height-t}),J())}var cr={drawer:z("[data-md-toggle=drawer]"),search:z("[data-md-toggle=search]")};function no(e){return cr[e].checked}function Ke(e,t){cr[e].checked!==t&&cr[e].click()}function Ue(e){let t=cr[e];return b(t,"change").pipe(l(()=>t.checked),V(t.checked))}function ka(e,t){switch(e.constructor){case HTMLInputElement:return e.type==="radio"?/^Arrow/.test(t):!0;case HTMLSelectElement:case HTMLTextAreaElement:return!0;default:return e.isContentEditable}}function Ha(){return L(b(window,"compositionstart").pipe(l(()=>!0)),b(window,"compositionend").pipe(l(()=>!1))).pipe(V(!1))}function oo(){let e=b(window,"keydown").pipe(A(t=>!(t.metaKey||t.ctrlKey)),l(t=>({mode:no("search")?"search":"global",type:t.key,claim(){t.preventDefault(),t.stopPropagation()}})),A(({mode:t,type:r})=>{if(t==="global"){let n=_e();if(typeof n!="undefined")return!ka(n,r)}return!0}),pe());return Ha().pipe(g(t=>t?_:e))}function le(){return new URL(location.href)}function ot(e){location.href=e.href}function io(){return new x}function ao(e,t){if(typeof t=="string"||typeof t=="number")e.innerHTML+=t.toString();else if(t instanceof Node)e.appendChild(t);else if(Array.isArray(t))for(let r of t)ao(e,r)}function M(e,t,...r){let n=document.createElement(e);if(t)for(let o of Object.keys(t))typeof t[o]!="undefined"&&(typeof t[o]!="boolean"?n.setAttribute(o,t[o]):n.setAttribute(o,""));for(let o of r)ao(n,o);return n}function fr(e){if(e>999){let t=+((e-950)%1e3>99);return`${((e+1e-6)/1e3).toFixed(t)}k`}else return e.toString()}function so(){return location.hash.substring(1)}function Dr(e){let t=M("a",{href:e});t.addEventListener("click",r=>r.stopPropagation()),t.click()}function Pa(e){return L(b(window,"hashchange"),e).pipe(l(so),V(so()),A(t=>t.length>0),X(1))}function co(e){return Pa(e).pipe(l(t=>ce(`[id="${t}"]`)),A(t=>typeof t!="undefined"))}function Vr(e){let t=matchMedia(e);return er(r=>t.addListener(()=>r(t.matches))).pipe(V(t.matches))}function fo(){let e=matchMedia("print");return L(b(window,"beforeprint").pipe(l(()=>!0)),b(window,"afterprint").pipe(l(()=>!1))).pipe(V(e.matches))}function zr(e,t){return e.pipe(g(r=>r?t():_))}function ur(e,t={credentials:"same-origin"}){return ue(fetch(`${e}`,t)).pipe(fe(()=>_),g(r=>r.status!==200?Ot(()=>new Error(r.statusText)):k(r)))}function We(e,t){return ur(e,t).pipe(g(r=>r.json()),X(1))}function uo(e,t){let r=new DOMParser;return ur(e,t).pipe(g(n=>n.text()),l(n=>r.parseFromString(n,"text/xml")),X(1))}function pr(e){let t=M("script",{src:e});return $(()=>(document.head.appendChild(t),L(b(t,"load"),b(t,"error").pipe(g(()=>Ot(()=>new ReferenceError(`Invalid script: ${e}`))))).pipe(l(()=>{}),R(()=>document.head.removeChild(t)),ge(1))))}function po(){return{x:Math.max(0,scrollX),y:Math.max(0,scrollY)}}function lo(){return L(b(window,"scroll",{passive:!0}),b(window,"resize",{passive:!0})).pipe(l(po),V(po()))}function mo(){return{width:innerWidth,height:innerHeight}}function ho(){return b(window,"resize",{passive:!0}).pipe(l(mo),V(mo()))}function bo(){return G([lo(),ho()]).pipe(l(([e,t])=>({offset:e,size:t})),X(1))}function lr(e,{viewport$:t,header$:r}){let n=t.pipe(ee("size")),o=G([n,r]).pipe(l(()=>Xe(e)));return G([r,t,o]).pipe(l(([{height:i},{offset:s,size:a},{x:f,y:c}])=>({offset:{x:s.x-f,y:s.y-c+i},size:a})))}(()=>{function e(n,o){parent.postMessage(n,o||"*")}function t(...n){return n.reduce((o,i)=>o.then(()=>new Promise(s=>{let a=document.createElement("script");a.src=i,a.onload=s,document.body.appendChild(a)})),Promise.resolve())}var r=class extends EventTarget{constructor(n){super(),this.url=n,this.m=i=>{i.source===this.w&&(this.dispatchEvent(new MessageEvent("message",{data:i.data})),this.onmessage&&this.onmessage(i))},this.e=(i,s,a,f,c)=>{if(s===`${this.url}`){let u=new ErrorEvent("error",{message:i,filename:s,lineno:a,colno:f,error:c});this.dispatchEvent(u),this.onerror&&this.onerror(u)}};let o=document.createElement("iframe");o.hidden=!0,document.body.appendChild(this.iframe=o),this.w.document.open(),this.w.document.write(` + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + +

GOAL

+

This short course on the Internet of Things is designed to give a first understanding of the main issues in developing IoT solutions. We do believe the most efficient way to learn this subject is experimenting, this is way the course encourage attendants to hands-on. No specific background is needed, even if a basic knowledge of electronics and computer science might help.

+

SESSION 1 (3 hours)

+

GOAL: Quick intro to the Internet of Things.

+
    +
  • Introduction to the IoT
  • +
  • IoT and Big Data: we can quantitatively measure phenomena that up to yesterday we were only able to guess
  • +
  • A network of Resource Constrained Devices: if computational power, memory, energy, bandwidth and costs are not constrained, it is likely Internet, not Internet of Things.
  • +
+

SESSION 2 (3 hours)

+

GOAL: Small experiment (a single sensor). Prove you can integrate sensors suitable for you monitoring purpose. Focus on effectiveness, namely doing the right things, i.e., the Maker approach.

+
    +
  • The reference hardware: ESP32
  • +
  • Hands on with the Arduino Ide and Wokwi
  • +
  • A simple integration of sensor/actuator
  • +
  • Communicating over WiFi
  • +
+

SESSION 3 (3 hours)

+

GOAL: Small experiment (a single sensor). Focus on efficiency, namely doing the things right, i.e., the Engineers approach. Use metrics to measure to what extent your solution satisfies the user requirements.

+
    +
  • The need of an Operating System
  • +
  • Hands on FREERTOS
  • +
  • A simple integration of sensor/actuator
  • +
  • Communicating over WiFi
  • +
+

SESSION 4 (3 hours)

+

GOAL: Scale-up in a realistic environment.

+ +

Tools and Materials

+

I like vscode with the Remote-SSH extension to interact with the virtual machine for FreeRTOS development. However, during the class there might be many issues due to different version of the OS and vscode. To further simplify the process I use micro a modern and intuitive terminal-based text editor. Give it a try, it is very convenient. I also suggest to install the file manager plugin. The only annoying issue I'm currently facing with micro is that copy/past does not work smoothly over SSH, you have to use ctr + shift + V

+

Material

+

The list of material for each group

+ + + +

Editions

+

First edition of the course 19th (Session 1 and 2) and 21st (Session 3 and 4) of July 2023 in the University of Stellenbosch (South Africa) during an Erasmus+ staff mobility for teaching visit. About 40 participants selected among more the 100 candidates. Keep in touch on the discord server. A new version soon.

+

+ + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/iot-lab/index.html b/iot-lab/index.html new file mode 100644 index 0000000..ebe7929 --- /dev/null +++ b/iot-lab/index.html @@ -0,0 +1,485 @@ + + + + + + + + + + + + + + + + + + + + + + IoT-LAB - A Short Course on IoT + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + +

IOT-LAB

+

Run a Lorawan experiment

+
ssh vitalett@saclay.iot-lab.info 
+
+
git clone https://github.com/FreeRTOS/FreeRTOS-LTS.git
+cd FreeRTOS-LTS/FreeRTOS
+git clone https://github.com/Lora-net/LoRaMac-node.git
+cd cd LoRaMac-node/
+
+
nano  src/boards/B-L072Z-LRWAN1/board.c
+
+// change the baudrate to 115200 to be compatible with iot-lab serial 
+
+UartConfig( &Uart2, RX_TX, 115200, UART_8_BIT, UART_1_STOP_BIT, NO_PARITY, NO_FLOW_CTRL );
+
+
nano src/peripherals/soft-se/se-identity.h
+
+// update credentials to connect to TTN
+#define STATIC_DEVICE_EUI       1
+#define LORAWAN_DEVICE_EUI      LORAWAN_DEVICE_EUI_ON_TTN
+#define LORAWAN_JOIN_EUI        LORAWAN_APP_EUI_ON_TTN
+#define STATIC_DEVICE_ADDRESS   1
+#define LORAWAN_DEVICE_ADDRESS  DEVICE_ADDRESS_TTN
+
+.KeyID = NWK_KEY,           
+.KeyValue = LORAWAN_APP_KEY_ON_TTN
+
+

+
mkdir build
+cd build
+
+cmake -DCMAKE_BUILD_TYPE=Release         -DTOOLCHAIN_PREFIX="/opt/gcc-arm-none-eabi-4_9-2015q1/"      -DCMAKE_TOOLCHAIN_FILE="../cmake/toolchain-arm-none-eabi.cmake"         -DAPPLICATION="LoRaMac"         -DSUB_PROJECT="periodic-uplink-lpp"         -DCLASSB_ENABLED="ON"         -DACTIVE_REGION="LORAMAC_REGION_EU868"         -DREGION_EU868="ON"         -DREGION_US915="OFF"         -DREGION_CN779="OFF"         -DREGION_EU433="OFF"         -DREGION_AU915="OFF"         -DREGION_AS923="OFF"         -DREGION_CN470="OFF"         -DREGION_KR920="OFF"         -DREGION_IN865="OFF"         -DREGION_RU864="OFF"         -DBOARD="B-L072Z-LRWAN1"       -DSECURE_ELEMENT="SOFT_SE"         -DSECURE_ELEMENT_PRE_PROVISIONED="OFF"         -DUSE_RADIO_DEBUG="ON" ..
+
+make
+
+
cd src/apps/LoRaMac/
+
+iotlab-experiment submit -n rtos_ttn -d 60 -l 1,archi=st-lrwan1:sx1276+site=saclay
+{
+    "id": 394774
+}
+
+iotlab-experiment get -i 394774 -s
+
+iotlab-experiment get -i 394774 -r
+
+iotlab-node --flash LoRaMac-periodic-uplink-lpp -l saclay,st-lrwan1,10
+
+nc st-lrwan1-10 20000
+
+ + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/search/search_index.json b/search/search_index.json new file mode 100644 index 0000000..423d51a --- /dev/null +++ b/search/search_index.json @@ -0,0 +1 @@ +{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"GOAL","text":"

This short course on the Internet of Things is designed to give a first understanding of the main issues in developing IoT solutions. We do believe the most efficient way to learn this subject is experimenting, this is way the course encourage attendants to hands-on. No specific background is needed, even if a basic knowledge of electronics and computer science might help.

"},{"location":"#session-1-3-hours","title":"SESSION 1 (3 hours)","text":"

GOAL: Quick intro to the Internet of Things.

  • Introduction to the IoT
  • IoT and Big Data: we can quantitatively measure phenomena that up to yesterday we were only able to guess
  • A network of Resource Constrained Devices: if computational power, memory, energy, bandwidth and costs are not constrained, it is likely Internet, not Internet of Things.
"},{"location":"#session-2-3-hours","title":"SESSION 2 (3 hours)","text":"

GOAL: Small experiment (a single sensor). Prove you can integrate sensors suitable for you monitoring purpose. Focus on effectiveness, namely doing the right things, i.e., the Maker approach.

  • The reference hardware: ESP32
  • Hands on with the Arduino Ide and Wokwi
  • A simple integration of sensor/actuator
  • Communicating over WiFi
"},{"location":"#session-3-3-hours","title":"SESSION 3 (3 hours)","text":"

GOAL: Small experiment (a single sensor). Focus on efficiency, namely doing the things right, i.e., the Engineers approach. Use metrics to measure to what extent your solution satisfies the user requirements.

  • The need of an Operating System
  • Hands on FREERTOS
  • A simple integration of sensor/actuator
  • Communicating over WiFi
"},{"location":"#session-4-3-hours","title":"SESSION 4 (3 hours)","text":"

GOAL: Scale-up in a realistic environment.

  • The Very Large Scale IoT Testbed IoT-LAB
  • FreeRTOS on IoT-lab
  • A simple Project
"},{"location":"#tools-and-materials","title":"Tools and Materials","text":"

I like vscode with the Remote-SSH extension to interact with the virtual machine for FreeRTOS development. However, during the class there might be many issues due to different version of the OS and vscode. To further simplify the process I use micro a modern and intuitive terminal-based text editor. Give it a try, it is very convenient. I also suggest to install the file manager plugin. The only annoying issue I'm currently facing with micro is that copy/past does not work smoothly over SSH, you have to use ctr + shift + V

"},{"location":"#material","title":"Material","text":"

The list of material for each group

  • ESP32-DevKit e.g. Available on Amazon

  • A breadboard e.g. Available on Amazon

  • Wires e.g. Available on Amazon

  • Minimal actuator, namely a led and resistors - resistor 330 Ohm e.g. Available on Amazon

  • Minimal analog sensor, namely a potentiometer 10 KOhm e.g. Available on Amazon

  • Minimal digital sensor, namely a button - resistor 1 KOhm e.g. Available on Amazon

  • A more interesting digital vibration sensor SW-420 e.g. Available on Amazon

"},{"location":"#useful-links","title":"Useful links","text":"
  • Code and Slides
  • The Espressif software development environment
  • Arduino Ide
  • Wokwi
  • FREERTOS
  • IoT-LAB
  • Thingsboard
  • A short paper on this course
"},{"location":"#editions","title":"Editions","text":"

First edition of the course 19th (Session 1 and 2) and 21st (Session 3 and 4) of July 2023 in the University of Stellenbosch (South Africa) during an Erasmus+ staff mobility for teaching visit. About 40 participants selected among more the 100 candidates. Keep in touch on the discord server. A new version soon.

"},{"location":"iot-lab/","title":"IOT-LAB","text":""},{"location":"iot-lab/#run-a-lorawan-experiment","title":"Run a Lorawan experiment","text":"
ssh vitalett@saclay.iot-lab.info \n
git clone https://github.com/FreeRTOS/FreeRTOS-LTS.git\ncd FreeRTOS-LTS/FreeRTOS\ngit clone https://github.com/Lora-net/LoRaMac-node.git\ncd cd LoRaMac-node/\n
nano  src/boards/B-L072Z-LRWAN1/board.c\n\n// change the baudrate to 115200 to be compatible with iot-lab serial \n\nUartConfig( &Uart2, RX_TX, 115200, UART_8_BIT, UART_1_STOP_BIT, NO_PARITY, NO_FLOW_CTRL );\n
nano src/peripherals/soft-se/se-identity.h\n\n// update credentials to connect to TTN\n#define STATIC_DEVICE_EUI       1\n#define LORAWAN_DEVICE_EUI      LORAWAN_DEVICE_EUI_ON_TTN\n#define LORAWAN_JOIN_EUI        LORAWAN_APP_EUI_ON_TTN\n#define STATIC_DEVICE_ADDRESS   1\n#define LORAWAN_DEVICE_ADDRESS  DEVICE_ADDRESS_TTN\n\n.KeyID = NWK_KEY,           \n.KeyValue = LORAWAN_APP_KEY_ON_TTN\n
mkdir build\ncd build\n\ncmake -DCMAKE_BUILD_TYPE=Release         -DTOOLCHAIN_PREFIX=\"/opt/gcc-arm-none-eabi-4_9-2015q1/\"      -DCMAKE_TOOLCHAIN_FILE=\"../cmake/toolchain-arm-none-eabi.cmake\"         -DAPPLICATION=\"LoRaMac\"         -DSUB_PROJECT=\"periodic-uplink-lpp\"         -DCLASSB_ENABLED=\"ON\"         -DACTIVE_REGION=\"LORAMAC_REGION_EU868\"         -DREGION_EU868=\"ON\"         -DREGION_US915=\"OFF\"         -DREGION_CN779=\"OFF\"         -DREGION_EU433=\"OFF\"         -DREGION_AU915=\"OFF\"         -DREGION_AS923=\"OFF\"         -DREGION_CN470=\"OFF\"         -DREGION_KR920=\"OFF\"         -DREGION_IN865=\"OFF\"         -DREGION_RU864=\"OFF\"         -DBOARD=\"B-L072Z-LRWAN1\"       -DSECURE_ELEMENT=\"SOFT_SE\"         -DSECURE_ELEMENT_PRE_PROVISIONED=\"OFF\"         -DUSE_RADIO_DEBUG=\"ON\" ..\n\nmake\n
cd src/apps/LoRaMac/\n\niotlab-experiment submit -n rtos_ttn -d 60 -l 1,archi=st-lrwan1:sx1276+site=saclay\n{\n    \"id\": 394774\n}\n\niotlab-experiment get -i 394774 -s\n\niotlab-experiment get -i 394774 -r\n\niotlab-node --flash LoRaMac-periodic-uplink-lpp -l saclay,st-lrwan1,10\n\nnc st-lrwan1-10 20000\n
"},{"location":"session1/","title":"SESSION 1","text":"

GOAL: Quick intro to the Internet of Things.

"},{"location":"session1/#takeaway-one","title":"Takeaway ONE","text":"
  • IoT is fuel for Big Data Analytics
  • \"When you can measure ... you know something\". Lord Kelvin docet.

  • IoT provide quantitative evidences on phenomena that we used to investigate only qualitatively
"},{"location":"session1/#takeaway-two","title":"Takeaway TWO","text":"
  • IoT is a network of resource constrained devices
  • Constraints require design choices
  • Beyond the maker approach, namely when effectiveness meets efficiency
"},{"location":"session2/","title":"SESSION 2","text":"

GOAL: Small experiment (a single sensor). Prove you can integrate sensors suitable for you monitoring purpose. Focus on effectiveness, namely doing the right things, i.e., the Maker approach.

"},{"location":"session2/#the-reference-hardware-esp32-devkit","title":"The reference hardware: ESP32-DevKit","text":""},{"location":"session2/#lets-start-by-simulating-it","title":"Let's start by simulating it","text":"

Wokwi is an online Electronics simulator. You can use it to simulate Arduino, ESP32, STM32, and many other popular boards, parts and sensors.

  • The simplest actuator, namely a led
  • The Simplest sensor, namely a button
  • A bit more interesting sensor, namely a potentiometer
  • A simple example with SR04 Ultrasonic Sensor. The width is measured by the function pulseIn().

  • It's time to be connected by MQTT. The mosto convenient way is to use your mobile an Access Point and configure SSID and password consequently.
mosquitto_pub -h test.mosquitto.org -t \"topicName/led\" -m \"on\"\nmosquitto_pub -h test.mosquitto.org -t \"topicName/led\" -m \"off\"\n
mosquitto_sub -h test.mosquitto.org -t \"wokwi/temperature\"\n

Another possible broker is mqtt://mqtt.eclipseprojects.io

  • Build a simple backend with thingsboard https://demo.thingsboard.io/login

https://thingsboard.io/docs/getting-started-guides/helloworld/?connectdevice=mqtt-linux

mosquitto_pub -d -q 1 -h \"$THINGSBOARD_HOST_NAME\" -p \"1883\" -t \"v1/devices/me/telemetry\" -u \"$ACCESS_TOKEN\" -m {\"temperature\":25}\n

NOTE: for the sake of convenience, we will use WiFi connectivity, however it should be now clear WiFi is usually not appropriate for IoT applications due to the excessive energy demand.

"},{"location":"session2/#it-is-time-to-work-with-a-real-device","title":"It is time to Work with a real device","text":"
  • Download the code from Wokwi. It is also available on https://github.com/andreavitaletti/IoT_short_course/tree/main/src/simulator
  • The easiest way it to use the Arduino IDE
  • Since we are using the ESP32, you have to follow these intructions
  • Select the DOIT ESP32 DEVKIT V1 as in the picture below and upload the code
  • The only novelty is the vibration sensor SW-420 ... but please have a look to the SR04 Ultrasonic Sensor and adapt it to make it working with the SW-420.

This sketch could help

// Watch video here: https://www.youtube.com/watch?v=235BLk7vk00\n\n/* Vibration sensor connected to Arduino pins as follows:\n\n ESP32 Arduino Vibration Sensor\n https://wolles-elektronikkiste.de/esp32-mit-arduino-code-programmieren\n   D18 --> GPIO18 --> G18        DOut\n   GND                           GND\n   +5V --> 3.3V                  VCC      \n*/\n\nint EP = 18;\n\nvoid setup(){\n  pinMode(EP, INPUT); //set EP input for measurment\n  Serial.begin(9600); //init serial 9600\n}\nvoid loop(){\n  long measurement =TP_init();\n  delay(50);\n  Serial.println(measurement);\n}\n\nlong TP_init(){\n  delay(10);\n  long measurement=pulseIn (EP, HIGH);  //wait for the pin to get HIGH and returns measurement\n  return measurement;\n}\n\n

Through pulseIn() we can measure the duration of a vibration event exceeding the threshold, not the frequency of the vibrations themselves. Indeed, the Arduino pulseIn() function waits for a change in the binary input (Low to High in this instance) and returns the duration that the detected pulse was active (time for it to go back High to Low after going High).

QUESTION: Can we use this function to help in anomaly detection? see here for inspiration!

"},{"location":"session3/","title":"SESSION 3","text":"

GOAL: Small experiment (a single sensor). Focus on efficiency, namely doing the things right, i.e., the Engineers approach. Use metrics to measure to what extent your solution satisfies the user requirements.

Well, to develop an IoT application as an engineer, we do need a Real-Time Operating System (RTOS). What is An RTOS?.

Embedded systems often have real time requirements. A real time requirements is one that specifies that the embedded system must respond to a certain event within a strictly defined time (the deadline). A guarantee to meet real time requirements can only be made if the behaviour of the operating system's scheduler can be predicted (and is therefore deterministic). FreeRTOS, achieve determinism by allowing the user to assign a priority to each thread of execution. The scheduler then uses the priority to know which thread of execution to run next. In FreeRTOS, a thread of execution is called a task.

Here there is a nice explanation of the main features provided by FreeRTOS, namely

  • Multitasking
  • Scheduling
  • Context Switching
  • Real Time Applications
  • Real Time Scheduling

FreeRTOS FAQ - What is This All About?

Finally a nice example of the design of a real-time application

A book on FreeRTOS.

"},{"location":"session3/#development-environment","title":"Development environment","text":"

To develop our solution on our ESP32, we need to setup the environment as described here. A very convenient way is to use docker as explained here.

To further simplify the development process, a multi-platform Virtualbox image is available here. Next we will focus on this method. Credentials to work with the virtual machine are root/root and devel/devel and we will connect in ssh as explained below.

The reference folder for the code and the examples is https://github.com/espressif/esp-idf.git which is already available on the virtual machine on

  1. ssh devel@localhost -p 2222 ... the password is devel
  2. get_idf ... we set up the development environment
  3. cd ~/esp
  4. cp -r ./esp-idf/examples/get-started/hello_world/ ./workshop/ ... make a copy of a dir in the example in the workshop dir
  5. cd ./workshop/hello_world/
  6. idf.py build ... i t takes some time
  7. idf.py flash ... be sure the ESP32 is connected to \\dev\\ttyUSB0 and check it is visible in the virtual machine
  8. idf.py monitor ... to exit from the monitor ctrl+T ctrl+X

NOTE in some cases you can configure specific parameters running idf.py menuconfig

In the virtual machine you have first to export LC_ALL=C

"},{"location":"session3/#clone-only-the-relevant-code","title":"Clone only the relevant code","text":"
# Create a directory, so Git doesn't get messy, and enter it\nmkdir code_from_git && cd code_from_git\n\n# Start a Git repository\ngit init\n\n# Track repository, do not enter subdirectory\ngit remote add -f origin https://github.com/andreavitaletti/IoT_short_course\n\n# Enable the tree check feature\ngit config core.sparseCheckout true\n\n# Create a file in the path: .git/info/sparse-checkout\n# That is inside the hidden .git directory that was created\n# by running the command: git init\n# And inside it enter the name of the sub directory you only want to clone\necho 'src/freertos' >> .git/info/sparse-checkout\n\n## Download with pull, not clone\ngit pull origin main\n
"},{"location":"session3/#a-quick-introduction-to-tasks","title":"A quick introduction to tasks","text":"

Based on ESP32 ESP-IDF FreeRTOS Tutorial: Learn to Create Tasks. The code is available on our github repo

"},{"location":"session3/#the-examples-of-session-2-on-freertos","title":"The examples of SESSION 2 on FreeRTOS","text":"

In this section we will implement the reference examples developed in day2 into FreeRTOS. The idea is to take inspiration from the examples available at https://github.com/espressif/esp-idf/tree/master/examples and modify them to get the same behavior of the ones developed in DAY2

  • The simplest actuator, namely a led
  • The Simplest sensor, namely a button
  • A bit more interesting sensor, namely a potentiometer
  • A simple example with SR04 Ultrasonic Sensor
  • It's time to be connected by MQTT
  • About the vibration sensor SW-420 ... have a look here

Nice tutorials on the same topics are also available here

"},{"location":"session3/#data-streaming-in-industrial-iot","title":"Data Streaming in Industrial IoT","text":"
  • MQTT
  • OPCUA
  • Apache Kafka
"},{"location":"session4/","title":"SESSION 4","text":"

GOAL: Scale-up in a realistic environment.

IoT-LAB provides a very large scale infrastructure suitable for testing small wireless sensor devices and heterogeneous communicating objects.

Even if the ESP32 is not currently among the supported hardware, IoT-LAB supports FreeRTOS.

  • Once you have setup a user connect to a lab by ssh, as an example
ssh vitalett@grenoble.iot-lab.info\n
  • Follow the instructions here to run FreeRTOS on IoT-LAB
  • The reference code is available on https://github.com/iot-lab/openlab and in particular we are inetrested in the tutorial
  • Once the code has been compiled, it can run into an experiment as explained here
  • A quite unique feature, is the ability to perform consuption monitoring as explained here. To plot the graphs, download the oml files and use the following tools
"},{"location":"supplement1/","title":"Tensorflow Lite Micro on ESP32","text":"

Reference examples are available here. You have to download from github esp-nn and esp32-camera and place them into the relative folders under components.

Then go to examples/hello_worlds and as usual idf.py build, idf.py flash and finally idf.py monitor.

A nice explanation on how the model is generated can be found on the book TinyML by Pete Warden, Daniel Situnayake. The corresponding jupyter notebook is available here

Some other useful resources are listed below:

  • First steps with ESP32 and TensorFlow Lite for Microcontrollers
  • Get started with microcontrollers
"},{"location":"supplement2/","title":"Kalman Filters","text":"

A very nice, rich an exhaustive source of documentation for Kalman filters is available here

The main idea is that both our prior knowledge and the measurements of our sensors are to some extent inaccurate. To model such inaccuracy we use a Gaussian with mean $$\\mu$$ and variance $$\\rho$$

At each iteration we have our prior knowledge (in blue) and a new measurement (in orange) and we account for both source of information computing an average (in green)

The final mean gets shifted which is in between the two old means, the mean of the prior, and the mean of the measurement. It\u2019s slightly further on the measurement side because the measurement was more certain as to where the vehicle is than prior. The more certain we are, the more we pull the mean on the direction of the certain answer. source

Iteration after iteration the variance decreases (the spread of the blue gaussian), providing a better estimation (in green) despite the noisy measurements (in red) source

A reference library is available here

"},{"location":"supplement3/","title":"Interact with a Mobile Phone via BLE","text":"

We aim at fast prototyping and Thunkable is an excellent tool

The arduino code, adapted from https://www.instructables.com/ESP32-BLE-Android-App-Arduino-IDE-AWESOME/ is the following

/*\n    Video: https://www.youtube.com/watch?v=oCMOYS71NIU\n    Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleNotify.cpp\n    Ported to Arduino ESP32 by Evandro Copercini\n\n   Create a BLE server that, once we receive a connection, will send periodic notifications.\n   The service advertises itself as: 6E400001-B5A3-F393-E0A9-E50E24DCCA9E\n   Has a characteristic of: 6E400002-B5A3-F393-E0A9-E50E24DCCA9E - used for receiving data with \"WRITE\" \n   Has a characteristic of: 6E400003-B5A3-F393-E0A9-E50E24DCCA9E - used to send data with  \"NOTIFY\"\n\n   The design of creating the BLE server is:\n   1. Create a BLE Server\n   2. Create a BLE Service\n   3. Create a BLE Characteristic on the Service\n   4. Create a BLE Descriptor on the characteristic\n   5. Start the service.\n   6. Start advertising.\n\n   In this example rxValue is the data received (only accessible inside that function).\n   And txValue is the data to be sent, in this example just a byte incremented every second. \n*/\n#include <BLEDevice.h>\n#include <BLEServer.h>\n#include <BLEUtils.h>\n#include <BLE2902.h>\n\n// 78:21:84:9f:24:de\n\nBLECharacteristic *pCharacteristic;\nbool deviceConnected = false;\nfloat txValue = 0;\nconst int readPin = 32; // Use GPIO number. See ESP32 board pinouts\n//const int LED = 9; // Could be different depending on the dev board. I used the DOIT ESP32 dev board.\n\n//std::string rxValue; // Could also make this a global var to access it in loop()\n\n// See the following for generating UUIDs:\n// https://www.uuidgenerator.net/\n\n#define SERVICE_UUID           \"6E400001-B5A3-F393-E0A9-E50E24DCCA9E\" // UART service UUID\n#define CHARACTERISTIC_UUID_RX \"6E400002-B5A3-F393-E0A9-E50E24DCCA9E\" // Qui ricevi da EPS32\n#define CHARACTERISTIC_UUID_TX \"6E400003-B5A3-F393-E0A9-E50E24DCCA9E\" // Qui invii\n\nclass MyServerCallbacks: public BLEServerCallbacks {\n    void onConnect(BLEServer* pServer) {\n      deviceConnected = true;\n    };\n\n    void onDisconnect(BLEServer* pServer) {\n      deviceConnected = false;\n    }\n};\n\n\n\nclass MyCallbacks: public BLECharacteristicCallbacks {\n    void onWrite(BLECharacteristic *pCharacteristic) {\n      std::string rxValue = pCharacteristic->getValue();\n\n      char letto[] = \"          \";\n\n      if (rxValue.length() > 0) {\n        Serial.println(\"*********\");\n        Serial.print(\"Received Value: \");\n\n        for (int i = 0; i < rxValue.length(); i++) {\n          Serial.print(rxValue[i]);\n          letto[i]=rxValue[i];\n        }\n\n        Serial.println();\n\n        String a = String(letto);\n\n        Serial.print(a.toFloat());\n\n        // Do stuff based on the command received from the app\n        if (rxValue.find(\"A\") != -1) { \n          Serial.print(\"Turning ON!\");\n          digitalWrite(LED, HIGH);\n        }\n        else if (rxValue.find(\"B\") != -1) {\n          Serial.print(\"Turning OFF!\");\n          digitalWrite(LED, LOW);\n        }\n\n        Serial.println();\n        Serial.println(\"*********\");\n      }\n    }\n};\n\nvoid setup() {\n  Serial.begin(115200);\n\n  pinMode(LED, OUTPUT);\n\n  // Create the BLE Device\n  BLEDevice::init(\"ESP32 UART Test\"); // Give it a name\n\n  Serial.print(\"local BLE Address is: \");\n  Serial.println(BLEDevice::getAddress().toString().c_str());\n\n  // Create the BLE Server\n  BLEServer *pServer = BLEDevice::createServer();\n  pServer->setCallbacks(new MyServerCallbacks());\n\n  // Create the BLE Service\n  BLEService *pService = pServer->createService(SERVICE_UUID);\n\n\n  // BleCharacteristic deviceHealthCharacteristic(\"deviceHealth\", BleCharacteristicProperty::READ, deviceHealthUuid, serviceUuid);\n  // see https://community.thunkable.com/t/ble-hrm-reading-bpm/2441754/6\n\n\n  // Create a BLE Characteristic\n  pCharacteristic = pService->createCharacteristic(\n                      CHARACTERISTIC_UUID_TX,\n                      //BLECharacteristic::PROPERTY_NOTIFY\n                      BLECharacteristic::PROPERTY_READ\n                    );\n\n  pCharacteristic->addDescriptor(new BLE2902());\n\n  BLECharacteristic *pCharacteristic = pService->createCharacteristic(\n                                         CHARACTERISTIC_UUID_RX,\n                                         BLECharacteristic::PROPERTY_WRITE\n                                       );\n\n  pCharacteristic->setCallbacks(new MyCallbacks());\n\n  // Start the service\n  pService->start();\n\n  // Start advertising\n  pServer->getAdvertising()->start();\n  Serial.println(\"Waiting a client connection to notify...\");\n}\n\nvoid loop() {\n\n  if (deviceConnected) {\n    // Fabricate some arbitrary junk for now...\n    txValue = analogRead(readPin) / 3.456; // This could be an actual sensor reading!\n\n    // Let's convert the value to a char array:\n    char txString[8]; // make sure this is big enuffz\n    dtostrf(txValue, 1, 2, txString); // float_val, min_width, digits_after_decimal, char_buffer\n\n//    pCharacteristic->setValue(&txValue, 1); // To send the integer value\n    pCharacteristic->setValue(\"Hello!\"); // Sending a test message\n    //pCharacteristic->setValue(txString);\n\n    pCharacteristic->notify(); // Send the value to the app!\n    Serial.print(\"*** Sent Value: \");\n    Serial.print(txString);\n    Serial.println(\" ***\");\n\n    // You can add the rxValue checks down here instead\n    // if you set \"rxValue\" as a global var at the top!\n    // Note you will have to delete \"std::string\" declaration\n    // of \"rxValue\" in the callback function.\n//    if (rxValue.find(\"A\") != -1) { \n//      Serial.println(\"Turning ON!\");\n//      digitalWrite(LED, HIGH);\n//    }\n//    else if (rxValue.find(\"B\") != -1) {\n//      Serial.println(\"Turning OFF!\");\n//      digitalWrite(LED, LOW);\n//    }\n  }\n\n  delay(1000);\n}\n\n
// Adapted from https://github.com/SIMS-IOT-Devices/FreeRTOS-ESP-IDF-BLE-Server\n\n#include <stdio.h>\n#include \"freertos/FreeRTOS.h\"\n#include \"freertos/task.h\"\n#include \"freertos/event_groups.h\"\n#include \"esp_event.h\"\n#include \"nvs_flash.h\"\n#include \"esp_log.h\"\n#include \"esp_nimble_hci.h\"\n#include \"nimble/nimble_port.h\"\n#include \"nimble/nimble_port_freertos.h\"\n#include \"host/ble_hs.h\"\n#include \"services/gap/ble_svc_gap.h\"\n#include \"services/gatt/ble_svc_gatt.h\"\n#include \"sdkconfig.h\"\n\nchar *TAG = \"BLE-Server\";\nuint8_t ble_addr_type;\nvoid ble_app_advertise(void);\n\n//@_____________________Define UUIDs______________________________________\n//!! b2bbc642-46da-11ed-b878-0242ac120002\n//static const ble_uuid128_t gatt_svr_svc_uuid =\n//    BLE_UUID128_INIT(0x02, 0x00, 0x12, 0xac, 0x42, 0x02, 0x78, 0xb8, 0xed, 0x11, 0xda, 0x46, 0x42, 0xc6, 0xbb, 0xb2);\n\nstatic const ble_uuid128_t gatt_svr_svc_uuid =\n    BLE_UUID128_INIT(0x9e, 0xca, 0xdc, 0x24, 0x0e, 0xe5, 0xa9, 0xe0, 0x93, 0xf3, 0xa3, 0xb5, 0x01, 0x00, 0x40, 0x6e);\n\n\n//!! c9af9c76-46de-11ed-b878-0242ac120002\n//static const ble_uuid128_t gatt_svr_chr_uuid =\n//    BLE_UUID128_INIT(0x02, 0x00, 0x12, 0xac, 0x42, 0x02, 0x78, 0xb8, 0xed, 0x11, 0xde, 0x46, 0x76, 0x9c, 0xaf, 0xc9);\n\n// 6E400002-B5A3-F393-E0A9-E50E24DCCA9E    \n\nstatic const ble_uuid128_t gatt_svr_chr_read_uuid =\n    BLE_UUID128_INIT(0x9e, 0xca, 0xdc, 0x24, 0x0e, 0xe5, 0xa9, 0xe0, 0x93, 0xf3, 0xa3, 0xb5, 0x02, 0x00, 0x40, 0x6e);\nstatic const ble_uuid128_t gatt_svr_chr_write_uuid =\n    BLE_UUID128_INIT(0x9e, 0xca, 0xdc, 0x24, 0x0e, 0xe5, 0xa9, 0xe0, 0x93, 0xf3, 0xa3, 0xb5, 0x03, 0x00, 0x40, 0x6e);\n\n\n// Write data to ESP32 defined as server\nstatic int device_write(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg)\n{\n    printf(\"Data from the client: %.*s\\n\", ctxt->om->om_len, ctxt->om->om_data);\n    return 0;\n}\n\n// Read data from ESP32 defined as server\nstatic int device_read(uint16_t con_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg)\n{\n    os_mbuf_append(ctxt->om, \"Data from the server\", strlen(\"Data from the server\"));\n    return 0;\n}\n\nstatic const struct ble_gatt_svc_def gatt_svcs[] = {\n    {.type = BLE_GATT_SVC_TYPE_PRIMARY,\n     .uuid = &gatt_svr_svc_uuid.u,                      // Define UUID for device type\n     .characteristics = (struct ble_gatt_chr_def[]){\n         {.uuid = &gatt_svr_chr_write_uuid.u,           // Define UUID for reading\n          .flags = BLE_GATT_CHR_F_READ,\n          .access_cb = device_read},\n         {.uuid = &gatt_svr_chr_read_uuid.u,           // Define UUID for writing\n          .flags = BLE_GATT_CHR_F_WRITE,\n          .access_cb = device_write},\n         {0}}},\n    {0}};\n\n\n\n// BLE event handling\nstatic int ble_gap_event(struct ble_gap_event *event, void *arg)\n{\n    switch (event->type)\n    {\n    // Advertise if connected\n    case BLE_GAP_EVENT_CONNECT:\n        ESP_LOGI(\"GAP\", \"BLE GAP EVENT CONNECT %s\", event->connect.status == 0 ? \"OK!\" : \"FAILED!\");\n        if (event->connect.status != 0)\n        {\n            ble_app_advertise();\n        }\n        break;\n    // Advertise again after completion of the event\n    case BLE_GAP_EVENT_ADV_COMPLETE:\n        ESP_LOGI(\"GAP\", \"BLE GAP EVENT\");\n        ble_app_advertise();\n        break;\n    default:\n        break;\n    }\n    return 0;\n}\n\n// Define the BLE connection\nvoid ble_app_advertise(void)\n{\n    // GAP - device name definition\n    struct ble_hs_adv_fields fields;\n    const char *device_name;\n    memset(&fields, 0, sizeof(fields));\n    device_name = ble_svc_gap_device_name(); // Read the BLE device name\n    fields.name = (uint8_t *)device_name;\n    fields.name_len = strlen(device_name);\n    fields.name_is_complete = 1;\n    ble_gap_adv_set_fields(&fields);\n\n    // GAP - device connectivity definition\n    struct ble_gap_adv_params adv_params;\n    memset(&adv_params, 0, sizeof(adv_params));\n    adv_params.conn_mode = BLE_GAP_CONN_MODE_UND; // connectable or non-connectable\n    adv_params.disc_mode = BLE_GAP_DISC_MODE_GEN; // discoverable or non-discoverable\n    ble_gap_adv_start(ble_addr_type, NULL, BLE_HS_FOREVER, &adv_params, ble_gap_event, NULL);\n}\n\n// The application\nvoid ble_app_on_sync(void)\n{\n    ble_hs_id_infer_auto(0, &ble_addr_type); // Determines the best address type automatically\n    ble_app_advertise();                     // Define the BLE connection\n}\n\n// The infinite task\nvoid host_task(void *param)\n{\n    nimble_port_run(); // This function will return only when nimble_port_stop() is executed\n}\n\nvoid app_main()\n{\n    nvs_flash_init();                          // 1 - Initialize NVS flash using\n    //esp_nimble_hci_and_controller_init();      // 2 - Initialize ESP controller\n    nimble_port_init();                        // 3 - Initialize the host stack\n    ble_svc_gap_device_name_set(\"BLE-Server\"); // 4 - Initialize NimBLE configuration - server name\n    ble_svc_gap_init();                        // 4 - Initialize NimBLE configuration - gap service\n    ble_svc_gatt_init();                       // 4 - Initialize NimBLE configuration - gatt service\n    ble_gatts_count_cfg(gatt_svcs);            // 4 - Initialize NimBLE configuration - config gatt services\n    ble_gatts_add_svcs(gatt_svcs);             // 4 - Initialize NimBLE configuration - queues gatt services.\n    ble_hs_cfg.sync_cb = ble_app_on_sync;      // 5 - Initialize application\n    nimble_port_freertos_init(host_task);      // 6 - Run the thread\n}\n
"}]} \ No newline at end of file diff --git a/session1/index.html b/session1/index.html new file mode 100644 index 0000000..219e7a8 --- /dev/null +++ b/session1/index.html @@ -0,0 +1,518 @@ + + + + + + + + + + + + + + + + + + + + + + + + Session 1 - A Short Course on IoT + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + +

SESSION 1

+

GOAL: Quick intro to the Internet of Things.

+

Takeaway ONE

+
    +
  • IoT is fuel for Big Data Analytics
  • +
  • +
    +

    "When you can measure ... you know something". Lord Kelvin docet.

    +
    +
  • +
  • IoT provide quantitative evidences on phenomena that we used to investigate only qualitatively
  • +
+ + +

Takeaway TWO

+
    +
  • IoT is a network of resource constrained devices
  • +
  • Constraints require design choices
  • +
  • Beyond the maker approach, namely when effectiveness meets efficiency
  • +
+ + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/session2/index.html b/session2/index.html new file mode 100644 index 0000000..f5c56b5 --- /dev/null +++ b/session2/index.html @@ -0,0 +1,587 @@ + + + + + + + + + + + + + + + + + + + + + + + + Session 2 - A Short Course on IoT + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + +

SESSION 2

+

GOAL: Small experiment (a single sensor). Prove you can integrate sensors suitable for you monitoring purpose. Focus on effectiveness, namely doing the right things, i.e., the Maker approach.

+

The reference hardware: ESP32-DevKit

+

+

Let's start by simulating it

+

Wokwi is an online Electronics simulator. You can use it to simulate Arduino, ESP32, STM32, and many other popular boards, parts and sensors.

+ +

+
    +
  • It's time to be connected by MQTT. The mosto convenient way is to use your mobile an Access Point and configure SSID and password consequently.
  • +
+
mosquitto_pub -h test.mosquitto.org -t "topicName/led" -m "on"
+mosquitto_pub -h test.mosquitto.org -t "topicName/led" -m "off"
+
+
mosquitto_sub -h test.mosquitto.org -t "wokwi/temperature"
+
+

Another possible broker is mqtt://mqtt.eclipseprojects.io

+ +

+

+

https://thingsboard.io/docs/getting-started-guides/helloworld/?connectdevice=mqtt-linux

+
mosquitto_pub -d -q 1 -h "$THINGSBOARD_HOST_NAME" -p "1883" -t "v1/devices/me/telemetry" -u "$ACCESS_TOKEN" -m {"temperature":25}
+
+

NOTE: for the sake of convenience, we will use WiFi connectivity, however it should be now clear WiFi is usually not appropriate for IoT applications due to the excessive energy demand.

+

It is time to Work with a real device

+ +

+
    +
  • The only novelty is the vibration sensor SW-420 ... but please have a look to the SR04 Ultrasonic Sensor and adapt it to make it working with the SW-420.
  • +
+

This sketch could help

+
// Watch video here: https://www.youtube.com/watch?v=235BLk7vk00
+
+/* Vibration sensor connected to Arduino pins as follows:
+
+ ESP32 Arduino Vibration Sensor
+ https://wolles-elektronikkiste.de/esp32-mit-arduino-code-programmieren
+   D18 --> GPIO18 --> G18        DOut
+   GND                           GND
+   +5V --> 3.3V                  VCC      
+*/
+
+int EP = 18;
+
+void setup(){
+  pinMode(EP, INPUT); //set EP input for measurment
+  Serial.begin(9600); //init serial 9600
+}
+void loop(){
+  long measurement =TP_init();
+  delay(50);
+  Serial.println(measurement);
+}
+
+long TP_init(){
+  delay(10);
+  long measurement=pulseIn (EP, HIGH);  //wait for the pin to get HIGH and returns measurement
+  return measurement;
+}
+
+
+

Through pulseIn() we can measure the duration of a vibration event exceeding the threshold, not the frequency of the vibrations themselves. Indeed, the Arduino pulseIn() function waits for a change in the binary input (Low to High in this instance) and returns the duration that the detected pulse was active (time for it to go back High to Low after going High).

+

QUESTION: Can we use this function to help in anomaly detection? see here for inspiration!

+

+ + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/session3/index.html b/session3/index.html new file mode 100644 index 0000000..46469f6 --- /dev/null +++ b/session3/index.html @@ -0,0 +1,618 @@ + + + + + + + + + + + + + + + + + + + + + + + + Session 3 - A Short Course on IoT + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + + + + + +
+
+ + + + +

SESSION 3

+

GOAL: Small experiment (a single sensor). Focus on efficiency, namely doing the things right, i.e., the Engineers approach. Use metrics to measure to what extent your solution satisfies the user requirements.

+ + + + +

Well, to develop an IoT application as an engineer, we do need a Real-Time Operating System (RTOS). What is An RTOS?.

+
+

Embedded systems often have real time requirements. A real time requirements is one that specifies that the embedded system must respond to a certain event within a strictly defined time (the deadline). A guarantee to meet real time requirements can only be made if the behaviour of the operating system's scheduler can be predicted (and is therefore deterministic). FreeRTOS, achieve determinism by allowing the user to assign a priority to each thread of execution. The scheduler then uses the priority to know which thread of execution to run next. In FreeRTOS, a thread of execution is called a task.

+
+

Here there is a nice explanation of the main features provided by FreeRTOS, namely

+
    +
  • Multitasking
  • +
  • Scheduling
  • +
  • Context Switching
  • +
  • Real Time Applications
  • +
  • Real Time Scheduling
  • +
+

FreeRTOS FAQ - What is This All About?

+

Finally a nice example of the design of a real-time application

+

A book on FreeRTOS.

+

Development environment

+

To develop our solution on our ESP32, we need to setup the environment as described here. A very convenient way is to use docker as explained here.

+

To further simplify the development process, a multi-platform Virtualbox image is available here. Next we will focus on this method. Credentials to work with the virtual machine are root/root and devel/devel and we will connect in ssh as explained below.

+

The reference folder for the code and the examples is https://github.com/espressif/esp-idf.git which is already available on the virtual machine on

+
    +
  1. ssh devel@localhost -p 2222 ... the password is devel
  2. +
  3. get_idf ... we set up the development environment
  4. +
  5. cd ~/esp
  6. +
  7. cp -r ./esp-idf/examples/get-started/hello_world/ ./workshop/ ... make a copy of a dir in the example in the workshop dir
  8. +
  9. cd ./workshop/hello_world/
  10. +
  11. idf.py build ... i t takes some time
  12. +
  13. idf.py flash ... be sure the ESP32 is connected to \dev\ttyUSB0 and check it is visible in the virtual machine
  14. +
  15. idf.py monitor ... to exit from the monitor ctrl+T ctrl+X
  16. +
+

NOTE in some cases you can configure specific parameters running idf.py menuconfig

+

In the virtual machine you have first to +export LC_ALL=C

+

Clone only the relevant code

+
# Create a directory, so Git doesn't get messy, and enter it
+mkdir code_from_git && cd code_from_git
+
+# Start a Git repository
+git init
+
+# Track repository, do not enter subdirectory
+git remote add -f origin https://github.com/andreavitaletti/IoT_short_course
+
+# Enable the tree check feature
+git config core.sparseCheckout true
+
+# Create a file in the path: .git/info/sparse-checkout
+# That is inside the hidden .git directory that was created
+# by running the command: git init
+# And inside it enter the name of the sub directory you only want to clone
+echo 'src/freertos' >> .git/info/sparse-checkout
+
+## Download with pull, not clone
+git pull origin main
+
+

A quick introduction to tasks

+

Based on ESP32 ESP-IDF FreeRTOS Tutorial: Learn to Create Tasks. The code is available on our github repo

+

The examples of SESSION 2 on FreeRTOS

+

In this section we will implement the reference examples developed in day2 into FreeRTOS. The idea is to take inspiration from the examples available at https://github.com/espressif/esp-idf/tree/master/examples and modify them to get the same behavior of the ones developed in DAY2

+
    +
  • The simplest actuator, namely a led
  • +
  • The Simplest sensor, namely a button
  • +
  • A bit more interesting sensor, namely a potentiometer
  • +
  • A simple example with SR04 Ultrasonic Sensor
  • +
  • It's time to be connected by MQTT
  • +
  • About the vibration sensor SW-420 ... have a look here
  • +
+

Nice tutorials on the same topics are also available here

+

Data Streaming in Industrial IoT

+ + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/session4/index.html b/session4/index.html new file mode 100644 index 0000000..6e8ae9f --- /dev/null +++ b/session4/index.html @@ -0,0 +1,456 @@ + + + + + + + + + + + + + + + + + + + + + + + + Session 4 - A Short Course on IoT + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + +

SESSION 4

+

GOAL: Scale-up in a realistic environment.

+ + + + +

IoT-LAB provides a very large scale infrastructure suitable for testing small wireless sensor devices and heterogeneous communicating objects.

+

Even if the ESP32 is not currently among the supported hardware, IoT-LAB supports FreeRTOS.

+
    +
  • Once you have setup a user connect to a lab by ssh, as an example
  • +
+
ssh vitalett@grenoble.iot-lab.info
+
+
    +
  • Follow the instructions here to run FreeRTOS on IoT-LAB
  • +
  • The reference code is available on https://github.com/iot-lab/openlab and in particular we are inetrested in the tutorial
  • +
  • Once the code has been compiled, it can run into an experiment as explained here
  • +
  • A quite unique feature, is the ability to perform consuption monitoring as explained here. To plot the graphs, download the oml files and use the following tools
  • +
+ + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/sitemap.xml b/sitemap.xml new file mode 100644 index 0000000..608012e --- /dev/null +++ b/sitemap.xml @@ -0,0 +1,48 @@ + + + + https://andreavitaletti.github.io/IoT_short_course/ + 2024-07-02 + daily + + + https://andreavitaletti.github.io/IoT_short_course/iot-lab/ + 2024-07-02 + daily + + + https://andreavitaletti.github.io/IoT_short_course/session1/ + 2024-07-02 + daily + + + https://andreavitaletti.github.io/IoT_short_course/session2/ + 2024-07-02 + daily + + + https://andreavitaletti.github.io/IoT_short_course/session3/ + 2024-07-02 + daily + + + https://andreavitaletti.github.io/IoT_short_course/session4/ + 2024-07-02 + daily + + + https://andreavitaletti.github.io/IoT_short_course/supplement1/ + 2024-07-02 + daily + + + https://andreavitaletti.github.io/IoT_short_course/supplement2/ + 2024-07-02 + daily + + + https://andreavitaletti.github.io/IoT_short_course/supplement3/ + 2024-07-02 + daily + + \ No newline at end of file diff --git a/sitemap.xml.gz b/sitemap.xml.gz new file mode 100644 index 0000000..8230f2f Binary files /dev/null and b/sitemap.xml.gz differ diff --git a/supplement1/index.html b/supplement1/index.html new file mode 100644 index 0000000..71e4e7c --- /dev/null +++ b/supplement1/index.html @@ -0,0 +1,446 @@ + + + + + + + + + + + + + + + + + + + + + + + + Sup. Mat. ML on node - A Short Course on IoT + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + +

Tensorflow Lite Micro on ESP32

+

Reference examples are available here. You have to download from github esp-nn and esp32-camera and place them into the relative folders under components.

+

Then go to examples/hello_worlds and as usual idf.py build, idf.py flash and finally idf.py monitor.

+

A nice explanation on how the model is generated can be found on the book TinyML by Pete Warden, Daniel Situnayake. The corresponding jupyter notebook is available here

+

Some other useful resources are listed below:

+ + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/supplement2/index.html b/supplement2/index.html new file mode 100644 index 0000000..f9196f2 --- /dev/null +++ b/supplement2/index.html @@ -0,0 +1,449 @@ + + + + + + + + + + + + + + + + + + + + + + + + Sup. Mat. Kalman Filters - A Short Course on IoT + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + +

Kalman Filters

+

A very nice, rich an exhaustive source of documentation for Kalman filters is available here

+

The main idea is that both our prior knowledge and the measurements of our sensors are to some extent inaccurate. To model such inaccuracy we use a Gaussian with mean $$\mu$$ and variance $$\rho$$

+

+

At each iteration we have our prior knowledge (in blue) and a new measurement (in orange) and we account for both source of information computing an average (in green)

+

+
+

The final mean gets shifted which is in between the two old means, the mean of the prior, and the mean of the measurement. It’s slightly further on the measurement side because the measurement was more certain as to where the vehicle is than prior. The more certain we are, the more we pull the mean on the direction of the certain answer. source

+
+

Iteration after iteration the variance decreases (the spread of the blue gaussian), providing a better estimation (in green) despite the noisy measurements (in red) source

+

+

A reference library is available here

+ + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/supplement3/index.html b/supplement3/index.html new file mode 100644 index 0000000..a1ed5a3 --- /dev/null +++ b/supplement3/index.html @@ -0,0 +1,760 @@ + + + + + + + + + + + + + + + + + + + + + + + + Sup. Mat. Mobile - A Short Course on IoT + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + +

Interact with a Mobile Phone via BLE

+

We aim at fast prototyping and Thunkable is an excellent tool

+

+

The arduino code, adapted from https://www.instructables.com/ESP32-BLE-Android-App-Arduino-IDE-AWESOME/ is the following

+
/*
+    Video: https://www.youtube.com/watch?v=oCMOYS71NIU
+    Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleNotify.cpp
+    Ported to Arduino ESP32 by Evandro Copercini
+
+   Create a BLE server that, once we receive a connection, will send periodic notifications.
+   The service advertises itself as: 6E400001-B5A3-F393-E0A9-E50E24DCCA9E
+   Has a characteristic of: 6E400002-B5A3-F393-E0A9-E50E24DCCA9E - used for receiving data with "WRITE" 
+   Has a characteristic of: 6E400003-B5A3-F393-E0A9-E50E24DCCA9E - used to send data with  "NOTIFY"
+
+   The design of creating the BLE server is:
+   1. Create a BLE Server
+   2. Create a BLE Service
+   3. Create a BLE Characteristic on the Service
+   4. Create a BLE Descriptor on the characteristic
+   5. Start the service.
+   6. Start advertising.
+
+   In this example rxValue is the data received (only accessible inside that function).
+   And txValue is the data to be sent, in this example just a byte incremented every second. 
+*/
+#include <BLEDevice.h>
+#include <BLEServer.h>
+#include <BLEUtils.h>
+#include <BLE2902.h>
+
+// 78:21:84:9f:24:de
+
+BLECharacteristic *pCharacteristic;
+bool deviceConnected = false;
+float txValue = 0;
+const int readPin = 32; // Use GPIO number. See ESP32 board pinouts
+//const int LED = 9; // Could be different depending on the dev board. I used the DOIT ESP32 dev board.
+
+//std::string rxValue; // Could also make this a global var to access it in loop()
+
+// See the following for generating UUIDs:
+// https://www.uuidgenerator.net/
+
+#define SERVICE_UUID           "6E400001-B5A3-F393-E0A9-E50E24DCCA9E" // UART service UUID
+#define CHARACTERISTIC_UUID_RX "6E400002-B5A3-F393-E0A9-E50E24DCCA9E" // Qui ricevi da EPS32
+#define CHARACTERISTIC_UUID_TX "6E400003-B5A3-F393-E0A9-E50E24DCCA9E" // Qui invii
+
+class MyServerCallbacks: public BLEServerCallbacks {
+    void onConnect(BLEServer* pServer) {
+      deviceConnected = true;
+    };
+
+    void onDisconnect(BLEServer* pServer) {
+      deviceConnected = false;
+    }
+};
+
+
+
+class MyCallbacks: public BLECharacteristicCallbacks {
+    void onWrite(BLECharacteristic *pCharacteristic) {
+      std::string rxValue = pCharacteristic->getValue();
+
+      char letto[] = "          ";
+
+      if (rxValue.length() > 0) {
+        Serial.println("*********");
+        Serial.print("Received Value: ");
+
+        for (int i = 0; i < rxValue.length(); i++) {
+          Serial.print(rxValue[i]);
+          letto[i]=rxValue[i];
+        }
+
+        Serial.println();
+
+        String a = String(letto);
+
+        Serial.print(a.toFloat());
+
+        // Do stuff based on the command received from the app
+        if (rxValue.find("A") != -1) { 
+          Serial.print("Turning ON!");
+          digitalWrite(LED, HIGH);
+        }
+        else if (rxValue.find("B") != -1) {
+          Serial.print("Turning OFF!");
+          digitalWrite(LED, LOW);
+        }
+
+        Serial.println();
+        Serial.println("*********");
+      }
+    }
+};
+
+void setup() {
+  Serial.begin(115200);
+
+  pinMode(LED, OUTPUT);
+
+  // Create the BLE Device
+  BLEDevice::init("ESP32 UART Test"); // Give it a name
+
+  Serial.print("local BLE Address is: ");
+  Serial.println(BLEDevice::getAddress().toString().c_str());
+
+  // Create the BLE Server
+  BLEServer *pServer = BLEDevice::createServer();
+  pServer->setCallbacks(new MyServerCallbacks());
+
+  // Create the BLE Service
+  BLEService *pService = pServer->createService(SERVICE_UUID);
+
+
+  // BleCharacteristic deviceHealthCharacteristic("deviceHealth", BleCharacteristicProperty::READ, deviceHealthUuid, serviceUuid);
+  // see https://community.thunkable.com/t/ble-hrm-reading-bpm/2441754/6
+
+
+  // Create a BLE Characteristic
+  pCharacteristic = pService->createCharacteristic(
+                      CHARACTERISTIC_UUID_TX,
+                      //BLECharacteristic::PROPERTY_NOTIFY
+                      BLECharacteristic::PROPERTY_READ
+                    );
+
+  pCharacteristic->addDescriptor(new BLE2902());
+
+  BLECharacteristic *pCharacteristic = pService->createCharacteristic(
+                                         CHARACTERISTIC_UUID_RX,
+                                         BLECharacteristic::PROPERTY_WRITE
+                                       );
+
+  pCharacteristic->setCallbacks(new MyCallbacks());
+
+  // Start the service
+  pService->start();
+
+  // Start advertising
+  pServer->getAdvertising()->start();
+  Serial.println("Waiting a client connection to notify...");
+}
+
+void loop() {
+
+  if (deviceConnected) {
+    // Fabricate some arbitrary junk for now...
+    txValue = analogRead(readPin) / 3.456; // This could be an actual sensor reading!
+
+    // Let's convert the value to a char array:
+    char txString[8]; // make sure this is big enuffz
+    dtostrf(txValue, 1, 2, txString); // float_val, min_width, digits_after_decimal, char_buffer
+
+//    pCharacteristic->setValue(&txValue, 1); // To send the integer value
+    pCharacteristic->setValue("Hello!"); // Sending a test message
+    //pCharacteristic->setValue(txString);
+
+    pCharacteristic->notify(); // Send the value to the app!
+    Serial.print("*** Sent Value: ");
+    Serial.print(txString);
+    Serial.println(" ***");
+
+    // You can add the rxValue checks down here instead
+    // if you set "rxValue" as a global var at the top!
+    // Note you will have to delete "std::string" declaration
+    // of "rxValue" in the callback function.
+//    if (rxValue.find("A") != -1) { 
+//      Serial.println("Turning ON!");
+//      digitalWrite(LED, HIGH);
+//    }
+//    else if (rxValue.find("B") != -1) {
+//      Serial.println("Turning OFF!");
+//      digitalWrite(LED, LOW);
+//    }
+  }
+
+  delay(1000);
+}
+
+
+
// Adapted from https://github.com/SIMS-IOT-Devices/FreeRTOS-ESP-IDF-BLE-Server
+
+#include <stdio.h>
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "freertos/event_groups.h"
+#include "esp_event.h"
+#include "nvs_flash.h"
+#include "esp_log.h"
+#include "esp_nimble_hci.h"
+#include "nimble/nimble_port.h"
+#include "nimble/nimble_port_freertos.h"
+#include "host/ble_hs.h"
+#include "services/gap/ble_svc_gap.h"
+#include "services/gatt/ble_svc_gatt.h"
+#include "sdkconfig.h"
+
+char *TAG = "BLE-Server";
+uint8_t ble_addr_type;
+void ble_app_advertise(void);
+
+//@_____________________Define UUIDs______________________________________
+//!! b2bbc642-46da-11ed-b878-0242ac120002
+//static const ble_uuid128_t gatt_svr_svc_uuid =
+//    BLE_UUID128_INIT(0x02, 0x00, 0x12, 0xac, 0x42, 0x02, 0x78, 0xb8, 0xed, 0x11, 0xda, 0x46, 0x42, 0xc6, 0xbb, 0xb2);
+
+static const ble_uuid128_t gatt_svr_svc_uuid =
+    BLE_UUID128_INIT(0x9e, 0xca, 0xdc, 0x24, 0x0e, 0xe5, 0xa9, 0xe0, 0x93, 0xf3, 0xa3, 0xb5, 0x01, 0x00, 0x40, 0x6e);
+
+
+//!! c9af9c76-46de-11ed-b878-0242ac120002
+//static const ble_uuid128_t gatt_svr_chr_uuid =
+//    BLE_UUID128_INIT(0x02, 0x00, 0x12, 0xac, 0x42, 0x02, 0x78, 0xb8, 0xed, 0x11, 0xde, 0x46, 0x76, 0x9c, 0xaf, 0xc9);
+
+// 6E400002-B5A3-F393-E0A9-E50E24DCCA9E    
+
+static const ble_uuid128_t gatt_svr_chr_read_uuid =
+    BLE_UUID128_INIT(0x9e, 0xca, 0xdc, 0x24, 0x0e, 0xe5, 0xa9, 0xe0, 0x93, 0xf3, 0xa3, 0xb5, 0x02, 0x00, 0x40, 0x6e);
+static const ble_uuid128_t gatt_svr_chr_write_uuid =
+    BLE_UUID128_INIT(0x9e, 0xca, 0xdc, 0x24, 0x0e, 0xe5, 0xa9, 0xe0, 0x93, 0xf3, 0xa3, 0xb5, 0x03, 0x00, 0x40, 0x6e);
+
+
+// Write data to ESP32 defined as server
+static int device_write(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg)
+{
+    printf("Data from the client: %.*s\n", ctxt->om->om_len, ctxt->om->om_data);
+    return 0;
+}
+
+// Read data from ESP32 defined as server
+static int device_read(uint16_t con_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg)
+{
+    os_mbuf_append(ctxt->om, "Data from the server", strlen("Data from the server"));
+    return 0;
+}
+
+static const struct ble_gatt_svc_def gatt_svcs[] = {
+    {.type = BLE_GATT_SVC_TYPE_PRIMARY,
+     .uuid = &gatt_svr_svc_uuid.u,                      // Define UUID for device type
+     .characteristics = (struct ble_gatt_chr_def[]){
+         {.uuid = &gatt_svr_chr_write_uuid.u,           // Define UUID for reading
+          .flags = BLE_GATT_CHR_F_READ,
+          .access_cb = device_read},
+         {.uuid = &gatt_svr_chr_read_uuid.u,           // Define UUID for writing
+          .flags = BLE_GATT_CHR_F_WRITE,
+          .access_cb = device_write},
+         {0}}},
+    {0}};
+
+
+
+// BLE event handling
+static int ble_gap_event(struct ble_gap_event *event, void *arg)
+{
+    switch (event->type)
+    {
+    // Advertise if connected
+    case BLE_GAP_EVENT_CONNECT:
+        ESP_LOGI("GAP", "BLE GAP EVENT CONNECT %s", event->connect.status == 0 ? "OK!" : "FAILED!");
+        if (event->connect.status != 0)
+        {
+            ble_app_advertise();
+        }
+        break;
+    // Advertise again after completion of the event
+    case BLE_GAP_EVENT_ADV_COMPLETE:
+        ESP_LOGI("GAP", "BLE GAP EVENT");
+        ble_app_advertise();
+        break;
+    default:
+        break;
+    }
+    return 0;
+}
+
+// Define the BLE connection
+void ble_app_advertise(void)
+{
+    // GAP - device name definition
+    struct ble_hs_adv_fields fields;
+    const char *device_name;
+    memset(&fields, 0, sizeof(fields));
+    device_name = ble_svc_gap_device_name(); // Read the BLE device name
+    fields.name = (uint8_t *)device_name;
+    fields.name_len = strlen(device_name);
+    fields.name_is_complete = 1;
+    ble_gap_adv_set_fields(&fields);
+
+    // GAP - device connectivity definition
+    struct ble_gap_adv_params adv_params;
+    memset(&adv_params, 0, sizeof(adv_params));
+    adv_params.conn_mode = BLE_GAP_CONN_MODE_UND; // connectable or non-connectable
+    adv_params.disc_mode = BLE_GAP_DISC_MODE_GEN; // discoverable or non-discoverable
+    ble_gap_adv_start(ble_addr_type, NULL, BLE_HS_FOREVER, &adv_params, ble_gap_event, NULL);
+}
+
+// The application
+void ble_app_on_sync(void)
+{
+    ble_hs_id_infer_auto(0, &ble_addr_type); // Determines the best address type automatically
+    ble_app_advertise();                     // Define the BLE connection
+}
+
+// The infinite task
+void host_task(void *param)
+{
+    nimble_port_run(); // This function will return only when nimble_port_stop() is executed
+}
+
+void app_main()
+{
+    nvs_flash_init();                          // 1 - Initialize NVS flash using
+    //esp_nimble_hci_and_controller_init();      // 2 - Initialize ESP controller
+    nimble_port_init();                        // 3 - Initialize the host stack
+    ble_svc_gap_device_name_set("BLE-Server"); // 4 - Initialize NimBLE configuration - server name
+    ble_svc_gap_init();                        // 4 - Initialize NimBLE configuration - gap service
+    ble_svc_gatt_init();                       // 4 - Initialize NimBLE configuration - gatt service
+    ble_gatts_count_cfg(gatt_svcs);            // 4 - Initialize NimBLE configuration - config gatt services
+    ble_gatts_add_svcs(gatt_svcs);             // 4 - Initialize NimBLE configuration - queues gatt services.
+    ble_hs_cfg.sync_cb = ble_app_on_sync;      // 5 - Initialize application
+    nimble_port_freertos_init(host_task);      // 6 - Run the thread
+}
+
+ + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file