addd ssd1306 lib

This commit is contained in:
2021-06-14 08:24:14 +02:00
parent 5c031f67ca
commit 5a5f977a5f
136 changed files with 17364 additions and 168 deletions

14
P1_gateway_FW/.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,14 @@
{
"files.associations": {
"*.tcc": "cpp",
"istream": "cpp",
"ostream": "cpp",
"array": "cpp",
"deque": "cpp",
"list": "cpp",
"string": "cpp",
"unordered_map": "cpp",
"vector": "cpp",
"initializer_list": "cpp"
}
}

View File

@@ -0,0 +1,43 @@
/**
* Arduino DSMR parser.
*
* This software is licensed under the MIT License.
*
* Copyright (c) 2015 Matthijs Kooijman <matthijs@stdin.nl>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* Main included file. If you include this, you'll get everything,
* imported into global scope
*/
#ifndef DSMR_INCLUDE_DSMR_H
#define DSMR_INCLUDE_DSMR_H
#include "../lib/dsmr/parser.h"
#include "../lib/dsmr/reader.h"
#include "../lib/dsmr/fields.h"
// Allow using everything without the namespace prefixes
using namespace dsmr;
using namespace dsmr::fields;
#endif // DSMR_INCLUDE_DSMR_H

View File

@@ -0,0 +1,7 @@
[Makefile]
indent_style = tab
indent_size = 4
[src/*.h,src/*.cpp,examples/**.ino]
indent_style = space
indent_size = 2

2
P1_gateway_FW/lib/MQTT/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
.DS_Store
cmake-build-debug/

View File

@@ -0,0 +1 @@
{"type": "library", "name": "MQTT", "version": "2.4.8", "spec": {"owner": "256dpi", "id": 617, "name": "MQTT", "requirements": null, "url": null}}

View File

@@ -0,0 +1,46 @@
language: generic
env:
global:
- IDE_VERSION=1.8.7
matrix:
- EXAMPLE="AdafruitHuzzahESP8266" BOARD="esp8266:esp8266:huzzah:eesz=4M3M,xtal=80"
- EXAMPLE="AdafruitHuzzahESP8266Secure" BOARD="esp8266:esp8266:huzzah:eesz=4M3M,xtal=80"
- EXAMPLE="ArduinoEthernetShield" BOARD="arduino:avr:uno"
- EXAMPLE="ArduinoMKRGSM1400" BOARD="arduino:samd:mkrgsm1400"
- EXAMPLE="ArduinoMKRGSM1400Secure" BOARD="arduino:samd:mkrgsm1400"
- EXAMPLE="ArduinoWiFi101Secure" BOARD="arduino:avr:uno"
- EXAMPLE="ArduinoWiFiShield" BOARD="arduino:avr:uno"
- EXAMPLE="ArduinoYun" BOARD="arduino:avr:yun"
- EXAMPLE="ArduinoYunSecure" BOARD="arduino:avr:yun"
- EXAMPLE="ESP32DevelopmentBoard" BOARD="espressif:esp32:esp32:FlashFreq=80"
- EXAMPLE="ESP32DevelopmentBoardSecure" BOARD="espressif:esp32:esp32:FlashFreq=80"
before_install:
- /sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_1.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :1 -ac -screen 0 1280x1024x16
- sleep 3
- export DISPLAY=:1.0
- wget http://downloads.arduino.cc/arduino-$IDE_VERSION-linux64.tar.xz
- tar xf arduino-$IDE_VERSION-linux64.tar.xz
- mv arduino-$IDE_VERSION ~/arduino-ide
- export PATH=$PATH:~/arduino-ide
- if [[ "$BOARD" =~ "esp8266:esp8266:" ]]; then
arduino --pref "boardsmanager.additional.urls=http://arduino.esp8266.com/stable/package_esp8266com_index.json" --install-boards esp8266:esp8266;
arduino --pref "boardsmanager.additional.urls=" --save-prefs;
fi
- if [[ "$BOARD" =~ "espressif:esp32:" ]]; then
mkdir -p ~/Arduino/hardware/espressif &&
cd ~/Arduino/hardware/espressif &&
git clone https://github.com/espressif/arduino-esp32.git esp32 &&
cd esp32/tools/ &&
python get.py &&
cd $TRAVIS_BUILD_DIR;
fi
- if [[ "$BOARD" =~ "arduino:samd:mkrgsm1400" ]]; then
arduino --install-boards arduino:samd;
arduino --install-library MKRGSM;
fi
- arduino --install-library WiFi101
install:
- mkdir -p ~/Arduino/libraries
- ln -s $PWD ~/Arduino/libraries/.
script:
- arduino --verbose-build --verify --board $BOARD $PWD/examples/$EXAMPLE/$EXAMPLE.ino;

View File

@@ -0,0 +1,39 @@
# Uncompilable CMake File to enable project editing with CLion IDE
cmake_minimum_required(VERSION 3.13)
project(arduino-mqtt)
include_directories(
/Applications/Arduino.app/Contents/Java/hardware/arduino/avr/cores/arduino
/Users/256dpi/Development/Arduino/libraries/Ethernet/src
/Users/256dpi/Development/Arduino/libraries/WiFi101/src
/Users/256dpi/Development/Arduino/libraries/MKRGSM/src
/Applications/Arduino.app/Contents/Java/libraries/Bridge/src
/Users/256dpi/Library/Arduino15/packages/esp8266/hardware/esp8266/2.3.0/libraries/ESP8266WiFi/src
/Users/256dpi/Library/Arduino15/packages/esp32/libraries/WiFi/src
/Users/256dpi/Library/Arduino15/packages/esp32/libraries/WiFiClientSecure/src
src)
include_directories(src/)
set(CMAKE_CXX_FLAGS -std=c++11)
set(SOURCE_FILES
examples/AdafruitHuzzahESP8266/AdafruitHuzzahESP8266.ino
examples/AdafruitHuzzahESP8266Secure/AdafruitHuzzahESP8266Secure.ino
examples/ArduinoEthernetShield/ArduinoEthernetShield.ino
examples/ArduinoMKRGSM1400/ArduinoMKRGSM1400.ino
examples/ArduinoMKRGSM1400Secure/ArduinoMKRGSM1400Secure.ino
examples/ArduinoWiFi101/ArduinoWiFi101.ino
examples/ArduinoWiFi101Secure/ArduinoWiFi101Secure.ino
examples/ArduinoWiFiShield/ArduinoWiFiShield.ino
examples/ArduinoYun/ArduinoYun.ino
examples/ArduinoYunSecure/ArduinoYunSecure.ino
examples/ESP32DevelopmentBoard/ESP32DevelopmentBoard.ino
examples/ESP32DevelopmentBoardSecure/ESP32DevelopmentBoardSecure.ino
src/lwmqtt
src/MQTT.h
src/MQTTClient.h
src/MQTTClient.cpp)
add_executable(arduino-mqtt ${SOURCE_FILES})

View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2015 Joël Gähwiler
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,14 @@
all: fmt
fmt:
clang-format -i src/*.h src/*.cpp -style="{BasedOnStyle: Google, ColumnLimit: 120}"
update:
rm -rf ./lwmqtt
git clone --branch v0.6.4 https://github.com/256dpi/lwmqtt.git ./lwmqtt
mkdir -p ./src/lwmqtt
cp -r ./lwmqtt/src/*.c ./src/lwmqtt/
cp -r ./lwmqtt/src/*.h ./src/lwmqtt/
cp -r ./lwmqtt/include/*.h ./src/lwmqtt/
rm -rf ./lwmqtt
sed -i '' "s/<lwmqtt.h>/\"lwmqtt.h\"/g" ./src/lwmqtt/*

View File

@@ -0,0 +1,260 @@
# arduino-mqtt
[![Build Status](https://travis-ci.org/256dpi/arduino-mqtt.svg?branch=master)](https://travis-ci.org/256dpi/arduino-mqtt)
[![GitHub release](https://img.shields.io/github/release/256dpi/arduino-mqtt.svg)](https://github.com/256dpi/arduino-mqtt/releases)
This library bundles the [lwmqtt](https://github.com/256dpi/lwmqtt) MQTT 3.1.1 client and adds a thin wrapper to get an Arduino like API.
Download the latest version from the [release](https://github.com/256dpi/arduino-mqtt/releases) section. Or even better use the builtin Library Manager in the Arduino IDE and search for "MQTT".
The library is also available on [PlatformIO](https://platformio.org/lib/show/617/MQTT). You can install it by running: `pio lib install "MQTT"`.
## Compatibility
The following examples show how you can use the library with various Arduino compatible hardware:
- [Arduino Yun & Yun-Shield](https://github.com/256dpi/arduino-mqtt/blob/master/examples/ArduinoYun/ArduinoYun.ino) ([Secure](https://github.com/256dpi/arduino-mqtt/blob/master/examples/ArduinoYunSecure/ArduinoYunSecure.ino))
- [Arduino Ethernet Shield](https://github.com/256dpi/arduino-mqtt/blob/master/examples/ArduinoEthernetShield/ArduinoEthernetShield.ino)
- [Arduino WiFi Shield](https://github.com/256dpi/arduino-mqtt/blob/master/examples/ArduinoWiFiShield/ArduinoWiFiShield.ino)
- [Adafruit HUZZAH ESP8266](https://github.com/256dpi/arduino-mqtt/blob/master/examples/AdafruitHuzzahESP8266/AdafruitHuzzahESP8266.ino) ([Secure](https://github.com/256dpi/arduino-mqtt/blob/master/examples/AdafruitHuzzahESP8266Secure/AdafruitHuzzahESP8266Secure.ino))
- [Arduino/Genuino WiFi101 Shield](https://github.com/256dpi/arduino-mqtt/blob/master/examples/ArduinoWiFi101/ArduinoWiFi101.ino) ([Secure](https://github.com/256dpi/arduino-mqtt/blob/master/examples/ArduinoWiFi101Secure/ArduinoWiFi101Secure.ino))
- [Arduino MKR GSM 1400](https://github.com/256dpi/arduino-mqtt/blob/master/examples/ArduinoMKRGSM1400/ArduinoMKRGSM1400.ino) ([Secure](https://github.com/256dpi/arduino-mqtt/blob/master/examples/ArduinoMKRGSM1400Secure/ArduinoMKRGSM1400Secure.ino))
- [ESP32 Development Board](https://github.com/256dpi/arduino-mqtt/blob/master/examples/ESP32DevelopmentBoard/ESP32DevelopmentBoard.ino) ([Secure](https://github.com/256dpi/arduino-mqtt/blob/master/examples/ESP32DevelopmentBoardSecure/ESP32DevelopmentBoardSecure.ino))
Other shields and boards should also work if they provide a [Client](https://www.arduino.cc/en/Reference/ClientConstructor) based network implementation.
**Check out the [Wiki](https://github.com/256dpi/arduino-mqtt/wiki) to find more examples.**
## Notes
- The maximum size for packets being published and received is set by default to 128 bytes. To change the buffer sizes, you need to use `MQTTClient client(256)` instead of just `MQTTClient client` on the top of your sketch. The passed value denotes the read and write buffer size.
- On the ESP8266 it has been reported that an additional `delay(10);` after `client.loop();` fixes many stability issues with WiFi connections.
- To use the library with shiftr.io, you need to provide the token key (username) and token secret (password) as the second and third argument to `client.connect(name, key, secret)`.
## Example
The following example uses an Arduino MKR1000 to connect to shiftr.io. You can check on your device after a successful connection here: https://shiftr.io/try.
```c++
#include <SPI.h>
#include <WiFi101.h>
#include <MQTT.h>
const char ssid[] = "ssid";
const char pass[] = "pass";
WiFiClient net;
MQTTClient client;
unsigned long lastMillis = 0;
void connect() {
Serial.print("checking wifi...");
while (WiFi.status() != WL_CONNECTED) {
Serial.print(".");
delay(1000);
}
Serial.print("\nconnecting...");
while (!client.connect("arduino", "try", "try")) {
Serial.print(".");
delay(1000);
}
Serial.println("\nconnected!");
client.subscribe("/hello");
// client.unsubscribe("/hello");
}
void messageReceived(String &topic, String &payload) {
Serial.println("incoming: " + topic + " - " + payload);
// Note: Do not use the client in the callback to publish, subscribe or
// unsubscribe as it may cause deadlocks when other things arrive while
// sending and receiving acknowledgments. Instead, change a global variable,
// or push to a queue and handle it in the loop after calling `client.loop()`.
}
void setup() {
Serial.begin(115200);
WiFi.begin(ssid, pass);
// Note: Local domain names (e.g. "Computer.local" on OSX) are not supported
// by Arduino. You need to set the IP address directly.
client.begin("broker.shiftr.io", net);
client.onMessage(messageReceived);
connect();
}
void loop() {
client.loop();
if (!client.connected()) {
connect();
}
// publish a message roughly every second.
if (millis() - lastMillis > 1000) {
lastMillis = millis();
client.publish("/hello", "world");
}
}
```
## API
Initialize the object using the hostname of the broker, the brokers port (default: `1883`) and the underlying Client class for network transport:
```c++
void begin(Client &client);
void begin(const char hostname[], Client &client);
void begin(const char hostname[], int port, Client &client);
void begin(IPAddress address, Client &client);
void begin(IPAddress address, int port, Client &client);
```
- Specify port `8883` when using secure clients for encrypted connections.
- Local domain names (e.g. `Computer.local` on OSX) are not supported by Arduino. You need to set the IP address directly.
The hostname and port can also be changed after calling `begin()`:
```c++
void setHost(const char hostname[]);
void setHost(const char hostname[], int port);
void setHost(IPAddress address);
void setHost(IPAddress address, int port);
```
Set a will message (last testament) that gets registered on the broker after connecting. `setWill()` has to be called before calling `connect()`:
```c++
void setWill(const char topic[]);
void setWill(const char topic[], const char payload[]);
void setWill(const char topic[], const char payload[], bool retained, int qos);
void clearWill();
```
Register a callback to receive messages:
```c++
void onMessage(MQTTClientCallbackSimple);
// Callback signature: void messageReceived(String &topic, String &payload) {}
void onMessageAdvanced(MQTTClientCallbackAdvanced);
// Callback signature: void messageReceived(MQTTClient *client, char topic[], char payload[], int payload_length) {}
```
- The set callback is mostly called during a call to `loop()` but may also be called during a call to `subscribe()`, `unsubscribe()` or `publish() // QoS > 0` if messages have been received before receiving the required acknowledgement. Therefore, it is strongly recommended to not call `subscribe()`, `unsubscribe()` or `publish() // QoS > 0` directly in the callback.
- In case you need a reference to an object that manages the client, use the `void * ref` property on the client to store a pointer, and access it directly from the advanced callback..
Set more advanced options:
```c++
void setKeepAlive(int keepAlive);
void setCleanSession(bool cleanSession);
void setTimeout(int timeout);
void setOptions(int keepAlive, bool cleanSession, int timeout);
```
- The `keepAlive` option controls the keep alive interval in seconds (default: 10).
- The `cleanSession` option controls the session retention on the broker side (default: true).
- The `timeout` option controls the default timeout for all commands in milliseconds (default: 1000).
Set a custom clock source "custom millis" callback to enable deep sleep applications:
```c++
void setClockSource(MQTTClientClockSource);
// Callback signature: uint32_t clockSource() {}
```
- The specified callback is used by the internal timers to get a monotonic time in milliseconds. Since the clock source for the built-in `millis` is stopped when the the Arduino goes into deep sleep, you need to provide a custom callback that first syncs with a built-in or external Real Time Clock (RTC). You can pass `NULL` to reset to the default implementation.
Connect to broker using the supplied client id and an optional username and password:
```c++
bool connect(const char clientID[], bool skip = false);
bool connect(const char clientID[], const char username[], bool skip = false);
bool connect(const char clientID[], const char username[], const char password[], bool skip = false);
```
- If the `skip` option is set to true, the client will skip the network level connection and jump to the MQTT level connection. This option can be used in order to establish and verify TLS connections manually before giving control to the MQTT client.
- The functions return a boolean that indicates if the connection has been established successfully (true).
Publishes a message to the broker with an optional payload:
```c++
bool publish(const String &topic);
bool publish(const char topic[]);
bool publish(const String &topic, const String &payload);
bool publish(const String &topic, const String &payload, bool retained, int qos);
bool publish(const char topic[], const String &payload);
bool publish(const char topic[], const String &payload, bool retained, int qos);
bool publish(const char topic[], const char payload[]);
bool publish(const char topic[], const char payload[], bool retained, int qos);
bool publish(const char topic[], const char payload[], int length);
bool publish(const char topic[], const char payload[], int length, bool retained, int qos);
```
- The functions return a boolean that indicates if the publish has been successful (true).
Subscribe to a topic:
```c++
bool subscribe(const String &topic);
bool subscribe(const String &topic, int qos);
bool subscribe(const char topic[]);
bool subscribe(const char topic[], int qos);
```
- The functions return a boolean that indicates if the subscribe has been successful (true).
Unsubscribe from a topic:
```c++
bool unsubscribe(const String &topic);
bool unsubscribe(const char topic[]);
```
- The functions return a boolean that indicates if the unsubscribe has been successful (true).
Sends and receives packets:
```c++
bool loop();
```
- This function should be called in every `loop`.
- The function returns a boolean that indicates if the loop has been successful (true).
Check if the client is currently connected:
```c++
bool connected();
```
Access low-level information for debugging:
```c++
lwmqtt_err_t lastError();
lwmqtt_return_code_t returnCode();
```
- The error codes can be found [here](https://github.com/256dpi/lwmqtt/blob/master/include/lwmqtt.h#L11).
- The return codes can be found [here](https://github.com/256dpi/lwmqtt/blob/master/include/lwmqtt.h#L243).
Disconnect from the broker:
```c++
bool disconnect();
```
- The function returns a boolean that indicates if the disconnect has been successful (true).
## Release Management
- Update version in `library.properties`.
- Create release on GitHub.

View File

@@ -0,0 +1,74 @@
// This example uses an Adafruit Huzzah ESP8266
// to connect to shiftr.io.
//
// You can check on your device after a successful
// connection here: https://shiftr.io/try.
//
// by Joël Gähwiler
// https://github.com/256dpi/arduino-mqtt
#include <ESP8266WiFi.h>
#include <MQTT.h>
const char ssid[] = "ssid";
const char pass[] = "pass";
WiFiClient net;
MQTTClient client;
unsigned long lastMillis = 0;
void connect() {
Serial.print("checking wifi...");
while (WiFi.status() != WL_CONNECTED) {
Serial.print(".");
delay(1000);
}
Serial.print("\nconnecting...");
while (!client.connect("arduino", "try", "try")) {
Serial.print(".");
delay(1000);
}
Serial.println("\nconnected!");
client.subscribe("/hello");
// client.unsubscribe("/hello");
}
void messageReceived(String &topic, String &payload) {
Serial.println("incoming: " + topic + " - " + payload);
// Note: Do not use the client in the callback to publish, subscribe or
// unsubscribe as it may cause deadlocks when other things arrive while
// sending and receiving acknowledgments. Instead, change a global variable,
// or push to a queue and handle it in the loop after calling `client.loop()`.
}
void setup() {
Serial.begin(115200);
WiFi.begin(ssid, pass);
// Note: Local domain names (e.g. "Computer.local" on OSX) are not supported
// by Arduino. You need to set the IP address directly.
client.begin("broker.shiftr.io", net);
client.onMessage(messageReceived);
connect();
}
void loop() {
client.loop();
delay(10); // <- fixes some issues with WiFi stability
if (!client.connected()) {
connect();
}
// publish a message roughly every second.
if (millis() - lastMillis > 1000) {
lastMillis = millis();
client.publish("/hello", "world");
}
}

View File

@@ -0,0 +1,81 @@
// This example uses an Adafruit Huzzah ESP8266
// to connect to shiftr.io.
//
// You can check on your device after a successful
// connection here: https://shiftr.io/try.
//
// by Joël Gähwiler
// https://github.com/256dpi/arduino-mqtt
#include <ESP8266WiFi.h>
#include <MQTT.h>
const char ssid[] = "ssid";
const char pass[] = "pass";
WiFiClientSecure net;
MQTTClient client;
unsigned long lastMillis = 0;
void connect() {
Serial.print("checking wifi...");
while (WiFi.status() != WL_CONNECTED) {
Serial.print(".");
delay(1000);
}
// do not verify tls certificate
// check the following example for methods to verify the server:
// https://github.com/esp8266/Arduino/blob/master/libraries/ESP8266WiFi/examples/BearSSL_Validation/BearSSL_Validation.ino
net.setInsecure();
Serial.print("\nconnecting...");
while (!client.connect("arduino", "try", "try")) {
Serial.print(".");
delay(1000);
}
Serial.println("\nconnected!");
client.subscribe("/hello");
// client.unsubscribe("/hello");
}
void messageReceived(String &topic, String &payload) {
Serial.println("incoming: " + topic + " - " + payload);
// Note: Do not use the client in the callback to publish, subscribe or
// unsubscribe as it may cause deadlocks when other things arrive while
// sending and receiving acknowledgments. Instead, change a global variable,
// or push to a queue and handle it in the loop after calling `client.loop()`.
}
void setup() {
Serial.begin(115200);
WiFi.begin(ssid, pass);
// Note: Local domain names (e.g. "Computer.local" on OSX) are not supported
// by Arduino. You need to set the IP address directly.
//
// MQTT brokers usually use port 8883 for secure connections.
client.begin("broker.shiftr.io", 8883, net);
client.onMessage(messageReceived);
connect();
}
void loop() {
client.loop();
delay(10); // <- fixes some issues with WiFi stability
if (!client.connected()) {
connect();
}
// publish a message roughly every second.
if (millis() - lastMillis > 1000) {
lastMillis = millis();
client.publish("/hello", "world");
}
}

View File

@@ -0,0 +1,67 @@
// This example uses an Arduino Uno together with
// an Ethernet Shield to connect to shiftr.io.
//
// You can check on your device after a successful
// connection here: https://shiftr.io/try.
//
// by Joël Gähwiler
// https://github.com/256dpi/arduino-mqtt
#include <Ethernet.h>
#include <MQTT.h>
byte mac[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED};
byte ip[] = {192, 168, 1, 177}; // <- change to match your network
EthernetClient net;
MQTTClient client;
unsigned long lastMillis = 0;
void connect() {
Serial.print("connecting...");
while (!client.connect("arduino", "try", "try")) {
Serial.print(".");
delay(1000);
}
Serial.println("\nconnected!");
client.subscribe("/hello");
// client.unsubscribe("/hello");
}
void messageReceived(String &topic, String &payload) {
Serial.println("incoming: " + topic + " - " + payload);
// Note: Do not use the client in the callback to publish, subscribe or
// unsubscribe as it may cause deadlocks when other things arrive while
// sending and receiving acknowledgments. Instead, change a global variable,
// or push to a queue and handle it in the loop after calling `client.loop()`.
}
void setup() {
Serial.begin(115200);
Ethernet.begin(mac, ip);
// Note: Local domain names (e.g. "Computer.local" on OSX) are not supported
// by Arduino. You need to set the IP address directly.
client.begin("broker.shiftr.io", net);
client.onMessage(messageReceived);
connect();
}
void loop() {
client.loop();
if (!client.connected()) {
connect();
}
// publish a message roughly every second.
if (millis() - lastMillis > 1000) {
lastMillis = millis();
client.publish("/hello", "world");
}
}

View File

@@ -0,0 +1,89 @@
// This example uses an Arduino MKR GSM 1400 board
// to connect to shiftr.io.
//
// IMPORTANT: This example uses the new MKRGSM library.
//
// You can check on your device after a successful
// connection here: https://shiftr.io/try.
//
// by Sandeep Mistry
// https://github.com/256dpi/arduino-mqtt
#include <MKRGSM.h>
#include <MQTT.h>
const char pin[] = "";
const char apn[] = "apn";
const char login[] = "login";
const char password[] = "password";
GSMClient net;
GPRS gprs;
GSM gsmAccess;
MQTTClient client;
unsigned long lastMillis = 0;
void connect() {
// connection state
bool connected = false;
Serial.print("connecting to cellular network ...");
// After starting the modem with gsmAccess.begin()
// attach to the GPRS network with the APN, login and password
while (!connected) {
if ((gsmAccess.begin(pin) == GSM_READY) &&
(gprs.attachGPRS(apn, login, password) == GPRS_READY)) {
connected = true;
} else {
Serial.print(".");
delay(1000);
}
}
Serial.print("\nconnecting...");
while (!client.connect("arduino", "try", "try")) {
Serial.print(".");
delay(1000);
}
Serial.println("\nconnected!");
client.subscribe("/hello");
// client.unsubscribe("/hello");
}
void messageReceived(String &topic, String &payload) {
Serial.println("incoming: " + topic + " - " + payload);
// Note: Do not use the client in the callback to publish, subscribe or
// unsubscribe as it may cause deadlocks when other things arrive while
// sending and receiving acknowledgments. Instead, change a global variable,
// or push to a queue and handle it in the loop after calling `client.loop()`.
}
void setup() {
Serial.begin(115200);
// Note: Local domain names (e.g. "Computer.local" on OSX) are not supported
// by Arduino. You need to set the IP address directly.
client.begin("broker.shiftr.io", net);
client.onMessage(messageReceived);
connect();
}
void loop() {
client.loop();
if (!client.connected()) {
connect();
}
// publish a message roughly every second.
if (millis() - lastMillis > 1000) {
lastMillis = millis();
client.publish("/hello", "world");
}
}

View File

@@ -0,0 +1,91 @@
// This example uses an Arduino MKR GSM 1400 board
// to securely connect to shiftr.io.
//
// IMPORTANT: This example uses the new MKRGSM library.
//
// You can check on your device after a successful
// connection here: https://shiftr.io/try.
//
// by Sandeep Mistry
// https://github.com/256dpi/arduino-mqtt
#include <MKRGSM.h>
#include <MQTT.h>
const char pin[] = "";
const char apn[] = "apn";
const char login[] = "login";
const char password[] = "password";
GSMSSLClient net;
GPRS gprs;
GSM gsmAccess;
MQTTClient client;
unsigned long lastMillis = 0;
void connect() {
// connection state
bool connected = false;
Serial.print("connecting to cellular network ...");
// After starting the modem with gsmAccess.begin()
// attach to the GPRS network with the APN, login and password
while (!connected) {
if ((gsmAccess.begin(pin) == GSM_READY) &&
(gprs.attachGPRS(apn, login, password) == GPRS_READY)) {
connected = true;
} else {
Serial.print(".");
delay(1000);
}
}
Serial.print("\nconnecting...");
while (!client.connect("arduino", "try", "try")) {
Serial.print(".");
delay(1000);
}
Serial.println("\nconnected!");
client.subscribe("/hello");
// client.unsubscribe("/hello");
}
void messageReceived(String &topic, String &payload) {
Serial.println("incoming: " + topic + " - " + payload);
// Note: Do not use the client in the callback to publish, subscribe or
// unsubscribe as it may cause deadlocks when other things arrive while
// sending and receiving acknowledgments. Instead, change a global variable,
// or push to a queue and handle it in the loop after calling `client.loop()`.
}
void setup() {
Serial.begin(115200);
// Note: Local domain names (e.g. "Computer.local" on OSX) are not supported
// by Arduino. You need to set the IP address directly.
//
// MQTT brokers usually use port 8883 for secure connections.
client.begin("broker.shiftr.io", 8883, net);
client.onMessage(messageReceived);
connect();
}
void loop() {
client.loop();
if (!client.connected()) {
connect();
}
// publish a message roughly every second.
if (millis() - lastMillis > 1000) {
lastMillis = millis();
client.publish("/hello", "world");
}
}

View File

@@ -0,0 +1,75 @@
// This example uses an Arduino/Genuino Zero together with
// a WiFi101 Shield or a MKR1000 to connect to shiftr.io.
//
// IMPORTANT: This example uses the new WiFi101 library.
//
// You can check on your device after a successful
// connection here: https://shiftr.io/try.
//
// by Gilberto Conti
// https://github.com/256dpi/arduino-mqtt
#include <WiFi101.h>
#include <MQTT.h>
const char ssid[] = "ssid";
const char pass[] = "pass";
WiFiClient net;
MQTTClient client;
unsigned long lastMillis = 0;
void connect() {
Serial.print("checking wifi...");
while (WiFi.status() != WL_CONNECTED) {
Serial.print(".");
delay(1000);
}
Serial.print("\nconnecting...");
while (!client.connect("arduino", "try", "try")) {
Serial.print(".");
delay(1000);
}
Serial.println("\nconnected!");
client.subscribe("/hello");
// client.unsubscribe("/hello");
}
void messageReceived(String &topic, String &payload) {
Serial.println("incoming: " + topic + " - " + payload);
// Note: Do not use the client in the callback to publish, subscribe or
// unsubscribe as it may cause deadlocks when other things arrive while
// sending and receiving acknowledgments. Instead, change a global variable,
// or push to a queue and handle it in the loop after calling `client.loop()`.
}
void setup() {
Serial.begin(115200);
WiFi.begin(ssid, pass);
// Note: Local domain names (e.g. "Computer.local" on OSX) are not supported
// by Arduino. You need to set the IP address directly.
client.begin("broker.shiftr.io", net);
client.onMessage(messageReceived);
connect();
}
void loop() {
client.loop();
if (!client.connected()) {
connect();
}
// publish a message roughly every second.
if (millis() - lastMillis > 1000) {
lastMillis = millis();
client.publish("/hello", "world");
}
}

View File

@@ -0,0 +1,80 @@
// This example uses an Arduino/Genuino Zero together with
// a WiFi101 Shield or a MKR1000 to connect to shiftr.io.
//
// IMPORTANT: This example uses the new WiFi101 library.
//
// IMPORTANT: You need to install/update the SSL certificates first:
// https://github.com/arduino-libraries/WiFi101-FirmwareUpdater#to-update-ssl-certificates
//
// You can check on your device after a successful
// connection here: https://shiftr.io/try.
//
// by Gilberto Conti
// https://github.com/256dpi/arduino-mqtt
#include <WiFi101.h>
#include <MQTT.h>
const char ssid[] = "ssid";
const char pass[] = "pass";
WiFiSSLClient net;
MQTTClient client;
unsigned long lastMillis = 0;
void connect() {
Serial.print("checking wifi...");
while (WiFi.status() != WL_CONNECTED) {
Serial.print(".");
delay(1000);
}
Serial.print("\nconnecting...");
while (!client.connect("arduino", "try", "try")) {
Serial.print(".");
delay(1000);
}
Serial.println("\nconnected!");
client.subscribe("/hello");
// client.unsubscribe("/hello");
}
void messageReceived(String &topic, String &payload) {
Serial.println("incoming: " + topic + " - " + payload);
// Note: Do not use the client in the callback to publish, subscribe or
// unsubscribe as it may cause deadlocks when other things arrive while
// sending and receiving acknowledgments. Instead, change a global variable,
// or push to a queue and handle it in the loop after calling `client.loop()`.
}
void setup() {
Serial.begin(115200);
WiFi.begin(ssid, pass);
// Note: Local domain names (e.g. "Computer.local" on OSX) are not supported
// by Arduino. You need to set the IP address directly.
//
// MQTT brokers usually use port 8883 for secure connections.
client.begin("broker.shiftr.io", 8883, net);
client.onMessage(messageReceived);
connect();
}
void loop() {
client.loop();
if (!client.connected()) {
connect();
}
// publish a message roughly every second.
if (millis() - lastMillis > 1000) {
lastMillis = millis();
client.publish("/hello", "world");
}
}

View File

@@ -0,0 +1,73 @@
// This example uses an Arduino Uno together with
// a WiFi Shield to connect to shiftr.io.
//
// You can check on your device after a successful
// connection here: https://shiftr.io/try.
//
// by Joël Gähwiler
// https://github.com/256dpi/arduino-mqtt
#include <WiFi.h>
#include <MQTT.h>
const char ssid[] = "ssid";
const char pass[] = "pass";
WiFiClient net;
MQTTClient client;
unsigned long lastMillis = 0;
void connect() {
Serial.print("checking wifi...");
while (WiFi.status() != WL_CONNECTED) {
Serial.print(".");
delay(1000);
}
Serial.print("\nconnecting...");
while (!client.connect("arduino", "try", "try")) {
Serial.print(".");
delay(1000);
}
Serial.println("\nconnected!");
client.subscribe("/hello");
// client.unsubscribe("/hello");
}
void messageReceived(String &topic, String &payload) {
Serial.println("incoming: " + topic + " - " + payload);
// Note: Do not use the client in the callback to publish, subscribe or
// unsubscribe as it may cause deadlocks when other things arrive while
// sending and receiving acknowledgments. Instead, change a global variable,
// or push to a queue and handle it in the loop after calling `client.loop()`.
}
void setup() {
Serial.begin(115200);
WiFi.begin(ssid, pass);
// Note: Local domain names (e.g. "Computer.local" on OSX) are not supported
// by Arduino. You need to set the IP address directly.
client.begin("broker.shiftr.io", net);
client.onMessage(messageReceived);
connect();
}
void loop() {
client.loop();
if (!client.connected()) {
connect();
}
// publish a message roughly every second.
if (millis() - lastMillis > 1000) {
lastMillis = millis();
client.publish("/hello", "world");
}
}

View File

@@ -0,0 +1,65 @@
// This example uses an Arduino Yun or a Yun-Shield
// and the MQTTClient to connect to shiftr.io.
//
// You can check on your device after a successful
// connection here: https://shiftr.io/try.
//
// by Joël Gähwiler
// https://github.com/256dpi/arduino-mqtt
#include <Bridge.h>
#include <BridgeClient.h>
#include <MQTT.h>
BridgeClient net;
MQTTClient client;
unsigned long lastMillis = 0;
void connect() {
Serial.print("connecting...");
while (!client.connect("arduino", "try", "try")) {
Serial.print(".");
delay(1000);
}
Serial.println("\nconnected!");
client.subscribe("/hello");
// client.unsubscribe("/hello");
}
void messageReceived(String &topic, String &payload) {
Serial.println("incoming: " + topic + " - " + payload);
// Note: Do not use the client in the callback to publish, subscribe or
// unsubscribe as it may cause deadlocks when other things arrive while
// sending and receiving acknowledgments. Instead, change a global variable,
// or push to a queue and handle it in the loop after calling `client.loop()`.
}
void setup() {
Bridge.begin();
Serial.begin(115200);
// Note: Local domain names (e.g. "Computer.local" on OSX) are not supported
// by Arduino. You need to set the IP address directly.
client.begin("broker.shiftr.io", net);
client.onMessage(messageReceived);
connect();
}
void loop() {
client.loop();
if (!client.connected()) {
connect();
}
// publish a message roughly every second.
if (millis() - lastMillis > 1000) {
lastMillis = millis();
client.publish("/hello", "world");
}
}

View File

@@ -0,0 +1,67 @@
// This example uses an Arduino Yun or a Yun-Shield
// and the MQTTClient to connect to shiftr.io.
//
// You can check on your device after a successful
// connection here: https://shiftr.io/try.
//
// by Joël Gähwiler
// https://github.com/256dpi/arduino-mqtt
#include <Bridge.h>
#include <BridgeSSLClient.h>
#include <MQTT.h>
BridgeSSLClient net;
MQTTClient client;
unsigned long lastMillis = 0;
void connect() {
Serial.print("connecting...");
while (!client.connect("arduino", "try", "try")) {
Serial.print(".");
delay(1000);
}
Serial.println("\nconnected!");
client.subscribe("/hello");
// client.unsubscribe("/hello");
}
void messageReceived(String &topic, String &payload) {
Serial.println("incoming: " + topic + " - " + payload);
// Note: Do not use the client in the callback to publish, subscribe or
// unsubscribe as it may cause deadlocks when other things arrive while
// sending and receiving acknowledgments. Instead, change a global variable,
// or push to a queue and handle it in the loop after calling `client.loop()`.
}
void setup() {
Bridge.begin();
Serial.begin(115200);
// Note: Local domain names (e.g. "Computer.local" on OSX) are not supported
// by Arduino. You need to set the IP address directly.
//
// MQTT brokers usually use port 8883 for secure connections.
client.begin("broker.shiftr.io", 8883, net);
client.onMessage(messageReceived);
connect();
}
void loop() {
client.loop();
if (!client.connected()) {
connect();
}
// publish a message roughly every second.
if (millis() - lastMillis > 1000) {
lastMillis = millis();
client.publish("/hello", "world");
}
}

View File

@@ -0,0 +1,74 @@
// This example uses an ESP32 Development Board
// to connect to shiftr.io.
//
// You can check on your device after a successful
// connection here: https://shiftr.io/try.
//
// by Joël Gähwiler
// https://github.com/256dpi/arduino-mqtt
#include <WiFi.h>
#include <MQTT.h>
const char ssid[] = "ssid";
const char pass[] = "pass";
WiFiClient net;
MQTTClient client;
unsigned long lastMillis = 0;
void connect() {
Serial.print("checking wifi...");
while (WiFi.status() != WL_CONNECTED) {
Serial.print(".");
delay(1000);
}
Serial.print("\nconnecting...");
while (!client.connect("arduino", "try", "try")) {
Serial.print(".");
delay(1000);
}
Serial.println("\nconnected!");
client.subscribe("/hello");
// client.unsubscribe("/hello");
}
void messageReceived(String &topic, String &payload) {
Serial.println("incoming: " + topic + " - " + payload);
// Note: Do not use the client in the callback to publish, subscribe or
// unsubscribe as it may cause deadlocks when other things arrive while
// sending and receiving acknowledgments. Instead, change a global variable,
// or push to a queue and handle it in the loop after calling `client.loop()`.
}
void setup() {
Serial.begin(115200);
WiFi.begin(ssid, pass);
// Note: Local domain names (e.g. "Computer.local" on OSX) are not supported
// by Arduino. You need to set the IP address directly.
client.begin("broker.shiftr.io", net);
client.onMessage(messageReceived);
connect();
}
void loop() {
client.loop();
delay(10); // <- fixes some issues with WiFi stability
if (!client.connected()) {
connect();
}
// publish a message roughly every second.
if (millis() - lastMillis > 1000) {
lastMillis = millis();
client.publish("/hello", "world");
}
}

View File

@@ -0,0 +1,76 @@
// This example uses an ESP32 Development Board
// to connect to shiftr.io.
//
// You can check on your device after a successful
// connection here: https://shiftr.io/try.
//
// by Joël Gähwiler
// https://github.com/256dpi/arduino-mqtt
#include <WiFiClientSecure.h>
#include <MQTT.h>
const char ssid[] = "ssid";
const char pass[] = "pass";
WiFiClientSecure net;
MQTTClient client;
unsigned long lastMillis = 0;
void connect() {
Serial.print("checking wifi...");
while (WiFi.status() != WL_CONNECTED) {
Serial.print(".");
delay(1000);
}
Serial.print("\nconnecting...");
while (!client.connect("arduino", "try", "try")) {
Serial.print(".");
delay(1000);
}
Serial.println("\nconnected!");
client.subscribe("/hello");
// client.unsubscribe("/hello");
}
void messageReceived(String &topic, String &payload) {
Serial.println("incoming: " + topic + " - " + payload);
// Note: Do not use the client in the callback to publish, subscribe or
// unsubscribe as it may cause deadlocks when other things arrive while
// sending and receiving acknowledgments. Instead, change a global variable,
// or push to a queue and handle it in the loop after calling `client.loop()`.
}
void setup() {
Serial.begin(115200);
WiFi.begin(ssid, pass);
// Note: Local domain names (e.g. "Computer.local" on OSX) are not supported
// by Arduino. You need to set the IP address directly.
//
// MQTT brokers usually use port 8883 for secure connections.
client.begin("broker.shiftr.io", 8883, net);
client.onMessage(messageReceived);
connect();
}
void loop() {
client.loop();
delay(10); // <- fixes some issues with WiFi stability
if (!client.connected()) {
connect();
}
// publish a message roughly every second.
if (millis() - lastMillis > 1000) {
lastMillis = millis();
client.publish("/hello", "world");
}
}

View File

@@ -0,0 +1,9 @@
name=MQTT
version=2.4.8
author=Joel Gaehwiler <joel.gaehwiler@gmail.com>
maintainer=Joel Gaehwiler <joel.gaehwiler@gmail.com>
sentence=MQTT library for Arduino
paragraph=This library bundles the lwmqtt client and adds a thin wrapper to get an Arduino like API.
category=Communication
url=https://github.com/256dpi/arduino-mqtt
architectures=*

View File

@@ -0,0 +1,6 @@
#ifndef MQTT_H
#define MQTT_H
#include "MQTTClient.h"
#endif

View File

@@ -0,0 +1,422 @@
#include "MQTTClient.h"
inline void lwmqtt_arduino_timer_set(void *ref, uint32_t timeout) {
// cast timer reference
auto t = (lwmqtt_arduino_timer_t *)ref;
// set future end time
if (t->millis != nullptr) {
t->end = (uint32_t)(t->millis() + timeout);
} else {
t->end = (uint32_t)(millis() + timeout);
}
}
inline int32_t lwmqtt_arduino_timer_get(void *ref) {
// cast timer reference
auto t = (lwmqtt_arduino_timer_t *)ref;
// get difference to end time
if (t->millis != nullptr) {
return (int32_t)(t->end - t->millis());
} else {
return (int32_t)(t->end - millis());
}
}
inline lwmqtt_err_t lwmqtt_arduino_network_read(void *ref, uint8_t *buffer, size_t len, size_t *read,
uint32_t timeout) {
// cast network reference
auto n = (lwmqtt_arduino_network_t *)ref;
// set timeout
uint32_t start = millis();
// reset counter
*read = 0;
// read until all bytes have been read or timeout has been reached
while (len > 0 && (millis() - start < timeout)) {
// read from connection
int r = n->client->read(buffer, len);
// handle read data
if (r > 0) {
buffer += r;
*read += r;
len -= r;
continue;
}
// wait/unblock for some time (RTOS based boards may otherwise fail since
// the wifi task cannot provide the data)
delay(0);
// otherwise check status
if (!n->client->connected()) {
return LWMQTT_NETWORK_FAILED_READ;
}
}
// check counter
if (*read == 0) {
return LWMQTT_NETWORK_TIMEOUT;
}
return LWMQTT_SUCCESS;
}
inline lwmqtt_err_t lwmqtt_arduino_network_write(void *ref, uint8_t *buffer, size_t len, size_t *sent,
uint32_t /*timeout*/) {
// cast network reference
auto n = (lwmqtt_arduino_network_t *)ref;
// write bytes
*sent = n->client->write(buffer, len);
if (*sent <= 0) {
return LWMQTT_NETWORK_FAILED_WRITE;
}
return LWMQTT_SUCCESS;
}
static void MQTTClientHandler(lwmqtt_client_t * /*client*/, void *ref, lwmqtt_string_t topic,
lwmqtt_message_t message) {
// get callback
auto cb = (MQTTClientCallback *)ref;
// null terminate topic
char terminated_topic[topic.len + 1];
memcpy(terminated_topic, topic.data, topic.len);
terminated_topic[topic.len] = '\0';
// null terminate payload if available
if (message.payload != nullptr) {
message.payload[message.payload_len] = '\0';
}
// call the advanced callback and return if available
if (cb->advanced != nullptr) {
cb->advanced(cb->client, terminated_topic, (char *)message.payload, (int)message.payload_len);
return;
}
// return if simple callback is not set
if (cb->simple == nullptr) {
return;
}
// create topic string
String str_topic = String(terminated_topic);
// create payload string
String str_payload;
if (message.payload != nullptr) {
str_payload = String((const char *)message.payload);
}
// call simple callback
cb->simple(str_topic, str_payload);
}
MQTTClient::MQTTClient(int bufSize) {
// allocate buffers
this->bufSize = (size_t)bufSize;
this->readBuf = (uint8_t *)malloc((size_t)bufSize + 1);
this->writeBuf = (uint8_t *)malloc((size_t)bufSize);
}
MQTTClient::~MQTTClient() {
// free will
this->clearWill();
// free hostname
if (this->hostname != nullptr) {
free((void *)this->hostname);
}
// free buffers
free(this->readBuf);
free(this->writeBuf);
}
void MQTTClient::begin(Client &_client) {
// set client
this->netClient = &_client;
// initialize client
lwmqtt_init(&this->client, this->writeBuf, this->bufSize, this->readBuf, this->bufSize);
// set timers
lwmqtt_set_timers(&this->client, &this->timer1, &this->timer2, lwmqtt_arduino_timer_set, lwmqtt_arduino_timer_get);
// set network
lwmqtt_set_network(&this->client, &this->network, lwmqtt_arduino_network_read, lwmqtt_arduino_network_write);
// set callback
lwmqtt_set_callback(&this->client, (void *)&this->callback, MQTTClientHandler);
}
void MQTTClient::onMessage(MQTTClientCallbackSimple cb) {
// set callback
this->callback.client = this;
this->callback.simple = cb;
this->callback.advanced = nullptr;
}
void MQTTClient::onMessageAdvanced(MQTTClientCallbackAdvanced cb) {
// set callback
this->callback.client = this;
this->callback.simple = nullptr;
this->callback.advanced = cb;
}
void MQTTClient::setClockSource(MQTTClientClockSource cb) {
this->timer1.millis = cb;
this->timer2.millis = cb;
}
void MQTTClient::setHost(IPAddress _address, int _port) {
// set address and port
this->address = _address;
this->port = _port;
}
void MQTTClient::setHost(const char _hostname[], int _port) {
// free hostname if set
if (this->hostname != nullptr) {
free((void *)this->hostname);
}
// set hostname and port
this->hostname = strdup(_hostname);
this->port = _port;
}
void MQTTClient::setWill(const char topic[], const char payload[], bool retained, int qos) {
// return if topic is missing
if (topic == nullptr || strlen(topic) == 0) {
return;
}
// clear existing will
this->clearWill();
// allocate will
this->will = (lwmqtt_will_t *)malloc(sizeof(lwmqtt_will_t));
memset(this->will, 0, sizeof(lwmqtt_will_t));
// set topic
this->will->topic = lwmqtt_string(strdup(topic));
// set payload if available
if (payload != nullptr && strlen(payload) > 0) {
this->will->payload = lwmqtt_string(strdup(payload));
}
// set flags
this->will->retained = retained;
this->will->qos = (lwmqtt_qos_t)qos;
}
void MQTTClient::clearWill() {
// return if not set
if (this->will == nullptr) {
return;
}
// free payload if set
if (this->will->payload.len > 0) {
free(this->will->payload.data);
}
// free topic if set
if (this->will->topic.len > 0) {
free(this->will->topic.data);
}
// free will
free(this->will);
this->will = nullptr;
}
void MQTTClient::setKeepAlive(int _keepAlive) { this->keepAlive = _keepAlive; }
void MQTTClient::setCleanSession(bool _cleanSession) { this->cleanSession = _cleanSession; }
void MQTTClient::setTimeout(int _timeout) { this->timeout = _timeout; }
bool MQTTClient::publish(const char topic[], const char payload[], int length, bool retained, int qos) {
// return immediately if not connected
if (!this->connected()) {
return false;
}
// prepare message
lwmqtt_message_t message = lwmqtt_default_message;
message.payload = (uint8_t *)payload;
message.payload_len = (size_t)length;
message.retained = retained;
message.qos = lwmqtt_qos_t(qos);
// publish message
this->_lastError = lwmqtt_publish(&this->client, lwmqtt_string(topic), message, this->timeout);
if (this->_lastError != LWMQTT_SUCCESS) {
// close connection
this->close();
return false;
}
return true;
}
bool MQTTClient::connect(const char clientID[], const char username[], const char password[], bool skip) {
// close left open connection if still connected
if (!skip && this->connected()) {
this->close();
}
// save client
this->network.client = this->netClient;
// connect to host
if (!skip) {
int ret;
if (this->hostname != nullptr) {
ret = this->netClient->connect(this->hostname, (uint16_t)this->port);
} else {
ret = this->netClient->connect(this->address, (uint16_t)this->port);
}
if (ret <= 0) {
this->_lastError = LWMQTT_NETWORK_FAILED_CONNECT;
return false;
}
}
// prepare options
lwmqtt_options_t options = lwmqtt_default_options;
options.keep_alive = this->keepAlive;
options.clean_session = this->cleanSession;
options.client_id = lwmqtt_string(clientID);
// set username and password if available
if (username != nullptr) {
options.username = lwmqtt_string(username);
if (password != nullptr) {
options.password = lwmqtt_string(password);
}
}
// connect to broker
this->_lastError = lwmqtt_connect(&this->client, options, this->will, &this->_returnCode, this->timeout);
if (this->_lastError != LWMQTT_SUCCESS) {
// close connection
this->close();
return false;
}
// set flag
this->_connected = true;
return true;
}
bool MQTTClient::subscribe(const char topic[], int qos) {
// return immediately if not connected
if (!this->connected()) {
return false;
}
// subscribe to topic
this->_lastError = lwmqtt_subscribe_one(&this->client, lwmqtt_string(topic), (lwmqtt_qos_t)qos, this->timeout);
if (this->_lastError != LWMQTT_SUCCESS) {
// close connection
this->close();
return false;
}
return true;
}
bool MQTTClient::unsubscribe(const char topic[]) {
// return immediately if not connected
if (!this->connected()) {
return false;
}
// unsubscribe from topic
this->_lastError = lwmqtt_unsubscribe_one(&this->client, lwmqtt_string(topic), this->timeout);
if (this->_lastError != LWMQTT_SUCCESS) {
// close connection
this->close();
return false;
}
return true;
}
bool MQTTClient::loop() {
// return immediately if not connected
if (!this->connected()) {
return false;
}
// get available bytes on the network
auto available = (size_t)this->netClient->available();
// yield if data is available
if (available > 0) {
this->_lastError = lwmqtt_yield(&this->client, available, this->timeout);
if (this->_lastError != LWMQTT_SUCCESS) {
// close connection
this->close();
return false;
}
}
// keep the connection alive
this->_lastError = lwmqtt_keep_alive(&this->client, this->timeout);
if (this->_lastError != LWMQTT_SUCCESS) {
// close connection
this->close();
return false;
}
return true;
}
bool MQTTClient::connected() {
// a client is connected if the network is connected, a client is available and
// the connection has been properly initiated
return this->netClient != nullptr && this->netClient->connected() == 1 && this->_connected;
}
bool MQTTClient::disconnect() {
// return immediately if not connected anymore
if (!this->connected()) {
return false;
}
// cleanly disconnect
this->_lastError = lwmqtt_disconnect(&this->client, this->timeout);
// close
this->close();
return this->_lastError == LWMQTT_SUCCESS;
}
void MQTTClient::close() {
// set flag
this->_connected = false;
// close network
this->netClient->stop();
}

View File

@@ -0,0 +1,151 @@
#ifndef MQTT_CLIENT_H
#define MQTT_CLIENT_H
#include <Arduino.h>
#include <Client.h>
#include <Stream.h>
extern "C" {
#include "lwmqtt/lwmqtt.h"
}
typedef uint32_t (*MQTTClientClockSource)();
typedef struct {
uint32_t end;
MQTTClientClockSource millis;
} lwmqtt_arduino_timer_t;
typedef struct {
Client *client;
} lwmqtt_arduino_network_t;
class MQTTClient;
typedef void (*MQTTClientCallbackSimple)(String &topic, String &payload);
typedef void (*MQTTClientCallbackAdvanced)(MQTTClient *client, char topic[], char bytes[], int length);
typedef struct {
MQTTClient *client = nullptr;
MQTTClientCallbackSimple simple = nullptr;
MQTTClientCallbackAdvanced advanced = nullptr;
} MQTTClientCallback;
class MQTTClient {
private:
size_t bufSize = 0;
uint8_t *readBuf = nullptr;
uint8_t *writeBuf = nullptr;
uint16_t keepAlive = 10;
bool cleanSession = true;
uint32_t timeout = 1000;
Client *netClient = nullptr;
const char *hostname = nullptr;
IPAddress address;
int port = 0;
lwmqtt_will_t *will = nullptr;
MQTTClientCallback callback;
lwmqtt_arduino_network_t network = {nullptr};
lwmqtt_arduino_timer_t timer1 = {0, nullptr};
lwmqtt_arduino_timer_t timer2 = {0, nullptr};
lwmqtt_client_t client = lwmqtt_client_t();
bool _connected = false;
lwmqtt_return_code_t _returnCode = (lwmqtt_return_code_t)0;
lwmqtt_err_t _lastError = (lwmqtt_err_t)0;
public:
void *ref = nullptr;
explicit MQTTClient(int bufSize = 128);
~MQTTClient();
void begin(Client &_client);
void begin(const char _hostname[], Client &_client) { this->begin(_hostname, 1883, _client); }
void begin(const char _hostname[], int _port, Client &_client) {
this->begin(_client);
this->setHost(_hostname, _port);
}
void begin(IPAddress _address, Client &_client) { this->begin(_address, 1883, _client); }
void begin(IPAddress _address, int _port, Client &_client) {
this->begin(_client);
this->setHost(_address, _port);
}
void onMessage(MQTTClientCallbackSimple cb);
void onMessageAdvanced(MQTTClientCallbackAdvanced cb);
void setClockSource(MQTTClientClockSource cb);
void setHost(const char _hostname[]) { this->setHost(_hostname, 1883); }
void setHost(const char hostname[], int port);
void setHost(IPAddress _address) { this->setHost(_address, 1883); }
void setHost(IPAddress _address, int port);
void setWill(const char topic[]) { this->setWill(topic, ""); }
void setWill(const char topic[], const char payload[]) { this->setWill(topic, payload, false, 0); }
void setWill(const char topic[], const char payload[], bool retained, int qos);
void clearWill();
void setKeepAlive(int keepAlive);
void setCleanSession(bool cleanSession);
void setTimeout(int timeout);
void setOptions(int _keepAlive, bool _cleanSession, int _timeout) {
this->setKeepAlive(_keepAlive);
this->setCleanSession(_cleanSession);
this->setTimeout(_timeout);
}
bool connect(const char clientId[], bool skip = false) { return this->connect(clientId, nullptr, nullptr, skip); }
bool connect(const char clientId[], const char username[], bool skip = false) {
return this->connect(clientId, username, nullptr, skip);
}
bool connect(const char clientID[], const char username[], const char password[], bool skip = false);
bool publish(const String &topic) { return this->publish(topic.c_str(), ""); }
bool publish(const char topic[]) { return this->publish(topic, ""); }
bool publish(const String &topic, const String &payload) { return this->publish(topic.c_str(), payload.c_str()); }
bool publish(const String &topic, const String &payload, bool retained, int qos) {
return this->publish(topic.c_str(), payload.c_str(), retained, qos);
}
bool publish(const char topic[], const String &payload) { return this->publish(topic, payload.c_str()); }
bool publish(const char topic[], const String &payload, bool retained, int qos) {
return this->publish(topic, payload.c_str(), retained, qos);
}
bool publish(const char topic[], const char payload[]) {
return this->publish(topic, (char *)payload, (int)strlen(payload));
}
bool publish(const char topic[], const char payload[], bool retained, int qos) {
return this->publish(topic, (char *)payload, (int)strlen(payload), retained, qos);
}
bool publish(const char topic[], const char payload[], int length) {
return this->publish(topic, payload, length, false, 0);
}
bool publish(const char topic[], const char payload[], int length, bool retained, int qos);
bool subscribe(const String &topic) { return this->subscribe(topic.c_str()); }
bool subscribe(const String &topic, int qos) { return this->subscribe(topic.c_str(), qos); }
bool subscribe(const char topic[]) { return this->subscribe(topic, 0); }
bool subscribe(const char topic[], int qos);
bool unsubscribe(const String &topic) { return this->unsubscribe(topic.c_str()); }
bool unsubscribe(const char topic[]);
bool loop();
bool connected();
lwmqtt_err_t lastError() { return this->_lastError; }
lwmqtt_return_code_t returnCode() { return this->_returnCode; }
bool disconnect();
private:
void close();
};
#endif

View File

@@ -0,0 +1,618 @@
#include "packet.h"
void lwmqtt_init(lwmqtt_client_t *client, uint8_t *write_buf, size_t write_buf_size, uint8_t *read_buf,
size_t read_buf_size) {
client->last_packet_id = 1;
client->keep_alive_interval = 0;
client->pong_pending = false;
client->write_buf = write_buf;
client->write_buf_size = write_buf_size;
client->read_buf = read_buf;
client->read_buf_size = read_buf_size;
client->callback = NULL;
client->callback_ref = NULL;
client->network = NULL;
client->network_read = NULL;
client->network_write = NULL;
client->keep_alive_timer = NULL;
client->command_timer = NULL;
client->timer_set = NULL;
client->timer_get = NULL;
}
void lwmqtt_set_network(lwmqtt_client_t *client, void *ref, lwmqtt_network_read_t read, lwmqtt_network_write_t write) {
client->network = ref;
client->network_read = read;
client->network_write = write;
}
void lwmqtt_set_timers(lwmqtt_client_t *client, void *keep_alive_timer, void *command_timer, lwmqtt_timer_set_t set,
lwmqtt_timer_get_t get) {
client->keep_alive_timer = keep_alive_timer;
client->command_timer = command_timer;
client->timer_set = set;
client->timer_get = get;
client->timer_set(client->keep_alive_timer, 0);
client->timer_set(client->command_timer, 0);
}
void lwmqtt_set_callback(lwmqtt_client_t *client, void *ref, lwmqtt_callback_t cb) {
client->callback_ref = ref;
client->callback = cb;
}
static uint16_t lwmqtt_get_next_packet_id(lwmqtt_client_t *client) {
// check overflow
if (client->last_packet_id == 65535) {
client->last_packet_id = 1;
return 1;
}
// increment packet id
client->last_packet_id++;
return client->last_packet_id;
}
static lwmqtt_err_t lwmqtt_read_from_network(lwmqtt_client_t *client, size_t offset, size_t len) {
// check read buffer capacity
if (client->read_buf_size < offset + len) {
return LWMQTT_BUFFER_TOO_SHORT;
}
// prepare counter
size_t read = 0;
// read while data is missing
while (read < len) {
// check remaining time
int32_t remaining_time = client->timer_get(client->command_timer);
if (remaining_time <= 0) {
return LWMQTT_NETWORK_TIMEOUT;
}
// read
size_t partial_read = 0;
lwmqtt_err_t err = client->network_read(client->network, client->read_buf + offset + read, len - read,
&partial_read, (uint32_t)remaining_time);
if (err != LWMQTT_SUCCESS) {
return err;
}
// increment counter
read += partial_read;
}
return LWMQTT_SUCCESS;
}
static lwmqtt_err_t lwmqtt_write_to_network(lwmqtt_client_t *client, size_t offset, size_t len) {
// prepare counter
size_t written = 0;
// write while data is left
while (written < len) {
// check remaining time
int32_t remaining_time = client->timer_get(client->command_timer);
if (remaining_time <= 0) {
return LWMQTT_NETWORK_TIMEOUT;
}
// write
size_t partial_write = 0;
lwmqtt_err_t err = client->network_write(client->network, client->write_buf + offset + written, len - written,
&partial_write, (uint32_t)remaining_time);
if (err != LWMQTT_SUCCESS) {
return err;
}
// increment counter
written += partial_write;
}
return LWMQTT_SUCCESS;
}
static lwmqtt_err_t lwmqtt_read_packet_in_buffer(lwmqtt_client_t *client, size_t *read,
lwmqtt_packet_type_t *packet_type) {
// preset packet type
*packet_type = LWMQTT_NO_PACKET;
// read or wait for header byte
lwmqtt_err_t err = lwmqtt_read_from_network(client, 0, 1);
if (err == LWMQTT_NETWORK_TIMEOUT) {
// this is ok as no data has been read at all
return LWMQTT_SUCCESS;
} else if (err != LWMQTT_SUCCESS) {
return err;
}
// detect packet type
err = lwmqtt_detect_packet_type(client->read_buf, 1, packet_type);
if (err != LWMQTT_SUCCESS) {
return err;
}
// prepare variables
size_t len = 0;
uint32_t rem_len = 0;
do {
// adjust len
len++;
// read next byte
err = lwmqtt_read_from_network(client, len, 1);
if (err != LWMQTT_SUCCESS) {
return err;
}
// attempt to detect remaining length
err = lwmqtt_detect_remaining_length(client->read_buf + 1, len, &rem_len);
} while (err == LWMQTT_BUFFER_TOO_SHORT);
// check final error
if (err != LWMQTT_SUCCESS) {
return err;
}
// read the rest of the buffer if needed
if (rem_len > 0) {
err = lwmqtt_read_from_network(client, 1 + len, rem_len);
if (err != LWMQTT_SUCCESS) {
return err;
}
}
// adjust counter
*read += 1 + len + rem_len;
return LWMQTT_SUCCESS;
}
static lwmqtt_err_t lwmqtt_send_packet_in_buffer(lwmqtt_client_t *client, size_t length) {
// write to network
lwmqtt_err_t err = lwmqtt_write_to_network(client, 0, length);
if (err != LWMQTT_SUCCESS) {
return err;
}
// reset keep alive timer
client->timer_set(client->keep_alive_timer, client->keep_alive_interval);
return LWMQTT_SUCCESS;
}
static lwmqtt_err_t lwmqtt_cycle(lwmqtt_client_t *client, size_t *read, lwmqtt_packet_type_t *packet_type) {
// read next packet from the network
lwmqtt_err_t err = lwmqtt_read_packet_in_buffer(client, read, packet_type);
if (err != LWMQTT_SUCCESS) {
return err;
} else if (*packet_type == LWMQTT_NO_PACKET) {
return LWMQTT_SUCCESS;
}
switch (*packet_type) {
// handle publish packets
case LWMQTT_PUBLISH_PACKET: {
// decode publish packet
bool dup;
uint16_t packet_id;
lwmqtt_string_t topic;
lwmqtt_message_t msg;
err = lwmqtt_decode_publish(client->read_buf, client->read_buf_size, &dup, &packet_id, &topic, &msg);
if (err != LWMQTT_SUCCESS) {
return err;
}
// call callback if set
if (client->callback != NULL) {
client->callback(client, client->callback_ref, topic, msg);
}
// break early on qos zero
if (msg.qos == LWMQTT_QOS0) {
break;
}
// define ack packet
lwmqtt_packet_type_t ack_type = LWMQTT_NO_PACKET;
if (msg.qos == LWMQTT_QOS1) {
ack_type = LWMQTT_PUBACK_PACKET;
} else if (msg.qos == LWMQTT_QOS2) {
ack_type = LWMQTT_PUBREC_PACKET;
}
// encode ack packet
size_t len;
err = lwmqtt_encode_ack(client->write_buf, client->write_buf_size, &len, ack_type, false, packet_id);
if (err != LWMQTT_SUCCESS) {
return err;
}
// send ack packet
err = lwmqtt_send_packet_in_buffer(client, len);
if (err != LWMQTT_SUCCESS) {
return err;
}
break;
}
// handle pubrec packets
case LWMQTT_PUBREC_PACKET: {
// decode pubrec packet
bool dup;
uint16_t packet_id;
err = lwmqtt_decode_ack(client->read_buf, client->read_buf_size, LWMQTT_PUBREC_PACKET, &dup, &packet_id);
if (err != LWMQTT_SUCCESS) {
return err;
}
// encode pubrel packet
size_t len;
err = lwmqtt_encode_ack(client->write_buf, client->write_buf_size, &len, LWMQTT_PUBREL_PACKET, 0, packet_id);
if (err != LWMQTT_SUCCESS) {
return err;
}
// send pubrel packet
err = lwmqtt_send_packet_in_buffer(client, len);
if (err != LWMQTT_SUCCESS) {
return err;
}
break;
}
// handle pubrel packets
case LWMQTT_PUBREL_PACKET: {
// decode pubrec packet
bool dup;
uint16_t packet_id;
err = lwmqtt_decode_ack(client->read_buf, client->read_buf_size, LWMQTT_PUBREL_PACKET, &dup, &packet_id);
if (err != LWMQTT_SUCCESS) {
return err;
}
// encode pubcomp packet
size_t len;
err = lwmqtt_encode_ack(client->write_buf, client->write_buf_size, &len, LWMQTT_PUBCOMP_PACKET, 0, packet_id);
if (err != LWMQTT_SUCCESS) {
return err;
}
// send pubcomp packet
err = lwmqtt_send_packet_in_buffer(client, len);
if (err != LWMQTT_SUCCESS) {
return err;
}
break;
}
// handle pingresp packets
case LWMQTT_PINGRESP_PACKET: {
// set flag
client->pong_pending = false;
break;
}
// handle all other packets
default: { break; }
}
return LWMQTT_SUCCESS;
}
static lwmqtt_err_t lwmqtt_cycle_until(lwmqtt_client_t *client, lwmqtt_packet_type_t *packet_type, size_t available,
lwmqtt_packet_type_t needle) {
// prepare counter
size_t read = 0;
// loop until timeout has been reached
do {
// do one cycle
lwmqtt_err_t err = lwmqtt_cycle(client, &read, packet_type);
if (err != LWMQTT_SUCCESS) {
return err;
}
// return when one packet has been successfully read when no availability has been given
if (needle == LWMQTT_NO_PACKET && available == 0) {
return LWMQTT_SUCCESS;
}
// otherwise check if needle has been found
if (*packet_type == needle) {
return LWMQTT_SUCCESS;
}
} while (client->timer_get(client->command_timer) > 0 && (available == 0 || read < available));
return LWMQTT_SUCCESS;
}
lwmqtt_err_t lwmqtt_yield(lwmqtt_client_t *client, size_t available, uint32_t timeout) {
// set command timer
client->timer_set(client->command_timer, timeout);
// cycle until timeout has been reached
lwmqtt_packet_type_t packet_type = LWMQTT_NO_PACKET;
lwmqtt_err_t err = lwmqtt_cycle_until(client, &packet_type, available, LWMQTT_NO_PACKET);
if (err != LWMQTT_SUCCESS) {
return err;
}
return LWMQTT_SUCCESS;
}
lwmqtt_err_t lwmqtt_connect(lwmqtt_client_t *client, lwmqtt_options_t options, lwmqtt_will_t *will,
lwmqtt_return_code_t *return_code, uint32_t timeout) {
// set command timer
client->timer_set(client->command_timer, timeout);
// save keep alive interval
client->keep_alive_interval = (uint32_t)(options.keep_alive) * 1000;
// set keep alive timer
client->timer_set(client->keep_alive_timer, client->keep_alive_interval);
// reset pong pending flag
client->pong_pending = false;
// initialize return code
*return_code = LWMQTT_UNKNOWN_RETURN_CODE;
// encode connect packet
size_t len;
lwmqtt_err_t err = lwmqtt_encode_connect(client->write_buf, client->write_buf_size, &len, options, will);
if (err != LWMQTT_SUCCESS) {
return err;
}
// send packet
err = lwmqtt_send_packet_in_buffer(client, len);
if (err != LWMQTT_SUCCESS) {
return err;
}
// wait for connack packet
lwmqtt_packet_type_t packet_type = LWMQTT_NO_PACKET;
err = lwmqtt_cycle_until(client, &packet_type, 0, LWMQTT_CONNACK_PACKET);
if (err != LWMQTT_SUCCESS) {
return err;
} else if (packet_type != LWMQTT_CONNACK_PACKET) {
return LWMQTT_MISSING_OR_WRONG_PACKET;
}
// decode connack packet
bool session_present;
err = lwmqtt_decode_connack(client->read_buf, client->read_buf_size, &session_present, return_code);
if (err != LWMQTT_SUCCESS) {
return err;
}
// return error if connection was not accepted
if (*return_code != LWMQTT_CONNECTION_ACCEPTED) {
return LWMQTT_CONNECTION_DENIED;
}
return LWMQTT_SUCCESS;
}
lwmqtt_err_t lwmqtt_subscribe(lwmqtt_client_t *client, int count, lwmqtt_string_t *topic_filter, lwmqtt_qos_t *qos,
uint32_t timeout) {
// set command timer
client->timer_set(client->command_timer, timeout);
// encode subscribe packet
size_t len;
lwmqtt_err_t err = lwmqtt_encode_subscribe(client->write_buf, client->write_buf_size, &len,
lwmqtt_get_next_packet_id(client), count, topic_filter, qos);
if (err != LWMQTT_SUCCESS) {
return err;
}
// send packet
err = lwmqtt_send_packet_in_buffer(client, len);
if (err != LWMQTT_SUCCESS) {
return err;
}
// wait for suback packet
lwmqtt_packet_type_t packet_type = LWMQTT_NO_PACKET;
err = lwmqtt_cycle_until(client, &packet_type, 0, LWMQTT_SUBACK_PACKET);
if (err != LWMQTT_SUCCESS) {
return err;
} else if (packet_type != LWMQTT_SUBACK_PACKET) {
return LWMQTT_MISSING_OR_WRONG_PACKET;
}
// decode packet
int suback_count = 0;
lwmqtt_qos_t granted_qos[count];
uint16_t packet_id;
err = lwmqtt_decode_suback(client->read_buf, client->read_buf_size, &packet_id, count, &suback_count, granted_qos);
if (err != LWMQTT_SUCCESS) {
return err;
}
// check suback codes
for (int i = 0; i < suback_count; i++) {
if (granted_qos[i] == LWMQTT_QOS_FAILURE) {
return LWMQTT_FAILED_SUBSCRIPTION;
}
}
return LWMQTT_SUCCESS;
}
lwmqtt_err_t lwmqtt_subscribe_one(lwmqtt_client_t *client, lwmqtt_string_t topic_filter, lwmqtt_qos_t qos,
uint32_t timeout) {
return lwmqtt_subscribe(client, 1, &topic_filter, &qos, timeout);
}
lwmqtt_err_t lwmqtt_unsubscribe(lwmqtt_client_t *client, int count, lwmqtt_string_t *topic_filter, uint32_t timeout) {
// set command timer
client->timer_set(client->command_timer, timeout);
// encode unsubscribe packet
size_t len;
lwmqtt_err_t err = lwmqtt_encode_unsubscribe(client->write_buf, client->write_buf_size, &len,
lwmqtt_get_next_packet_id(client), count, topic_filter);
if (err != LWMQTT_SUCCESS) {
return err;
}
// send unsubscribe packet
err = lwmqtt_send_packet_in_buffer(client, len);
if (err != LWMQTT_SUCCESS) {
return err;
}
// wait for unsuback packet
lwmqtt_packet_type_t packet_type = LWMQTT_NO_PACKET;
err = lwmqtt_cycle_until(client, &packet_type, 0, LWMQTT_UNSUBACK_PACKET);
if (err != LWMQTT_SUCCESS) {
return err;
} else if (packet_type != LWMQTT_UNSUBACK_PACKET) {
return LWMQTT_MISSING_OR_WRONG_PACKET;
}
// decode unsuback packet
bool dup;
uint16_t packet_id;
err = lwmqtt_decode_ack(client->read_buf, client->read_buf_size, LWMQTT_UNSUBACK_PACKET, &dup, &packet_id);
if (err != LWMQTT_SUCCESS) {
return err;
}
return LWMQTT_SUCCESS;
}
lwmqtt_err_t lwmqtt_unsubscribe_one(lwmqtt_client_t *client, lwmqtt_string_t topic_filter, uint32_t timeout) {
return lwmqtt_unsubscribe(client, 1, &topic_filter, timeout);
}
lwmqtt_err_t lwmqtt_publish(lwmqtt_client_t *client, lwmqtt_string_t topic, lwmqtt_message_t message,
uint32_t timeout) {
// set command timer
client->timer_set(client->command_timer, timeout);
// add packet id if at least qos 1
uint16_t packet_id = 0;
if (message.qos == LWMQTT_QOS1 || message.qos == LWMQTT_QOS2) {
packet_id = lwmqtt_get_next_packet_id(client);
}
// encode publish packet
size_t len = 0;
lwmqtt_err_t err =
lwmqtt_encode_publish(client->write_buf, client->write_buf_size, &len, 0, packet_id, topic, message);
if (err != LWMQTT_SUCCESS) {
return err;
}
// send packet
err = lwmqtt_send_packet_in_buffer(client, len);
if (err != LWMQTT_SUCCESS) {
return err;
}
// immediately return on qos zero
if (message.qos == LWMQTT_QOS0) {
return LWMQTT_SUCCESS;
}
// define ack packet
lwmqtt_packet_type_t ack_type = LWMQTT_NO_PACKET;
if (message.qos == LWMQTT_QOS1) {
ack_type = LWMQTT_PUBACK_PACKET;
} else if (message.qos == LWMQTT_QOS2) {
ack_type = LWMQTT_PUBCOMP_PACKET;
}
// wait for ack packet
lwmqtt_packet_type_t packet_type = LWMQTT_NO_PACKET;
err = lwmqtt_cycle_until(client, &packet_type, 0, ack_type);
if (err != LWMQTT_SUCCESS) {
return err;
} else if (packet_type != ack_type) {
return LWMQTT_MISSING_OR_WRONG_PACKET;
}
// decode ack packet
bool dup;
err = lwmqtt_decode_ack(client->read_buf, client->read_buf_size, ack_type, &dup, &packet_id);
if (err != LWMQTT_SUCCESS) {
return err;
}
return LWMQTT_SUCCESS;
}
lwmqtt_err_t lwmqtt_disconnect(lwmqtt_client_t *client, uint32_t timeout) {
// set command timer
client->timer_set(client->command_timer, timeout);
// encode disconnect packet
size_t len;
lwmqtt_err_t err = lwmqtt_encode_zero(client->write_buf, client->write_buf_size, &len, LWMQTT_DISCONNECT_PACKET);
if (err != LWMQTT_SUCCESS) {
return err;
}
// send disconnected packet
err = lwmqtt_send_packet_in_buffer(client, len);
if (err != LWMQTT_SUCCESS) {
return err;
}
return LWMQTT_SUCCESS;
}
lwmqtt_err_t lwmqtt_keep_alive(lwmqtt_client_t *client, uint32_t timeout) {
// set command timer
client->timer_set(client->command_timer, timeout);
// return immediately if keep alive interval is zero
if (client->keep_alive_interval == 0) {
return LWMQTT_SUCCESS;
}
// return immediately if no ping is due
if (client->timer_get(client->keep_alive_timer) > 0) {
return LWMQTT_SUCCESS;
}
// a ping is due
// fail immediately if a pong is already pending
if (client->pong_pending) {
return LWMQTT_PONG_TIMEOUT;
}
// encode pingreq packet
size_t len;
lwmqtt_err_t err = lwmqtt_encode_zero(client->write_buf, client->write_buf_size, &len, LWMQTT_PINGREQ_PACKET);
if (err != LWMQTT_SUCCESS) {
return err;
}
// send packet
err = lwmqtt_send_packet_in_buffer(client, len);
if (err != LWMQTT_SUCCESS) {
return err;
}
// set flag
client->pong_pending = true;
return LWMQTT_SUCCESS;
}

View File

@@ -0,0 +1,249 @@
#include <string.h>
#include "helpers.h"
uint8_t lwmqtt_read_bits(uint8_t byte, int pos, int num) { return (byte & (uint8_t)((~(0xFF << num)) << pos)) >> pos; }
void lwmqtt_write_bits(uint8_t *byte, uint8_t value, int pos, int num) {
*byte = (*byte & ~(uint8_t)((~(0xFF << num)) << pos)) | (value << pos);
}
lwmqtt_err_t lwmqtt_read_data(uint8_t **buf, const uint8_t *buf_end, uint8_t **data, size_t len) {
// check zero length
if (len == 0) {
*data = NULL;
return LWMQTT_SUCCESS;
}
// check buffer size
if ((size_t)(buf_end - (*buf)) < len) {
return LWMQTT_BUFFER_TOO_SHORT;
}
// read data
*data = *buf;
// advance pointer
*buf += len;
return LWMQTT_SUCCESS;
}
lwmqtt_err_t lwmqtt_write_data(uint8_t **buf, const uint8_t *buf_end, uint8_t *data, size_t len) {
// check zero length
if (len == 0) {
return LWMQTT_SUCCESS;
}
// check buffer size
if ((size_t)(buf_end - (*buf)) < len) {
return LWMQTT_BUFFER_TOO_SHORT;
}
// write data
memcpy(*buf, data, len);
// advance pointer
*buf += len;
return LWMQTT_SUCCESS;
}
lwmqtt_err_t lwmqtt_read_num(uint8_t **buf, const uint8_t *buf_end, uint16_t *num) {
// check buffer size
if ((size_t)(buf_end - (*buf)) < 2) {
*num = 0;
return LWMQTT_BUFFER_TOO_SHORT;
}
// read two byte integer
*num = (uint16_t)256 * (*buf)[0] + (*buf)[1];
// adjust pointer
*buf += 2;
return LWMQTT_SUCCESS;
}
lwmqtt_err_t lwmqtt_write_num(uint8_t **buf, const uint8_t *buf_end, uint16_t num) {
// check buffer size
if ((size_t)(buf_end - (*buf)) < 2) {
return LWMQTT_BUFFER_TOO_SHORT;
}
// write bytes
(*buf)[0] = (uint8_t)(num / 256);
(*buf)[1] = (uint8_t)(num % 256);
// adjust pointer
*buf += 2;
return LWMQTT_SUCCESS;
}
lwmqtt_err_t lwmqtt_read_string(uint8_t **buf, const uint8_t *buf_end, lwmqtt_string_t *str) {
// read length
uint16_t len;
lwmqtt_err_t err = lwmqtt_read_num(buf, buf_end, &len);
if (err != LWMQTT_SUCCESS) {
return err;
}
// read data
err = lwmqtt_read_data(buf, buf_end, (uint8_t **)&str->data, len);
if (err != LWMQTT_SUCCESS) {
return err;
}
// set length
str->len = len;
return LWMQTT_SUCCESS;
}
lwmqtt_err_t lwmqtt_write_string(uint8_t **buf, const uint8_t *buf_end, lwmqtt_string_t str) {
// write string length
lwmqtt_err_t err = lwmqtt_write_num(buf, buf_end, str.len);
if (err != LWMQTT_SUCCESS) {
return err;
}
// write data
err = lwmqtt_write_data(buf, buf_end, (uint8_t *)str.data, str.len);
if (err != LWMQTT_SUCCESS) {
return err;
}
return LWMQTT_SUCCESS;
}
lwmqtt_err_t lwmqtt_read_byte(uint8_t **buf, const uint8_t *buf_end, uint8_t *byte) {
// check buffer size
if ((size_t)(buf_end - (*buf)) < 1) {
*byte = 0;
return LWMQTT_BUFFER_TOO_SHORT;
}
// read byte
*byte = (*buf)[0];
// adjust pointer
*buf += 1;
return LWMQTT_SUCCESS;
}
lwmqtt_err_t lwmqtt_write_byte(uint8_t **buf, const uint8_t *buf_end, uint8_t byte) {
// check buffer size
if ((size_t)(buf_end - (*buf)) < 1) {
return LWMQTT_BUFFER_TOO_SHORT;
}
// write byte
(*buf)[0] = byte;
// adjust pointer
*buf += 1;
return LWMQTT_SUCCESS;
}
lwmqtt_err_t lwmqtt_varnum_length(uint32_t varnum, int *len) {
if (varnum < 128) {
*len = 1;
return LWMQTT_SUCCESS;
} else if (varnum < 16384) {
*len = 2;
return LWMQTT_SUCCESS;
} else if (varnum < 2097151) {
*len = 3;
return LWMQTT_SUCCESS;
} else if (varnum < 268435455) {
*len = 4;
return LWMQTT_SUCCESS;
} else {
*len = 0;
return LWMQTT_VARNUM_OVERFLOW;
}
}
lwmqtt_err_t lwmqtt_read_varnum(uint8_t **buf, const uint8_t *buf_end, uint32_t *varnum) {
// prepare last byte
uint8_t byte;
// prepare multiplier
uint32_t multiplier = 1;
// prepare length
size_t len = 0;
// initialize number
*varnum = 0;
// decode variadic number
do {
// increment length
len++;
// return error if buffer is to small
if ((size_t)(buf_end - (*buf)) < len) {
return LWMQTT_BUFFER_TOO_SHORT;
}
// return error if the length has overflowed
if (len > 4) {
return LWMQTT_VARNUM_OVERFLOW;
}
// read byte
byte = (*buf)[len - 1];
// add byte to number
*varnum += (byte & 127) * multiplier;
// increase multiplier
multiplier *= 128;
} while ((byte & 128) != 0);
// adjust pointer
*buf += len;
return LWMQTT_SUCCESS;
}
lwmqtt_err_t lwmqtt_write_varnum(uint8_t **buf, const uint8_t *buf_end, uint32_t varnum) {
// init len counter
size_t len = 0;
// encode variadic number
do {
// check overflow
if (len == 4) {
return LWMQTT_VARNUM_OVERFLOW;
}
// return error if buffer is to small
if ((size_t)(buf_end - (*buf)) < len + 1) {
return LWMQTT_BUFFER_TOO_SHORT;
}
// calculate current byte
uint8_t byte = (uint8_t)(varnum % 128);
// change remaining length
varnum /= 128;
// set the top bit of this byte if there are more to encode
if (varnum > 0) {
byte |= 0x80;
}
// write byte
(*buf)[len++] = byte;
} while (varnum > 0);
// adjust pointer
*buf += len;
return LWMQTT_SUCCESS;
}

View File

@@ -0,0 +1,137 @@
#ifndef LWMQTT_HELPERS_H
#define LWMQTT_HELPERS_H
#include "lwmqtt.h"
/**
* Reads bits from a byte.
*
* @param byte - The byte to read from.
* @param pos - The position of the first bit.
* @param num - The number of bits to read.
* @return The read bits as a byte.
*/
uint8_t lwmqtt_read_bits(uint8_t byte, int pos, int num);
/**
* Write bits to a byte.
*
* @param byte - The byte to write bits to.
* @param value - The bits to write as a byte.
* @param pos - The position of the first bit.
* @param num - The number of bits to write.
*/
void lwmqtt_write_bits(uint8_t *byte, uint8_t value, int pos, int num);
/**
* Reads arbitrary data from the specified buffer. The pointer is incremented by bytes read.
*
* @param buf - Pointer to the buffer.
* @param buf_end - Pointer to the end of the buffer.
* @param data - Pointer to beginning of data.
* @param len - The amount of data to read.
* @return LWMQTT_SUCCESS or LWMQTT_BUFFER_TOO_SHORT.
*/
lwmqtt_err_t lwmqtt_read_data(uint8_t **buf, const uint8_t *buf_end, uint8_t **data, size_t len);
/**
* Writes arbitrary data to the specified buffer. The pointer is incremented by the bytes written.
*
* @param buf - Pointer to the buffer.
* @param buf_end - Pointer to the end of the buffer.
* @param data - Pointer to the to be written data.
* @param len - The amount of data to write.
* @return LWMQTT_SUCCESS or LWMQTT_BUFFER_TOO_SHORT.
*/
lwmqtt_err_t lwmqtt_write_data(uint8_t **buf, const uint8_t *buf_end, uint8_t *data, size_t len);
/**
* Reads two byte number from the specified buffer. The pointer is incremented by two.
*
* @param buf - Pointer to the buffer.
* @param buf_end - Pointer to the end of the buffer.
* @param num - The read number.
* @return LWMQTT_SUCCESS or LWMQTT_BUFFER_TOO_SHORT.
*/
lwmqtt_err_t lwmqtt_read_num(uint8_t **buf, const uint8_t *buf_end, uint16_t *num);
/**
* Writes a two byte number to the specified buffer. The pointer is incremented by two.
*
* @param buf - Pointer to the buffer.
* @param buf_end - Pointer to the end of the buffer.
* @param num - The number to write.
* @return LWMQTT_SUCCESS or LWMQTT_BUFFER_TOO_SHORT.
*/
lwmqtt_err_t lwmqtt_write_num(uint8_t **buf, const uint8_t *buf_end, uint16_t num);
/**
* Reads a string from the specified buffer into the passed object. The pointer is incremented by the bytes read.
*
* @param buf - Pointer to the buffer.
* @param buf_end - Pointer to the end of the buffer.
* @param str - The object into which the data is to be read.
* @return LWMQTT_SUCCESS or LWMQTT_BUFFER_TOO_SHORT.
*/
lwmqtt_err_t lwmqtt_read_string(uint8_t **buf, const uint8_t *buf_end, lwmqtt_string_t *str);
/**
* Writes a string to the specified buffer. The pointer is incremented by the bytes written.
*
* @param buf - Pointer to the buffer.
* @param buf_end - Pointer to the end of the buffer.
* @param str - The string to write.
* @return LWMQTT_SUCCESS or LWMQTT_BUFFER_TOO_SHORT.
*/
lwmqtt_err_t lwmqtt_write_string(uint8_t **buf, const uint8_t *buf_end, lwmqtt_string_t str);
/**
* Reads one byte from the buffer. The pointer is incremented by one.
*
* @param buf - Pointer to the buffer.
* @param buf_end - Pointer to the end of the buffer.
* @param byte - The read byte.
* @return LWMQTT_SUCCESS or LWMQTT_BUFFER_TOO_SHORT.
*/
lwmqtt_err_t lwmqtt_read_byte(uint8_t **buf, const uint8_t *buf_end, uint8_t *byte);
/**
* Writes one byte to the specified buffer. The pointer is incremented by one.
*
* @param buf - Pointer to the buffer.
* @param buf_end - Pointer to the end of the buffer.
* @param byte - The byte to write.
* @return LWMQTT_SUCCESS or LWMQTT_BUFFER_TOO_SHORT.
*/
lwmqtt_err_t lwmqtt_write_byte(uint8_t **buf, const uint8_t *buf_end, uint8_t byte);
/**
* Returns the amount of bytes required by the variable number.
*
* @param varnum - The number to check.
* @param len - The required length;
* @return LWMQTT_SUCCESS or LWMQTT_VARNUM_OVERFLOW.
*/
lwmqtt_err_t lwmqtt_varnum_length(uint32_t varnum, int *len);
/**
* Reads a variable number from the specified buffer. The pointer is incremented by the bytes read.
*
* @param buf - Pointer to the buffer.
* @param buf_end - Pointer to the end of the buffer.
* @param varnum - The read varnum.
* @return LWMQTT_SUCCESS, LWMQTT_BUFFER_TOO_SHORT or LWMQTT_VARNUM_OVERFLOW.
*/
lwmqtt_err_t lwmqtt_read_varnum(uint8_t **buf, const uint8_t *buf_end, uint32_t *varnum);
/**
* Writes a variable number to the specified buffer. The pointer is incremented by the bytes written.
*
* @param buf - Pointer to the buffer.
* @param buf_end - Pointer to the end of the buffer.
* @param varnum - The number to write.
* @return LWMQTT_SUCCESS, LWMQTT_BUFFER_TOO_SHORT or LWMQTT_VARNUM_OVERFLOW.
*/
lwmqtt_err_t lwmqtt_write_varnum(uint8_t **buf, const uint8_t *buf_end, uint32_t varnum);
#endif

View File

@@ -0,0 +1,381 @@
#ifndef LWMQTT_H
#define LWMQTT_H
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
/**
* The error type used by all exposed APIs.
*
* If a function returns an error that operates on a connected client (e.g publish, keep_alive, etc.) the caller should
* switch into a disconnected state, close and cleanup the current connection and start over by creating a new
* connection.
*/
typedef enum {
LWMQTT_SUCCESS = 0,
LWMQTT_BUFFER_TOO_SHORT = -1,
LWMQTT_VARNUM_OVERFLOW = -2,
LWMQTT_NETWORK_FAILED_CONNECT = -3,
LWMQTT_NETWORK_TIMEOUT = -4,
LWMQTT_NETWORK_FAILED_READ = -5,
LWMQTT_NETWORK_FAILED_WRITE = -6,
LWMQTT_REMAINING_LENGTH_OVERFLOW = -7,
LWMQTT_REMAINING_LENGTH_MISMATCH = -8,
LWMQTT_MISSING_OR_WRONG_PACKET = -9,
LWMQTT_CONNECTION_DENIED = -10,
LWMQTT_FAILED_SUBSCRIPTION = -11,
LWMQTT_SUBACK_ARRAY_OVERFLOW = -12,
LWMQTT_PONG_TIMEOUT = -13,
} lwmqtt_err_t;
/**
* A common string object.
*/
typedef struct {
uint16_t len;
char *data;
} lwmqtt_string_t;
/**
* The initializer for string objects.
*/
#define lwmqtt_default_string \
{ 0, NULL }
/**
* Returns a string object for the passed C string.
*
* @param str - The C string.
* @return A string object.
*/
lwmqtt_string_t lwmqtt_string(const char *str);
/**
* Compares a string object to a C string.
*
* @param a - The string object to compare.
* @param b - The C string to compare.
* @return Similarity e.g. strcmp().
*/
int lwmqtt_strcmp(lwmqtt_string_t a, const char *b);
/**
* The available QOS levels.
*/
typedef enum { LWMQTT_QOS0 = 0, LWMQTT_QOS1 = 1, LWMQTT_QOS2 = 2, LWMQTT_QOS_FAILURE = 128 } lwmqtt_qos_t;
/**
* The message object used to publish and receive messages.
*/
typedef struct {
lwmqtt_qos_t qos;
bool retained;
uint8_t *payload;
size_t payload_len;
} lwmqtt_message_t;
/**
* The initializer for message objects.
*/
#define lwmqtt_default_message \
{ LWMQTT_QOS0, false, NULL, 0 }
/**
* Forward declaration of the client object.
*/
typedef struct lwmqtt_client_t lwmqtt_client_t;
/**
* The callback used to read from a network object.
*
* The callbacks is expected to read up to the amount of bytes in to the passed buffer. It should block the specified
* timeout and wait for more incoming data.
*
* @param ref - A custom reference.
* @param buf - The buffer.
* @param len - The length of the buffer.
* @param read - Variable that must be set with the amount of read bytes.
* @param timeout - The timeout in milliseconds for the operation.
*/
typedef lwmqtt_err_t (*lwmqtt_network_read_t)(void *ref, uint8_t *buf, size_t len, size_t *read, uint32_t timeout);
/**
* The callback used to write to a network object.
*
* The callback is expected to write up to the amount of bytes from the passed buffer. It should wait up to the
* specified timeout to write the specified data to the network.
*
* @param ref - A custom reference.
* @param buf - The buffer.
* @param len - The length of the buffer.
* @param sent - Variable that must be set with the amount of written bytes.
* @param timeout - The timeout in milliseconds for the operation.
*/
typedef lwmqtt_err_t (*lwmqtt_network_write_t)(void *ref, uint8_t *buf, size_t len, size_t *sent, uint32_t timeout);
/**
* The callback used to set a timer.
*
* @param ref - A custom reference.
* @param timeout - The amount of milliseconds until the deadline.
*/
typedef void (*lwmqtt_timer_set_t)(void *ref, uint32_t timeout);
/**
* The callback used to get a timers value.
*
* @param - A custom reference.
* @return The amount of milliseconds until the deadline. May return negative numbers if the deadline has been reached.
*/
typedef int32_t (*lwmqtt_timer_get_t)(void *ref);
/**
* The callback used to forward incoming messages.
*
* Note: The callback is mostly executed because of a call to lwmqtt_yield() that processes incoming messages. However,
* it is possible that the callback is also executed during a call to lwmqtt_subscribe(), lwmqtt_publish() or
* lwmqtt_unsubscribe() if incoming messages are received between the required acknowledgements. It is therefore not
* recommended to call any further lwmqtt methods in the callback as this might result in weird call stacks. The
* callback should place the received messages in a queue and dispatch them after the caller has returned.
*/
typedef void (*lwmqtt_callback_t)(lwmqtt_client_t *client, void *ref, lwmqtt_string_t str, lwmqtt_message_t msg);
/**
* The client object.
*/
struct lwmqtt_client_t {
uint16_t last_packet_id;
uint32_t keep_alive_interval;
bool pong_pending;
size_t write_buf_size, read_buf_size;
uint8_t *write_buf, *read_buf;
lwmqtt_callback_t callback;
void *callback_ref;
void *network;
lwmqtt_network_read_t network_read;
lwmqtt_network_write_t network_write;
void *keep_alive_timer;
void *command_timer;
lwmqtt_timer_set_t timer_set;
lwmqtt_timer_get_t timer_get;
};
/**
* Will initialize the specified client object.
*
* @param client - The client object.
* @param write_buf - The write buffer.
* @param write_buf_size - The write buffer size.
* @param read_buf - The read buffer.
* @param read_buf_size - The read buffer size.
*/
void lwmqtt_init(lwmqtt_client_t *client, uint8_t *write_buf, size_t write_buf_size, uint8_t *read_buf,
size_t read_buf_size);
/**
* Will set the network reference and callbacks for this client object.
*
* @param client - The client object.
* @param ref - The reference to the network object.
* @param read - The read callback.
* @param write - The write callback.
*/
void lwmqtt_set_network(lwmqtt_client_t *client, void *ref, lwmqtt_network_read_t read, lwmqtt_network_write_t write);
/**
* Will set the timer references and callbacks for this client object.
*
* @param client - The client object.
* @param keep_alive_timer - The reference to the keep alive timer.
* @param command_timer - The reference to the command timer.
* @param set - The set callback.
* @param get - The get callback.
*/
void lwmqtt_set_timers(lwmqtt_client_t *client, void *keep_alive_timer, void *command_timer, lwmqtt_timer_set_t set,
lwmqtt_timer_get_t get);
/**
* Will set the callback used to receive incoming messages.
*
* @param client - The client object.
* @param ref - A custom reference that will passed to the callback.
* @param cb - The callback to be called.
*/
void lwmqtt_set_callback(lwmqtt_client_t *client, void *ref, lwmqtt_callback_t cb);
/**
* The object defining the last will of a client.
*/
typedef struct {
lwmqtt_string_t topic;
lwmqtt_qos_t qos;
bool retained;
lwmqtt_string_t payload;
} lwmqtt_will_t;
/**
* The default initializer for the will object.
*/
#define lwmqtt_default_will \
{ lwmqtt_default_string, LWMQTT_QOS0, false, lwmqtt_default_string }
/**
* The object containing the connection options for a client.
*/
typedef struct {
lwmqtt_string_t client_id;
uint16_t keep_alive;
bool clean_session;
lwmqtt_string_t username;
lwmqtt_string_t password;
} lwmqtt_options_t;
/**
* The default initializer for the options object.
*/
#define lwmqtt_default_options \
{ lwmqtt_default_string, 60, true, lwmqtt_default_string, lwmqtt_default_string }
/**
* The available return codes transported by the connack packet.
*/
typedef enum {
LWMQTT_CONNECTION_ACCEPTED = 0,
LWMQTT_UNACCEPTABLE_PROTOCOL = 1,
LWMQTT_IDENTIFIER_REJECTED = 2,
LWMQTT_SERVER_UNAVAILABLE = 3,
LWMQTT_BAD_USERNAME_OR_PASSWORD = 4,
LWMQTT_NOT_AUTHORIZED = 5,
LWMQTT_UNKNOWN_RETURN_CODE = 6
} lwmqtt_return_code_t;
/**
* Will send a connect packet and wait for a connack response and set the return code.
*
* The network object must already be connected to the server. An error is returned if the broker rejects the
* connection.
*
* @param client - The client object.
* @param options - The options object.
* @param will - The will object.
* @param return_code - The variable that will receive the return code.
* @param timeout - The command timeout.
* @return An error value.
*/
lwmqtt_err_t lwmqtt_connect(lwmqtt_client_t *client, lwmqtt_options_t options, lwmqtt_will_t *will,
lwmqtt_return_code_t *return_code, uint32_t timeout);
/**
* Will send a publish packet and wait for all acks to complete.
*
* Note: The message callback might be called with incoming messages as part of this call.
*
* @param client - The client object.
* @param topic - The topic.
* @param message - The message.
* @param timeout - The command timeout.
* @return An error value.
*/
lwmqtt_err_t lwmqtt_publish(lwmqtt_client_t *client, lwmqtt_string_t topic, lwmqtt_message_t msg, uint32_t timeout);
/**
* Will send a subscribe packet with multiple topic filters plus QOS levels and wait for the suback to complete.
*
* Note: The message callback might be called with incoming messages as part of this call.
*
* @param client - The client object.
* @param count - The number of topic filters and QOS levels.
* @param topic_filter - The list of topic filters.
* @param qos - The list of QOS levels.
* @param timeout - The command timeout.
* @return An error value.
*/
lwmqtt_err_t lwmqtt_subscribe(lwmqtt_client_t *client, int count, lwmqtt_string_t *topic_filter, lwmqtt_qos_t *qos,
uint32_t timeout);
/**
* Will send a subscribe packet with a single topic filter plus QOS level and wait for the suback to complete.
*
* Note: The message callback might be called with incoming messages as part of this call.
*
* @param client - The client object.
* @param topic_filter - The topic filter.
* @param qos - The QOS level.
* @param timeout - The command timeout.
* @return An error value.
*/
lwmqtt_err_t lwmqtt_subscribe_one(lwmqtt_client_t *client, lwmqtt_string_t topic_filter, lwmqtt_qos_t qos,
uint32_t timeout);
/**
* Will send an unsubscribe packet with multiple topic filters and wait for the unsuback to complete.
*
* Note: The message callback might be called with incoming messages as part of this call.
*
* @param client - The client object.
* @param count - The number of topic filters.
* @param topic_filter - The topic filter.
* @param timeout - The command timeout.
* @return An error value.
*/
lwmqtt_err_t lwmqtt_unsubscribe(lwmqtt_client_t *client, int count, lwmqtt_string_t *topic_filter, uint32_t timeout);
/**
* Will send an unsubscribe packet with a single topic filter and wait for the unsuback to complete.
*
* Note: The message callback might be called with incoming messages as part of this call.
*
* @param client - The client object.
* @param topic_filter - The topic filter.
* @param timeout - The command timeout.
* @return An error value.
*/
lwmqtt_err_t lwmqtt_unsubscribe_one(lwmqtt_client_t *client, lwmqtt_string_t topic_filter, uint32_t timeout);
/**
* Will send a disconnect packet and finish the client.
*
* @param client - The client object.
* @param timeout - The command timeout.
* @return An error value.
*/
lwmqtt_err_t lwmqtt_disconnect(lwmqtt_client_t *client, uint32_t timeout);
/**
* Will yield control to the client and receive incoming packets from the network.
*
* Single-threaded applications may peek on the network and assess if data is available to read before calling yield and
* potentially block until the timeout is reached. Multi-threaded applications may select on the socket and block until
* data is available and then yield to the client if data is available. All applications may specify the amount of bytes
* available to read in order to constrain the yield to only receive packets that are already in-flight.
*
* If no availability info is given the yield will return after one packet has been successfully read or the deadline
* has been reached but no single bytes has been received.
*
* Note: The message callback might be called with incoming messages as part of this call.
*
* @param client - The client object.
* @param available - The available bytes to read.
* @param timeout - The command timeout.
* @return An error value.
*/
lwmqtt_err_t lwmqtt_yield(lwmqtt_client_t *client, size_t available, uint32_t timeout);
/**
* Will yield control to the client to keep the connection alive.
*
* This functions must be called at a rate slightly lower than 25% of the configured keep alive. If keep alive is zero,
* the function must not be called at all.
*
* @param client - The client object.
* @param timeout - The command timeout.
* @return An error value.
*/
lwmqtt_err_t lwmqtt_keep_alive(lwmqtt_client_t *client, uint32_t timeout);
#endif // LWMQTT_H

View File

@@ -0,0 +1,742 @@
#include "packet.h"
lwmqtt_err_t lwmqtt_detect_packet_type(uint8_t *buf, size_t buf_len, lwmqtt_packet_type_t *packet_type) {
// set default packet type
*packet_type = LWMQTT_NO_PACKET;
// prepare pointer
uint8_t *buf_ptr = buf;
uint8_t *buf_end = buf + buf_len;
// prepare header
uint8_t header;
// read header
lwmqtt_err_t err = lwmqtt_read_byte(&buf_ptr, buf_end, &header);
if (err != LWMQTT_SUCCESS) {
return err;
}
// get packet type
*packet_type = (lwmqtt_packet_type_t)lwmqtt_read_bits(header, 4, 4);
// check if packet type is correct and can be received
switch (*packet_type) {
case LWMQTT_CONNACK_PACKET:
case LWMQTT_PUBLISH_PACKET:
case LWMQTT_PUBACK_PACKET:
case LWMQTT_PUBREC_PACKET:
case LWMQTT_PUBREL_PACKET:
case LWMQTT_PUBCOMP_PACKET:
case LWMQTT_SUBACK_PACKET:
case LWMQTT_UNSUBACK_PACKET:
case LWMQTT_PINGRESP_PACKET:
return LWMQTT_SUCCESS;
default:
*packet_type = LWMQTT_NO_PACKET;
return LWMQTT_MISSING_OR_WRONG_PACKET;
}
}
lwmqtt_err_t lwmqtt_detect_remaining_length(uint8_t *buf, size_t buf_len, uint32_t *rem_len) {
// prepare pointer
uint8_t *ptr = buf;
// attempt to decode remaining length
lwmqtt_err_t err = lwmqtt_read_varnum(&ptr, buf + buf_len, rem_len);
if (err == LWMQTT_VARNUM_OVERFLOW) {
*rem_len = 0;
return LWMQTT_REMAINING_LENGTH_OVERFLOW;
} else if (err != LWMQTT_SUCCESS) {
*rem_len = 0;
return err;
}
return LWMQTT_SUCCESS;
}
lwmqtt_err_t lwmqtt_encode_connect(uint8_t *buf, size_t buf_len, size_t *len, lwmqtt_options_t options,
lwmqtt_will_t *will) {
// prepare pointers
uint8_t *buf_ptr = buf;
uint8_t *buf_end = buf + buf_len;
// fixed header is 10
uint32_t rem_len = 10;
// add client id to remaining length
rem_len += options.client_id.len + 2;
// add will if present to remaining length
if (will != NULL) {
rem_len += will->topic.len + 2 + will->payload.len + 2;
}
// add username if present to remaining length
if (options.username.len > 0) {
rem_len += options.username.len + 2;
// add password if present to remaining length
if (options.password.len > 0) {
rem_len += options.password.len + 2;
}
}
// check remaining length length
int rem_len_len;
lwmqtt_err_t err = lwmqtt_varnum_length(rem_len, &rem_len_len);
if (err == LWMQTT_VARNUM_OVERFLOW) {
return LWMQTT_REMAINING_LENGTH_OVERFLOW;
}
// prepare header
uint8_t header = 0;
lwmqtt_write_bits(&header, LWMQTT_CONNECT_PACKET, 4, 4);
// write header
err = lwmqtt_write_byte(&buf_ptr, buf_end, header);
if (err != LWMQTT_SUCCESS) {
return err;
}
// write remaining length
err = lwmqtt_write_varnum(&buf_ptr, buf_end, rem_len);
if (err != LWMQTT_SUCCESS) {
return err;
}
// write version string
err = lwmqtt_write_string(&buf_ptr, buf_end, lwmqtt_string("MQTT"));
if (err != LWMQTT_SUCCESS) {
return err;
}
// write version number
err = lwmqtt_write_byte(&buf_ptr, buf_end, 4);
if (err != LWMQTT_SUCCESS) {
return err;
}
// prepare flags
uint8_t flags = 0;
// set clean session
lwmqtt_write_bits(&flags, (uint8_t)(options.clean_session), 1, 1);
// set will flags if present
if (will != NULL) {
lwmqtt_write_bits(&flags, 1, 2, 1);
lwmqtt_write_bits(&flags, will->qos, 3, 2);
lwmqtt_write_bits(&flags, (uint8_t)(will->retained), 5, 1);
}
// set username flag if present
if (options.username.len > 0) {
lwmqtt_write_bits(&flags, 1, 7, 1);
// set password flag if present
if (options.password.len > 0) {
lwmqtt_write_bits(&flags, 1, 6, 1);
}
}
// write flags
err = lwmqtt_write_byte(&buf_ptr, buf_end, flags);
if (err != LWMQTT_SUCCESS) {
return err;
}
// write keep alive
err = lwmqtt_write_num(&buf_ptr, buf_end, options.keep_alive);
if (err != LWMQTT_SUCCESS) {
return err;
}
// write client id
err = lwmqtt_write_string(&buf_ptr, buf_end, options.client_id);
if (err != LWMQTT_SUCCESS) {
return err;
}
// write will if present
if (will != NULL) {
// write topic
err = lwmqtt_write_string(&buf_ptr, buf_end, will->topic);
if (err != LWMQTT_SUCCESS) {
return err;
}
// write payload length
err = lwmqtt_write_num(&buf_ptr, buf_end, (uint16_t)will->payload.len);
if (err != LWMQTT_SUCCESS) {
return err;
}
// write payload
err = lwmqtt_write_data(&buf_ptr, buf_end, (uint8_t *)will->payload.data, will->payload.len);
if (err != LWMQTT_SUCCESS) {
return err;
}
}
// write username if present
if (options.username.len > 0) {
err = lwmqtt_write_string(&buf_ptr, buf_end, options.username);
if (err != LWMQTT_SUCCESS) {
return err;
}
}
// write password if present
if (options.username.len > 0 && options.password.len > 0) {
err = lwmqtt_write_string(&buf_ptr, buf_end, options.password);
if (err != LWMQTT_SUCCESS) {
return err;
}
}
// set written length
*len = buf_ptr - buf;
return LWMQTT_SUCCESS;
}
lwmqtt_err_t lwmqtt_decode_connack(uint8_t *buf, size_t buf_len, bool *session_present,
lwmqtt_return_code_t *return_code) {
// prepare pointers
uint8_t *buf_ptr = buf;
uint8_t *buf_end = buf + buf_len;
// read header
uint8_t header;
lwmqtt_err_t err = lwmqtt_read_byte(&buf_ptr, buf_end, &header);
if (err != LWMQTT_SUCCESS) {
return err;
}
// check packet type
if (lwmqtt_read_bits(header, 4, 4) != LWMQTT_CONNACK_PACKET) {
return LWMQTT_MISSING_OR_WRONG_PACKET;
}
// read remaining length
uint32_t rem_len;
err = lwmqtt_read_varnum(&buf_ptr, buf_end, &rem_len);
if (err != LWMQTT_SUCCESS) {
return err;
}
// check remaining length
if (rem_len != 2) {
return LWMQTT_REMAINING_LENGTH_MISMATCH;
}
// read flags
uint8_t flags;
err = lwmqtt_read_byte(&buf_ptr, buf_end, &flags);
if (err != LWMQTT_SUCCESS) {
return err;
}
// read return code
uint8_t raw_return_code;
err = lwmqtt_read_byte(&buf_ptr, buf_end, &raw_return_code);
if (err != LWMQTT_SUCCESS) {
return err;
}
// get session present
*session_present = lwmqtt_read_bits(flags, 7, 1) == 1;
// get return code
switch (raw_return_code) {
case 0:
*return_code = LWMQTT_CONNECTION_ACCEPTED;
break;
case 1:
*return_code = LWMQTT_UNACCEPTABLE_PROTOCOL;
break;
case 2:
*return_code = LWMQTT_IDENTIFIER_REJECTED;
break;
case 3:
*return_code = LWMQTT_SERVER_UNAVAILABLE;
break;
case 4:
*return_code = LWMQTT_BAD_USERNAME_OR_PASSWORD;
break;
case 5:
*return_code = LWMQTT_NOT_AUTHORIZED;
break;
default:
*return_code = LWMQTT_UNKNOWN_RETURN_CODE;
}
return LWMQTT_SUCCESS;
}
lwmqtt_err_t lwmqtt_encode_zero(uint8_t *buf, size_t buf_len, size_t *len, lwmqtt_packet_type_t packet_type) {
// prepare pointer
uint8_t *buf_ptr = buf;
uint8_t *buf_end = buf + buf_len;
// write header
uint8_t header = 0;
lwmqtt_write_bits(&header, packet_type, 4, 4);
lwmqtt_err_t err = lwmqtt_write_byte(&buf_ptr, buf_end, header);
if (err != LWMQTT_SUCCESS) {
return err;
}
// write remaining length
err = lwmqtt_write_varnum(&buf_ptr, buf_end, 0);
if (err != LWMQTT_SUCCESS) {
return err;
}
// set length
*len = buf_ptr - buf;
return LWMQTT_SUCCESS;
}
lwmqtt_err_t lwmqtt_decode_ack(uint8_t *buf, size_t buf_len, lwmqtt_packet_type_t packet_type, bool *dup,
uint16_t *packet_id) {
// prepare pointer
uint8_t *buf_ptr = buf;
uint8_t *buf_end = buf + buf_len;
// read header
uint8_t header = 0;
lwmqtt_err_t err = lwmqtt_read_byte(&buf_ptr, buf_end, &header);
if (err != LWMQTT_SUCCESS) {
return err;
}
// check packet type
if (lwmqtt_read_bits(header, 4, 4) != packet_type) {
return LWMQTT_MISSING_OR_WRONG_PACKET;
}
// get dup
*dup = lwmqtt_read_bits(header, 3, 1) == 1;
// read remaining length
uint32_t rem_len;
err = lwmqtt_read_varnum(&buf_ptr, buf + buf_len, &rem_len);
if (err != LWMQTT_SUCCESS) {
return err;
}
// check remaining length
if (rem_len != 2) {
return LWMQTT_REMAINING_LENGTH_MISMATCH;
}
// read packet id
err = lwmqtt_read_num(&buf_ptr, buf_end, packet_id);
if (err != LWMQTT_SUCCESS) {
return err;
}
return LWMQTT_SUCCESS;
}
lwmqtt_err_t lwmqtt_encode_ack(uint8_t *buf, size_t buf_len, size_t *len, lwmqtt_packet_type_t packet_type, bool dup,
uint16_t packet_id) {
// prepare pointer
uint8_t *buf_ptr = buf;
uint8_t *buf_end = buf + buf_len;
// prepare header
uint8_t header = 0;
// set packet type
lwmqtt_write_bits(&header, packet_type, 4, 4);
// set dup
lwmqtt_write_bits(&header, (uint8_t)(dup), 3, 1);
// set qos
lwmqtt_write_bits(&header, (uint8_t)(packet_type == LWMQTT_PUBREL_PACKET ? LWMQTT_QOS1 : LWMQTT_QOS0), 1, 2);
// write header
lwmqtt_err_t err = lwmqtt_write_byte(&buf_ptr, buf_end, header);
if (err != LWMQTT_SUCCESS) {
return err;
}
// write remaining length
err = lwmqtt_write_varnum(&buf_ptr, buf_end, 2);
if (err != LWMQTT_SUCCESS) {
return err;
}
// write packet id
err = lwmqtt_write_num(&buf_ptr, buf_end, packet_id);
if (err != LWMQTT_SUCCESS) {
return err;
}
// set written length
*len = buf_ptr - buf;
return LWMQTT_SUCCESS;
}
lwmqtt_err_t lwmqtt_decode_publish(uint8_t *buf, size_t buf_len, bool *dup, uint16_t *packet_id, lwmqtt_string_t *topic,
lwmqtt_message_t *msg) {
// prepare pointer
uint8_t *buf_ptr = buf;
uint8_t *buf_end = buf + buf_len;
// read header
uint8_t header;
lwmqtt_err_t err = lwmqtt_read_byte(&buf_ptr, buf_end, &header);
if (err != LWMQTT_SUCCESS) {
return err;
}
// check packet type
if (lwmqtt_read_bits(header, 4, 4) != LWMQTT_PUBLISH_PACKET) {
return LWMQTT_MISSING_OR_WRONG_PACKET;
}
// get dup
*dup = lwmqtt_read_bits(header, 3, 1) == 1;
// get retained
msg->retained = lwmqtt_read_bits(header, 0, 1) == 1;
// get qos
switch (lwmqtt_read_bits(header, 1, 2)) {
case 0:
msg->qos = LWMQTT_QOS0;
break;
case 1:
msg->qos = LWMQTT_QOS1;
break;
case 2:
msg->qos = LWMQTT_QOS2;
break;
default:
msg->qos = LWMQTT_QOS0;
break;
}
// read remaining length
uint32_t rem_len;
err = lwmqtt_read_varnum(&buf_ptr, buf_end, &rem_len);
if (err != LWMQTT_SUCCESS) {
return err;
}
// check remaining length (topic length)
if (rem_len < 2) {
return LWMQTT_REMAINING_LENGTH_MISMATCH;
}
// check buffer capacity
if ((uint32_t)(buf_end - buf_ptr) < rem_len) {
return LWMQTT_BUFFER_TOO_SHORT;
}
// reset buf end
buf_end = buf_ptr + rem_len;
// read topic
err = lwmqtt_read_string(&buf_ptr, buf_end, topic);
if (err != LWMQTT_SUCCESS) {
return err;
}
// read packet id if qos is at least 1
if (msg->qos > 0) {
err = lwmqtt_read_num(&buf_ptr, buf_end, packet_id);
if (err != LWMQTT_SUCCESS) {
return err;
}
} else {
*packet_id = 0;
}
// set payload length
msg->payload_len = buf_end - buf_ptr;
// read payload
err = lwmqtt_read_data(&buf_ptr, buf_end, &msg->payload, buf_end - buf_ptr);
if (err != LWMQTT_SUCCESS) {
return err;
}
return LWMQTT_SUCCESS;
}
lwmqtt_err_t lwmqtt_encode_publish(uint8_t *buf, size_t buf_len, size_t *len, bool dup, uint16_t packet_id,
lwmqtt_string_t topic, lwmqtt_message_t msg) {
// prepare pointer
uint8_t *buf_ptr = buf;
uint8_t *buf_end = buf + buf_len;
// calculate remaining length
uint32_t rem_len = 2 + topic.len + (uint32_t)msg.payload_len;
if (msg.qos > 0) {
rem_len += 2;
}
// check remaining length length
int rem_len_len;
lwmqtt_err_t err = lwmqtt_varnum_length(rem_len, &rem_len_len);
if (err == LWMQTT_VARNUM_OVERFLOW) {
return LWMQTT_REMAINING_LENGTH_OVERFLOW;
}
// prepare header
uint8_t header = 0;
// set packet type
lwmqtt_write_bits(&header, LWMQTT_PUBLISH_PACKET, 4, 4);
// set dup
lwmqtt_write_bits(&header, (uint8_t)(dup), 3, 1);
// set qos
lwmqtt_write_bits(&header, msg.qos, 1, 2);
// set retained
lwmqtt_write_bits(&header, (uint8_t)(msg.retained), 0, 1);
// write header
err = lwmqtt_write_byte(&buf_ptr, buf_end, header);
if (err != LWMQTT_SUCCESS) {
return err;
}
// write remaining length
err = lwmqtt_write_varnum(&buf_ptr, buf_end, rem_len);
if (err != LWMQTT_SUCCESS) {
return err;
}
// write topic
err = lwmqtt_write_string(&buf_ptr, buf_end, topic);
if (err != LWMQTT_SUCCESS) {
return err;
}
// write packet id if qos is at least 1
if (msg.qos > 0) {
err = lwmqtt_write_num(&buf_ptr, buf_end, packet_id);
if (err != LWMQTT_SUCCESS) {
return err;
}
}
// write payload
err = lwmqtt_write_data(&buf_ptr, buf_end, msg.payload, msg.payload_len);
if (err != LWMQTT_SUCCESS) {
return err;
}
// set length
*len = buf_ptr - buf;
return LWMQTT_SUCCESS;
}
lwmqtt_err_t lwmqtt_encode_subscribe(uint8_t *buf, size_t buf_len, size_t *len, uint16_t packet_id, int count,
lwmqtt_string_t *topic_filters, lwmqtt_qos_t *qos_levels) {
// prepare pointer
uint8_t *buf_ptr = buf;
uint8_t *buf_end = buf + buf_len;
// calculate remaining length
uint32_t rem_len = 2;
for (int i = 0; i < count; i++) {
rem_len += 2 + topic_filters[i].len + 1;
}
// check remaining length length
int rem_len_len;
lwmqtt_err_t err = lwmqtt_varnum_length(rem_len, &rem_len_len);
if (err == LWMQTT_VARNUM_OVERFLOW) {
return LWMQTT_REMAINING_LENGTH_OVERFLOW;
}
// prepare header
uint8_t header = 0;
// set packet type
lwmqtt_write_bits(&header, LWMQTT_SUBSCRIBE_PACKET, 4, 4);
// set qos
lwmqtt_write_bits(&header, LWMQTT_QOS1, 1, 2);
// write header
err = lwmqtt_write_byte(&buf_ptr, buf_end, header);
if (err != LWMQTT_SUCCESS) {
return err;
}
// write remaining length
err = lwmqtt_write_varnum(&buf_ptr, buf_end, rem_len);
if (err != LWMQTT_SUCCESS) {
return err;
}
// write packet id
err = lwmqtt_write_num(&buf_ptr, buf_end, packet_id);
if (err != LWMQTT_SUCCESS) {
return err;
}
// write all subscriptions
for (int i = 0; i < count; i++) {
// write topic
err = lwmqtt_write_string(&buf_ptr, buf_end, topic_filters[i]);
if (err != LWMQTT_SUCCESS) {
return err;
}
// write qos level
err = lwmqtt_write_byte(&buf_ptr, buf_end, (uint8_t)qos_levels[i]);
if (err != LWMQTT_SUCCESS) {
return err;
}
}
// set length
*len = buf_ptr - buf;
return LWMQTT_SUCCESS;
}
lwmqtt_err_t lwmqtt_decode_suback(uint8_t *buf, size_t buf_len, uint16_t *packet_id, int max_count, int *count,
lwmqtt_qos_t *granted_qos_levels) {
// prepare pointer
uint8_t *buf_ptr = buf;
uint8_t *buf_end = buf + buf_len;
// read header
uint8_t header;
lwmqtt_err_t err = lwmqtt_read_byte(&buf_ptr, buf_end, &header);
if (err != LWMQTT_SUCCESS) {
return err;
}
// check packet type
if (lwmqtt_read_bits(header, 4, 4) != LWMQTT_SUBACK_PACKET) {
return LWMQTT_MISSING_OR_WRONG_PACKET;
}
// read remaining length
uint32_t rem_len;
err = lwmqtt_read_varnum(&buf_ptr, buf_end, &rem_len);
if (err != LWMQTT_SUCCESS) {
return err;
}
// check remaining length (packet id + min. one suback code)
if (rem_len < 3) {
return LWMQTT_REMAINING_LENGTH_MISMATCH;
}
// read packet id
err = lwmqtt_read_num(&buf_ptr, buf_end, packet_id);
if (err != LWMQTT_SUCCESS) {
return err;
}
// read all suback codes
for (*count = 0; *count < (int)rem_len - 2; (*count)++) {
// check max count
if (*count > max_count) {
return LWMQTT_SUBACK_ARRAY_OVERFLOW;
}
// read qos level
uint8_t raw_qos_level;
err = lwmqtt_read_byte(&buf_ptr, buf_end, &raw_qos_level);
if (err != LWMQTT_SUCCESS) {
return err;
}
// set qos level
switch (raw_qos_level) {
case 0:
granted_qos_levels[*count] = LWMQTT_QOS0;
break;
case 1:
granted_qos_levels[*count] = LWMQTT_QOS1;
break;
case 2:
granted_qos_levels[*count] = LWMQTT_QOS2;
break;
default:
granted_qos_levels[*count] = LWMQTT_QOS_FAILURE;
break;
}
}
return LWMQTT_SUCCESS;
}
lwmqtt_err_t lwmqtt_encode_unsubscribe(uint8_t *buf, size_t buf_len, size_t *len, uint16_t packet_id, int count,
lwmqtt_string_t *topic_filters) {
// prepare pointer
uint8_t *buf_ptr = buf;
uint8_t *buf_end = buf + buf_len;
// calculate remaining length
uint32_t rem_len = 2;
for (int i = 0; i < count; i++) {
rem_len += 2 + topic_filters[i].len;
}
// check remaining length length
int rem_len_len;
lwmqtt_err_t err = lwmqtt_varnum_length(rem_len, &rem_len_len);
if (err == LWMQTT_VARNUM_OVERFLOW) {
return LWMQTT_REMAINING_LENGTH_OVERFLOW;
}
// prepare header
uint8_t header = 0;
// set packet type
lwmqtt_write_bits(&header, LWMQTT_UNSUBSCRIBE_PACKET, 4, 4);
// set qos
lwmqtt_write_bits(&header, LWMQTT_QOS1, 1, 2);
// write header
err = lwmqtt_write_byte(&buf_ptr, buf_end, header);
if (err != LWMQTT_SUCCESS) {
return err;
}
// write remaining length
err = lwmqtt_write_varnum(&buf_ptr, buf_end, rem_len);
if (err != LWMQTT_SUCCESS) {
return err;
}
// write packet id
err = lwmqtt_write_num(&buf_ptr, buf_end, packet_id);
if (err != LWMQTT_SUCCESS) {
return err;
}
// write topics
for (int i = 0; i < count; i++) {
err = lwmqtt_write_string(&buf_ptr, buf_end, topic_filters[i]);
if (err != LWMQTT_SUCCESS) {
return err;
}
}
// set length
*len = buf_ptr - buf;
return LWMQTT_SUCCESS;
}

View File

@@ -0,0 +1,185 @@
#ifndef LWMQTT_PACKET_H
#define LWMQTT_PACKET_H
#include "helpers.h"
/**
* The available packet types.
*/
typedef enum {
LWMQTT_NO_PACKET = 0,
LWMQTT_CONNECT_PACKET = 1,
LWMQTT_CONNACK_PACKET,
LWMQTT_PUBLISH_PACKET,
LWMQTT_PUBACK_PACKET,
LWMQTT_PUBREC_PACKET,
LWMQTT_PUBREL_PACKET,
LWMQTT_PUBCOMP_PACKET,
LWMQTT_SUBSCRIBE_PACKET,
LWMQTT_SUBACK_PACKET,
LWMQTT_UNSUBSCRIBE_PACKET,
LWMQTT_UNSUBACK_PACKET,
LWMQTT_PINGREQ_PACKET,
LWMQTT_PINGRESP_PACKET,
LWMQTT_DISCONNECT_PACKET
} lwmqtt_packet_type_t;
/**
* Will detect the packet type from the at least one byte long buffer.
*
* @param buf - The buffer from which the packet type will be detected.
* @param buf_len - The length of the specified buffer.
* @param packet_type - The packet type.
* @return An error value.
*/
lwmqtt_err_t lwmqtt_detect_packet_type(uint8_t *buf, size_t buf_len, lwmqtt_packet_type_t *packet_type);
/**
* Will detect the remaining length form the at least on byte long buffer.
*
* It will return LWMQTT_BUFFER_TOO_SHORT if the buffer is to short and an additional byte should be read from the
* network. In case the remaining length is overflowed it will return LWMQTT_REMAINING_LENGTH_OVERFLOW.
*
* @param buf - The buffer from which the remaining length will be detected.
* @param buf_len - The length of the specified buffer.
* @param rem_len - The detected remaining length.
* @return An error value.
*/
lwmqtt_err_t lwmqtt_detect_remaining_length(uint8_t *buf, size_t buf_len, uint32_t *rem_len);
/**
* Encodes a connect packet into the supplied buffer.
*
* @param buf - The buffer into which the packet will be encoded.
* @param buf_len - The length of the specified buffer.
* @param len - The encoded length of the packet.
* @param options - The options to be used to build the connect packet.
* @param will - The last will and testament.
* @return An error value.
*/
lwmqtt_err_t lwmqtt_encode_connect(uint8_t *buf, size_t buf_len, size_t *len, lwmqtt_options_t options,
lwmqtt_will_t *will);
/**
* Decodes a connack packet from the supplied buffer.
*
* @param buf - The raw buffer data.
* @param buf_len - The length of the specified buffer.
* @param session_present - The session present flag.
* @param return_code - The return code.
* @return An error value.
*/
lwmqtt_err_t lwmqtt_decode_connack(uint8_t *buf, size_t buf_len, bool *session_present,
lwmqtt_return_code_t *return_code);
/**
* Encodes a zero (disconnect, pingreq) packet into the supplied buffer.
*
* @param buf - The buffer into which the packet will be encoded.
* @param buf_len - The length of the specified buffer.
* @param len - The encoded length of the packet.
* @param packet_type - The packets type.
* @return An error value.
*/
lwmqtt_err_t lwmqtt_encode_zero(uint8_t *buf, size_t buf_len, size_t *len, lwmqtt_packet_type_t packet_type);
/**
* Decodes an ack (puback, pubrec, pubrel, pubcomp, unsuback) packet from the supplied buffer.
*
* @param buf - The raw buffer data.
* @param buf_len - The length of the specified buffer.
* @param packet_type - The packet type.
* @param dup - The dup flag.
* @param packet_id - The packet id.
* @return An error value.
*/
lwmqtt_err_t lwmqtt_decode_ack(uint8_t *buf, size_t buf_len, lwmqtt_packet_type_t packet_type, bool *dup,
uint16_t *packet_id);
/**
* Encodes an ack (puback, pubrec, pubrel, pubcomp) packet into the supplied buffer.
*
* @param buf - The buffer into which the packet will be encoded.
* @param buf_len - The length of the specified buffer.
* @param len - The encoded length of the packet.
* @param packet_type - The packets type.
* @param dup - The dup flag.
* @param packet_id - The packet id.
* @return An error value.
*/
lwmqtt_err_t lwmqtt_encode_ack(uint8_t *buf, size_t buf_len, size_t *len, lwmqtt_packet_type_t packet_type, bool dup,
uint16_t packet_id);
/**
* Decodes a publish packet from the supplied buffer.
*
* @param buf - The raw buffer data.
* @param buf_len - The length of the specified buffer.
* @param dup - The dup flag.
* @param packet_id - The packet id.
* @param topic - The topic.
* @parma msg - The message.
* @return An error value.
*/
lwmqtt_err_t lwmqtt_decode_publish(uint8_t *buf, size_t buf_len, bool *dup, uint16_t *packet_id, lwmqtt_string_t *topic,
lwmqtt_message_t *msg);
/**
* Encodes a publish packet into the supplied buffer.
*
* @param buf - The buffer into which the packet will be encoded.
* @param buf_len - The length of the specified buffer.
* @param len - The encoded length of the packet.
* @param dup - The dup flag.
* @param packet_id - The packet id.
* @param topic - The topic.
* @param msg - The message.
* @return An error value.
*/
lwmqtt_err_t lwmqtt_encode_publish(uint8_t *buf, size_t buf_len, size_t *len, bool dup, uint16_t packet_id,
lwmqtt_string_t topic, lwmqtt_message_t msg);
/**
* Encodes a subscribe packet into the supplied buffer.
*
* @param buf - The buffer into which the packet will be encoded.
* @param buf_len - The length of the specified buffer.
* @param len - The encoded length of the packet.
* @param packet_id - The packet id.
* @param count - The number of members in the topic_filters and qos_levels array.
* @param topic_filters - The array of topic filter.
* @param qos_levels - The array of requested QoS levels.
* @return An error value.
*/
lwmqtt_err_t lwmqtt_encode_subscribe(uint8_t *buf, size_t buf_len, size_t *len, uint16_t packet_id, int count,
lwmqtt_string_t *topic_filters, lwmqtt_qos_t *qos_levels);
/**
* Decodes a suback packet from the supplied buffer.
*
* @param buf - The raw buffer data.
* @param buf_len - The length of the specified buffer.
* @param packet_id - The packet id.
* @param max_count - The maximum number of members allowed in the granted_qos_levels array.
* @param count - The number of members in the granted_qos_levels array.
* @param granted_qos_levels - The granted QoS levels.
* @return An error value.
*/
lwmqtt_err_t lwmqtt_decode_suback(uint8_t *buf, size_t buf_len, uint16_t *packet_id, int max_count, int *count,
lwmqtt_qos_t *granted_qos_levels);
/**
* Encodes the supplied unsubscribe data into the supplied buffer, ready for sending
*
* @param buf - The buffer into which the packet will be encoded.
* @param buf_len - The length of the specified buffer.
* @param len - The encoded length of the packet.
* @param packet_id - The packet id.
* @param count - The number of members in the topic_filters array.
* @param topic_filters - The array of topic filters.
* @return An error value.
*/
lwmqtt_err_t lwmqtt_encode_unsubscribe(uint8_t *buf, size_t buf_len, size_t *len, uint16_t packet_id, int count,
lwmqtt_string_t *topic_filters);
#endif // LWMQTT_PACKET_H

View File

@@ -0,0 +1,38 @@
#include <string.h>
#include "lwmqtt.h"
lwmqtt_string_t lwmqtt_string(const char *str) {
// check for null
if (str == NULL) {
return (lwmqtt_string_t){0, NULL};
}
// get length
uint16_t len = (uint16_t)strlen(str);
// check zero length
if (len == 0) {
return (lwmqtt_string_t){0, NULL};
}
return (lwmqtt_string_t){len, (char *)str};
}
int lwmqtt_strcmp(lwmqtt_string_t a, const char *b) {
// get string of b
lwmqtt_string_t b_str = lwmqtt_string(b);
// return if both are zero length
if (a.len == 0 && b_str.len == 0) {
return 0;
}
// return if lengths are different
if (a.len != b_str.len) {
return -1;
}
// compare memory of same length
return strncmp(a.data, b_str.data, a.len);
}

View File

@@ -0,0 +1,5 @@
tests/bin
.pioenvs
.piolibdeps
.clang_complete
.gcc-flags.json

View File

@@ -0,0 +1 @@
{"type": "library", "name": "PubSubClient", "version": "2.8.0", "spec": {"owner": "knolleary", "id": 89, "name": "PubSubClient", "requirements": null, "url": null}}

View File

@@ -0,0 +1,7 @@
sudo: false
language: cpp
compiler:
- g++
script: cd tests && make && make test
os:
- linux

View File

@@ -0,0 +1,85 @@
2.8
* Add setBufferSize() to override MQTT_MAX_PACKET_SIZE
* Add setKeepAlive() to override MQTT_KEEPALIVE
* Add setSocketTimeout() to overide MQTT_SOCKET_TIMEOUT
* Added check to prevent subscribe/unsubscribe to empty topics
* Declare wifi mode prior to connect in ESP example
* Use `strnlen` to avoid overruns
* Support pre-connected Client objects
2.7
* Fix remaining-length handling to prevent buffer overrun
* Add large-payload API - beginPublish/write/publish/endPublish
* Add yield call to improve reliability on ESP
* Add Clean Session flag to connect options
* Add ESP32 support for functional callback signature
* Various other fixes
2.4
* Add MQTT_SOCKET_TIMEOUT to prevent it blocking indefinitely
whilst waiting for inbound data
* Fixed return code when publishing >256 bytes
2.3
* Add publish(topic,payload,retained) function
2.2
* Change code layout to match Arduino Library reqs
2.1
* Add MAX_TRANSFER_SIZE def to chunk messages if needed
* Reject topic/payloads that exceed MQTT_MAX_PACKET_SIZE
2.0
* Add (and default to) MQTT 3.1.1 support
* Fix PROGMEM handling for Intel Galileo/ESP8266
* Add overloaded constructors for convenience
* Add chainable setters for server/callback/client/stream
* Add state function to return connack return code
1.9
* Do not split MQTT packets over multiple calls to _client->write()
* API change: All constructors now require an instance of Client
to be passed in.
* Fixed example to match 1.8 api changes - dpslwk
* Added username/password support - WilHall
* Added publish_P - publishes messages from PROGMEM - jobytaffey
1.8
* KeepAlive interval is configurable in PubSubClient.h
* Maximum packet size is configurable in PubSubClient.h
* API change: Return boolean rather than int from various functions
* API change: Length parameter in message callback changed
from int to unsigned int
* Various internal tidy-ups around types
1.7
* Improved keepalive handling
* Updated to the Arduino-1.0 API
1.6
* Added the ability to publish a retained message
1.5
* Added default constructor
* Fixed compile error when used with arduino-0021 or later
1.4
* Fixed connection lost handling
1.3
* Fixed packet reading bug in PubSubClient.readPacket
1.2
* Fixed compile error when used with arduino-0016 or later
1.1
* Reduced size of library
* Added support for Will messages
* Clarified licensing - see LICENSE.txt
1.0
* Only Quality of Service (QOS) 0 messaging is supported
* The maximum message size, including header, is 128 bytes
* The keepalive interval is set to 30 seconds
* No support for Will messages

View File

@@ -0,0 +1,20 @@
Copyright (c) 2008-2020 Nicholas O'Leary
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,50 @@
# Arduino Client for MQTT
This library provides a client for doing simple publish/subscribe messaging with
a server that supports MQTT.
## Examples
The library comes with a number of example sketches. See File > Examples > PubSubClient
within the Arduino application.
Full API documentation is available here: https://pubsubclient.knolleary.net
## Limitations
- It can only publish QoS 0 messages. It can subscribe at QoS 0 or QoS 1.
- The maximum message size, including header, is **256 bytes** by default. This
is configurable via `MQTT_MAX_PACKET_SIZE` in `PubSubClient.h` or can be changed
by calling `PubSubClient::setBufferSize(size)`.
- The keepalive interval is set to 15 seconds by default. This is configurable
via `MQTT_KEEPALIVE` in `PubSubClient.h` or can be changed by calling
`PubSubClient::setKeepAlive(keepAlive)`.
- The client uses MQTT 3.1.1 by default. It can be changed to use MQTT 3.1 by
changing value of `MQTT_VERSION` in `PubSubClient.h`.
## Compatible Hardware
The library uses the Arduino Ethernet Client api for interacting with the
underlying network hardware. This means it Just Works with a growing number of
boards and shields, including:
- Arduino Ethernet
- Arduino Ethernet Shield
- Arduino YUN use the included `YunClient` in place of `EthernetClient`, and
be sure to do a `Bridge.begin()` first
- Arduino WiFi Shield - if you want to send packets > 90 bytes with this shield,
enable the `MQTT_MAX_TRANSFER_SIZE` define in `PubSubClient.h`.
- Sparkfun WiFly Shield [library](https://github.com/dpslwk/WiFly)
- TI CC3000 WiFi - [library](https://github.com/sparkfun/SFE_CC3000_Library)
- Intel Galileo/Edison
- ESP8266
- ESP32
The library cannot currently be used with hardware based on the ENC28J60 chip
such as the Nanode or the Nuelectronics Ethernet Shield. For those, there is an
[alternative library](https://github.com/njh/NanodeMQTT) available.
## License
This code is released under the MIT License.

View File

@@ -0,0 +1,43 @@
/*
Basic MQTT example with Authentication
- connects to an MQTT server, providing username
and password
- publishes "hello world" to the topic "outTopic"
- subscribes to the topic "inTopic"
*/
#include <SPI.h>
#include <Ethernet.h>
#include <PubSubClient.h>
// Update these with values suitable for your network.
byte mac[] = { 0xDE, 0xED, 0xBA, 0xFE, 0xFE, 0xED };
IPAddress ip(172, 16, 0, 100);
IPAddress server(172, 16, 0, 2);
void callback(char* topic, byte* payload, unsigned int length) {
// handle message arrived
}
EthernetClient ethClient;
PubSubClient client(server, 1883, callback, ethClient);
void setup()
{
Ethernet.begin(mac, ip);
// Note - the default maximum packet size is 128 bytes. If the
// combined length of clientId, username and password exceed this use the
// following to increase the buffer size:
// client.setBufferSize(255);
if (client.connect("arduinoClient", "testuser", "testpass")) {
client.publish("outTopic","hello world");
client.subscribe("inTopic");
}
}
void loop()
{
client.loop();
}

View File

@@ -0,0 +1,77 @@
/*
Basic MQTT example
This sketch demonstrates the basic capabilities of the library.
It connects to an MQTT server then:
- publishes "hello world" to the topic "outTopic"
- subscribes to the topic "inTopic", printing out any messages
it receives. NB - it assumes the received payloads are strings not binary
It will reconnect to the server if the connection is lost using a blocking
reconnect function. See the 'mqtt_reconnect_nonblocking' example for how to
achieve the same result without blocking the main loop.
*/
#include <SPI.h>
#include <Ethernet.h>
#include <PubSubClient.h>
// Update these with values suitable for your network.
byte mac[] = { 0xDE, 0xED, 0xBA, 0xFE, 0xFE, 0xED };
IPAddress ip(172, 16, 0, 100);
IPAddress server(172, 16, 0, 2);
void callback(char* topic, byte* payload, unsigned int length) {
Serial.print("Message arrived [");
Serial.print(topic);
Serial.print("] ");
for (int i=0;i<length;i++) {
Serial.print((char)payload[i]);
}
Serial.println();
}
EthernetClient ethClient;
PubSubClient client(ethClient);
void reconnect() {
// Loop until we're reconnected
while (!client.connected()) {
Serial.print("Attempting MQTT connection...");
// Attempt to connect
if (client.connect("arduinoClient")) {
Serial.println("connected");
// Once connected, publish an announcement...
client.publish("outTopic","hello world");
// ... and resubscribe
client.subscribe("inTopic");
} else {
Serial.print("failed, rc=");
Serial.print(client.state());
Serial.println(" try again in 5 seconds");
// Wait 5 seconds before retrying
delay(5000);
}
}
}
void setup()
{
Serial.begin(57600);
client.setServer(server, 1883);
client.setCallback(callback);
Ethernet.begin(mac, ip);
// Allow the hardware to sort itself out
delay(1500);
}
void loop()
{
if (!client.connected()) {
reconnect();
}
client.loop();
}

View File

@@ -0,0 +1,129 @@
/*
Basic ESP8266 MQTT example
This sketch demonstrates the capabilities of the pubsub library in combination
with the ESP8266 board/library.
It connects to an MQTT server then:
- publishes "hello world" to the topic "outTopic" every two seconds
- subscribes to the topic "inTopic", printing out any messages
it receives. NB - it assumes the received payloads are strings not binary
- If the first character of the topic "inTopic" is an 1, switch ON the ESP Led,
else switch it off
It will reconnect to the server if the connection is lost using a blocking
reconnect function. See the 'mqtt_reconnect_nonblocking' example for how to
achieve the same result without blocking the main loop.
To install the ESP8266 board, (using Arduino 1.6.4+):
- Add the following 3rd party board manager under "File -> Preferences -> Additional Boards Manager URLs":
http://arduino.esp8266.com/stable/package_esp8266com_index.json
- Open the "Tools -> Board -> Board Manager" and click install for the ESP8266"
- Select your ESP8266 in "Tools -> Board"
*/
#include <ESP8266WiFi.h>
#include <PubSubClient.h>
// Update these with values suitable for your network.
const char* ssid = "........";
const char* password = "........";
const char* mqtt_server = "broker.mqtt-dashboard.com";
WiFiClient espClient;
PubSubClient client(espClient);
unsigned long lastMsg = 0;
#define MSG_BUFFER_SIZE (50)
char msg[MSG_BUFFER_SIZE];
int value = 0;
void setup_wifi() {
delay(10);
// We start by connecting to a WiFi network
Serial.println();
Serial.print("Connecting to ");
Serial.println(ssid);
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
randomSeed(micros());
Serial.println("");
Serial.println("WiFi connected");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
}
void callback(char* topic, byte* payload, unsigned int length) {
Serial.print("Message arrived [");
Serial.print(topic);
Serial.print("] ");
for (int i = 0; i < length; i++) {
Serial.print((char)payload[i]);
}
Serial.println();
// Switch on the LED if an 1 was received as first character
if ((char)payload[0] == '1') {
digitalWrite(BUILTIN_LED, LOW); // Turn the LED on (Note that LOW is the voltage level
// but actually the LED is on; this is because
// it is active low on the ESP-01)
} else {
digitalWrite(BUILTIN_LED, HIGH); // Turn the LED off by making the voltage HIGH
}
}
void reconnect() {
// Loop until we're reconnected
while (!client.connected()) {
Serial.print("Attempting MQTT connection...");
// Create a random client ID
String clientId = "ESP8266Client-";
clientId += String(random(0xffff), HEX);
// Attempt to connect
if (client.connect(clientId.c_str())) {
Serial.println("connected");
// Once connected, publish an announcement...
client.publish("outTopic", "hello world");
// ... and resubscribe
client.subscribe("inTopic");
} else {
Serial.print("failed, rc=");
Serial.print(client.state());
Serial.println(" try again in 5 seconds");
// Wait 5 seconds before retrying
delay(5000);
}
}
}
void setup() {
pinMode(BUILTIN_LED, OUTPUT); // Initialize the BUILTIN_LED pin as an output
Serial.begin(115200);
setup_wifi();
client.setServer(mqtt_server, 1883);
client.setCallback(callback);
}
void loop() {
if (!client.connected()) {
reconnect();
}
client.loop();
unsigned long now = millis();
if (now - lastMsg > 2000) {
lastMsg = now;
++value;
snprintf (msg, MSG_BUFFER_SIZE, "hello world #%ld", value);
Serial.print("Publish message: ");
Serial.println(msg);
client.publish("outTopic", msg);
}
}

View File

@@ -0,0 +1,179 @@
/*
Long message ESP8266 MQTT example
This sketch demonstrates sending arbitrarily large messages in combination
with the ESP8266 board/library.
It connects to an MQTT server then:
- publishes "hello world" to the topic "outTopic"
- subscribes to the topic "greenBottles/#", printing out any messages
it receives. NB - it assumes the received payloads are strings not binary
- If the sub-topic is a number, it publishes a "greenBottles/lyrics" message
with a payload consisting of the lyrics to "10 green bottles", replacing
10 with the number given in the sub-topic.
It will reconnect to the server if the connection is lost using a blocking
reconnect function. See the 'mqtt_reconnect_nonblocking' example for how to
achieve the same result without blocking the main loop.
To install the ESP8266 board, (using Arduino 1.6.4+):
- Add the following 3rd party board manager under "File -> Preferences -> Additional Boards Manager URLs":
http://arduino.esp8266.com/stable/package_esp8266com_index.json
- Open the "Tools -> Board -> Board Manager" and click install for the ESP8266"
- Select your ESP8266 in "Tools -> Board"
*/
#include <ESP8266WiFi.h>
#include <PubSubClient.h>
// Update these with values suitable for your network.
const char* ssid = "........";
const char* password = "........";
const char* mqtt_server = "broker.mqtt-dashboard.com";
WiFiClient espClient;
PubSubClient client(espClient);
long lastMsg = 0;
char msg[50];
int value = 0;
void setup_wifi() {
delay(10);
// We start by connecting to a WiFi network
Serial.println();
Serial.print("Connecting to ");
Serial.println(ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
randomSeed(micros());
Serial.println("");
Serial.println("WiFi connected");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
}
void callback(char* topic, byte* payload, unsigned int length) {
Serial.print("Message arrived [");
Serial.print(topic);
Serial.print("] ");
for (int i = 0; i < length; i++) {
Serial.print((char)payload[i]);
}
Serial.println();
// Find out how many bottles we should generate lyrics for
String topicStr(topic);
int bottleCount = 0; // assume no bottles unless we correctly parse a value from the topic
if (topicStr.indexOf('/') >= 0) {
// The topic includes a '/', we'll try to read the number of bottles from just after that
topicStr.remove(0, topicStr.indexOf('/')+1);
// Now see if there's a number of bottles after the '/'
bottleCount = topicStr.toInt();
}
if (bottleCount > 0) {
// Work out how big our resulting message will be
int msgLen = 0;
for (int i = bottleCount; i > 0; i--) {
String numBottles(i);
msgLen += 2*numBottles.length();
if (i == 1) {
msgLen += 2*String(" green bottle, standing on the wall\n").length();
} else {
msgLen += 2*String(" green bottles, standing on the wall\n").length();
}
msgLen += String("And if one green bottle should accidentally fall\nThere'll be ").length();
switch (i) {
case 1:
msgLen += String("no green bottles, standing on the wall\n\n").length();
break;
case 2:
msgLen += String("1 green bottle, standing on the wall\n\n").length();
break;
default:
numBottles = i-1;
msgLen += numBottles.length();
msgLen += String(" green bottles, standing on the wall\n\n").length();
break;
};
}
// Now we can start to publish the message
client.beginPublish("greenBottles/lyrics", msgLen, false);
for (int i = bottleCount; i > 0; i--) {
for (int j = 0; j < 2; j++) {
client.print(i);
if (i == 1) {
client.print(" green bottle, standing on the wall\n");
} else {
client.print(" green bottles, standing on the wall\n");
}
}
client.print("And if one green bottle should accidentally fall\nThere'll be ");
switch (i) {
case 1:
client.print("no green bottles, standing on the wall\n\n");
break;
case 2:
client.print("1 green bottle, standing on the wall\n\n");
break;
default:
client.print(i-1);
client.print(" green bottles, standing on the wall\n\n");
break;
};
}
// Now we're done!
client.endPublish();
}
}
void reconnect() {
// Loop until we're reconnected
while (!client.connected()) {
Serial.print("Attempting MQTT connection...");
// Create a random client ID
String clientId = "ESP8266Client-";
clientId += String(random(0xffff), HEX);
// Attempt to connect
if (client.connect(clientId.c_str())) {
Serial.println("connected");
// Once connected, publish an announcement...
client.publish("outTopic", "hello world");
// ... and resubscribe
client.subscribe("greenBottles/#");
} else {
Serial.print("failed, rc=");
Serial.print(client.state());
Serial.println(" try again in 5 seconds");
// Wait 5 seconds before retrying
delay(5000);
}
}
}
void setup() {
pinMode(BUILTIN_LED, OUTPUT); // Initialize the BUILTIN_LED pin as an output
Serial.begin(115200);
setup_wifi();
client.setServer(mqtt_server, 1883);
client.setCallback(callback);
}
void loop() {
if (!client.connected()) {
reconnect();
}
client.loop();
}

View File

@@ -0,0 +1,60 @@
/*
Publishing in the callback
- connects to an MQTT server
- subscribes to the topic "inTopic"
- when a message is received, republishes it to "outTopic"
This example shows how to publish messages within the
callback function. The callback function header needs to
be declared before the PubSubClient constructor and the
actual callback defined afterwards.
This ensures the client reference in the callback function
is valid.
*/
#include <SPI.h>
#include <Ethernet.h>
#include <PubSubClient.h>
// Update these with values suitable for your network.
byte mac[] = { 0xDE, 0xED, 0xBA, 0xFE, 0xFE, 0xED };
IPAddress ip(172, 16, 0, 100);
IPAddress server(172, 16, 0, 2);
// Callback function header
void callback(char* topic, byte* payload, unsigned int length);
EthernetClient ethClient;
PubSubClient client(server, 1883, callback, ethClient);
// Callback function
void callback(char* topic, byte* payload, unsigned int length) {
// In order to republish this payload, a copy must be made
// as the orignal payload buffer will be overwritten whilst
// constructing the PUBLISH packet.
// Allocate the correct amount of memory for the payload copy
byte* p = (byte*)malloc(length);
// Copy the payload to the new buffer
memcpy(p,payload,length);
client.publish("outTopic", p, length);
// Free the memory
free(p);
}
void setup()
{
Ethernet.begin(mac, ip);
if (client.connect("arduinoClient")) {
client.publish("outTopic","hello world");
client.subscribe("inTopic");
}
}
void loop()
{
client.loop();
}

View File

@@ -0,0 +1,67 @@
/*
Reconnecting MQTT example - non-blocking
This sketch demonstrates how to keep the client connected
using a non-blocking reconnect function. If the client loses
its connection, it attempts to reconnect every 5 seconds
without blocking the main loop.
*/
#include <SPI.h>
#include <Ethernet.h>
#include <PubSubClient.h>
// Update these with values suitable for your hardware/network.
byte mac[] = { 0xDE, 0xED, 0xBA, 0xFE, 0xFE, 0xED };
IPAddress ip(172, 16, 0, 100);
IPAddress server(172, 16, 0, 2);
void callback(char* topic, byte* payload, unsigned int length) {
// handle message arrived
}
EthernetClient ethClient;
PubSubClient client(ethClient);
long lastReconnectAttempt = 0;
boolean reconnect() {
if (client.connect("arduinoClient")) {
// Once connected, publish an announcement...
client.publish("outTopic","hello world");
// ... and resubscribe
client.subscribe("inTopic");
}
return client.connected();
}
void setup()
{
client.setServer(server, 1883);
client.setCallback(callback);
Ethernet.begin(mac, ip);
delay(1500);
lastReconnectAttempt = 0;
}
void loop()
{
if (!client.connected()) {
long now = millis();
if (now - lastReconnectAttempt > 5000) {
lastReconnectAttempt = now;
// Attempt to reconnect
if (reconnect()) {
lastReconnectAttempt = 0;
}
}
} else {
// Client connected
client.loop();
}
}

View File

@@ -0,0 +1,57 @@
/*
Example of using a Stream object to store the message payload
Uses SRAM library: https://github.com/ennui2342/arduino-sram
but could use any Stream based class such as SD
- connects to an MQTT server
- publishes "hello world" to the topic "outTopic"
- subscribes to the topic "inTopic"
*/
#include <SPI.h>
#include <Ethernet.h>
#include <PubSubClient.h>
#include <SRAM.h>
// Update these with values suitable for your network.
byte mac[] = { 0xDE, 0xED, 0xBA, 0xFE, 0xFE, 0xED };
IPAddress ip(172, 16, 0, 100);
IPAddress server(172, 16, 0, 2);
SRAM sram(4, SRAM_1024);
void callback(char* topic, byte* payload, unsigned int length) {
sram.seek(1);
// do something with the message
for(uint8_t i=0; i<length; i++) {
Serial.write(sram.read());
}
Serial.println();
// Reset position for the next message to be stored
sram.seek(1);
}
EthernetClient ethClient;
PubSubClient client(server, 1883, callback, ethClient, sram);
void setup()
{
Ethernet.begin(mac, ip);
if (client.connect("arduinoClient")) {
client.publish("outTopic","hello world");
client.subscribe("inTopic");
}
sram.begin();
sram.seek(1);
Serial.begin(9600);
}
void loop()
{
client.loop();
}

View File

@@ -0,0 +1,36 @@
#######################################
# Syntax Coloring Map For PubSubClient
#######################################
#######################################
# Datatypes (KEYWORD1)
#######################################
PubSubClient KEYWORD1
#######################################
# Methods and Functions (KEYWORD2)
#######################################
connect KEYWORD2
disconnect KEYWORD2
publish KEYWORD2
publish_P KEYWORD2
beginPublish KEYWORD2
endPublish KEYWORD2
write KEYWORD2
subscribe KEYWORD2
unsubscribe KEYWORD2
loop KEYWORD2
connected KEYWORD2
setServer KEYWORD2
setCallback KEYWORD2
setClient KEYWORD2
setStream KEYWORD2
setKeepAlive KEYWORD2
setBufferSize KEYWORD2
setSocketTimeout KEYWORD2
#######################################
# Constants (LITERAL1)
#######################################

View File

@@ -0,0 +1,18 @@
{
"name": "PubSubClient",
"keywords": "ethernet, mqtt, m2m, iot",
"description": "A client library for MQTT messaging. MQTT is a lightweight messaging protocol ideal for small devices. This library allows you to send and receive MQTT messages. It supports the latest MQTT 3.1.1 protocol and can be configured to use the older MQTT 3.1 if needed. It supports all Arduino Ethernet Client compatible hardware, including the Intel Galileo/Edison, ESP8266 and TI CC3000.",
"repository": {
"type": "git",
"url": "https://github.com/knolleary/pubsubclient.git"
},
"version": "2.8",
"exclude": "tests",
"examples": "examples/*/*.ino",
"frameworks": "arduino",
"platforms": [
"atmelavr",
"espressif8266",
"espressif32"
]
}

View File

@@ -0,0 +1,9 @@
name=PubSubClient
version=2.8
author=Nick O'Leary <nick.oleary@gmail.com>
maintainer=Nick O'Leary <nick.oleary@gmail.com>
sentence=A client library for MQTT messaging.
paragraph=MQTT is a lightweight messaging protocol ideal for small devices. This library allows you to send and receive MQTT messages. It supports the latest MQTT 3.1.1 protocol and can be configured to use the older MQTT 3.1 if needed. It supports all Arduino Ethernet Client compatible hardware, including the Intel Galileo/Edison, ESP8266 and TI CC3000.
category=Communication
url=http://pubsubclient.knolleary.net
architectures=*

View File

@@ -0,0 +1,769 @@
/*
PubSubClient.cpp - A simple client for MQTT.
Nick O'Leary
http://knolleary.net
*/
#include "PubSubClient.h"
#include "Arduino.h"
PubSubClient::PubSubClient() {
this->_state = MQTT_DISCONNECTED;
this->_client = NULL;
this->stream = NULL;
setCallback(NULL);
this->bufferSize = 0;
setBufferSize(MQTT_MAX_PACKET_SIZE);
setKeepAlive(MQTT_KEEPALIVE);
setSocketTimeout(MQTT_SOCKET_TIMEOUT);
}
PubSubClient::PubSubClient(Client& client) {
this->_state = MQTT_DISCONNECTED;
setClient(client);
this->stream = NULL;
this->bufferSize = 0;
setBufferSize(MQTT_MAX_PACKET_SIZE);
setKeepAlive(MQTT_KEEPALIVE);
setSocketTimeout(MQTT_SOCKET_TIMEOUT);
}
PubSubClient::PubSubClient(IPAddress addr, uint16_t port, Client& client) {
this->_state = MQTT_DISCONNECTED;
setServer(addr, port);
setClient(client);
this->stream = NULL;
this->bufferSize = 0;
setBufferSize(MQTT_MAX_PACKET_SIZE);
setKeepAlive(MQTT_KEEPALIVE);
setSocketTimeout(MQTT_SOCKET_TIMEOUT);
}
PubSubClient::PubSubClient(IPAddress addr, uint16_t port, Client& client, Stream& stream) {
this->_state = MQTT_DISCONNECTED;
setServer(addr,port);
setClient(client);
setStream(stream);
this->bufferSize = 0;
setBufferSize(MQTT_MAX_PACKET_SIZE);
setKeepAlive(MQTT_KEEPALIVE);
setSocketTimeout(MQTT_SOCKET_TIMEOUT);
}
PubSubClient::PubSubClient(IPAddress addr, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client) {
this->_state = MQTT_DISCONNECTED;
setServer(addr, port);
setCallback(callback);
setClient(client);
this->stream = NULL;
this->bufferSize = 0;
setBufferSize(MQTT_MAX_PACKET_SIZE);
setKeepAlive(MQTT_KEEPALIVE);
setSocketTimeout(MQTT_SOCKET_TIMEOUT);
}
PubSubClient::PubSubClient(IPAddress addr, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client, Stream& stream) {
this->_state = MQTT_DISCONNECTED;
setServer(addr,port);
setCallback(callback);
setClient(client);
setStream(stream);
this->bufferSize = 0;
setBufferSize(MQTT_MAX_PACKET_SIZE);
setKeepAlive(MQTT_KEEPALIVE);
setSocketTimeout(MQTT_SOCKET_TIMEOUT);
}
PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, Client& client) {
this->_state = MQTT_DISCONNECTED;
setServer(ip, port);
setClient(client);
this->stream = NULL;
this->bufferSize = 0;
setBufferSize(MQTT_MAX_PACKET_SIZE);
setKeepAlive(MQTT_KEEPALIVE);
setSocketTimeout(MQTT_SOCKET_TIMEOUT);
}
PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, Client& client, Stream& stream) {
this->_state = MQTT_DISCONNECTED;
setServer(ip,port);
setClient(client);
setStream(stream);
this->bufferSize = 0;
setBufferSize(MQTT_MAX_PACKET_SIZE);
setKeepAlive(MQTT_KEEPALIVE);
setSocketTimeout(MQTT_SOCKET_TIMEOUT);
}
PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client) {
this->_state = MQTT_DISCONNECTED;
setServer(ip, port);
setCallback(callback);
setClient(client);
this->stream = NULL;
this->bufferSize = 0;
setBufferSize(MQTT_MAX_PACKET_SIZE);
setKeepAlive(MQTT_KEEPALIVE);
setSocketTimeout(MQTT_SOCKET_TIMEOUT);
}
PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client, Stream& stream) {
this->_state = MQTT_DISCONNECTED;
setServer(ip,port);
setCallback(callback);
setClient(client);
setStream(stream);
this->bufferSize = 0;
setBufferSize(MQTT_MAX_PACKET_SIZE);
setKeepAlive(MQTT_KEEPALIVE);
setSocketTimeout(MQTT_SOCKET_TIMEOUT);
}
PubSubClient::PubSubClient(const char* domain, uint16_t port, Client& client) {
this->_state = MQTT_DISCONNECTED;
setServer(domain,port);
setClient(client);
this->stream = NULL;
this->bufferSize = 0;
setBufferSize(MQTT_MAX_PACKET_SIZE);
setKeepAlive(MQTT_KEEPALIVE);
setSocketTimeout(MQTT_SOCKET_TIMEOUT);
}
PubSubClient::PubSubClient(const char* domain, uint16_t port, Client& client, Stream& stream) {
this->_state = MQTT_DISCONNECTED;
setServer(domain,port);
setClient(client);
setStream(stream);
this->bufferSize = 0;
setBufferSize(MQTT_MAX_PACKET_SIZE);
setKeepAlive(MQTT_KEEPALIVE);
setSocketTimeout(MQTT_SOCKET_TIMEOUT);
}
PubSubClient::PubSubClient(const char* domain, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client) {
this->_state = MQTT_DISCONNECTED;
setServer(domain,port);
setCallback(callback);
setClient(client);
this->stream = NULL;
this->bufferSize = 0;
setBufferSize(MQTT_MAX_PACKET_SIZE);
setKeepAlive(MQTT_KEEPALIVE);
setSocketTimeout(MQTT_SOCKET_TIMEOUT);
}
PubSubClient::PubSubClient(const char* domain, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client, Stream& stream) {
this->_state = MQTT_DISCONNECTED;
setServer(domain,port);
setCallback(callback);
setClient(client);
setStream(stream);
this->bufferSize = 0;
setBufferSize(MQTT_MAX_PACKET_SIZE);
setKeepAlive(MQTT_KEEPALIVE);
setSocketTimeout(MQTT_SOCKET_TIMEOUT);
}
PubSubClient::~PubSubClient() {
free(this->buffer);
}
boolean PubSubClient::connect(const char *id) {
return connect(id,NULL,NULL,0,0,0,0,1);
}
boolean PubSubClient::connect(const char *id, const char *user, const char *pass) {
return connect(id,user,pass,0,0,0,0,1);
}
boolean PubSubClient::connect(const char *id, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage) {
return connect(id,NULL,NULL,willTopic,willQos,willRetain,willMessage,1);
}
boolean PubSubClient::connect(const char *id, const char *user, const char *pass, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage) {
return connect(id,user,pass,willTopic,willQos,willRetain,willMessage,1);
}
boolean PubSubClient::connect(const char *id, const char *user, const char *pass, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage, boolean cleanSession) {
if (!connected()) {
int result = 0;
if(_client->connected()) {
result = 1;
} else {
if (domain != NULL) {
result = _client->connect(this->domain, this->port);
} else {
result = _client->connect(this->ip, this->port);
}
}
if (result == 1) {
nextMsgId = 1;
// Leave room in the buffer for header and variable length field
uint16_t length = MQTT_MAX_HEADER_SIZE;
unsigned int j;
#if MQTT_VERSION == MQTT_VERSION_3_1
uint8_t d[9] = {0x00,0x06,'M','Q','I','s','d','p', MQTT_VERSION};
#define MQTT_HEADER_VERSION_LENGTH 9
#elif MQTT_VERSION == MQTT_VERSION_3_1_1
uint8_t d[7] = {0x00,0x04,'M','Q','T','T',MQTT_VERSION};
#define MQTT_HEADER_VERSION_LENGTH 7
#endif
for (j = 0;j<MQTT_HEADER_VERSION_LENGTH;j++) {
this->buffer[length++] = d[j];
}
uint8_t v;
if (willTopic) {
v = 0x04|(willQos<<3)|(willRetain<<5);
} else {
v = 0x00;
}
if (cleanSession) {
v = v|0x02;
}
if(user != NULL) {
v = v|0x80;
if(pass != NULL) {
v = v|(0x80>>1);
}
}
this->buffer[length++] = v;
this->buffer[length++] = ((this->keepAlive) >> 8);
this->buffer[length++] = ((this->keepAlive) & 0xFF);
CHECK_STRING_LENGTH(length,id)
length = writeString(id,this->buffer,length);
if (willTopic) {
CHECK_STRING_LENGTH(length,willTopic)
length = writeString(willTopic,this->buffer,length);
CHECK_STRING_LENGTH(length,willMessage)
length = writeString(willMessage,this->buffer,length);
}
if(user != NULL) {
CHECK_STRING_LENGTH(length,user)
length = writeString(user,this->buffer,length);
if(pass != NULL) {
CHECK_STRING_LENGTH(length,pass)
length = writeString(pass,this->buffer,length);
}
}
write(MQTTCONNECT,this->buffer,length-MQTT_MAX_HEADER_SIZE);
lastInActivity = lastOutActivity = millis();
while (!_client->available()) {
unsigned long t = millis();
if (t-lastInActivity >= ((int32_t) this->socketTimeout*1000UL)) {
_state = MQTT_CONNECTION_TIMEOUT;
_client->stop();
return false;
}
}
uint8_t llen;
uint32_t len = readPacket(&llen);
if (len == 4) {
if (buffer[3] == 0) {
lastInActivity = millis();
pingOutstanding = false;
_state = MQTT_CONNECTED;
return true;
} else {
_state = buffer[3];
}
}
_client->stop();
} else {
_state = MQTT_CONNECT_FAILED;
}
return false;
}
return true;
}
// reads a byte into result
boolean PubSubClient::readByte(uint8_t * result) {
uint32_t previousMillis = millis();
while(!_client->available()) {
yield();
uint32_t currentMillis = millis();
if(currentMillis - previousMillis >= ((int32_t) this->socketTimeout * 1000)){
return false;
}
}
*result = _client->read();
return true;
}
// reads a byte into result[*index] and increments index
boolean PubSubClient::readByte(uint8_t * result, uint16_t * index){
uint16_t current_index = *index;
uint8_t * write_address = &(result[current_index]);
if(readByte(write_address)){
*index = current_index + 1;
return true;
}
return false;
}
uint32_t PubSubClient::readPacket(uint8_t* lengthLength) {
uint16_t len = 0;
if(!readByte(this->buffer, &len)) return 0;
bool isPublish = (this->buffer[0]&0xF0) == MQTTPUBLISH;
uint32_t multiplier = 1;
uint32_t length = 0;
uint8_t digit = 0;
uint16_t skip = 0;
uint32_t start = 0;
do {
if (len == 5) {
// Invalid remaining length encoding - kill the connection
_state = MQTT_DISCONNECTED;
_client->stop();
return 0;
}
if(!readByte(&digit)) return 0;
this->buffer[len++] = digit;
length += (digit & 127) * multiplier;
multiplier <<=7; //multiplier *= 128
} while ((digit & 128) != 0);
*lengthLength = len-1;
if (isPublish) {
// Read in topic length to calculate bytes to skip over for Stream writing
if(!readByte(this->buffer, &len)) return 0;
if(!readByte(this->buffer, &len)) return 0;
skip = (this->buffer[*lengthLength+1]<<8)+this->buffer[*lengthLength+2];
start = 2;
if (this->buffer[0]&MQTTQOS1) {
// skip message id
skip += 2;
}
}
uint32_t idx = len;
for (uint32_t i = start;i<length;i++) {
if(!readByte(&digit)) return 0;
if (this->stream) {
if (isPublish && idx-*lengthLength-2>skip) {
this->stream->write(digit);
}
}
if (len < this->bufferSize) {
this->buffer[len] = digit;
len++;
}
idx++;
}
if (!this->stream && idx > this->bufferSize) {
len = 0; // This will cause the packet to be ignored.
}
return len;
}
boolean PubSubClient::loop() {
if (connected()) {
unsigned long t = millis();
if ((t - lastInActivity > this->keepAlive*1000UL) || (t - lastOutActivity > this->keepAlive*1000UL)) {
if (pingOutstanding) {
this->_state = MQTT_CONNECTION_TIMEOUT;
_client->stop();
return false;
} else {
this->buffer[0] = MQTTPINGREQ;
this->buffer[1] = 0;
_client->write(this->buffer,2);
lastOutActivity = t;
lastInActivity = t;
pingOutstanding = true;
}
}
if (_client->available()) {
uint8_t llen;
uint16_t len = readPacket(&llen);
uint16_t msgId = 0;
uint8_t *payload;
if (len > 0) {
lastInActivity = t;
uint8_t type = this->buffer[0]&0xF0;
if (type == MQTTPUBLISH) {
if (callback) {
uint16_t tl = (this->buffer[llen+1]<<8)+this->buffer[llen+2]; /* topic length in bytes */
memmove(this->buffer+llen+2,this->buffer+llen+3,tl); /* move topic inside buffer 1 byte to front */
this->buffer[llen+2+tl] = 0; /* end the topic as a 'C' string with \x00 */
char *topic = (char*) this->buffer+llen+2;
// msgId only present for QOS>0
if ((this->buffer[0]&0x06) == MQTTQOS1) {
msgId = (this->buffer[llen+3+tl]<<8)+this->buffer[llen+3+tl+1];
payload = this->buffer+llen+3+tl+2;
callback(topic,payload,len-llen-3-tl-2);
this->buffer[0] = MQTTPUBACK;
this->buffer[1] = 2;
this->buffer[2] = (msgId >> 8);
this->buffer[3] = (msgId & 0xFF);
_client->write(this->buffer,4);
lastOutActivity = t;
} else {
payload = this->buffer+llen+3+tl;
callback(topic,payload,len-llen-3-tl);
}
}
} else if (type == MQTTPINGREQ) {
this->buffer[0] = MQTTPINGRESP;
this->buffer[1] = 0;
_client->write(this->buffer,2);
} else if (type == MQTTPINGRESP) {
pingOutstanding = false;
}
} else if (!connected()) {
// readPacket has closed the connection
return false;
}
}
return true;
}
return false;
}
boolean PubSubClient::publish(const char* topic, const char* payload) {
return publish(topic,(const uint8_t*)payload, payload ? strnlen(payload, this->bufferSize) : 0,false);
}
boolean PubSubClient::publish(const char* topic, const char* payload, boolean retained) {
return publish(topic,(const uint8_t*)payload, payload ? strnlen(payload, this->bufferSize) : 0,retained);
}
boolean PubSubClient::publish(const char* topic, const uint8_t* payload, unsigned int plength) {
return publish(topic, payload, plength, false);
}
boolean PubSubClient::publish(const char* topic, const uint8_t* payload, unsigned int plength, boolean retained) {
if (connected()) {
if (this->bufferSize < MQTT_MAX_HEADER_SIZE + 2+strnlen(topic, this->bufferSize) + plength) {
// Too long
return false;
}
// Leave room in the buffer for header and variable length field
uint16_t length = MQTT_MAX_HEADER_SIZE;
length = writeString(topic,this->buffer,length);
// Add payload
uint16_t i;
for (i=0;i<plength;i++) {
this->buffer[length++] = payload[i];
}
// Write the header
uint8_t header = MQTTPUBLISH;
if (retained) {
header |= 1;
}
return write(header,this->buffer,length-MQTT_MAX_HEADER_SIZE);
}
return false;
}
boolean PubSubClient::publish_P(const char* topic, const char* payload, boolean retained) {
return publish_P(topic, (const uint8_t*)payload, payload ? strnlen(payload, this->bufferSize) : 0, retained);
}
boolean PubSubClient::publish_P(const char* topic, const uint8_t* payload, unsigned int plength, boolean retained) {
uint8_t llen = 0;
uint8_t digit;
unsigned int rc = 0;
uint16_t tlen;
unsigned int pos = 0;
unsigned int i;
uint8_t header;
unsigned int len;
int expectedLength;
if (!connected()) {
return false;
}
tlen = strnlen(topic, this->bufferSize);
header = MQTTPUBLISH;
if (retained) {
header |= 1;
}
this->buffer[pos++] = header;
len = plength + 2 + tlen;
do {
digit = len & 127; //digit = len %128
len >>= 7; //len = len / 128
if (len > 0) {
digit |= 0x80;
}
this->buffer[pos++] = digit;
llen++;
} while(len>0);
pos = writeString(topic,this->buffer,pos);
rc += _client->write(this->buffer,pos);
for (i=0;i<plength;i++) {
rc += _client->write((char)pgm_read_byte_near(payload + i));
}
lastOutActivity = millis();
expectedLength = 1 + llen + 2 + tlen + plength;
return (rc == expectedLength);
}
boolean PubSubClient::beginPublish(const char* topic, unsigned int plength, boolean retained) {
if (connected()) {
// Send the header and variable length field
uint16_t length = MQTT_MAX_HEADER_SIZE;
length = writeString(topic,this->buffer,length);
uint8_t header = MQTTPUBLISH;
if (retained) {
header |= 1;
}
size_t hlen = buildHeader(header, this->buffer, plength+length-MQTT_MAX_HEADER_SIZE);
uint16_t rc = _client->write(this->buffer+(MQTT_MAX_HEADER_SIZE-hlen),length-(MQTT_MAX_HEADER_SIZE-hlen));
lastOutActivity = millis();
return (rc == (length-(MQTT_MAX_HEADER_SIZE-hlen)));
}
return false;
}
int PubSubClient::endPublish() {
return 1;
}
size_t PubSubClient::write(uint8_t data) {
lastOutActivity = millis();
return _client->write(data);
}
size_t PubSubClient::write(const uint8_t *buffer, size_t size) {
lastOutActivity = millis();
return _client->write(buffer,size);
}
size_t PubSubClient::buildHeader(uint8_t header, uint8_t* buf, uint16_t length) {
uint8_t lenBuf[4];
uint8_t llen = 0;
uint8_t digit;
uint8_t pos = 0;
uint16_t len = length;
do {
digit = len & 127; //digit = len %128
len >>= 7; //len = len / 128
if (len > 0) {
digit |= 0x80;
}
lenBuf[pos++] = digit;
llen++;
} while(len>0);
buf[4-llen] = header;
for (int i=0;i<llen;i++) {
buf[MQTT_MAX_HEADER_SIZE-llen+i] = lenBuf[i];
}
return llen+1; // Full header size is variable length bit plus the 1-byte fixed header
}
boolean PubSubClient::write(uint8_t header, uint8_t* buf, uint16_t length) {
uint16_t rc;
uint8_t hlen = buildHeader(header, buf, length);
#ifdef MQTT_MAX_TRANSFER_SIZE
uint8_t* writeBuf = buf+(MQTT_MAX_HEADER_SIZE-hlen);
uint16_t bytesRemaining = length+hlen; //Match the length type
uint8_t bytesToWrite;
boolean result = true;
while((bytesRemaining > 0) && result) {
bytesToWrite = (bytesRemaining > MQTT_MAX_TRANSFER_SIZE)?MQTT_MAX_TRANSFER_SIZE:bytesRemaining;
rc = _client->write(writeBuf,bytesToWrite);
result = (rc == bytesToWrite);
bytesRemaining -= rc;
writeBuf += rc;
}
return result;
#else
rc = _client->write(buf+(MQTT_MAX_HEADER_SIZE-hlen),length+hlen);
lastOutActivity = millis();
return (rc == hlen+length);
#endif
}
boolean PubSubClient::subscribe(const char* topic) {
return subscribe(topic, 0);
}
boolean PubSubClient::subscribe(const char* topic, uint8_t qos) {
size_t topicLength = strnlen(topic, this->bufferSize);
if (topic == 0) {
return false;
}
if (qos > 1) {
return false;
}
if (this->bufferSize < 9 + topicLength) {
// Too long
return false;
}
if (connected()) {
// Leave room in the buffer for header and variable length field
uint16_t length = MQTT_MAX_HEADER_SIZE;
nextMsgId++;
if (nextMsgId == 0) {
nextMsgId = 1;
}
this->buffer[length++] = (nextMsgId >> 8);
this->buffer[length++] = (nextMsgId & 0xFF);
length = writeString((char*)topic, this->buffer,length);
this->buffer[length++] = qos;
return write(MQTTSUBSCRIBE|MQTTQOS1,this->buffer,length-MQTT_MAX_HEADER_SIZE);
}
return false;
}
boolean PubSubClient::unsubscribe(const char* topic) {
size_t topicLength = strnlen(topic, this->bufferSize);
if (topic == 0) {
return false;
}
if (this->bufferSize < 9 + topicLength) {
// Too long
return false;
}
if (connected()) {
uint16_t length = MQTT_MAX_HEADER_SIZE;
nextMsgId++;
if (nextMsgId == 0) {
nextMsgId = 1;
}
this->buffer[length++] = (nextMsgId >> 8);
this->buffer[length++] = (nextMsgId & 0xFF);
length = writeString(topic, this->buffer,length);
return write(MQTTUNSUBSCRIBE|MQTTQOS1,this->buffer,length-MQTT_MAX_HEADER_SIZE);
}
return false;
}
void PubSubClient::disconnect() {
this->buffer[0] = MQTTDISCONNECT;
this->buffer[1] = 0;
_client->write(this->buffer,2);
_state = MQTT_DISCONNECTED;
_client->flush();
_client->stop();
lastInActivity = lastOutActivity = millis();
}
uint16_t PubSubClient::writeString(const char* string, uint8_t* buf, uint16_t pos) {
const char* idp = string;
uint16_t i = 0;
pos += 2;
while (*idp) {
buf[pos++] = *idp++;
i++;
}
buf[pos-i-2] = (i >> 8);
buf[pos-i-1] = (i & 0xFF);
return pos;
}
boolean PubSubClient::connected() {
boolean rc;
if (_client == NULL ) {
rc = false;
} else {
rc = (int)_client->connected();
if (!rc) {
if (this->_state == MQTT_CONNECTED) {
this->_state = MQTT_CONNECTION_LOST;
_client->flush();
_client->stop();
}
} else {
return this->_state == MQTT_CONNECTED;
}
}
return rc;
}
PubSubClient& PubSubClient::setServer(uint8_t * ip, uint16_t port) {
IPAddress addr(ip[0],ip[1],ip[2],ip[3]);
return setServer(addr,port);
}
PubSubClient& PubSubClient::setServer(IPAddress ip, uint16_t port) {
this->ip = ip;
this->port = port;
this->domain = NULL;
return *this;
}
PubSubClient& PubSubClient::setServer(const char * domain, uint16_t port) {
this->domain = domain;
this->port = port;
return *this;
}
PubSubClient& PubSubClient::setCallback(MQTT_CALLBACK_SIGNATURE) {
this->callback = callback;
return *this;
}
PubSubClient& PubSubClient::setClient(Client& client){
this->_client = &client;
return *this;
}
PubSubClient& PubSubClient::setStream(Stream& stream){
this->stream = &stream;
return *this;
}
int PubSubClient::state() {
return this->_state;
}
boolean PubSubClient::setBufferSize(uint16_t size) {
if (size == 0) {
// Cannot set it back to 0
return false;
}
if (this->bufferSize == 0) {
this->buffer = (uint8_t*)malloc(size);
} else {
uint8_t* newBuffer = (uint8_t*)realloc(this->buffer, size);
if (newBuffer != NULL) {
this->buffer = newBuffer;
} else {
return false;
}
}
this->bufferSize = size;
return (this->buffer != NULL);
}
uint16_t PubSubClient::getBufferSize() {
return this->bufferSize;
}
PubSubClient& PubSubClient::setKeepAlive(uint16_t keepAlive) {
this->keepAlive = keepAlive;
return *this;
}
PubSubClient& PubSubClient::setSocketTimeout(uint16_t timeout) {
this->socketTimeout = timeout;
return *this;
}

View File

@@ -0,0 +1,184 @@
/*
PubSubClient.h - A simple client for MQTT.
Nick O'Leary
http://knolleary.net
*/
#ifndef PubSubClient_h
#define PubSubClient_h
#include <Arduino.h>
#include "IPAddress.h"
#include "Client.h"
#include "Stream.h"
#define MQTT_VERSION_3_1 3
#define MQTT_VERSION_3_1_1 4
// MQTT_VERSION : Pick the version
//#define MQTT_VERSION MQTT_VERSION_3_1
#ifndef MQTT_VERSION
#define MQTT_VERSION MQTT_VERSION_3_1_1
#endif
// MQTT_MAX_PACKET_SIZE : Maximum packet size. Override with setBufferSize().
#ifndef MQTT_MAX_PACKET_SIZE
#define MQTT_MAX_PACKET_SIZE 256
#endif
// MQTT_KEEPALIVE : keepAlive interval in Seconds. Override with setKeepAlive()
#ifndef MQTT_KEEPALIVE
#define MQTT_KEEPALIVE 15
#endif
// MQTT_SOCKET_TIMEOUT: socket timeout interval in Seconds. Override with setSocketTimeout()
#ifndef MQTT_SOCKET_TIMEOUT
#define MQTT_SOCKET_TIMEOUT 15
#endif
// MQTT_MAX_TRANSFER_SIZE : limit how much data is passed to the network client
// in each write call. Needed for the Arduino Wifi Shield. Leave undefined to
// pass the entire MQTT packet in each write call.
//#define MQTT_MAX_TRANSFER_SIZE 80
// Possible values for client.state()
#define MQTT_CONNECTION_TIMEOUT -4
#define MQTT_CONNECTION_LOST -3
#define MQTT_CONNECT_FAILED -2
#define MQTT_DISCONNECTED -1
#define MQTT_CONNECTED 0
#define MQTT_CONNECT_BAD_PROTOCOL 1
#define MQTT_CONNECT_BAD_CLIENT_ID 2
#define MQTT_CONNECT_UNAVAILABLE 3
#define MQTT_CONNECT_BAD_CREDENTIALS 4
#define MQTT_CONNECT_UNAUTHORIZED 5
#define MQTTCONNECT 1 << 4 // Client request to connect to Server
#define MQTTCONNACK 2 << 4 // Connect Acknowledgment
#define MQTTPUBLISH 3 << 4 // Publish message
#define MQTTPUBACK 4 << 4 // Publish Acknowledgment
#define MQTTPUBREC 5 << 4 // Publish Received (assured delivery part 1)
#define MQTTPUBREL 6 << 4 // Publish Release (assured delivery part 2)
#define MQTTPUBCOMP 7 << 4 // Publish Complete (assured delivery part 3)
#define MQTTSUBSCRIBE 8 << 4 // Client Subscribe request
#define MQTTSUBACK 9 << 4 // Subscribe Acknowledgment
#define MQTTUNSUBSCRIBE 10 << 4 // Client Unsubscribe request
#define MQTTUNSUBACK 11 << 4 // Unsubscribe Acknowledgment
#define MQTTPINGREQ 12 << 4 // PING Request
#define MQTTPINGRESP 13 << 4 // PING Response
#define MQTTDISCONNECT 14 << 4 // Client is Disconnecting
#define MQTTReserved 15 << 4 // Reserved
#define MQTTQOS0 (0 << 1)
#define MQTTQOS1 (1 << 1)
#define MQTTQOS2 (2 << 1)
// Maximum size of fixed header and variable length size header
#define MQTT_MAX_HEADER_SIZE 5
#if defined(ESP8266) || defined(ESP32)
#include <functional>
#define MQTT_CALLBACK_SIGNATURE std::function<void(char*, uint8_t*, unsigned int)> callback
#else
#define MQTT_CALLBACK_SIGNATURE void (*callback)(char*, uint8_t*, unsigned int)
#endif
#define CHECK_STRING_LENGTH(l,s) if (l+2+strnlen(s, this->bufferSize) > this->bufferSize) {_client->stop();return false;}
class PubSubClient : public Print {
private:
Client* _client;
uint8_t* buffer;
uint16_t bufferSize;
uint16_t keepAlive;
uint16_t socketTimeout;
uint16_t nextMsgId;
unsigned long lastOutActivity;
unsigned long lastInActivity;
bool pingOutstanding;
MQTT_CALLBACK_SIGNATURE;
uint32_t readPacket(uint8_t*);
boolean readByte(uint8_t * result);
boolean readByte(uint8_t * result, uint16_t * index);
boolean write(uint8_t header, uint8_t* buf, uint16_t length);
uint16_t writeString(const char* string, uint8_t* buf, uint16_t pos);
// Build up the header ready to send
// Returns the size of the header
// Note: the header is built at the end of the first MQTT_MAX_HEADER_SIZE bytes, so will start
// (MQTT_MAX_HEADER_SIZE - <returned size>) bytes into the buffer
size_t buildHeader(uint8_t header, uint8_t* buf, uint16_t length);
IPAddress ip;
const char* domain;
uint16_t port;
Stream* stream;
int _state;
public:
PubSubClient();
PubSubClient(Client& client);
PubSubClient(IPAddress, uint16_t, Client& client);
PubSubClient(IPAddress, uint16_t, Client& client, Stream&);
PubSubClient(IPAddress, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client);
PubSubClient(IPAddress, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client, Stream&);
PubSubClient(uint8_t *, uint16_t, Client& client);
PubSubClient(uint8_t *, uint16_t, Client& client, Stream&);
PubSubClient(uint8_t *, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client);
PubSubClient(uint8_t *, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client, Stream&);
PubSubClient(const char*, uint16_t, Client& client);
PubSubClient(const char*, uint16_t, Client& client, Stream&);
PubSubClient(const char*, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client);
PubSubClient(const char*, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client, Stream&);
~PubSubClient();
PubSubClient& setServer(IPAddress ip, uint16_t port);
PubSubClient& setServer(uint8_t * ip, uint16_t port);
PubSubClient& setServer(const char * domain, uint16_t port);
PubSubClient& setCallback(MQTT_CALLBACK_SIGNATURE);
PubSubClient& setClient(Client& client);
PubSubClient& setStream(Stream& stream);
PubSubClient& setKeepAlive(uint16_t keepAlive);
PubSubClient& setSocketTimeout(uint16_t timeout);
boolean setBufferSize(uint16_t size);
uint16_t getBufferSize();
boolean connect(const char* id);
boolean connect(const char* id, const char* user, const char* pass);
boolean connect(const char* id, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage);
boolean connect(const char* id, const char* user, const char* pass, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage);
boolean connect(const char* id, const char* user, const char* pass, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage, boolean cleanSession);
void disconnect();
boolean publish(const char* topic, const char* payload);
boolean publish(const char* topic, const char* payload, boolean retained);
boolean publish(const char* topic, const uint8_t * payload, unsigned int plength);
boolean publish(const char* topic, const uint8_t * payload, unsigned int plength, boolean retained);
boolean publish_P(const char* topic, const char* payload, boolean retained);
boolean publish_P(const char* topic, const uint8_t * payload, unsigned int plength, boolean retained);
// Start to publish a message.
// This API:
// beginPublish(...)
// one or more calls to write(...)
// endPublish()
// Allows for arbitrarily large payloads to be sent without them having to be copied into
// a new buffer and held in memory at one time
// Returns 1 if the message was started successfully, 0 if there was an error
boolean beginPublish(const char* topic, unsigned int plength, boolean retained);
// Finish off this publish message (started with beginPublish)
// Returns 1 if the packet was sent successfully, 0 if there was an error
int endPublish();
// Write a single byte of payload (only to be used with beginPublish/endPublish)
virtual size_t write(uint8_t);
// Write size bytes from buffer into the payload (only to be used with beginPublish/endPublish)
// Returns the number of bytes written
virtual size_t write(const uint8_t *buffer, size_t size);
boolean subscribe(const char* topic);
boolean subscribe(const char* topic, uint8_t qos);
boolean unsubscribe(const char* topic);
boolean loop();
boolean connected();
int state();
};
#endif

View File

@@ -0,0 +1,102 @@
/* CRC compatibility, adapted from the Teensy 3 core at:
https://github.com/PaulStoffregen/cores/tree/master/teensy3
which was in turn adapted by Paul Stoffregen from the C-only comments here:
http://svn.savannah.nongnu.org/viewvc/trunk/avr-libc/include/util/crc16.h?revision=933&root=avr-libc&view=markup */
/* Copyright (c) 2002, 2003, 2004 Marek Michalkiewicz
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the
distribution.
* Neither the name of the copyright holders nor the names of
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE. */
#ifndef _UTIL_CRC16_H_
#ifdef ARDUINO_ARCH_AVR
#include <util/crc16.h>
#else
#define _UTIL_CRC16_H_
#include <stdint.h>
static inline uint16_t _crc16_update(uint16_t crc, uint8_t data) __attribute__((always_inline, unused));
static inline uint16_t _crc16_update(uint16_t crc, uint8_t data)
{
unsigned int i;
crc ^= data;
for (i = 0; i < 8; ++i) {
if (crc & 1) {
crc = (crc >> 1) ^ 0xA001;
} else {
crc = (crc >> 1);
}
}
return crc;
}
static inline uint16_t _crc_xmodem_update(uint16_t crc, uint8_t data) __attribute__((always_inline, unused));
static inline uint16_t _crc_xmodem_update(uint16_t crc, uint8_t data)
{
unsigned int i;
crc = crc ^ ((uint16_t)data << 8);
for (i=0; i<8; i++) {
if (crc & 0x8000) {
crc = (crc << 1) ^ 0x1021;
} else {
crc <<= 1;
}
}
return crc;
}
static inline uint16_t _crc_ccitt_update (uint16_t crc, uint8_t data) __attribute__((always_inline, unused));
static inline uint16_t _crc_ccitt_update (uint16_t crc, uint8_t data)
{
data ^= (crc & 255);
data ^= data << 4;
return ((((uint16_t)data << 8) | (crc >> 8)) ^ (uint8_t)(data >> 4)
^ ((uint16_t)data << 3));
}
static inline uint8_t _crc_ibutton_update(uint8_t crc, uint8_t data) __attribute__((always_inline, unused));
static inline uint8_t _crc_ibutton_update(uint8_t crc, uint8_t data)
{
unsigned int i;
crc = crc ^ data;
for (i = 0; i < 8; i++) {
if (crc & 0x01) {
crc = (crc >> 1) ^ 0x8C;
} else {
crc >>= 1;
}
}
return crc;
}
#endif
#endif

View File

@@ -0,0 +1,262 @@
/**
* Arduino DSMR parser.
*
* This software is licensed under the MIT License.
*
* Copyright (c) 2015 Matthijs Kooijman <matthijs@stdin.nl>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* Field parsing functions
*/
#include "fields.h"
using namespace dsmr;
using namespace dsmr::fields;
// Since C++11 it is possible to define the initial values for static
// const members in the class declaration, but if their address is
// taken, they still need a normal definition somewhere (to allocate
// storage).
constexpr char units::none[];
constexpr char units::kWh[];
constexpr char units::Wh[];
constexpr char units::kW[];
constexpr char units::W[];
constexpr char units::V[];
constexpr char units::mV[];
constexpr char units::A[];
constexpr char units::mA[];
constexpr char units::m3[];
constexpr char units::dm3[];
constexpr char units::GJ[];
constexpr char units::MJ[];
constexpr ObisId identification::id;
constexpr char identification::name_progmem[];
constexpr const __FlashStringHelper *identification::name;
constexpr ObisId p1_version::id;
constexpr char p1_version::name_progmem[];
constexpr const __FlashStringHelper *p1_version::name;
constexpr ObisId timestamp::id;
constexpr char timestamp::name_progmem[];
constexpr const __FlashStringHelper *timestamp::name;
constexpr ObisId equipment_id::id;
constexpr char equipment_id::name_progmem[];
constexpr const __FlashStringHelper *equipment_id::name;
constexpr ObisId energy_delivered_tariff1::id;
constexpr char energy_delivered_tariff1::name_progmem[];
constexpr const __FlashStringHelper *energy_delivered_tariff1::name;
constexpr ObisId energy_delivered_tariff2::id;
constexpr char energy_delivered_tariff2::name_progmem[];
constexpr const __FlashStringHelper *energy_delivered_tariff2::name;
constexpr ObisId energy_returned_tariff1::id;
constexpr char energy_returned_tariff1::name_progmem[];
constexpr const __FlashStringHelper *energy_returned_tariff1::name;
constexpr ObisId energy_returned_tariff2::id;
constexpr char energy_returned_tariff2::name_progmem[];
constexpr const __FlashStringHelper *energy_returned_tariff2::name;
constexpr ObisId electricity_tariff::id;
constexpr char electricity_tariff::name_progmem[];
constexpr const __FlashStringHelper *electricity_tariff::name;
constexpr ObisId power_delivered::id;
constexpr char power_delivered::name_progmem[];
constexpr const __FlashStringHelper *power_delivered::name;
constexpr ObisId power_returned::id;
constexpr char power_returned::name_progmem[];
constexpr const __FlashStringHelper *power_returned::name;
constexpr ObisId electricity_threshold::id;
constexpr char electricity_threshold::name_progmem[];
constexpr const __FlashStringHelper *electricity_threshold::name;
constexpr ObisId electricity_switch_position::id;
constexpr char electricity_switch_position::name_progmem[];
constexpr const __FlashStringHelper *electricity_switch_position::name;
constexpr ObisId electricity_failures::id;
constexpr char electricity_failures::name_progmem[];
constexpr const __FlashStringHelper *electricity_failures::name;
constexpr ObisId electricity_long_failures::id;
constexpr char electricity_long_failures::name_progmem[];
constexpr const __FlashStringHelper *electricity_long_failures::name;
constexpr ObisId electricity_failure_log::id;
constexpr char electricity_failure_log::name_progmem[];
constexpr const __FlashStringHelper *electricity_failure_log::name;
constexpr ObisId electricity_sags_l1::id;
constexpr char electricity_sags_l1::name_progmem[];
constexpr const __FlashStringHelper *electricity_sags_l1::name;
constexpr ObisId electricity_sags_l2::id;
constexpr char electricity_sags_l2::name_progmem[];
constexpr const __FlashStringHelper *electricity_sags_l2::name;
constexpr ObisId electricity_sags_l3::id;
constexpr char electricity_sags_l3::name_progmem[];
constexpr const __FlashStringHelper *electricity_sags_l3::name;
constexpr ObisId electricity_swells_l1::id;
constexpr char electricity_swells_l1::name_progmem[];
constexpr const __FlashStringHelper *electricity_swells_l1::name;
constexpr ObisId electricity_swells_l2::id;
constexpr char electricity_swells_l2::name_progmem[];
constexpr const __FlashStringHelper *electricity_swells_l2::name;
constexpr ObisId electricity_swells_l3::id;
constexpr char electricity_swells_l3::name_progmem[];
constexpr const __FlashStringHelper *electricity_swells_l3::name;
constexpr ObisId message_short::id;
constexpr char message_short::name_progmem[];
constexpr const __FlashStringHelper *message_short::name;
constexpr ObisId message_long::id;
constexpr char message_long::name_progmem[];
constexpr const __FlashStringHelper *message_long::name;
constexpr ObisId voltage_l1::id;
constexpr char voltage_l1::name_progmem[];
constexpr const __FlashStringHelper *voltage_l1::name;
constexpr ObisId voltage_l2::id;
constexpr char voltage_l2::name_progmem[];
constexpr const __FlashStringHelper *voltage_l2::name;
constexpr ObisId voltage_l3::id;
constexpr char voltage_l3::name_progmem[];
constexpr const __FlashStringHelper *voltage_l3::name;
constexpr ObisId current_l1::id;
constexpr char current_l1::name_progmem[];
constexpr const __FlashStringHelper *current_l1::name;
constexpr ObisId current_l2::id;
constexpr char current_l2::name_progmem[];
constexpr const __FlashStringHelper *current_l2::name;
constexpr ObisId current_l3::id;
constexpr char current_l3::name_progmem[];
constexpr const __FlashStringHelper *current_l3::name;
constexpr ObisId power_delivered_l1::id;
constexpr char power_delivered_l1::name_progmem[];
constexpr const __FlashStringHelper *power_delivered_l1::name;
constexpr ObisId power_delivered_l2::id;
constexpr char power_delivered_l2::name_progmem[];
constexpr const __FlashStringHelper *power_delivered_l2::name;
constexpr ObisId power_delivered_l3::id;
constexpr char power_delivered_l3::name_progmem[];
constexpr const __FlashStringHelper *power_delivered_l3::name;
constexpr ObisId power_returned_l1::id;
constexpr char power_returned_l1::name_progmem[];
constexpr const __FlashStringHelper *power_returned_l1::name;
constexpr ObisId power_returned_l2::id;
constexpr char power_returned_l2::name_progmem[];
constexpr const __FlashStringHelper *power_returned_l2::name;
constexpr ObisId power_returned_l3::id;
constexpr char power_returned_l3::name_progmem[];
constexpr const __FlashStringHelper *power_returned_l3::name;
constexpr ObisId gas_device_type::id;
constexpr char gas_device_type::name_progmem[];
constexpr const __FlashStringHelper *gas_device_type::name;
constexpr ObisId gas_equipment_id::id;
constexpr char gas_equipment_id::name_progmem[];
constexpr const __FlashStringHelper *gas_equipment_id::name;
constexpr ObisId gas_valve_position::id;
constexpr char gas_valve_position::name_progmem[];
constexpr const __FlashStringHelper *gas_valve_position::name;
constexpr ObisId gas_delivered::id;
constexpr char gas_delivered::name_progmem[];
constexpr const __FlashStringHelper *gas_delivered::name;
constexpr ObisId thermal_device_type::id;
constexpr char thermal_device_type::name_progmem[];
constexpr const __FlashStringHelper *thermal_device_type::name;
constexpr ObisId thermal_equipment_id::id;
constexpr char thermal_equipment_id::name_progmem[];
constexpr const __FlashStringHelper *thermal_equipment_id::name;
constexpr ObisId thermal_valve_position::id;
constexpr char thermal_valve_position::name_progmem[];
constexpr const __FlashStringHelper *thermal_valve_position::name;
constexpr ObisId thermal_delivered::id;
constexpr char thermal_delivered::name_progmem[];
constexpr const __FlashStringHelper *thermal_delivered::name;
constexpr ObisId water_device_type::id;
constexpr char water_device_type::name_progmem[];
constexpr const __FlashStringHelper *water_device_type::name;
constexpr ObisId water_equipment_id::id;
constexpr char water_equipment_id::name_progmem[];
constexpr const __FlashStringHelper *water_equipment_id::name;
constexpr ObisId water_valve_position::id;
constexpr char water_valve_position::name_progmem[];
constexpr const __FlashStringHelper *water_valve_position::name;
constexpr ObisId water_delivered::id;
constexpr char water_delivered::name_progmem[];
constexpr const __FlashStringHelper *water_delivered::name;
constexpr ObisId slave_device_type::id;
constexpr char slave_device_type::name_progmem[];
constexpr const __FlashStringHelper *slave_device_type::name;
constexpr ObisId slave_equipment_id::id;
constexpr char slave_equipment_id::name_progmem[];
constexpr const __FlashStringHelper *slave_equipment_id::name;
constexpr ObisId slave_valve_position::id;
constexpr char slave_valve_position::name_progmem[];
constexpr const __FlashStringHelper *slave_valve_position::name;
constexpr ObisId slave_delivered::id;
constexpr char slave_delivered::name_progmem[];
constexpr const __FlashStringHelper *slave_delivered::name;

View File

@@ -0,0 +1,352 @@
/**
* Arduino DSMR parser.
*
* This software is licensed under the MIT License.
*
* Copyright (c) 2015 Matthijs Kooijman <matthijs@stdin.nl>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* Field parsing functions
*/
#ifndef DSMR_INCLUDE_FIELDS_H
#define DSMR_INCLUDE_FIELDS_H
#include "util.h"
#include "parser.h"
namespace dsmr {
/**
* Superclass for data items in a P1 message.
*/
template <typename T>
struct ParsedField {
template <typename F>
void apply(F& f) {
f.apply(*static_cast<T*>(this));
}
// By defaults, fields have no unit
static const char *unit() { return ""; }
};
template <typename T, size_t minlen, size_t maxlen>
struct StringField : ParsedField<T> {
ParseResult<void> parse(const char *str, const char *end) {
ParseResult<String> res = StringParser::parse_string(minlen, maxlen, str, end);
if (!res.err)
static_cast<T*>(this)->val() = res.result;
return res;
}
};
// A timestamp is essentially a string using YYMMDDhhmmssX format (where
// X is W or S for wintertime or summertime). Parsing this into a proper
// (UNIX) timestamp is hard to do generically. Parsing it into a
// single integer needs > 4 bytes top fit and isn't very useful (you
// cannot really do any calculation with those values). So we just parse
// into a string for now.
template <typename T>
struct TimestampField : StringField<T, 13, 13> { };
// Value that is parsed as a three-decimal float, but stored as an
// integer (by multiplying by 1000). Supports val() (or implicit cast to
// float) to get the original value, and int_val() to get the more
// efficient integer value. The unit() and int_unit() methods on
// FixedField return the corresponding units for these values.
struct FixedValue {
operator float() { return val();}
float val() { return _value / 1000.0;}
uint32_t int_val() { return _value; }
uint32_t _value;
};
// Floating point numbers in the message never have more than 3 decimal
// digits. To prevent inefficient floating point operations, we store
// them as a fixed-point number: an integer that stores the value in
// thousands. For example, a value of 1.234 kWh is stored as 1234. This
// effectively means that the integer value is het value in Wh. To allow
// automatic printing of these values, both the original unit and the
// integer unit is passed as a template argument.
template <typename T, const char *_unit, const char *_int_unit>
struct FixedField : ParsedField<T> {
ParseResult<void> parse(const char *str, const char *end) {
ParseResult<uint32_t> res = NumParser::parse(3, _unit, str, end);
if (!res.err)
static_cast<T*>(this)->val()._value = res.result;
return res;
}
static const char *unit() { return _unit; }
static const char *int_unit() { return _int_unit; }
};
struct TimestampedFixedValue : public FixedValue {
String timestamp;
};
// Some numerical values are prefixed with a timestamp. This is simply
// both of them concatenated, e.g. 0-1:24.2.1(150117180000W)(00473.789*m3)
template <typename T, const char *_unit, const char *_int_unit>
struct TimestampedFixedField : public FixedField<T, _unit, _int_unit> {
ParseResult<void> parse(const char *str, const char *end) {
// First, parse timestamp
ParseResult<String> res = StringParser::parse_string(13, 13, str, end);
if (res.err)
return res;
static_cast<T*>(this)->val().timestamp = res.result;
// Which is immediately followed by the numerical value
return FixedField<T, _unit, _int_unit>::parse(res.next, end);
}
};
// A integer number is just represented as an integer.
template <typename T, const char *_unit>
struct IntField : ParsedField<T> {
ParseResult<void> parse(const char *str, const char *end) {
ParseResult<uint32_t> res = NumParser::parse(0, _unit, str, end);
if (!res.err)
static_cast<T*>(this)->val() = res.result;
return res;
}
static const char *unit() { return _unit; }
};
// A RawField is not parsed, the entire value (including any
// parenthesis around it) is returned as a string.
template <typename T>
struct RawField : ParsedField<T> {
ParseResult<void> parse(const char *str, const char *end) {
// Just copy the string verbatim value without any parsing
concat_hack(static_cast<T*>(this)->val(), str, end - str);
return ParseResult<void>().until(end);
}
};
namespace fields {
struct units {
// These variables are inside a struct, since that allows us to make
// them constexpr and define their values here, but define the storage
// in a cpp file. Global const(expr) variables have implicitly
// internal linkage, meaning each cpp file that includes us will have
// its own copy of the variable. Since we take the address of these
// variables (passing it as a template argument), this would cause a
// compiler warning. By putting these in a struct, this is prevented.
static constexpr char none[] = "";
static constexpr char kWh[] = "kWh";
static constexpr char Wh[] = "Wh";
static constexpr char kW[] = "kW";
static constexpr char W[] = "W";
static constexpr char V[] = "V";
static constexpr char mV[] = "mV";
static constexpr char A[] = "A";
static constexpr char mA[] = "mA";
static constexpr char m3[] = "m3";
static constexpr char dm3[] = "dm3";
static constexpr char GJ[] = "GJ";
static constexpr char MJ[] = "MJ";
};
const uint8_t GAS_MBUS_ID = 1;
const uint8_t WATER_MBUS_ID = 2;
const uint8_t THERMAL_MBUS_ID = 3;
const uint8_t SLAVE_MBUS_ID = 4;
#define DEFINE_FIELD(fieldname, value_t, obis, field_t, field_args...) \
struct fieldname : field_t<fieldname, ##field_args> { \
value_t fieldname; \
bool fieldname ## _present = false; \
static constexpr ObisId id = obis; \
static constexpr char name_progmem[] DSMR_PROGMEM = #fieldname; \
static constexpr const __FlashStringHelper *name = reinterpret_cast<const __FlashStringHelper*>(&name_progmem); \
value_t& val() { return fieldname; } \
bool& present() { return fieldname ## _present; } \
}
/* Meter identification. This is not a normal field, but a
* specially-formatted first line of the message */
DEFINE_FIELD(identification, String, ObisId(255, 255, 255, 255, 255, 255), RawField);
/* Version information for P1 output */
DEFINE_FIELD(p1_version, String, ObisId(1, 3, 0, 2, 8), StringField, 2, 2);
/* Date-time stamp of the P1 message */
DEFINE_FIELD(timestamp, String, ObisId(0, 0, 1, 0, 0), TimestampField);
/* Equipment identifier */
DEFINE_FIELD(equipment_id, String, ObisId(0, 0, 96, 1, 1), StringField, 0, 96);
/* Meter Reading electricity delivered to client (Tariff 1) in 0,001 kWh */
DEFINE_FIELD(energy_delivered_tariff1, FixedValue, ObisId(1, 0, 1, 8, 1), FixedField, units::kWh, units::Wh);
/* Meter Reading electricity delivered to client (Tariff 2) in 0,001 kWh */
DEFINE_FIELD(energy_delivered_tariff2, FixedValue, ObisId(1, 0, 1, 8, 2), FixedField, units::kWh, units::Wh);
/* Meter Reading electricity delivered by client (Tariff 1) in 0,001 kWh */
DEFINE_FIELD(energy_returned_tariff1, FixedValue, ObisId(1, 0, 2, 8, 1), FixedField, units::kWh, units::Wh);
/* Meter Reading electricity delivered by client (Tariff 2) in 0,001 kWh */
DEFINE_FIELD(energy_returned_tariff2, FixedValue, ObisId(1, 0, 2, 8, 2), FixedField, units::kWh, units::Wh);
/* Tariff indicator electricity. The tariff indicator can also be used
* to switch tariff dependent loads e.g boilers. This is the
* responsibility of the P1 user */
DEFINE_FIELD(electricity_tariff, String, ObisId(0, 0, 96, 14, 0), StringField, 4, 4);
/* Actual electricity power delivered (+P) in 1 Watt resolution */
DEFINE_FIELD(power_delivered, FixedValue, ObisId(1, 0, 1, 7, 0), FixedField, units::kW, units::W);
/* Actual electricity power received (-P) in 1 Watt resolution */
DEFINE_FIELD(power_returned, FixedValue, ObisId(1, 0, 2, 7, 0), FixedField, units::kW, units::W);
/* The actual threshold Electricity in kW. Removed in 4.0.7 / 4.2.2 / 5.0 */
DEFINE_FIELD(electricity_threshold, FixedValue, ObisId(0, 0, 17, 0, 0), FixedField, units::kW, units::W);
/* Switch position Electricity (in/out/enabled). Removed in 4.0.7 / 4.2.2 / 5.0 */
DEFINE_FIELD(electricity_switch_position, uint8_t, ObisId(0, 0, 96, 3, 10), IntField, units::none);
/* Number of power failures in any phase */
DEFINE_FIELD(electricity_failures, uint32_t, ObisId(0, 0, 96, 7, 21), IntField, units::none);
/* Number of long power failures in any phase */
DEFINE_FIELD(electricity_long_failures, uint32_t, ObisId(0, 0, 96, 7, 9), IntField, units::none);
/* Power Failure Event Log (long power failures) */
DEFINE_FIELD(electricity_failure_log, String, ObisId(1, 0, 99, 97, 0), RawField);
/* Number of voltage sags in phase L1 */
DEFINE_FIELD(electricity_sags_l1, uint32_t, ObisId(1, 0, 32, 32, 0), IntField, units::none);
/* Number of voltage sags in phase L2 (polyphase meters only) */
DEFINE_FIELD(electricity_sags_l2, uint32_t, ObisId(1, 0, 52, 32, 0), IntField, units::none);
/* Number of voltage sags in phase L3 (polyphase meters only) */
DEFINE_FIELD(electricity_sags_l3, uint32_t, ObisId(1, 0, 72, 32, 0), IntField, units::none);
/* Number of voltage swells in phase L1 */
DEFINE_FIELD(electricity_swells_l1, uint32_t, ObisId(1, 0, 32, 36, 0), IntField, units::none);
/* Number of voltage swells in phase L2 (polyphase meters only) */
DEFINE_FIELD(electricity_swells_l2, uint32_t, ObisId(1, 0, 52, 36, 0), IntField, units::none);
/* Number of voltage swells in phase L3 (polyphase meters only) */
DEFINE_FIELD(electricity_swells_l3, uint32_t, ObisId(1, 0, 72, 36, 0), IntField, units::none);
/* Text message codes: numeric 8 digits (Note: Missing from 5.0 spec)
* */
DEFINE_FIELD(message_short, String, ObisId(0, 0, 96, 13, 1), StringField, 0, 16);
/* Text message max 2048 characters (Note: Spec says 1024 in comment and
* 2048 in format spec, so we stick to 2048). */
DEFINE_FIELD(message_long, String, ObisId(0, 0, 96, 13, 0), StringField, 0, 2048);
/* Instantaneous voltage L1 in 0.1V resolution (Note: Spec says V
* resolution in comment, but 0.1V resolution in format spec. Added in
* 5.0) */
DEFINE_FIELD(voltage_l1, FixedValue, ObisId(1, 0, 32, 7, 0), FixedField, units::V, units::mV);
/* Instantaneous voltage L2 in 0.1V resolution (Note: Spec says V
* resolution in comment, but 0.1V resolution in format spec. Added in
* 5.0) */
DEFINE_FIELD(voltage_l2, FixedValue, ObisId(1, 0, 52, 7, 0), FixedField, units::V, units::mV);
/* Instantaneous voltage L3 in 0.1V resolution (Note: Spec says V
* resolution in comment, but 0.1V resolution in format spec. Added in
* 5.0) */
DEFINE_FIELD(voltage_l3, FixedValue, ObisId(1, 0, 72, 7, 0), FixedField, units::V, units::mV);
/* Instantaneous current L1 in A resolution */
DEFINE_FIELD(current_l1, uint16_t, ObisId(1, 0, 31, 7, 0), IntField, units::A);
/* Instantaneous current L2 in A resolution */
DEFINE_FIELD(current_l2, uint16_t, ObisId(1, 0, 51, 7, 0), IntField, units::A);
/* Instantaneous current L3 in A resolution */
DEFINE_FIELD(current_l3, uint16_t, ObisId(1, 0, 71, 7, 0), IntField, units::A);
/* Instantaneous active power L1 (+P) in W resolution */
DEFINE_FIELD(power_delivered_l1, FixedValue, ObisId(1, 0, 21, 7, 0), FixedField, units::kW, units::W);
/* Instantaneous active power L2 (+P) in W resolution */
DEFINE_FIELD(power_delivered_l2, FixedValue, ObisId(1, 0, 41, 7, 0), FixedField, units::kW, units::W);
/* Instantaneous active power L3 (+P) in W resolution */
DEFINE_FIELD(power_delivered_l3, FixedValue, ObisId(1, 0, 61, 7, 0), FixedField, units::kW, units::W);
/* Instantaneous active power L1 (-P) in W resolution */
DEFINE_FIELD(power_returned_l1, FixedValue, ObisId(1, 0, 22, 7, 0), FixedField, units::kW, units::W);
/* Instantaneous active power L2 (-P) in W resolution */
DEFINE_FIELD(power_returned_l2, FixedValue, ObisId(1, 0, 42, 7, 0), FixedField, units::kW, units::W);
/* Instantaneous active power L3 (-P) in W resolution */
DEFINE_FIELD(power_returned_l3, FixedValue, ObisId(1, 0, 62, 7, 0), FixedField, units::kW, units::W);
/* Device-Type */
DEFINE_FIELD(gas_device_type, uint16_t, ObisId(0, GAS_MBUS_ID, 24, 1, 0), IntField, units::none);
/* Equipment identifier (Gas) */
DEFINE_FIELD(gas_equipment_id, String, ObisId(0, GAS_MBUS_ID, 96, 1, 0), StringField, 0, 96);
/* Valve position Gas (on/off/released) (Note: Removed in 4.0.7 / 4.2.2 / 5.0). */
DEFINE_FIELD(gas_valve_position, uint8_t, ObisId(0, GAS_MBUS_ID, 24, 4, 0), IntField, units::none);
/* Last 5-minute value (temperature converted), gas delivered to client
* in m3, including decimal values and capture time (Note: 4.x spec has
* "hourly value") */
DEFINE_FIELD(gas_delivered, TimestampedFixedValue, ObisId(0, GAS_MBUS_ID, 24, 2, 1), TimestampedFixedField, units::m3, units::dm3);
/* Device-Type */
DEFINE_FIELD(thermal_device_type, uint16_t, ObisId(0, THERMAL_MBUS_ID, 24, 1, 0), IntField, units::none);
/* Equipment identifier (Thermal: heat or cold) */
DEFINE_FIELD(thermal_equipment_id, String, ObisId(0, THERMAL_MBUS_ID, 96, 1, 0), StringField, 0, 96);
/* Valve position (on/off/released) (Note: Removed in 4.0.7 / 4.2.2 / 5.0). */
DEFINE_FIELD(thermal_valve_position, uint8_t, ObisId(0, THERMAL_MBUS_ID, 24, 4, 0), IntField, units::none);
/* Last 5-minute Meter reading Heat or Cold in 0,01 GJ and capture time
* (Note: 4.x spec has "hourly meter reading") */
DEFINE_FIELD(thermal_delivered, TimestampedFixedValue, ObisId(0, THERMAL_MBUS_ID, 24, 2, 1), TimestampedFixedField, units::GJ, units::MJ);
/* Device-Type */
DEFINE_FIELD(water_device_type, uint16_t, ObisId(0, WATER_MBUS_ID, 24, 1, 0), IntField, units::none);
/* Equipment identifier (Thermal: heat or cold) */
DEFINE_FIELD(water_equipment_id, String, ObisId(0, WATER_MBUS_ID, 96, 1, 0), StringField, 0, 96);
/* Valve position (on/off/released) (Note: Removed in 4.0.7 / 4.2.2 / 5.0). */
DEFINE_FIELD(water_valve_position, uint8_t, ObisId(0, WATER_MBUS_ID, 24, 4, 0), IntField, units::none);
/* Last 5-minute Meter reading in 0,001 m3 and capture time
* (Note: 4.x spec has "hourly meter reading") */
DEFINE_FIELD(water_delivered, TimestampedFixedValue, ObisId(0, WATER_MBUS_ID, 24, 2, 1), TimestampedFixedField, units::m3, units::dm3);
/* Device-Type */
DEFINE_FIELD(slave_device_type, uint16_t, ObisId(0, SLAVE_MBUS_ID, 24, 1, 0), IntField, units::none);
/* Equipment identifier (Thermal: heat or cold) */
DEFINE_FIELD(slave_equipment_id, String, ObisId(0, SLAVE_MBUS_ID, 96, 1, 0), StringField, 0, 96);
/* Valve position (on/off/released) (Note: Removed in 4.0.7 / 4.2.2 / 5.0). */
DEFINE_FIELD(slave_valve_position, uint8_t, ObisId(0, SLAVE_MBUS_ID, 24, 4, 0), IntField, units::none);
/* Last 5-minute Meter reading Heat or Cold and capture time (e.g. slave
* E meter) (Note: 4.x spec has "hourly meter reading") */
DEFINE_FIELD(slave_delivered, TimestampedFixedValue, ObisId(0, SLAVE_MBUS_ID, 24, 2, 1), TimestampedFixedField, units::m3, units::dm3);
} // namespace fields
} // namespace dsmr
#endif // DSMR_INCLUDE_FIELDS_H

View File

@@ -0,0 +1,425 @@
/**
* Arduino DSMR parser.
*
* This software is licensed under the MIT License.
*
* Copyright (c) 2015 Matthijs Kooijman <matthijs@stdin.nl>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* Message parsing core
*/
#ifndef DSMR_INCLUDE_PARSER_H
#define DSMR_INCLUDE_PARSER_H
#include "crc16.h"
#include "util.h"
namespace dsmr {
/**
* ParsedData is a template for the result of parsing a Dsmr P1 message.
* You pass the fields you want to add to it as template arguments.
*
* This template will then generate a class that extends all the fields
* passed (the fields really are classes themselves). Since each field
* class has a single member variable, with the same name as the field
* class, all of these fields will be available on the generated class.
*
* In other words, if I have:
*
* using MyData = ParsedData<
* identification,
* equipment_id
* >;
*
* MyData data;
*
* then I can refer to the fields like data.identification and
* data.equipment_id normally.
*
* Furthermore, this class offers some helper methods that can be used
* to loop over all the fields inside it.
*/
template<typename... Ts>
struct ParsedData;
/**
* Base case: No fields present.
*/
template<>
struct ParsedData<> {
ParseResult<void> __attribute__((__always_inline__)) parse_line_inlined(const ObisId& /* id */, const char *str, const char * /* end */) {
// Parsing succeeded, but found no matching handler (so return
// set the next pointer to show nothing was parsed).
return ParseResult<void>().until(str);
}
template<typename F>
void __attribute__((__always_inline__)) applyEach_inlined(F&& /* f */) {
// Nothing to do
}
bool all_present_inlined() {
return true;
}
};
// Do not use F() for multiply-used strings (including strings used from
// multiple template instantiations), that would result in multiple
// instances of the string in the binary
static constexpr char DUPLICATE_FIELD[] DSMR_PROGMEM = "Duplicate field";
/**
* General case: At least one typename is passed.
*/
template<typename T, typename... Ts>
struct ParsedData<T, Ts...> : public T, ParsedData<Ts...> {
/**
* This method is used by the parser to parse a single line. The
* OBIS id of the line is passed, and this method recursively finds a
* field with a matching id. If any, it calls it's parse method, which
* parses the value and stores it in the field.
*/
ParseResult<void> parse_line(const ObisId& id, const char *str, const char *end) {
return parse_line_inlined(id, str, end);
}
/**
* always_inline version of parse_line. This is a separate method, to
* allow recursively inlining all calls, but still have a non-inlined
* top-level parse_line method.
*/
ParseResult<void> __attribute__((__always_inline__)) parse_line_inlined(const ObisId& id, const char *str, const char *end) {
if (id == T::id) {
if (T::present())
return ParseResult<void>().fail((const __FlashStringHelper*)DUPLICATE_FIELD, str);
T::present() = true;
return T::parse(str, end);
}
return ParsedData<Ts...>::parse_line_inlined(id, str, end);
}
template<typename F>
void applyEach(F&& f) {
applyEach_inlined(f);
}
template<typename F>
void __attribute__((__always_inline__)) applyEach_inlined(F&& f) {
T::apply(f);
return ParsedData<Ts...>::applyEach_inlined(f);
}
/**
* Returns true when all defined fields are present.
*/
bool all_present() {
return all_present_inlined();
}
bool all_present_inlined() {
return T::present() && ParsedData<Ts...>::all_present_inlined();
}
};
struct StringParser {
static ParseResult<String> parse_string(size_t min, size_t max, const char *str, const char *end) {
ParseResult<String> res;
if (str >= end || *str != '(')
return res.fail(F("Missing ("), str);
const char *str_start = str + 1; // Skip (
const char *str_end = str_start;
while(str_end < end && *str_end != ')')
++str_end;
if (str_end == end)
return res.fail(F("Missing )"), str_end);
size_t len = str_end - str_start;
if (len < min || len > max)
return res.fail(F("Invalid string length"), str_start);
concat_hack(res.result, str_start, len);
return res.until(str_end + 1); // Skip )
}
};
// Do not use F() for multiply-used strings (including strings used from
// multiple template instantiations), that would result in multiple
// instances of the string in the binary
static constexpr char INVALID_NUMBER[] DSMR_PROGMEM = "Invalid number";
static constexpr char INVALID_UNIT[] DSMR_PROGMEM = "Invalid unit";
struct NumParser {
static ParseResult<uint32_t> parse(size_t max_decimals, const char* unit, const char *str, const char *end) {
ParseResult<uint32_t> res;
if (str >= end || *str != '(')
return res.fail(F("Missing ("), str);
const char *num_start = str + 1; // Skip (
const char *num_end = num_start;
uint32_t value = 0;
// Parse integer part
while(num_end < end && !strchr("*.)", *num_end)) {
if (*num_end < '0' || *num_end > '9')
return res.fail((const __FlashStringHelper*)INVALID_NUMBER, num_end);
value *= 10;
value += *num_end - '0';
++num_end;
}
// Parse decimal part, if any
if (max_decimals && num_end < end && *num_end == '.') {
++num_end;
while(num_end < end && !strchr("*)", *num_end) && max_decimals--) {
if (*num_end < '0' || *num_end > '9')
return res.fail((const __FlashStringHelper*)INVALID_NUMBER, num_end);
value *= 10;
value += *num_end - '0';
++num_end;
}
}
// Fill in missing decimals with zeroes
while(max_decimals--)
value *= 10;
if (unit && *unit) {
if (num_end >= end || *num_end != '*')
return res.fail(F("Missing unit"), num_end);
const char *unit_start = ++num_end; // skip *
while(num_end < end && *num_end != ')' && *unit) {
if (*num_end++ != *unit++)
return res.fail((const __FlashStringHelper*)INVALID_UNIT, unit_start);
}
if (*unit)
return res.fail((const __FlashStringHelper*)INVALID_UNIT, unit_start);
}
if (num_end >= end || *num_end != ')')
return res.fail(F("Extra data"), num_end);
return res.succeed(value).until(num_end + 1); // Skip )
}
};
struct ObisIdParser {
static ParseResult<ObisId> parse(const char *str, const char *end) {
// Parse a Obis ID of the form 1-2:3.4.5.6
// Stops parsing on the first unrecognized character. Any unparsed
// parts are set to 255.
ParseResult<ObisId> res;
ObisId& id = res.result;
res.next = str;
uint8_t part = 0;
while (res.next < end) {
char c = *res.next;
if (c >= '0' && c <= '9') {
uint8_t digit = c - '0';
if (id.v[part] > 25 || (id.v[part] == 25 && digit > 5))
return res.fail(F("Obis ID has number over 255"), res.next);
id.v[part] = id.v[part] * 10 + digit;
} else if (part == 0 && c == '-') {
part++;
} else if (part == 1 && c == ':') {
part++;
} else if (part > 1 && part < 5 && c == '.') {
part++;
} else {
break;
}
++res.next;
}
if (res.next == str)
return res.fail(F("OBIS id Empty"), str);
for (++part; part < 6; ++part)
id.v[part] = 255;
return res;
}
};
struct CrcParser {
static const size_t CRC_LEN = 4;
// Parse a crc value. str must point to the first of the four hex
// bytes in the CRC.
static ParseResult<uint16_t> parse(const char *str, const char *end) {
ParseResult<uint16_t> res;
// This should never happen with the code in this library, but
// check anyway
if (str + CRC_LEN > end)
return res.fail(F("No checksum found"), str);
// A bit of a messy way to parse the checksum, but all
// integer-parse functions assume nul-termination
char buf[CRC_LEN + 1];
memcpy(buf, str, CRC_LEN);
buf[CRC_LEN] = '\0';
char *endp;
uint16_t check = strtoul(buf, &endp, 16);
// See if all four bytes formed a valid number
if (endp != buf + CRC_LEN)
return res.fail(F("Incomplete or malformed checksum"), str);
res.next = str + CRC_LEN;
return res.succeed(check);
}
};
struct P1Parser {
/**
* Parse a complete P1 telegram. The string passed should start
* with '/' and run up to and including the ! and the following
* four byte checksum. It's ok if the string is longer, the .next
* pointer in the result will indicate the next unprocessed byte.
*/
template <typename... Ts>
static ParseResult<void> parse(ParsedData<Ts...> *data, const char *str, size_t n, bool unknown_error = false) {
ParseResult<void> res;
if (!n || str[0] != '/')
return res.fail(F("Data should start with /"), str);
// Skip /
const char *data_start = str + 1;
// Look for ! that terminates the data
const char *data_end = data_start;
uint16_t crc = _crc16_update(0, *str); // Include the / in CRC
while (data_end < str + n && *data_end != '!') {
crc = _crc16_update(crc, *data_end);
++data_end;
}
if (data_end >= str + n)
return res.fail(F("No checksum found"), data_end);
crc = _crc16_update(crc, *data_end); // Include the ! in CRC
ParseResult<uint16_t> check_res = CrcParser::parse(data_end + 1, str + n);
if (check_res.err)
return check_res;
// Check CRC
if (check_res.result != crc)
return res.fail(F("Checksum mismatch"), data_end + 1);
res = parse_data(data, data_start, data_end, unknown_error);
res.next = check_res.next;
return res;
}
/**
* Parse the data part of a message. Str should point to the first
* character after the leading /, end should point to the ! before the
* checksum. Does not verify the checksum.
*/
template <typename... Ts>
static ParseResult<void> parse_data(ParsedData<Ts...> *data, const char *str, const char *end, bool unknown_error = false) {
ParseResult<void> res;
// Split into lines and parse those
const char *line_end = str, *line_start = str;
// Parse ID line
while (line_end < end) {
if (*line_end == '\r' || *line_end == '\n') {
// The first identification line looks like:
// XXX5<id string>
// The DSMR spec is vague on details, but in 62056-21, the X's
// are a three-leter (registerd) manufacturer ID, the id
// string is up to 16 chars of arbitrary characters and the
// '5' is a baud rate indication. 5 apparently means 9600,
// which DSMR 3.x and below used. It seems that DSMR 2.x
// passed '3' here (which is mandatory for "mode D"
// communication according to 62956-21), so we also allow
// that.
if (line_start + 3 >= line_end || (line_start[3] != '5' && line_start[3] != '3'))
return res.fail(F("Invalid identification string"), line_start);
// Offer it for processing using the all-ones Obis ID, which
// is not otherwise valid.
ParseResult<void> tmp = data->parse_line(ObisId(255, 255, 255, 255, 255, 255), line_start, line_end);
if (tmp.err)
return tmp;
line_start = ++line_end;
break;
}
++line_end;
}
// Parse data lines
while (line_end < end) {
if (*line_end == '\r' || *line_end == '\n') {
ParseResult<void> tmp = parse_line(data, line_start, line_end, unknown_error);
if (tmp.err)
return tmp;
line_start = line_end + 1;
}
line_end++;
}
if (line_end != line_start)
return res.fail(F("Last dataline not CRLF terminated"), line_end);
return res;
}
template <typename Data>
static ParseResult<void> parse_line(Data *data, const char *line, const char *end, bool unknown_error) {
ParseResult<void> res;
if (line == end)
return res;
ParseResult<ObisId> idres = ObisIdParser::parse(line, end);
if (idres.err)
return idres;
ParseResult<void> datares = data->parse_line(idres.result, idres.next, end);
if (datares.err)
return datares;
// If datares.next didn't move at all, there was no parser for
// this field, that's ok. But if it did move, but not all the way
// to the end, that's an error.
if (datares.next != idres.next && datares.next != end)
return res.fail(F("Trailing characters on data line"), datares.next);
else if (datares.next == idres.next && unknown_error)
return res.fail(F("Unknown field"), line);
return res.until(end);
}
};
} // namespace dsmr
#endif // DSMR_INCLUDE_PARSER_H

View File

@@ -0,0 +1,242 @@
/**
* Arduino DSMR parser.
*
* This software is licensed under the MIT License.
*
* Copyright (c) 2015 Matthijs Kooijman <matthijs@stdin.nl>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* P1 reader, that takes care of toggling a request pin, reading data
* from a serial port and parsing it.
*/
#ifndef DSMR_INCLUDE_READER_H
#define DSMR_INCLUDE_READER_H
#include <Arduino.h>
#include "crc16.h"
#include "parser.h"
namespace dsmr {
/**
* Controls the request pin on the P1 port to enable (periodic)
* transmission of messages and reads those messages.
*
* To enable the request pin, call enable(). This lets the Smart Meter
* start periodically sending messages. While the request pin is
* enabled, loop() should be regularly called to read pending bytes.
*
* Once a full and correct message is received, loop() (and available())
* start returning true, until the message is cleared. You can then
* either read the raw message using raw(), or parse it using parse().
*
* The message is cleared when:
* - clear() is called
* - parse() is called
* - loop() is called and the start of a new message is available
*
* When disable is called, the request pin is disabled again and any
* partial message is discarded. Any bytes received while disabled are
* dropped.
*/
class P1Reader {
public:
/**
* Create a new P1Reader. The stream passed should be the serial
* port to which the P1 TX pin is connected. The req_pin is the
* pin connected to the request pin. The pin is configured as an
* output, the Stream is assumed to be already set up (e.g. baud
* rate configured).
*/
P1Reader(Stream *stream, uint8_t req_pin)
: stream(stream), req_pin(req_pin), once(false), state(State::DISABLED_STATE) {
pinMode(req_pin, OUTPUT);
digitalWrite(req_pin, LOW);
}
/**
* Enable the request pin, to request data on the P1 port.
* @param once When true, the request pin is automatically
* disabled once a complete and correct message was
* receivedc. When false, the request pin stays
* enabled, so messages will continue to be sent
* periodically.
*/
void enable(bool once) {
digitalWrite(this->req_pin, HIGH);
this->state = State::WAITING_STATE;
this->once = once;
}
/* Disable the request pin again, to stop data from being sent on
* the P1 port. This will also clear any incomplete data that was
* previously received, but a complete message will be kept until
* clear() is called.
*/
void disable() {
digitalWrite(this->req_pin, LOW);
this->state = State::DISABLED_STATE;
if (!this->_available)
this->buffer = "";
// Clear any pending bytes
while(this->stream->read() >= 0) /* nothing */;
}
/**
* Returns true when a complete and correct message was received,
* until it is cleared.
*/
bool available() {
return this->_available;
}
/**
* Check for new data to read. Should be called regularly, such as
* once every loop. Returns true if a complete message is available
* (just like available).
*/
bool loop() {
while(true) {
if (state == State::CHECKSUM_STATE) {
// Let the Stream buffer the CRC bytes. Convert to size_t to
// prevent unsigned vs signed comparison
if ((size_t)this->stream->available() < CrcParser::CRC_LEN)
return false;
char buf[CrcParser::CRC_LEN];
for (uint8_t i = 0; i < CrcParser::CRC_LEN; ++i)
buf[i] = this->stream->read();
ParseResult<uint16_t> crc = CrcParser::parse(buf, buf + lengthof(buf));
// Prepare for next message
state = State::WAITING_STATE;
if (!crc.err && crc.result == this->crc) {
// Message complete, checksum correct
this->_available = true;
if (once)
this->disable();
return true;
}
} else {
// For other states, read bytes one by one
int c = this->stream->read();
if (c < 0)
return false;
switch (this->state) {
case State::DISABLED_STATE:
// Where did this byte come from? Just toss it
break;
case State::WAITING_STATE:
if (c == '/') {
this->state = State::READING_STATE;
// Include the / in the CRC
this->crc = _crc16_update(0, c);
this->clear();
}
break;
case State::READING_STATE:
// Include the ! in the CRC
this->crc = _crc16_update(this->crc, c);
if (c == '!')
this->state = State::CHECKSUM_STATE;
else
buffer.concat((char)c);
break;
case State::CHECKSUM_STATE:
// This cannot happen (given the surrounding if), but the
// compiler is not smart enough to see this, so list this
// case to prevent a warning.
abort();
break;
}
}
}
return false;
}
/**
* Returns the data read so far.
*/
const String &raw() {
return buffer;
}
/**
* If a complete message has been received, parse it and store the
* result into the ParsedData object passed.
*
* After parsing, the message is cleared.
*
* If parsing fails, false is returned. If err is passed, the error
* message is appended to that string.
*/
template<typename... Ts>
bool parse(ParsedData<Ts...> *data, String *err) {
const char *str = buffer.c_str(), *end = buffer.c_str() + buffer.length();
ParseResult<void> res = P1Parser::parse_data(data, str, end);
if (res.err && err)
*err = res.fullError(str, end);
// Clear the message
this->clear();
return res.err == NULL;
}
/**
* Clear any complete message from the buffer.
*/
void clear() {
if (_available) {
buffer = "";
_available = false;
}
}
protected:
Stream *stream;
uint8_t req_pin;
enum class State : uint8_t {
DISABLED_STATE,
WAITING_STATE,
READING_STATE,
CHECKSUM_STATE,
};
bool _available;
bool once;
State state;
String buffer;
uint16_t crc;
};
} // namespace dsmr
#endif // DSMR_INCLUDE_READER_H

View File

@@ -0,0 +1,186 @@
/**
* Arduino DSMR parser.
*
* This software is licensed under the MIT License.
*
* Copyright (c) 2015 Matthijs Kooijman <matthijs@stdin.nl>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* Various utility functions
*/
#ifndef DSMR_INCLUDE_UTIL_H
#define DSMR_INCLUDE_UTIL_H
#ifdef ARDUINO_ARCH_ESP8266
#define DSMR_PROGMEM
#else
#define DSMR_PROGMEM PROGMEM
#endif
#include <Arduino.h>
namespace dsmr {
/**
* Small utility to get the length of an array at compiletime.
*/
template<typename T, unsigned int sz>
inline unsigned int lengthof(const T (&)[sz]) { return sz; }
// Hack until https://github.com/arduino/Arduino/pull/1936 is merged.
// This appends the given number of bytes from the given C string to the
// given Arduino string, without requiring a trailing NUL.
// Requires that there _is_ room for nul-termination
static void concat_hack(String& s, const char *append, size_t n) {
// Add null termination. Inefficient, but it works...
char buf[n + 1];
memcpy(buf, append, n);
buf[n] = 0;
s.concat(buf);
}
/**
* The ParseResult<T> class wraps the result of a parse function. The type
* of the result is passed as a template parameter and can be void to
* not return any result.
*
* A ParseResult can either:
* - Return an error. In this case, err is set to an error message, ctx
* is optionally set to where the error occurred. The result (if any)
* and the next pointer are meaningless.
* - Return succesfully. In this case, err and ctx are NULL, result
* contains the result (if any) and next points one past the last
* byte processed by the parser.
*
* The ParseResult class has some convenience functions:
* - succeed(result): sets the result to the given value and returns
* the ParseResult again.
* - fail(err): Set the err member to the error message passed,
* optionally sets the ctx and return the ParseResult again.
* - until(next): Set the next member and return the ParseResult again.
*
* Furthermore, ParseResults can be implicitely converted to other
* types. In this case, the error message, context and and next pointer are
* conserved, the return value is reset to the default value for the
* target type.
*
* Note that ctx points into the string being parsed, so it does not
* need to be freed, lives as long as the original string and is
* probably way longer that needed.
*/
// Superclass for ParseResult so we can specialize for void without
// having to duplicate all content
template <typename P, typename T>
struct _ParseResult {
T result;
P& succeed(T& result) {
this->result = result; return *static_cast<P*>(this);
}
P& succeed(T&& result) {
this->result = result;
return *static_cast<P*>(this);
}
};
// partial specialization for void result
template <typename P>
struct _ParseResult<P, void> {
};
// Actual ParseResult class
template <typename T>
struct ParseResult : public _ParseResult<ParseResult<T>, T> {
const char *next = NULL;
const __FlashStringHelper *err = NULL;
const char *ctx = NULL;
ParseResult& fail(const __FlashStringHelper *err, const char* ctx = NULL) {
this->err = err;
this->ctx = ctx;
return *this;
}
ParseResult& until(const char *next) {
this->next = next;
return *this;
}
ParseResult() = default;
ParseResult(const ParseResult& other) = default;
template <typename T2>
ParseResult(const ParseResult<T2>& other): next(other.next), err(other.err), ctx(other.ctx) { }
/**
* Returns the error, including context in a fancy multi-line format.
* The start and end passed are the first and one-past-the-end
* characters in the total parsed string. These are needed to properly
* limit the context output.
*/
String fullError(const char* start, const char* end) const {
String res;
if (this->ctx && start && end) {
// Find the entire line surrounding the context
const char *line_end = this->ctx;
while(line_end < end && line_end[0] != '\r' && line_end[0] != '\n') ++line_end;
const char *line_start = this->ctx;
while(line_start > start && line_start[-1] != '\r' && line_start[-1] != '\n') --line_start;
// We can now predict the context string length, so let String allocate
// memory in advance
res.reserve((line_end - line_start) + 2 + (this->ctx - line_start) + 1 + 2);
// Write the line
concat_hack(res, line_start, line_end - line_start);
res += "\r\n";
// Write a marker to point out ctx
while (line_start++ < this->ctx)
res += ' ';
res += '^';
res += "\r\n";
}
res += this->err;
return res;
}
};
/**
* An OBIS id is 6 bytes, usually noted as a-b:c.d.e.f. Here we put them
* in an array for easy parsing.
*/
struct ObisId {
uint8_t v[6];
constexpr ObisId(uint8_t a, uint8_t b = 255, uint8_t c = 255, uint8_t d = 255, uint8_t e = 255, uint8_t f = 255)
: v{a, b, c, d, e, f} { };
constexpr ObisId() : v() {} // Zeroes
bool operator==(const ObisId &other) const {
return memcmp(&v, &other.v, sizeof(v)) == 0;
}
};
} // namespace dsmr
#endif // DSMR_INCLUDE_UTIL_H

View File

@@ -0,0 +1,14 @@
# This file is for unifying the coding style for different editors and IDEs
# editorconfig.org
root = true
[*]
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
indent_style = space
indent_size = 2
max_line_length = 120
curly_bracket_next_line = false

View File

@@ -0,0 +1,17 @@
# Number of days of inactivity before an issue becomes stale
daysUntilStale: 180
# Number of days of inactivity before a stale issue is closed
daysUntilClose: 14
# Issues with these labels will never be considered stale
exemptLabels:
- pinned
- security
# Label to use when marking an issue as stale
staleLabel: stale
# Comment to post when marking an issue as stale. Set to `false` to disable
markComment: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Thank you
for your contributions.
# Comment to post when closing a stale issue. Set to `false` to disable
closeComment: false

View File

@@ -0,0 +1,2 @@
.vscode
.pio

View File

@@ -0,0 +1,29 @@
# documentation at https://docs.platformio.org/en/latest/integration/ci/travis.html
language: python
python:
- "3.7"
# Cache PlatformIO packages using Travis CI container-based infrastructure
sudo: false
cache:
directories:
- "~/.platformio"
- $HOME/.cache/pip
env:
- PLATFORMIO_CI_SRC=examples/SSD1306UiDemo
- PLATFORMIO_CI_SRC=examples/SSD1306SimpleDemo
- PLATFORMIO_CI_SRC=examples/SSD1306DrawingDemo
- PLATFORMIO_CI_SRC=examples/SSD1306OTADemo
- PLATFORMIO_CI_SRC=examples/SSD1306ClockDemo
- PLATFORMIO_CI_SRC=examples/SSD1306TwoScreenDemo
install:
- pip install -U platformio
- pio update
- platformio lib -g install "paulstoffregen/Time@^1.6"
script:
- platformio ci --lib="." --board=nodemcuv2

View File

@@ -0,0 +1,4 @@
set(COMPONENT_ADD_INCLUDEDIRS src)
set(COMPONENT_PRIV_REQUIRES arduino-esp32)
set(COMPONENT_SRCDIRS src)
register_component()

View File

@@ -0,0 +1,70 @@
# Contributing to ThingPulse OLED SSD1306
:+1::tada: First off, thanks for taking the time to contribute! :tada::+1:
The following is a set of guidelines for contributing to the ThingPulse OLED SSD1306 library on GitHub. These are just guidelines, not rules, use your best judgment and feel free to propose changes to this document in a pull request.
It is appreciated if you raise an issue _before_ you start changing the code, discussing the proposed change; emphasizing that you are proposing to develop the patch yourself, and outlining the strategy for implementation. This type of discussion is what we should be doing on the issues list and it is better to do this before or in parallel to developing the patch rather than having "you should have done it this way" type of feedback on the PR itself.
### Table Of Contents
* [General remarks](#general-remarks)
* [Writing Documentation](#writing-documentation)
* [Working with Git and GitHub](#working-with-git-and-github)
* [General flow](#general-flow)
* [Keeping your fork in sync](#keeping-your-fork-in-sync)
* [Commit messages](#commit-messages)
## General remarks
We are a friendly and welcoming community and look forward to your contributions. Once your contribution is integrated into this repository we feel responsible for it. Therefore, be prepared for constructive feedback. Before we merge anything we need to ensure that it fits in and is consistent with the rest of code.
If you made something really cool but won't spend the time to integrate it into this upstream project please still share it in your fork on GitHub. If you mention it in an issue we'll take a look at it anyway.
## Writing Documentation
ThingPulse maintains documentation for its products at [https://github.com/thingpulse/docs/](https://github.com/thingpulse/docs/). If you contribute features for this project that require altering the respective product guide then we ask you to prepare a pull request with the necessary documentation changes as well.
## Working with Git and GitHub
Avoid intermediate merge commits. [Rebase](https://www.atlassian.com/git/tutorials/merging-vs-rebasing) your feature branch onto `master` to pull updates and verify your local changes against them before placing the pull request.
### General flow
1. [Fork](https://help.github.com/articles/fork-a-repo) this repository on GitHub.
1. [Create a branch](https://help.github.com/articles/creating-and-deleting-branches-within-your-repository/#creating-a-branch) in your fork on GitHub **based on the `master` branch**.
1. Clone the fork on your machine with `git clone https://github.com/<your-account>/<esp8266-oled-ssd1306>.git`
1. `cd <weather-station-fork>` then run `git remote add upstream https://github.com/ThingPulse/esp8266-oled-ssd1306`
1. `git checkout <branch-name>`
1. Make changes to the code base and commit them using e.g. `git commit -a -m 'Look ma, I did it'`
1. When you're done bring your fork up-to-date with the upstream repo ([see below](#keeping-your-fork-in-sync)). Then rebase your branch on `master` running `git rebase master`.
1. `git push`
1. [Create a pull request](https://help.github.com/articles/creating-a-pull-request/) (PR) on GitHub.
This is just one way of doing things. If you're proficient in Git matters you're free to choose your own. If you want to read more then the [GitHub chapter in the Git book](http://git-scm.com/book/en/v2/GitHub-Contributing-to-a-Project#The-GitHub-Flow) is a way to start. [GitHub's own documentation](https://help.github.com/categories/collaborating/) contains a wealth of information as well.
### Keeping your fork in sync
You need to sync your fork with the upstream repository from time to time, latest before you rebase (see flow above).
1. `git fetch upstream`
1. `git checkout master`
1. `git merge upstream/master`
### Commit messages
From: [http://git-scm.com/book/ch5-2.html](http://git-scm.com/book/ch5-2.html)
<pre>
Short (50 chars or less) summary of changes
More detailed explanatory text, if necessary. Wrap it to about 72
characters or so. In some contexts, the first line is treated as the
subject of an email and the rest of the text as the body. The blank
line separating the summary from the body is critical (unless you omit
the body entirely); tools like rebase can get confused if you run the
two together.
Further paragraphs come after blank lines.
- Bullet points are okay, too
- Typically a hyphen or asterisk is used for the bullet, preceded by a
single space, with blank lines in between, but conventions vary here
</pre>
Don't forget to [reference affected issues](https://help.github.com/articles/closing-issues-via-commit-messages/) in the commit message to have them closed automatically on GitHub.
[Amend](https://help.github.com/articles/changing-a-commit-message/) your commit messages if necessary to make sure what the world sees on GitHub is as expressive and meaningful as possible.

View File

@@ -0,0 +1,429 @@
[![Build Status](https://travis-ci.org/ThingPulse/esp8266-oled-ssd1306.svg?branch=master)](https://travis-ci.org/ThingPulse/esp8266-oled-ssd1306)
# ThingPulse OLED SSD1306 (ESP8266/ESP32/Mbed-OS)
> We just released version 4.0.0. Please have a look at our [upgrade guide](UPGRADE-4.0.md)
This is a driver for SSD1306 128x64, 128x32, 64x48 and 64x32 OLED displays running on the Arduino/ESP8266 & ESP32 and mbed-os platforms.
Can be used with either the I2C or SPI version of the display.
You can either download this library as a zip file and unpack it to your Arduino/libraries folder or find it in the Arduino library manager under "ESP8266 and ESP32 Oled Driver for SSD1306 display". For mbed-os a copy of the files are available as an mbed-os library.
It is also available as a platformio library. Just execute the following command:
```
platformio lib install 562
```
## Service level promise
<table><tr><td><img src="https://thingpulse.com/assets/ThingPulse-open-source-prime.png" width="150">
</td><td>This is a ThingPulse <em>prime</em> project. See our <a href="https://thingpulse.com/about/open-source-commitment/">open-source commitment declaration</a> for what this means.</td></tr></table>
## Credits
This library has initially been written by Daniel Eichhorn ([@squix78](https://github.com/squix78)). Many thanks go to Fabrice Weinberg ([@FWeinb](https://github.com/FWeinb)) for optimizing and refactoring many aspects of the library. Also many thanks to the many committers who helped to add new features and who fixed many bugs. Mbed-OS support and other improvements were contributed by Helmut Tschemernjak ([@helmut64](https://github.com/helmut64)).
The init sequence for the SSD1306 was inspired by Adafruit's library for the same display.
## mbed-os
This library has been adopted to support the ARM mbed-os environment. A copy of this library is available in mbed-os under the name OLED_SSD1306 by Helmut Tschemernjak. An alternate installation option is to copy the following files into your mbed-os project: OLEDDisplay.cpp OLEDDisplay.h OLEDDisplayFonts.h OLEDDisplayUi.cpp OLEDDisplayUi.h SSD1306I2C.h
## Usage
Check out the examples folder for a few comprehensive demonstrations how to use the library. Also check out the [ESP8266 Weather Station](https://github.com/ThingPulse/esp8266-weather-station) library which uses the OLED library to display beautiful weather information.
## Upgrade
The API changed a lot with the 3.0 release. If you were using this library with older versions please have a look at the [Upgrade Guide](UPGRADE-3.0.md).
Going from 3.x version to 4.0 a lot of internals changed and compatibility for more displays was added. Please read the [Upgrade Guide](UPGRADE-4.0.md).
## Features
* Draw pixels at given coordinates
* Draw lines from given coordinates to given coordinates
* Draw or fill a rectangle with given dimensions
* Draw Text at given coordinates:
* Define Alignment: Left, Right and Center
* Set the Fontface you want to use (see section Fonts below)
* Limit the width of the text by an amount of pixels. Before this widths will be reached, the renderer will wrap the text to a new line if possible
* Display content in automatically side scrolling carousel
* Define transition cycles
* Define how long one frame will be displayed
* Draw the different frames in callback methods
* One indicator per frame will be automatically displayed. The active frame will be displayed from inactive once
## Fonts
Fonts are defined in a proprietary but open format. You can create new font files by choosing from a given list
of open sourced Fonts from this web app: http://oleddisplay.squix.ch
Choose the font family, style and size, check the preview image and if you like what you see click the "Create" button. This will create the font array in a text area form where you can copy and paste it into a new or existing header file.
![FontTool](https://github.com/squix78/esp8266-oled-ssd1306/raw/master/resources/FontTool.png)
## Hardware Abstraction
The library supports different protocols to access the OLED display. Currently there is support for I2C using the built in Wire.h library, I2C by using the much faster [BRZO I2C library](https://github.com/pasko-zh/brzo_i2c) written in assembler and it also supports displays which come with the SPI interface.
### I2C with Wire.h
```C++
#include <Wire.h>
#include "SSD1306Wire.h"
// for 128x64 displays:
SSD1306Wire display(0x3c, SDA, SCL); // ADDRESS, SDA, SCL
// for 128x32 displays:
// SSD1306Wire display(0x3c, SDA, SCL, GEOMETRY_128_32); // ADDRESS, SDA, SCL, GEOMETRY_128_32 (or 128_64)
// for using 2nd Hardware I2C (if available)
// SSD1306Wire(0x3c, SDA, SCL, GEOMETRY_128_64, I2C_TWO); //default value is I2C_ONE if not mentioned
// By default SD1306Wire set I2C frequency to 700000, you can use set either another frequency or skip setting the frequency by providing -1 value
// SSD1306Wire(0x3c, SDA, SCL, GEOMETRY_128_64, I2C_ONE, 400000); //set I2C frequency to 400kHz
// SSD1306Wire(0x3c, SDA, SCL, GEOMETRY_128_64, I2C_ONE, -1); //skip setting the I2C bus frequency
```
for a SH1106:
```C++
#include <Wire.h>
#include "SH1106Wire.h"
SH1106Wire display(0x3c, SDA, SCL); // ADDRESS, SDA, SCL
// By default SH1106Wire set I2C frequency to 700000, you can use set either another frequency or skip setting the frequency by providing -1 value
// SH1106Wire(0x3c, SDA, SCL, GEOMETRY_128_64, I2C_ONE, 400000); //set I2C frequency to 400kHz
// SH1106Wire(0x3c, SDA, SCL, GEOMETRY_128_64, I2C_ONE, -1); //skip setting the I2C bus frequency
```
### I2C with brzo_i2c
```C++
#include <brzo_i2c.h>
#include "SSD1306Brzo.h"
SSD1306Brzo display(0x3c, SDA, SCL); // ADDRESS, SDA, SCL
```
or for the SH1106:
```C++
#include <brzo_i2c.h>
#include "SH1106Brzo.h"
SH1106Brzo display(0x3c, SDA, SCL); // ADDRESS, SDA, SCL
```
### SPI
```C++
#include <SPI.h>
#include "SSD1306Spi.h"
SSD1306Spi display(D0, D2, D8); // RES, DC, CS
```
or for the SH1106:
```C++
#include <SPI.h>
#include "SH1106Spi.h"
SH1106Spi display(D0, D2); // RES, DC
```
## API
### Display Control
```C++
// Initialize the display
void init();
// Free the memory used by the display
void end();
// Cycle through the initialization
void resetDisplay(void);
// Connect again to the display through I2C
void reconnect(void);
// Turn the display on
void displayOn(void);
// Turn the display offs
void displayOff(void);
// Clear the local pixel buffer
void clear(void);
// Write the buffer to the display memory
void display(void);
// Inverted display mode
void invertDisplay(void);
// Normal display mode
void normalDisplay(void);
// Set display contrast
// really low brightness & contrast: contrast = 10, precharge = 5, comdetect = 0
// normal brightness & contrast: contrast = 100
void setContrast(uint8_t contrast, uint8_t precharge = 241, uint8_t comdetect = 64);
// Convenience method to access
void setBrightness(uint8_t);
// Turn the display upside down
void flipScreenVertically();
// Draw the screen mirrored
void mirrorScreen();
```
## Pixel drawing
```C++
/* Drawing functions */
// Sets the color of all pixel operations
// color : BLACK, WHITE, INVERSE
void setColor(OLEDDISPLAY_COLOR color);
// Draw a pixel at given position
void setPixel(int16_t x, int16_t y);
// Draw a line from position 0 to position 1
void drawLine(int16_t x0, int16_t y0, int16_t x1, int16_t y1);
// Draw the border of a rectangle at the given location
void drawRect(int16_t x, int16_t y, int16_t width, int16_t height);
// Fill the rectangle
void fillRect(int16_t x, int16_t y, int16_t width, int16_t height);
// Draw the border of a circle
void drawCircle(int16_t x, int16_t y, int16_t radius);
// Fill circle
void fillCircle(int16_t x, int16_t y, int16_t radius);
// Draw a line horizontally
void drawHorizontalLine(int16_t x, int16_t y, int16_t length);
// Draw a lin vertically
void drawVerticalLine(int16_t x, int16_t y, int16_t length);
// Draws a rounded progress bar with the outer dimensions given by width and height. Progress is
// a unsigned byte value between 0 and 100
void drawProgressBar(uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint8_t progress);
// Draw a bitmap in the internal image format
void drawFastImage(int16_t x, int16_t y, int16_t width, int16_t height, const uint8_t *image);
// Draw a XBM
void drawXbm(int16_t x, int16_t y, int16_t width, int16_t height, const char* xbm);
```
## Text operations
``` C++
void drawString(int16_t x, int16_t y, String text);
// Draws a String with a maximum width at the given location.
// If the given String is wider than the specified width
// The text will be wrapped to the next line at a space or dash
void drawStringMaxWidth(int16_t x, int16_t y, int16_t maxLineWidth, String text);
// Returns the width of the const char* with the current
// font settings
uint16_t getStringWidth(const char* text, uint16_t length);
// Convencience method for the const char version
uint16_t getStringWidth(String text);
// Specifies relative to which anchor point
// the text is rendered. Available constants:
// TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER, TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER_BOTH
void setTextAlignment(OLEDDISPLAY_TEXT_ALIGNMENT textAlignment);
// Sets the current font. Available default fonts
// ArialMT_Plain_10, ArialMT_Plain_16, ArialMT_Plain_24
// Or create one with the font tool at http://oleddisplay.squix.ch
void setFont(const uint8_t* fontData);
```
## Ui Library (OLEDDisplayUi)
The Ui Library is used to provide a basic set of Ui elements called, `Frames` and `Overlays`. A `Frame` is used to provide
information the default behaviour is to display a `Frame` for a defined time and than move to the next. The library also provides an `Indicator` that will be updated accordingly. An `Overlay` on the other hand is a pieces of information (e.g. a clock) that is displayed always at the same position.
```C++
/**
* Initialise the display
*/
void init();
/**
* Configure the internal used target FPS
*/
void setTargetFPS(uint8_t fps);
/**
* Enable automatic transition to next frame after the some time can be configured with
* `setTimePerFrame` and `setTimePerTransition`.
*/
void enableAutoTransition();
/**
* Disable automatic transition to next frame.
*/
void disableAutoTransition();
/**
* Set the direction if the automatic transitioning
*/
void setAutoTransitionForwards();
void setAutoTransitionBackwards();
/**
* Set the approx. time a frame is displayed
*/
void setTimePerFrame(uint16_t time);
/**
* Set the approx. time a transition will take
*/
void setTimePerTransition(uint16_t time);
/**
* Draw the indicator.
* This is the default state for all frames if
* the indicator was hidden on the previous frame
* it will be slided in.
*/
void enableIndicator();
/**
* Don't draw the indicator.
* This will slide out the indicator
* when transitioning to the next frame.
*/
void disableIndicator();
/**
* Enable drawing of all indicators.
*/
void enableAllIndicators();
/**
* Disable drawing of all indicators.
*/
void disableAllIndicators();
/**
* Set the position of the indicator bar.
*/
void setIndicatorPosition(IndicatorPosition pos);
/**
* Set the direction of the indicator bar. Defining the order of frames ASCENDING / DESCENDING
*/
void setIndicatorDirection(IndicatorDirection dir);
/**
* Set the symbol to indicate an active frame in the indicator bar.
*/
void setActiveSymbol(const char* symbol);
/**
* Set the symbol to indicate an inactive frame in the indicator bar.
*/
void setInactiveSymbol(const char* symbol);
/**
* Configure what animation is used to transition from one frame to another
*/
void setFrameAnimation(AnimationDirection dir);
/**
* Add frame drawing functions
*/
void setFrames(FrameCallback* frameFunctions, uint8_t frameCount);
/**
* Add overlays drawing functions that are draw independent of the Frames
*/
void setOverlays(OverlayCallback* overlayFunctions, uint8_t overlayCount);
/**
* Set the function that will draw each step
* in the loading animation
*/
void setLoadingDrawFunction(LoadingDrawFunction loadingDrawFunction);
/**
* Run the loading process
*/
void runLoadingProcess(LoadingStage* stages, uint8_t stagesCount);
// Manuell Controll
void nextFrame();
void previousFrame();
/**
* Switch without transition to frame `frame`.
*/
void switchToFrame(uint8_t frame);
/**
* Transition to frame `frame`, when the `frame` number is bigger than the current
* frame the forward animation will be used, otherwise the backwards animation is used.
*/
void transitionToFrame(uint8_t frame);
// State Info
OLEDDisplayUiState* getUiState();
// This needs to be called in the main loop
// the returned value is the remaining time (in ms)
// you have to draw after drawing to keep the frame budget.
int8_t update();
```
## Example: SSD1306Demo
### Frame 1
![DemoFrame1](https://github.com/squix78/esp8266-oled-ssd1306/raw/master/resources/DemoFrame1.jpg)
This frame shows three things:
* How to draw an xbm image
* How to draw a static text which is not moved by the frame transition
* The active/inactive frame indicators
### Frame 2
![DemoFrame2](https://github.com/squix78/esp8266-oled-ssd1306/raw/master/resources/DemoFrame2.jpg)
Currently there are one fontface with three sizes included in the library: Arial 10, 16 and 24. Once the converter is published you will be able to convert any ttf font into the used format.
### Frame 3
![DemoFrame3](https://github.com/squix78/esp8266-oled-ssd1306/raw/master/resources/DemoFrame3.jpg)
This frame demonstrates the text alignment. The coordinates in the frame show relative to which position the texts have been rendered.
### Frame 4
![DemoFrame4](https://github.com/squix78/esp8266-oled-ssd1306/raw/master/resources/DemoFrame4.jpg)
This shows how to use define a maximum width after which the driver automatically wraps a word to the next line. This comes in very handy if you have longer texts to display.
### SPI version
![SPIVersion](https://github.com/neptune2/esp8266-oled-ssd1306/raw/master/resources/SPI_version.jpg)
This shows the code working on the SPI version of the display. See demo code for ESP8266 pins used.
## Selection of projects using this library
* [QRCode ESP8266](https://github.com/anunpanya/ESP8266_QRcode) (by @anunpanya)
* [Scan I2C](https://github.com/hallard/Scan-I2C-WiFi) (by @hallard)
* [ThingPulse Weather Station](https://github.com/ThingPulse/esp8266-weather-station)
* [Meshtastic](https://www.meshtastic.org/) - an open source GPS communicator mesh radio
* Yours?

View File

@@ -0,0 +1,20 @@
# GEOMETRY_64_48
The 64x48 geometry setting are working with the `Wire.h` and `brzo_i2c` libraries.
I've tested it successfully with a WEMOS D1 mini Lite and a WEMOS OLED shield
Initialization code:
- Wire
```
#include <Wire.h>
#include <SSD1306Wire.h>
SSD1306Wire display(0x3c, D2, D1, GEOMETRY_64_48 ); // WEMOS OLED shield
```
- BRZO i2c
```
#include <SSD1306Brzo.h>
SSD1306Brzo display(0x3c, D2, D1, GEOMETRY_64_48 ); // WEMOS OLED Shield
```

View File

@@ -0,0 +1,125 @@
# Upgrade from 2.0 to 3.0
While developing version 3.0 we made some breaking changes to the public
API of this library. This document will help you update your code to work with
version 3.0
## Font Definitions
To get better performance and a smaller font definition format, we change the memory
layout of the font definition format. If you are using custom fonts not included in
this library we updated the font generator [here](http://oleddisplay.squix.ch/#/home).
Please update your fonts to be working with 3.0 by selecting the respective version in the dropdown.
## Architectural Changes
To become a more versatile library for the SSD1306 chipset we abstracted the
hardware connection into subclasses of the base display class now called `OLEDDisplay`.
This library is currently shipping with three implementations:
* `SSD1306Wire` implementing the I2C protocol using the Wire Library.
* `SSD1306Brzo` implementing the I2C protocol using the faster [`brzo_i2c`](https://github.com/pasko-zh/brzo_i2c) library.
* `SSD1306Spi` implementing the SPI protocol.
To keep backwards compatiblity with the old API `SSD1306` is an alias of `SSD1306Wire`.
If you are not using the UI components you don't have to change anything to keep your code working.
## Name Changes
[Naming things is hard](http://martinfowler.com/bliki/TwoHardThings.html), to better reflect our intention with this library
we changed the name of the base class to `OLEDDisplay` and the UI library accordingly to `OLEDDisplayUi`.
As a consequence the type definitions of all frame and overlay related functions changed.
This means that you have to update all your frame drawing callbacks from:
```c
bool frame1(SSD1306 *display, SSD1306UiState* state, int x, int y);
```
too
```c
void frame1(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y);
```
And your overlay drawing functions from:
```c
bool overlay1(SSD1306 *display, SSD1306UiState* state);
```
too
```c
void overlay1(OLEDDisplay *display, OLEDDisplayUiState* state);
```
## New Features
### Loading Animation
While using this library ourself we noticed a pattern emerging. We want to drawing
a loading progress while connecting to WiFi and updating weather data etc.
The simplest thing was to add the function `drawProgressBar(x, y, width, height, progress)`
,where `progress` is between `0` and `100`, right to the `OLEDDisplay` class.
But we didn't stop there. We added a new feature to the `OLEDDisplayUi` called `LoadingStages`.
You can define your loading process like this:
```c++
LoadingStage loadingStages[] = {
{
.process = "Connect to WiFi",
.callback = []() {
// Connect to WiFi
}
},
{
.process = "Get time from NTP",
.callback = []() {
// Get current time via NTP
}
}
// more steps
};
int LOADING_STAGES_COUNT = sizeof(loadingStages) / sizeof(LoadingStage);
```
After defining your array of `LoadingStages` you can then run the loading process by using
`ui.runLoadingProcess(loadingStages, LOADING_STAGES_COUNT)`. This will give you a
nice little loading animation you can see in the beginning of [this](https://vimeo.com/168362918)
video.
To further customize this you are free to define your own `LoadingDrawFunction` like this:
```c
void myLoadingDraw(OLEDDisplay *display, LoadingStage* stage, uint8_t progress) {
display->setTextAlignment(TEXT_ALIGN_CENTER);
display->setFont(ArialMT_Plain_10);
// stage->process contains the text of the current progress e.q. "Connect to WiFi"
display->drawString(64, 18, stage->process);
// you could just print the current process without the progress bar
display->drawString(64, 28, progress);
}
```
After defining a function like that, you can pass it to the Ui library by use
`ui.setLoadingDrawFunction(myLoadingDraw)`.
### Text Logging
It is always useful to display some text on the display without worrying to much
where it goes and managing it. In 3.0 we made the `OLEDDisplay` class implement
[`Print`](https://github.com/arduino/Arduino/blob/master/hardware/arduino/avr/cores/arduino/Print.h)
so you can use it like you would use `Serial`. We calls this feature `LogBuffer`
and the only thing you have to do is to define how many lines you want to display
and how many characters there are on average on each. This is done by calling
`setLogBuffer(lines, chars);`. If there is not enough memory the function will
return false.
After that you can draw the `LogBuffer` anywhere you want by calling `drawLogBuffer(x, y)`.
(Note: You have to call `display()` to update the screen)
We made a [video](https://www.youtube.com/watch?v=8Fiss77A3TE) showing this feature in action.

View File

@@ -0,0 +1,27 @@
# Upgrade from 3.x to 4.0
There are changes that breaks compatibility with older versions.
1. You'll have to change data type for all your binary resources such as images and fonts from
```c
const char MySymbol[] PROGMEM = {
```
to
```c
const uint8_t MySymbol[] PROGMEM = {
```
1. Arguments of `setContrast` from `char` to `uint8_t`
```c++
void OLEDDisplay::setContrast(char contrast, char precharge, char comdetect);
```
to
```c++
void OLEDDisplay::setContrast(uint8_t contrast, uint8_t precharge, uint8_t comdetect);
```

View File

@@ -0,0 +1,3 @@
COMPONENT_ADD_INCLUDEDIRS := src
COMPONENT_SRCDIRS := src
CXXFLAGS += -Wno-ignored-qualifiers

View File

@@ -0,0 +1,214 @@
/**
* The MIT License (MIT)
*
* Copyright (c) 2018 by ThingPulse, Daniel Eichhorn
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* ThingPulse invests considerable time and money to develop these open source libraries.
* Please support us by buying our products (and not the clones) from
* https://thingpulse.com
*
*/
#include <TimeLib.h>
// Include the correct display library
// For a connection via I2C using Wire include
#include <Wire.h> // Only needed for Arduino 1.6.5 and earlier
#include "SSD1306Wire.h" // legacy include: `#include "SSD1306.h"`
// or #include "SH1106Wire.h", legacy include: `#include "SH1106.h"`
// For a connection via I2C using brzo_i2c (must be installed) include
// #include <brzo_i2c.h> // Only needed for Arduino 1.6.5 and earlier
// #include "SSD1306Brzo.h"
// #include "SH1106Brzo.h"
// For a connection via SPI include
// #include <SPI.h> // Only needed for Arduino 1.6.5 and earlier
// #include "SSD1306Spi.h"
// #include "SH1106SPi.h"
// Include the UI lib
#include "OLEDDisplayUi.h"
// Include custom images
#include "images.h"
// Use the corresponding display class:
// Initialize the OLED display using SPI
// D5 -> CLK
// D7 -> MOSI (DOUT)
// D0 -> RES
// D2 -> DC
// D8 -> CS
// SSD1306Spi display(D0, D2, D8);
// or
// SH1106Spi display(D0, D2);
// Initialize the OLED display using brzo_i2c
// D3 -> SDA
// D5 -> SCL
// SSD1306Brzo display(0x3c, D3, D5);
// or
// SH1106Brzo display(0x3c, D3, D5);
// Initialize the OLED display using Wire library
SSD1306Wire display(0x3c, D3, D5);
// SH1106 display(0x3c, D3, D5);
OLEDDisplayUi ui ( &display );
int screenW = 128;
int screenH = 64;
int clockCenterX = screenW/2;
int clockCenterY = ((screenH-16)/2)+16; // top yellow part is 16 px height
int clockRadius = 23;
// utility function for digital clock display: prints leading 0
String twoDigits(int digits){
if(digits < 10) {
String i = '0'+String(digits);
return i;
}
else {
return String(digits);
}
}
void clockOverlay(OLEDDisplay *display, OLEDDisplayUiState* state) {
}
void analogClockFrame(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y) {
// ui.disableIndicator();
// Draw the clock face
// display->drawCircle(clockCenterX + x, clockCenterY + y, clockRadius);
display->drawCircle(clockCenterX + x, clockCenterY + y, 2);
//
//hour ticks
for( int z=0; z < 360;z= z + 30 ){
//Begin at 0° and stop at 360°
float angle = z ;
angle = ( angle / 57.29577951 ) ; //Convert degrees to radians
int x2 = ( clockCenterX + ( sin(angle) * clockRadius ) );
int y2 = ( clockCenterY - ( cos(angle) * clockRadius ) );
int x3 = ( clockCenterX + ( sin(angle) * ( clockRadius - ( clockRadius / 8 ) ) ) );
int y3 = ( clockCenterY - ( cos(angle) * ( clockRadius - ( clockRadius / 8 ) ) ) );
display->drawLine( x2 + x , y2 + y , x3 + x , y3 + y);
}
// display second hand
float angle = second() * 6 ;
angle = ( angle / 57.29577951 ) ; //Convert degrees to radians
int x3 = ( clockCenterX + ( sin(angle) * ( clockRadius - ( clockRadius / 5 ) ) ) );
int y3 = ( clockCenterY - ( cos(angle) * ( clockRadius - ( clockRadius / 5 ) ) ) );
display->drawLine( clockCenterX + x , clockCenterY + y , x3 + x , y3 + y);
//
// display minute hand
angle = minute() * 6 ;
angle = ( angle / 57.29577951 ) ; //Convert degrees to radians
x3 = ( clockCenterX + ( sin(angle) * ( clockRadius - ( clockRadius / 4 ) ) ) );
y3 = ( clockCenterY - ( cos(angle) * ( clockRadius - ( clockRadius / 4 ) ) ) );
display->drawLine( clockCenterX + x , clockCenterY + y , x3 + x , y3 + y);
//
// display hour hand
angle = hour() * 30 + int( ( minute() / 12 ) * 6 ) ;
angle = ( angle / 57.29577951 ) ; //Convert degrees to radians
x3 = ( clockCenterX + ( sin(angle) * ( clockRadius - ( clockRadius / 2 ) ) ) );
y3 = ( clockCenterY - ( cos(angle) * ( clockRadius - ( clockRadius / 2 ) ) ) );
display->drawLine( clockCenterX + x , clockCenterY + y , x3 + x , y3 + y);
}
void digitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y) {
String timenow = String(hour())+":"+twoDigits(minute())+":"+twoDigits(second());
display->setTextAlignment(TEXT_ALIGN_CENTER);
display->setFont(ArialMT_Plain_24);
display->drawString(clockCenterX + x , clockCenterY + y, timenow );
}
// This array keeps function pointers to all frames
// frames are the single views that slide in
FrameCallback frames[] = { analogClockFrame, digitalClockFrame };
// how many frames are there?
int frameCount = 2;
// Overlays are statically drawn on top of a frame eg. a clock
OverlayCallback overlays[] = { clockOverlay };
int overlaysCount = 1;
void setup() {
Serial.begin(9600);
Serial.println();
// The ESP is capable of rendering 60fps in 80Mhz mode
// but that won't give you much time for anything else
// run it in 160Mhz mode or just set it to 30 fps
ui.setTargetFPS(60);
// Customize the active and inactive symbol
ui.setActiveSymbol(activeSymbol);
ui.setInactiveSymbol(inactiveSymbol);
// You can change this to
// TOP, LEFT, BOTTOM, RIGHT
ui.setIndicatorPosition(TOP);
// Defines where the first frame is located in the bar.
ui.setIndicatorDirection(LEFT_RIGHT);
// You can change the transition that is used
// SLIDE_LEFT, SLIDE_RIGHT, SLIDE_UP, SLIDE_DOWN
ui.setFrameAnimation(SLIDE_LEFT);
// Add frames
ui.setFrames(frames, frameCount);
// Add overlays
ui.setOverlays(overlays, overlaysCount);
// Initialising the UI will init the display too.
ui.init();
display.flipScreenVertically();
unsigned long secsSinceStart = millis();
// Unix time starts on Jan 1 1970. In seconds, that's 2208988800:
const unsigned long seventyYears = 2208988800UL;
// subtract seventy years:
unsigned long epoch = secsSinceStart - seventyYears * SECS_PER_HOUR;
setTime(epoch);
}
void loop() {
int remainingTimeBudget = ui.update();
if (remainingTimeBudget > 0) {
// You can do some work here
// Don't do stuff if you are below your
// time budget.
delay(remainingTimeBudget);
}
}

View File

@@ -0,0 +1,21 @@
const unsigned char activeSymbol[] PROGMEM = {
B00000000,
B00000000,
B00011000,
B00100100,
B01000010,
B01000010,
B00100100,
B00011000
};
const unsigned char inactiveSymbol[] PROGMEM = {
B00000000,
B00000000,
B00000000,
B00000000,
B00011000,
B00011000,
B00000000,
B00000000
};

View File

@@ -0,0 +1,233 @@
/**
* The MIT License (MIT)
*
* Copyright (c) 2018 by ThingPulse, Daniel Eichhorn
* Copyright (c) 2018 by Fabrice Weinberg
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* ThingPulse invests considerable time and money to develop these open source libraries.
* Please support us by buying our products (and not the clones) from
* https://thingpulse.com
*
*/
// Include the correct display library
// For a connection via I2C using Wire include
#include <Wire.h> // Only needed for Arduino 1.6.5 and earlier
#include "SSD1306Wire.h" // legacy include: `#include "SSD1306.h"`
// or #include "SH1106Wire.h", legacy include: `#include "SH1106.h"`
// For a connection via I2C using brzo_i2c (must be installed) include
// #include <brzo_i2c.h> // Only needed for Arduino 1.6.5 and earlier
// #include "SSD1306Brzo.h"
// #include "SH1106Brzo.h"
// For a connection via SPI include
// #include <SPI.h> // Only needed for Arduino 1.6.5 and earlier
// #include "SSD1306Spi.h"
// #include "SH1106SPi.h"
// Use the corresponding display class:
// Initialize the OLED display using SPI
// D5 -> CLK
// D7 -> MOSI (DOUT)
// D0 -> RES
// D2 -> DC
// D8 -> CS
// SSD1306Spi display(D0, D2, D8);
// or
// SH1106Spi display(D0, D2);
// Initialize the OLED display using brzo_i2c
// D3 -> SDA
// D5 -> SCL
// SSD1306Brzo display(0x3c, D3, D5);
// or
// SH1106Brzo display(0x3c, D3, D5);
// Initialize the OLED display using Wire library
SSD1306Wire display(0x3c, D3, D5);
// SH1106 display(0x3c, D3, D5);
// Adapted from Adafruit_SSD1306
void drawLines() {
for (int16_t i=0; i<display.getWidth(); i+=4) {
display.drawLine(0, 0, i, display.getHeight()-1);
display.display();
delay(10);
}
for (int16_t i=0; i<display.getHeight(); i+=4) {
display.drawLine(0, 0, display.getWidth()-1, i);
display.display();
delay(10);
}
delay(250);
display.clear();
for (int16_t i=0; i<display.getWidth(); i+=4) {
display.drawLine(0, display.getHeight()-1, i, 0);
display.display();
delay(10);
}
for (int16_t i=display.getHeight()-1; i>=0; i-=4) {
display.drawLine(0, display.getHeight()-1, display.getWidth()-1, i);
display.display();
delay(10);
}
delay(250);
display.clear();
for (int16_t i=display.getWidth()-1; i>=0; i-=4) {
display.drawLine(display.getWidth()-1, display.getHeight()-1, i, 0);
display.display();
delay(10);
}
for (int16_t i=display.getHeight()-1; i>=0; i-=4) {
display.drawLine(display.getWidth()-1, display.getHeight()-1, 0, i);
display.display();
delay(10);
}
delay(250);
display.clear();
for (int16_t i=0; i<display.getHeight(); i+=4) {
display.drawLine(display.getWidth()-1, 0, 0, i);
display.display();
delay(10);
}
for (int16_t i=0; i<display.getWidth(); i+=4) {
display.drawLine(display.getWidth()-1, 0, i, display.getHeight()-1);
display.display();
delay(10);
}
delay(250);
}
// Adapted from Adafruit_SSD1306
void drawRect(void) {
for (int16_t i=0; i<display.getHeight()/2; i+=2) {
display.drawRect(i, i, display.getWidth()-2*i, display.getHeight()-2*i);
display.display();
delay(10);
}
}
// Adapted from Adafruit_SSD1306
void fillRect(void) {
uint8_t color = 1;
for (int16_t i=0; i<display.getHeight()/2; i+=3) {
display.setColor((color % 2 == 0) ? BLACK : WHITE); // alternate colors
display.fillRect(i, i, display.getWidth() - i*2, display.getHeight() - i*2);
display.display();
delay(10);
color++;
}
// Reset back to WHITE
display.setColor(WHITE);
}
// Adapted from Adafruit_SSD1306
void drawCircle(void) {
for (int16_t i=0; i<display.getHeight(); i+=2) {
display.drawCircle(display.getWidth()/2, display.getHeight()/2, i);
display.display();
delay(10);
}
delay(1000);
display.clear();
// This will draw the part of the circel in quadrant 1
// Quadrants are numberd like this:
// 0010 | 0001
// ------|-----
// 0100 | 1000
//
display.drawCircleQuads(display.getWidth()/2, display.getHeight()/2, display.getHeight()/4, 0b00000001);
display.display();
delay(200);
display.drawCircleQuads(display.getWidth()/2, display.getHeight()/2, display.getHeight()/4, 0b00000011);
display.display();
delay(200);
display.drawCircleQuads(display.getWidth()/2, display.getHeight()/2, display.getHeight()/4, 0b00000111);
display.display();
delay(200);
display.drawCircleQuads(display.getWidth()/2, display.getHeight()/2, display.getHeight()/4, 0b00001111);
display.display();
}
void printBuffer(void) {
// Initialize the log buffer
// allocate memory to store 8 lines of text and 30 chars per line.
display.setLogBuffer(5, 30);
// Some test data
const char* test[] = {
"Hello",
"World" ,
"----",
"Show off",
"how",
"the log buffer",
"is",
"working.",
"Even",
"scrolling is",
"working"
};
for (uint8_t i = 0; i < 11; i++) {
display.clear();
// Print to the screen
display.println(test[i]);
// Draw it to the internal screen buffer
display.drawLogBuffer(0, 0);
// Display it on the screen
display.display();
delay(500);
}
}
void setup() {
display.init();
// display.flipScreenVertically();
display.setContrast(255);
drawLines();
delay(1000);
display.clear();
drawRect();
delay(1000);
display.clear();
fillRect();
delay(1000);
display.clear();
drawCircle();
delay(1000);
display.clear();
printBuffer();
delay(1000);
display.clear();
}
void loop() { }

View File

@@ -0,0 +1,123 @@
/**
* The MIT License (MIT)
*
* Copyright (c) 2018 by ThingPulse, Daniel Eichhorn
* Copyright (c) 2018 by Fabrice Weinberg
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* ThingPulse invests considerable time and money to develop these open source libraries.
* Please support us by buying our products (and not the clones) from
* https://thingpulse.com
*
*/
// WiFi includes
#include <ESP8266WiFi.h>
// OTA Includes
#include <ESP8266mDNS.h>
#include <ArduinoOTA.h>
const char *ssid = "[Your SSID]";
const char *password = "[Your Password]";
// Include the correct display library
// For a connection via I2C using Wire include
#include <Wire.h> // Only needed for Arduino 1.6.5 and earlier
#include "SSD1306Wire.h" // legacy include: `#include "SSD1306.h"`
// or #include "SH1106Wire.h", legacy include: `#include "SH1106.h"`
// For a connection via I2C using brzo_i2c (must be installed) include
// #include <brzo_i2c.h> // Only needed for Arduino 1.6.5 and earlier
// #include "SSD1306Brzo.h"
// #include "SH1106Brzo.h"
// For a connection via SPI include
// #include <SPI.h> // Only needed for Arduino 1.6.5 and earlier
// #include "SSD1306Spi.h"
// #include "SH1106SPi.h"
// Use the corresponding display class:
// Initialize the OLED display using SPI
// D5 -> CLK
// D7 -> MOSI (DOUT)
// D0 -> RES
// D2 -> DC
// D8 -> CS
// SSD1306Spi display(D0, D2, D8);
// or
// SH1106Spi display(D0, D2);
// Initialize the OLED display using brzo_i2c
// D3 -> SDA
// D5 -> SCL
// SSD1306Brzo display(0x3c, D3, D5);
// or
// SH1106Brzo display(0x3c, D3, D5);
// Initialize the OLED display using Wire library
SSD1306Wire display(0x3c, D3, D5);
// SH1106 display(0x3c, D3, D5);
void setup() {
WiFi.begin ( ssid, password );
// Wait for connection
while ( WiFi.status() != WL_CONNECTED ) {
delay ( 10 );
}
display.init();
display.flipScreenVertically();
display.setContrast(255);
ArduinoOTA.begin();
ArduinoOTA.onStart([]() {
display.clear();
display.setFont(ArialMT_Plain_10);
display.setTextAlignment(TEXT_ALIGN_CENTER_BOTH);
display.drawString(display.getWidth()/2, display.getHeight()/2 - 10, "OTA Update");
display.display();
});
ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
display.drawProgressBar(4, 32, 120, 8, progress / (total / 100) );
display.display();
});
ArduinoOTA.onEnd([]() {
display.clear();
display.setFont(ArialMT_Plain_10);
display.setTextAlignment(TEXT_ALIGN_CENTER_BOTH);
display.drawString(display.getWidth()/2, display.getHeight()/2, "Restart");
display.display();
});
// Align text vertical/horizontal center
display.setTextAlignment(TEXT_ALIGN_CENTER_BOTH);
display.setFont(ArialMT_Plain_10);
display.drawString(display.getWidth()/2, display.getHeight()/2, "Ready for OTA:\n" + WiFi.localIP().toString());
display.display();
}
void loop() {
ArduinoOTA.handle();
}

View File

@@ -0,0 +1,197 @@
/**
* The MIT License (MIT)
*
* Copyright (c) 2018 by ThingPulse, Daniel Eichhorn
* Copyright (c) 2018 by Fabrice Weinberg
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* ThingPulse invests considerable time and money to develop these open source libraries.
* Please support us by buying our products (and not the clones) from
* https://thingpulse.com
*
*/
// Include the correct display library
// For a connection via I2C using the Arduino Wire include:
#include <Wire.h> // Only needed for Arduino 1.6.5 and earlier
#include "SSD1306Wire.h" // legacy: #include "SSD1306.h"
// OR #include "SH1106Wire.h" // legacy: #include "SH1106.h"
// For a connection via I2C using brzo_i2c (must be installed) include:
// #include <brzo_i2c.h> // Only needed for Arduino 1.6.5 and earlier
// #include "SSD1306Brzo.h"
// OR #include "SH1106Brzo.h"
// For a connection via SPI include:
// #include <SPI.h> // Only needed for Arduino 1.6.5 and earlier
// #include "SSD1306Spi.h"
// OR #include "SH1106SPi.h"
// Optionally include custom images
#include "images.h"
// Initialize the OLED display using Arduino Wire:
SSD1306Wire display(0x3c, SDA, SCL); // ADDRESS, SDA, SCL - SDA and SCL usually populate automatically based on your board's pins_arduino.h
// SSD1306Wire display(0x3c, D3, D5); // ADDRESS, SDA, SCL - If not, they can be specified manually.
// SSD1306Wire display(0x3c, SDA, SCL, GEOMETRY_128_32); // ADDRESS, SDA, SCL, OLEDDISPLAY_GEOMETRY - Extra param required for 128x32 displays.
// SH1106 display(0x3c, SDA, SCL); // ADDRESS, SDA, SCL
// Initialize the OLED display using brzo_i2c:
// SSD1306Brzo display(0x3c, D3, D5); // ADDRESS, SDA, SCL
// or
// SH1106Brzo display(0x3c, D3, D5); // ADDRESS, SDA, SCL
// Initialize the OLED display using SPI:
// D5 -> CLK
// D7 -> MOSI (DOUT)
// D0 -> RES
// D2 -> DC
// D8 -> CS
// SSD1306Spi display(D0, D2, D8); // RES, DC, CS
// or
// SH1106Spi display(D0, D2); // RES, DC
#define DEMO_DURATION 3000
typedef void (*Demo)(void);
int demoMode = 0;
int counter = 1;
void setup() {
Serial.begin(115200);
Serial.println();
Serial.println();
// Initialising the UI will init the display too.
display.init();
display.flipScreenVertically();
display.setFont(ArialMT_Plain_10);
}
void drawFontFaceDemo() {
// Font Demo1
// create more fonts at http://oleddisplay.squix.ch/
display.setTextAlignment(TEXT_ALIGN_LEFT);
display.setFont(ArialMT_Plain_10);
display.drawString(0, 0, "Hello world");
display.setFont(ArialMT_Plain_16);
display.drawString(0, 10, "Hello world");
display.setFont(ArialMT_Plain_24);
display.drawString(0, 26, "Hello world");
}
void drawTextFlowDemo() {
display.setFont(ArialMT_Plain_10);
display.setTextAlignment(TEXT_ALIGN_LEFT);
display.drawStringMaxWidth(0, 0, 128,
"Lorem ipsum\n dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore." );
}
void drawTextAlignmentDemo() {
// Text alignment demo
display.setFont(ArialMT_Plain_10);
// The coordinates define the left starting point of the text
display.setTextAlignment(TEXT_ALIGN_LEFT);
display.drawString(0, 10, "Left aligned (0,10)");
// The coordinates define the center of the text
display.setTextAlignment(TEXT_ALIGN_CENTER);
display.drawString(64, 22, "Center aligned (64,22)");
// The coordinates define the right end of the text
display.setTextAlignment(TEXT_ALIGN_RIGHT);
display.drawString(128, 33, "Right aligned (128,33)");
}
void drawRectDemo() {
// Draw a pixel at given position
for (int i = 0; i < 10; i++) {
display.setPixel(i, i);
display.setPixel(10 - i, i);
}
display.drawRect(12, 12, 20, 20);
// Fill the rectangle
display.fillRect(14, 14, 17, 17);
// Draw a line horizontally
display.drawHorizontalLine(0, 40, 20);
// Draw a line horizontally
display.drawVerticalLine(40, 0, 20);
}
void drawCircleDemo() {
for (int i=1; i < 8; i++) {
display.setColor(WHITE);
display.drawCircle(32, 32, i*3);
if (i % 2 == 0) {
display.setColor(BLACK);
}
display.fillCircle(96, 32, 32 - i* 3);
}
}
void drawProgressBarDemo() {
int progress = (counter / 5) % 100;
// draw the progress bar
display.drawProgressBar(0, 32, 120, 10, progress);
// draw the percentage as String
display.setTextAlignment(TEXT_ALIGN_CENTER);
display.drawString(64, 15, String(progress) + "%");
}
void drawImageDemo() {
// see http://blog.squix.org/2015/05/esp8266-nodemcu-how-to-create-xbm.html
// on how to create xbm files
display.drawXbm(34, 14, WiFi_Logo_width, WiFi_Logo_height, WiFi_Logo_bits);
}
Demo demos[] = {drawFontFaceDemo, drawTextFlowDemo, drawTextAlignmentDemo, drawRectDemo, drawCircleDemo, drawProgressBarDemo, drawImageDemo};
int demoLength = (sizeof(demos) / sizeof(Demo));
long timeSinceLastModeSwitch = 0;
void loop() {
// clear the display
display.clear();
// draw the current demo method
demos[demoMode]();
display.setTextAlignment(TEXT_ALIGN_RIGHT);
display.drawString(10, 128, String(millis()));
// write the buffer to the display
display.display();
if (millis() - timeSinceLastModeSwitch > DEMO_DURATION) {
demoMode = (demoMode + 1) % demoLength;
timeSinceLastModeSwitch = millis();
}
counter++;
delay(10);
}

View File

@@ -0,0 +1,28 @@
#define WiFi_Logo_width 60
#define WiFi_Logo_height 36
const uint8_t WiFi_Logo_bits[] PROGMEM = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xFF, 0x07, 0x00, 0x00, 0x00,
0x00, 0x00, 0xE0, 0xFF, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0xFF,
0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0xFF, 0xFF, 0x00, 0x00, 0x00,
0x00, 0x00, 0xFE, 0xFF, 0xFF, 0x01, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF,
0xFF, 0x03, 0x00, 0x00, 0x00, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00,
0x00, 0xFF, 0xFF, 0xFF, 0x07, 0xC0, 0x83, 0x01, 0x80, 0xFF, 0xFF, 0xFF,
0x01, 0x00, 0x07, 0x00, 0xC0, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x0C, 0x00,
0xC0, 0xFF, 0xFF, 0x7C, 0x00, 0x60, 0x0C, 0x00, 0xC0, 0x31, 0x46, 0x7C,
0xFC, 0x77, 0x08, 0x00, 0xE0, 0x23, 0xC6, 0x3C, 0xFC, 0x67, 0x18, 0x00,
0xE0, 0x23, 0xE4, 0x3F, 0x1C, 0x00, 0x18, 0x00, 0xE0, 0x23, 0x60, 0x3C,
0x1C, 0x70, 0x18, 0x00, 0xE0, 0x03, 0x60, 0x3C, 0x1C, 0x70, 0x18, 0x00,
0xE0, 0x07, 0x60, 0x3C, 0xFC, 0x73, 0x18, 0x00, 0xE0, 0x87, 0x70, 0x3C,
0xFC, 0x73, 0x18, 0x00, 0xE0, 0x87, 0x70, 0x3C, 0x1C, 0x70, 0x18, 0x00,
0xE0, 0x87, 0x70, 0x3C, 0x1C, 0x70, 0x18, 0x00, 0xE0, 0x8F, 0x71, 0x3C,
0x1C, 0x70, 0x18, 0x00, 0xC0, 0xFF, 0xFF, 0x3F, 0x00, 0x00, 0x08, 0x00,
0xC0, 0xFF, 0xFF, 0x1F, 0x00, 0x00, 0x0C, 0x00, 0x80, 0xFF, 0xFF, 0x1F,
0x00, 0x00, 0x06, 0x00, 0x80, 0xFF, 0xFF, 0x0F, 0x00, 0x00, 0x07, 0x00,
0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0x00, 0x00, 0xF8, 0xFF, 0xFF,
0xFF, 0x7F, 0x00, 0x00, 0x00, 0x00, 0xFE, 0xFF, 0xFF, 0x01, 0x00, 0x00,
0x00, 0x00, 0xFC, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0xFF,
0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x1F, 0x00, 0x00, 0x00,
0x00, 0x00, 0x80, 0xFF, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};

View File

@@ -0,0 +1,75 @@
/**
* The MIT License (MIT)
*
* Copyright (c) 2018 by ThingPulse, Daniel Eichhorn
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* ThingPulse invests considerable time and money to develop these open source libraries.
* Please support us by buying our products (and not the clones) from
* https://thingpulse.com
*
*/
// Include the correct display library
// For a connection via I2C using Wire include
#include <Wire.h> // Only needed for Arduino 1.6.5 and earlier
#include "SSD1306Wire.h" // legacy include: `#include "SSD1306.h"`
#include "images.h"
// Initialize the OLED display using Wire library
SSD1306Wire display(0x3c, D3, D5);
SSD1306Wire display2(0x3c, D1, D2);
void setup() {
Serial.begin(115200);
Serial.println();
Serial.println();
// Initialising the UI will init the display too.
display.init();
display2.init();
// This will make sure that multiple instances of a display driver
// running on different ports will work together transparently
display.setI2cAutoInit(true);
display2.setI2cAutoInit(true);
display.flipScreenVertically();
display.setFont(ArialMT_Plain_10);
display.setTextAlignment(TEXT_ALIGN_LEFT);
display2.flipScreenVertically();
display2.setFont(ArialMT_Plain_10);
display2.setTextAlignment(TEXT_ALIGN_LEFT);
}
void loop() {
display.clear();
display.drawString(0, 0, "Hello world: " + String(millis()));
display.display();
display2.clear();
display2.drawString(0, 0, "Hello world: " + String(millis()));
display2.display();
delay(10);
}

View File

@@ -0,0 +1,28 @@
#define WiFi_Logo_width 60
#define WiFi_Logo_height 36
const uint8_t WiFi_Logo_bits[] PROGMEM = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xFF, 0x07, 0x00, 0x00, 0x00,
0x00, 0x00, 0xE0, 0xFF, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0xFF,
0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0xFF, 0xFF, 0x00, 0x00, 0x00,
0x00, 0x00, 0xFE, 0xFF, 0xFF, 0x01, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF,
0xFF, 0x03, 0x00, 0x00, 0x00, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00,
0x00, 0xFF, 0xFF, 0xFF, 0x07, 0xC0, 0x83, 0x01, 0x80, 0xFF, 0xFF, 0xFF,
0x01, 0x00, 0x07, 0x00, 0xC0, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x0C, 0x00,
0xC0, 0xFF, 0xFF, 0x7C, 0x00, 0x60, 0x0C, 0x00, 0xC0, 0x31, 0x46, 0x7C,
0xFC, 0x77, 0x08, 0x00, 0xE0, 0x23, 0xC6, 0x3C, 0xFC, 0x67, 0x18, 0x00,
0xE0, 0x23, 0xE4, 0x3F, 0x1C, 0x00, 0x18, 0x00, 0xE0, 0x23, 0x60, 0x3C,
0x1C, 0x70, 0x18, 0x00, 0xE0, 0x03, 0x60, 0x3C, 0x1C, 0x70, 0x18, 0x00,
0xE0, 0x07, 0x60, 0x3C, 0xFC, 0x73, 0x18, 0x00, 0xE0, 0x87, 0x70, 0x3C,
0xFC, 0x73, 0x18, 0x00, 0xE0, 0x87, 0x70, 0x3C, 0x1C, 0x70, 0x18, 0x00,
0xE0, 0x87, 0x70, 0x3C, 0x1C, 0x70, 0x18, 0x00, 0xE0, 0x8F, 0x71, 0x3C,
0x1C, 0x70, 0x18, 0x00, 0xC0, 0xFF, 0xFF, 0x3F, 0x00, 0x00, 0x08, 0x00,
0xC0, 0xFF, 0xFF, 0x1F, 0x00, 0x00, 0x0C, 0x00, 0x80, 0xFF, 0xFF, 0x1F,
0x00, 0x00, 0x06, 0x00, 0x80, 0xFF, 0xFF, 0x0F, 0x00, 0x00, 0x07, 0x00,
0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0x00, 0x00, 0xF8, 0xFF, 0xFF,
0xFF, 0x7F, 0x00, 0x00, 0x00, 0x00, 0xFE, 0xFF, 0xFF, 0x01, 0x00, 0x00,
0x00, 0x00, 0xFC, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0xFF,
0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x1F, 0x00, 0x00, 0x00,
0x00, 0x00, 0x80, 0xFF, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};

View File

@@ -0,0 +1,194 @@
/**
* The MIT License (MIT)
*
* Copyright (c) 2018 by ThingPulse, Daniel Eichhorn
* Copyright (c) 2018 by Fabrice Weinberg
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* ThingPulse invests considerable time and money to develop these open source libraries.
* Please support us by buying our products (and not the clones) from
* https://thingpulse.com
*
*/
// Include the correct display library
// For a connection via I2C using Wire include
#include <Wire.h> // Only needed for Arduino 1.6.5 and earlier
#include "SSD1306Wire.h" // legacy include: `#include "SSD1306.h"`
// or #include "SH1106Wire.h", legacy include: `#include "SH1106.h"`
// For a connection via I2C using brzo_i2c (must be installed) include
// #include <brzo_i2c.h> // Only needed for Arduino 1.6.5 and earlier
// #include "SSD1306Brzo.h"
// #include "SH1106Brzo.h"
// For a connection via SPI include
// #include <SPI.h> // Only needed for Arduino 1.6.5 and earlier
// #include "SSD1306Spi.h"
// #include "SH1106SPi.h"
// Include the UI lib
#include "OLEDDisplayUi.h"
// Include custom images
#include "images.h"
// Use the corresponding display class:
// Initialize the OLED display using SPI
// D5 -> CLK
// D7 -> MOSI (DOUT)
// D0 -> RES
// D2 -> DC
// D8 -> CS
// SSD1306Spi display(D0, D2, D8);
// or
// SH1106Spi display(D0, D2);
// Initialize the OLED display using brzo_i2c
// D3 -> SDA
// D5 -> SCL
// SSD1306Brzo display(0x3c, D3, D5);
// or
// SH1106Brzo display(0x3c, D3, D5);
// Initialize the OLED display using Wire library
SSD1306Wire display(0x3c, D3, D5);
// SH1106Wire display(0x3c, D3, D5);
OLEDDisplayUi ui ( &display );
void msOverlay(OLEDDisplay *display, OLEDDisplayUiState* state) {
display->setTextAlignment(TEXT_ALIGN_RIGHT);
display->setFont(ArialMT_Plain_10);
display->drawString(128, 0, String(millis()));
}
void drawFrame1(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y) {
// draw an xbm image.
// Please note that everything that should be transitioned
// needs to be drawn relative to x and y
display->drawXbm(x + 34, y + 14, WiFi_Logo_width, WiFi_Logo_height, WiFi_Logo_bits);
}
void drawFrame2(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y) {
// Demonstrates the 3 included default sizes. The fonts come from SSD1306Fonts.h file
// Besides the default fonts there will be a program to convert TrueType fonts into this format
display->setTextAlignment(TEXT_ALIGN_LEFT);
display->setFont(ArialMT_Plain_10);
display->drawString(0 + x, 10 + y, "Arial 10");
display->setFont(ArialMT_Plain_16);
display->drawString(0 + x, 20 + y, "Arial 16");
display->setFont(ArialMT_Plain_24);
display->drawString(0 + x, 34 + y, "Arial 24");
}
void drawFrame3(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y) {
// Text alignment demo
display->setFont(ArialMT_Plain_10);
// The coordinates define the left starting point of the text
display->setTextAlignment(TEXT_ALIGN_LEFT);
display->drawString(0 + x, 11 + y, "Left aligned (0,10)");
// The coordinates define the center of the text
display->setTextAlignment(TEXT_ALIGN_CENTER);
display->drawString(64 + x, 22 + y, "Center aligned (64,22)");
// The coordinates define the right end of the text
display->setTextAlignment(TEXT_ALIGN_RIGHT);
display->drawString(128 + x, 33 + y, "Right aligned (128,33)");
}
void drawFrame4(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y) {
// Demo for drawStringMaxWidth:
// with the third parameter you can define the width after which words will be wrapped.
// Currently only spaces and "-" are allowed for wrapping
display->setTextAlignment(TEXT_ALIGN_LEFT);
display->setFont(ArialMT_Plain_10);
display->drawStringMaxWidth(0 + x, 10 + y, 128, "Lorem ipsum\n dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore.");
}
void drawFrame5(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y) {
}
// This array keeps function pointers to all frames
// frames are the single views that slide in
FrameCallback frames[] = { drawFrame1, drawFrame2, drawFrame3, drawFrame4, drawFrame5 };
// how many frames are there?
int frameCount = 5;
// Overlays are statically drawn on top of a frame eg. a clock
OverlayCallback overlays[] = { msOverlay };
int overlaysCount = 1;
void setup() {
Serial.begin(115200);
Serial.println();
Serial.println();
// The ESP is capable of rendering 60fps in 80Mhz mode
// but that won't give you much time for anything else
// run it in 160Mhz mode or just set it to 30 fps
ui.setTargetFPS(60);
// Customize the active and inactive symbol
ui.setActiveSymbol(activeSymbol);
ui.setInactiveSymbol(inactiveSymbol);
// You can change this to
// TOP, LEFT, BOTTOM, RIGHT
ui.setIndicatorPosition(BOTTOM);
// Defines where the first frame is located in the bar.
ui.setIndicatorDirection(LEFT_RIGHT);
// You can change the transition that is used
// SLIDE_LEFT, SLIDE_RIGHT, SLIDE_UP, SLIDE_DOWN
ui.setFrameAnimation(SLIDE_LEFT);
// Add frames
ui.setFrames(frames, frameCount);
// Add overlays
ui.setOverlays(overlays, overlaysCount);
// Initialising the UI will init the display too.
ui.init();
display.flipScreenVertically();
}
void loop() {
int remainingTimeBudget = ui.update();
if (remainingTimeBudget > 0) {
// You can do some work here
// Don't do stuff if you are below your
// time budget.
delay(remainingTimeBudget);
}
}

View File

@@ -0,0 +1,50 @@
#define WiFi_Logo_width 60
#define WiFi_Logo_height 36
const uint8_t WiFi_Logo_bits[] PROGMEM = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xFF, 0x07, 0x00, 0x00, 0x00,
0x00, 0x00, 0xE0, 0xFF, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0xFF,
0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0xFF, 0xFF, 0x00, 0x00, 0x00,
0x00, 0x00, 0xFE, 0xFF, 0xFF, 0x01, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF,
0xFF, 0x03, 0x00, 0x00, 0x00, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00,
0x00, 0xFF, 0xFF, 0xFF, 0x07, 0xC0, 0x83, 0x01, 0x80, 0xFF, 0xFF, 0xFF,
0x01, 0x00, 0x07, 0x00, 0xC0, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x0C, 0x00,
0xC0, 0xFF, 0xFF, 0x7C, 0x00, 0x60, 0x0C, 0x00, 0xC0, 0x31, 0x46, 0x7C,
0xFC, 0x77, 0x08, 0x00, 0xE0, 0x23, 0xC6, 0x3C, 0xFC, 0x67, 0x18, 0x00,
0xE0, 0x23, 0xE4, 0x3F, 0x1C, 0x00, 0x18, 0x00, 0xE0, 0x23, 0x60, 0x3C,
0x1C, 0x70, 0x18, 0x00, 0xE0, 0x03, 0x60, 0x3C, 0x1C, 0x70, 0x18, 0x00,
0xE0, 0x07, 0x60, 0x3C, 0xFC, 0x73, 0x18, 0x00, 0xE0, 0x87, 0x70, 0x3C,
0xFC, 0x73, 0x18, 0x00, 0xE0, 0x87, 0x70, 0x3C, 0x1C, 0x70, 0x18, 0x00,
0xE0, 0x87, 0x70, 0x3C, 0x1C, 0x70, 0x18, 0x00, 0xE0, 0x8F, 0x71, 0x3C,
0x1C, 0x70, 0x18, 0x00, 0xC0, 0xFF, 0xFF, 0x3F, 0x00, 0x00, 0x08, 0x00,
0xC0, 0xFF, 0xFF, 0x1F, 0x00, 0x00, 0x0C, 0x00, 0x80, 0xFF, 0xFF, 0x1F,
0x00, 0x00, 0x06, 0x00, 0x80, 0xFF, 0xFF, 0x0F, 0x00, 0x00, 0x07, 0x00,
0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0x00, 0x00, 0xF8, 0xFF, 0xFF,
0xFF, 0x7F, 0x00, 0x00, 0x00, 0x00, 0xFE, 0xFF, 0xFF, 0x01, 0x00, 0x00,
0x00, 0x00, 0xFC, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0xFF,
0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x1F, 0x00, 0x00, 0x00,
0x00, 0x00, 0x80, 0xFF, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
const uint8_t activeSymbol[] PROGMEM = {
B00000000,
B00000000,
B00011000,
B00100100,
B01000010,
B01000010,
B00100100,
B00011000
};
const uint8_t inactiveSymbol[] PROGMEM = {
B00000000,
B00000000,
B00000000,
B00000000,
B00011000,
B00011000,
B00000000,
B00000000
};

View File

@@ -0,0 +1,100 @@
#######################################
# Syntax Coloring Map List
#######################################
#######################################
# Constants (LITERAL1)
#######################################
INVERSE LITERAL1
TEXT_ALIGN_LEFT LITERAL1
TEXT_ALIGN_RIGHT LITERAL1
TEXT_ALIGN_CENTER LITERAL1
TEXT_ALIGN_CENTER_BOTH LITERAL1
GEOMETRY_128_64 LITERAL1
GEOMETRY_128_32 LITERAL1
GEOMETRY_RAWMODE LITERAL1
ArialMT_Plain_10 LITERAL1
ArialMT_Plain_16 LITERAL1
ArialMT_Plain_24 LITERAL1
SLIDE_UP LITERAL1
SLIDE_DOWN LITERAL1
SLIDE_LEFT LITERAL1
SLIDE_RIGHT LITERAL1
TOP LITERAL1
RIGHT LITERAL1
BOTTOM LITERAL1
LEFT LITERAL1
LEFT_RIGHT LITERAL1
RIGHT_LEFT LITERAL1
IN_TRANSITION LITERAL1
FIXED LITERAL1
#######################################
# Datatypes (KEYWORD1)
#######################################
OLEDDisplay KEYWORD1
OLEDDisplayUi KEYWORD1
SH1106Wire KEYWORD1
SH1106Brzo KEYWORD1
SH1106Spi KEYWORD1
SSD1306Wire KEYWORD1
SSD1306Brzo KEYWORD1
SSD1306I2C KEYWORD1
SSD1306Spi KEYWORD1
#######################################
# Methods and Functions (KEYWORD2)
#######################################
allocateBuffer KEYWORD2
init KEYWORD2
resetDisplay KEYWORD2
setColor KEYWORD2
getColor KEYWORD2
setPixel KEYWORD2
setPixelColor KEYWORD2
clearPixel KEYWORD2
drawLine KEYWORD2
drawRect KEYWORD2
fillRect KEYWORD2
drawCircle KEYWORD2
drawCircleQuads KEYWORD2
fillCircle KEYWORD2
fillRing KEYWORD2
drawHorizontalLine KEYWORD2
drawVerticalLine KEYWORD2
drawProgressBar KEYWORD2
drawFastImage KEYWORD2
drawXbm KEYWORD2
drawIco16x16 KEYWORD2
drawString KEYWORD2
drawStringMaxWidth KEYWORD2
getStringWidth KEYWORD2
setTextAlignment KEYWORD2
setFont KEYWORD2
setFontTableLookupFunction KEYWORD2
displayOn KEYWORD2
displayOff KEYWORD2
invertDisplay KEYWORD2
normalDisplay KEYWORD2
setContrast KEYWORD2
setBrightness KEYWORD2
resetOrientation KEYWORD2
flipScreenVertically KEYWORD2
mirrorScreen KEYWORD2
display KEYWORD2
setLogBuffer KEYWORD2
drawLogBuffer KEYWORD2
getWidth KEYWORD2
getHeight KEYWORD2

View File

@@ -0,0 +1,29 @@
{
"name": "ESP8266 and ESP32 OLED driver for SSD1306 displays",
"version": "4.2.0",
"keywords": "ssd1306, oled, display, i2c",
"description": "I2C display driver for SSD1306 OLED displays connected to ESP8266, ESP32, Mbed-OS",
"repository":
{
"type": "git",
"url": "https://github.com/ThingPulse/esp8266-oled-ssd1306"
},
"authors":
[
{
"name": "Daniel Eichhorn, ThingPulse",
"email": "squix78@gmail.com",
"url": "https://thingpulse.com"
},
{
"name": "Fabrice Weinberg",
"email": "fabrice@weinberg.me"
}
],
"frameworks": "arduino",
"platforms": [
"espressif8266",
"espressif32",
"nordicnrf52"
]
}

View File

@@ -0,0 +1,9 @@
name=ESP8266 and ESP32 OLED driver for SSD1306 displays
version=4.2.0
author=ThingPulse, Fabrice Weinberg
maintainer=ThingPulse <info@thingpulse.com>
sentence=I2C display driver for SSD1306 OLED displays connected to ESP8266, ESP32, Mbed-OS
paragraph=The following geometries are currently supported: 128x64, 128x32, 64x48. The init sequence was inspired by Adafruit's library for the same display.
category=Display
url=https://github.com/ThingPulse/esp8266-oled-ssd1306
architectures=esp8266,esp32

View File

@@ -0,0 +1,24 @@
The MIT License (MIT)
Copyright (c) 2016 by Daniel Eichhorn
Copyright (c) 2016 by Fabrice Weinberg
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
See more at http://blog.squix.ch

View File

@@ -0,0 +1,19 @@
; PlatformIO Project Configuration File
;
; Build options: build flags, source filter
; Upload options: custom upload port, speed and extra flags
; Library options: dependencies, extra library storages
; Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; http://docs.platformio.org/page/projectconf.html
[env:d1_mini]
platform = espressif8266
board = d1_mini
framework = arduino
upload_speed = 921600
board_build.f_cpu = 160000000L
upload_port = /dev/cu.SLAB_USBtoUART
monitor_port = /dev/cu.SLAB_USBtoUART
lib_deps =

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

View File

@@ -0,0 +1,664 @@
<!--The MIT License (MIT)
Copyright (c) 2017 by Xavier Grosjean
Based on work
Copyright (c) 2016 by Daniel Eichhorn
Copyright (c) 2016 by Fabrice Weinberg
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
-->
<!--
Standalone page to draw icons in matrix and generates the map definition with the format required by
library for OLED SD1306 screen: https://github.com/squix78/esp8266-oled-ssd1306
100% quick and dirty vanilla ECS6, no framework or library was injured for this project.
-->
<html>
<head>
<meta charset="utf-8" />
<style>
#form, #chars table {
user-select: none;
-moz-user-select: none;
margin-top: 5px;
}
#chars table, tr, td, th {
border-collapse: collapse;
}
#chars td, th {
border: 1px solid #CCC;
width: 12px;
height: 15px;
}
#chars th[action] {
cursor:pointer;
}
#chars th[action="up"], #chars th[action="down"], #chars th[action="left"], #chars th[action="right"] {
font-size: 10px;
}
#chars td.on {
background-color: #00F;
}
#addChar, #generate {
display: none;
}
body.started #addChar, body.started #generate {
display: inline;
}
body.started #create {
display: none;
}
#page {
position:relative;
}
#page>div {
position: relative;
float: left;
}
#output {
margin-left: 20px;
margin-top: 12px;
font-size:12px;
user-select: all;
-moz-user-select: all;
max-width:60%;
}
#header {
margin-top: 10px;
}
#inputText {
width: 100%;
height:120px;
}
</style>
</head>
<body>
<div id="form">
<table width="100%">
<tr>
<td>Font array name:</td><td><input placeholder="Font array name" type="text" id="name" value="My_Font"/></td>
<td width="75%" rowspan="5">
<textarea id="inputText" placeholder="Or paste a char array font definition here">
const uint8_t My_Font[] PROGMEM = {
0x0A, // Width: 10
0x0D, // Height: 13
0x01, // First char: 1
0x02, // Number of chars: 2
// Jump Table:
0x00, 0x00, 0x13, 0x0A, // 1
0x00, 0x13, 0x14, 0x0A, // 2
// Font Data:
0xF0, 0x03, 0x10, 0x02, 0xF0, 0x03, 0x10, 0x02, 0xF0, 0x03, 0x10, 0x02, 0xF0, 0x03, 0x10, 0x02, 0xF0, 0x03, 0xC0, // 1
0x00, 0x0C, 0x80, 0x0B, 0x70, 0x08, 0x0C, 0x08, 0xF3, 0x0A, 0xF3, 0x0A, 0x0C, 0x08, 0x70, 0x08, 0x80, 0x0B, 0x00, 0x0C, // 2
};
</textarea></td>
</tr>
<tr>
<td>First char code:</td><td><input placeholder="First char code" type="number" id="code" value="1" min="1"/></td>
</tr>
<tr>
<td>Width:</td><td><input placeholder="Font width" type="number" id="width" value="10"/></td>
</tr>
<tr>
<td>Height:</td><td><input placeholder="Font height" type="number" id="height" value="13" max="64"/></td>
</tr>
<tr>
<td colspan="3"> </td> <!--slightly improves layout-->
</tr>
<tr>
<td colspan="2">
<button id="create">Create</button>
<button id="shiftUp">Shift all up</button>
<button id="generate">Generate</button>
<button id="savetoFile">Save To File</button>
</td>
<td><input type="file" id="fileinput" /> <button id="parse">Parse</button></td>
</tr>
</table>
<br/>
</div>
<div id="page">
<div id="charxContainer" ondragstart="return false;">
<div id="chars"></div><br/>
<button id="addChar">Add a character</button>
</div>
<div id="output">
<div class="output" id="header"> </div>
<div class="output" id="jump"> </div>
<div class="output" id="data"> </div>
</div>
</div>
<script>
(function() {
let font;
class Font {
constructor() {
this.height = parseInt(document.getElementById('height').value);
this.width = parseInt(document.getElementById('width').value);
this.currentCharCode = parseInt(document.getElementById('code').value);
this.fontContainer = document.getElementById('chars');
this.bytesForHeight = (1 + ((this.height - 1) >> 3)); // number of bytes needed for this font height
document.body.className = "started";
document.getElementById('height').disabled = true;
document.getElementById('width').disabled = true;
}
// Should we have a Char and a Pixel class ? not sure it's worth it.
// Let's use the DOM to store everything for now
static updateCaption(element, code) {
element.textContent = `Char #${code}`;
}
// Add a pixel matrix to draw a new character
// jumpaData not used for now: some day, use the last byte for optimisation
addChar(jumpData, charData) {
let charContainer = this.fontContainer.appendChild(document.createElement("table"));
charContainer.setAttribute("code", this.currentCharCode);
let caption = charContainer.appendChild(document.createElement("caption"));
Font.updateCaption(caption, this.currentCharCode);
let header = charContainer.appendChild(document.createElement("tr"));
header.innerHTML = '<th title="Delete this char" action="delete">&cross;</th>'
+ '<th title="Add a char above" action="add">+</th>'
+ '<th title="Shift pixels to the left" action="left">&larr;</th>'
+ '<th title="Shift pixels down" action="down">&darr;</th>'
+ '<th title="Shift pixels up" action="up">&uarr;</th>'
+ '<th title="Shift pixels to the right" action="right">&rarr;</th>'
+ '<th title="Copy from another character" action="copy">&copy;</th>'
+ '<th title="Reset all pixels" action="clean">&empty;</th>';
// If data is provided, decode it to pixel initialization friendly structure
let pixelInit = [];
if (charData && charData.length) {
// charData byte count needs to be a multiple of bytesForHeight. End bytes with value 0 may have been trimmed
let missingBytes = charData.length % this.bytesForHeight;
for(let b = 0; b < missingBytes ; b++) {
charData.push(0);
}
while(charData.length) {
let row = charData.splice(0, this.bytesForHeight).reverse();
let pixelRow = [];
for (let b = 0; b < row.length; b++) {
let mask = 0x80;
let byte = row[b];
for (let bit = 0; bit < 8; bit++) {
if (byte & mask) {
pixelRow.push(1);
} else {
pixelRow.push(0);
}
mask = mask >> 1;
}
}
pixelRow.splice(0, pixelRow.length - this.height);
//Font.output('data', pixelRow);
pixelInit.push(pixelRow.reverse());
}
}
for(let r = 0; r < this.height; r++) {
let rowContainer = charContainer.appendChild(document.createElement("tr"));
for(let c = 0; c < this.width; c++) {
let pixContainer = rowContainer.appendChild(document.createElement("td"));
pixContainer.setAttribute('action', 'toggle');
// If there is some init data, set the pixel accordingly
if (pixelInit.length && pixelInit[c]) {
if (pixelInit[c][r]) {
pixContainer.className = 'on';
}
}
}
}
this.currentCharCode++;
return charContainer;
}
static togglePixel(pixel) {
pixel.className = pixel.className === 'on' ? '': 'on';
}
// Return anInt as hex string
static toHexString(aByte) {
let zero = aByte < 16?'0':'';
return `0x${zero}${aByte.toString(16).toUpperCase()}`
}
// Return least significant byte as hex string
static getLsB(anInt) {
return Font.toHexString(anInt & 0xFF);
}
// Return most significant byte as hex string
static getMsB(anInt) {
return Font.toHexString(anInt>>>8);
}
static output(targetId, msg) {
let output = document.getElementById(targetId);
let line = output.appendChild(document.createElement('div'));
line.textContent = msg;
}
static emptyChars() {
document.getElementById('chars').textContent = '';
}
static emptyOutput() {
document.getElementById('header').textContent = '';
document.getElementById('jump').textContent = '';
document.getElementById('data').textContent = '';
}
saveFile() {
let filename = document.getElementById('name').value.replace(/[^a-zA-Z0-9_$]/g, '_') + ".h";
let data = document.getElementById("output").innerText;
if(data.length < 10) return;
let blobObject = new Blob([data], {type:'text/plain'});
if(window.navigator.msSaveBlob) {
window.navigator.msSaveBlob(blobObject, filename);
} else {
let a = document.createElement("a");
a.setAttribute("href", URL.createObjectURL(blobObject));
a.setAttribute("download", filename);
if (document.createEvent) {
let event = document.createEvent('MouseEvents');
event.initEvent('click', true, true);
a.dispatchEvent(event);
} else {
a.click();
}
}
}
// Read from the <td> css class the pixels that need to be on
// generates the jump table and font data
generate() {
// this.width -= 3; // hack to narrow an existing font
Font.emptyOutput();
let chars = this.fontContainer.getElementsByTagName('table');
let firstCharCode = parseInt(document.getElementById('code').value);
let name = document.getElementById('name').value.replace(/[^a-zA-Z0-9_$]/g, '_');
let bits2add = this.bytesForHeight*8 - this.height; // number of missing bits to fill leftmost byte
let charCount = chars.length;
let charAddr = 0;
// Comments are used when parsing back a generated font
Font.output('jump', ' // Jump Table:');
Font.output('data', ' // Font Data:');
// Browse each character
for(let ch = 0; ch < charCount; ch++) {
// Fix renumbering in case first char code was modified
Font.updateCaption(chars[ch].getElementsByTagName('caption')[0], ch + firstCharCode);
let charBytes = [];
let charCode = ch + firstCharCode;
let rows = chars[ch].getElementsByTagName('tr');
let charNotZero = false;
// Browse each column
for(let col = 0; col < this.width ; col++) {
let bits = ""; // using string because js uses 32b ints when performing bit operations
// Browse each row starting from the bottom one, going up, and accumulate pixels in
// a string: this rotates the glyph
// Beware, row 0 is table headers.
for(let r = rows.length-1; r >=1 ; r--) {
let pixelState = rows[r].children[col].className;
bits += (pixelState === 'on' ? '1': '0');
}
// Need to complete missing bits to have a sizeof byte multiple number of bits
for(let b = 0; b < bits2add; b++) {
bits = '0' + bits;
}
// Font.output('data', ` // ${bits}`); // Debugging help: rotated bitmap
// read bytes from the end
for(let b = bits.length - 1; b >= 7; b -= 8) {
//Font.output('data', ` // ${bits.substr(b-7, 8)}`); // Debugging help: rotated bitmap
let byte = parseInt(bits.substr(b-7, 8), 2);
if (byte !== 0) {
charNotZero = true;
}
charBytes.push(Font.toHexString(byte));
}
}
// Compute the used width of the character:
// rightmost column with pixels plus one
// one column is spread over several bytes...
let charWidth = 0;
for(let i=charBytes.length - 1; i >= 0; i-=this.bytesForHeight) {
let sum = 0;
for(let j=0; j < this.bytesForHeight; j++) {
sum += parseInt(charBytes[i - j], 16);
}
if(sum !== 0) {
charWidth = (i + 1)/this.bytesForHeight + 1;
break;
}
}
// Memory optim: Remove bytes with value 0 at the end of the array.
while(parseInt(charBytes[charBytes.length-1], 16) === 0 && charBytes.length > 1) {
charBytes.pop();
}
if (charNotZero) {
Font.output('data', ` ${charBytes.join(', ')}, // ${charCode}`);
Font.output('jump', ` ${Font.getMsB(charAddr)}, ${Font.getLsB(charAddr)}, ${Font.toHexString(charBytes.length)}, ${Font.toHexString(charWidth)}, // ${charCode} `);
charAddr += charBytes.length;
} else {
Font.output('jump', ` 0xFF, 0xFF, 0x00, ${Font.toHexString(this.width)}, // ${charCode} `);
}
}
Font.output('data', '};');
Font.output('header', "// Font generated or edited with the glyphEditor");
Font.output('header', `const uint8_t ${name}[] PROGMEM = {`);
// Comments are used when parsing back a generated font
Font.output('header', ` ${Font.toHexString(this.width)}, // Width: ${this.width}`);
Font.output('header', ` ${Font.toHexString(this.height)}, // Height: ${this.height}`);
Font.output('header', ` ${Font.toHexString(firstCharCode)}, // First char: ${firstCharCode}`);
Font.output('header', ` ${Font.toHexString(charCount)}, // Number of chars: ${charCount}`);
}
}
document.getElementById('fileinput').addEventListener('change', function(e) {
let f = e.target.files[0];
if (f) {
let r = new FileReader();
r.onload = function(e) {
let contents = e.target.result;
alert( "Got the file.\n"
+"name: " + f.name + "\n"
+"type: " + f.type + "\n"
+"size: " + f.size + " bytes\n"
+"starts with: " + contents.substr(0, contents.indexOf("\n"))
);
document.getElementById("inputText").value = contents;
};
r.readAsText(f);
} else {
alert("Failed to load file");
}
});
document.getElementById('savetoFile').addEventListener('click', function() {
font.saveFile();
});
document.getElementById('shiftUp').addEventListener('click', function() {
var chars = document.getElementById("chars");
var tables = chars.getElementsByTagName("table");
for(var i=0; i< tables.length; i++) {
shiftUp(tables[i]);
}
});
document.getElementById('generate').addEventListener('click', function() {
font.generate();
});
document.getElementById('addChar').addEventListener('click', function() {
font.addChar();
});
document.getElementById('inputText').addEventListener('click', function(e) {
let target = e.target;
target.select();
});
document.getElementById('chars').addEventListener('mousedown', function(e) {
if (e.button !== 0) return;
let target = e.target;
let action = target.getAttribute('action') || '';
if (action === '') return;
let result, code, nextContainer, previousContainer, pixels ;
let currentContainer = target.parentNode.parentNode;
switch(action) {
case 'add':
code = currentContainer.getAttribute('code');
nextContainer = font.addChar();
currentContainer.parentNode.insertBefore(nextContainer, currentContainer);
do {
nextContainer.setAttribute('code', code);
Font.updateCaption(nextContainer.getElementsByTagName('caption')[0], code);
code ++;
} while (nextContainer = nextContainer.nextSibling);
break;
case 'delete':
result = confirm("Delete this character ?");
if (!result) return;
code = currentContainer.getAttribute('code');
nextContainer = currentContainer;
while (nextContainer = nextContainer.nextSibling) {
nextContainer.setAttribute('code', code);
Font.updateCaption(nextContainer.getElementsByTagName('caption')[0], code);
code ++;
}
currentContainer.parentNode.removeChild(currentContainer);
break;
// Shift pixels to the left
case 'left':
pixels = currentContainer.getElementsByTagName('td');
for(p = 0; p < pixels.length; p++) {
if((p + 1) % font.width) {
pixels[p].className = pixels[p + 1].className;
} else {
pixels[p].className = '';
}
}
break;
// Shift pixels to the right
case 'right':
pixels = currentContainer.getElementsByTagName('td');
for(p = pixels.length-1; p >=0 ; p--) {
if(p % font.width) {
pixels[p].className = pixels[p - 1].className;
} else {
pixels[p].className = '';
}
}
break;
// Shift pixels down
case 'down':
pixels = currentContainer.getElementsByTagName('td');
for(p = pixels.length-1; p >=0 ; p--) {
if(p >= font.width) {
pixels[p].className = pixels[p - font.width].className;
} else {
pixels[p].className = '';
}
} break;
// Shift pixels up
case 'up':
shiftUp(currentContainer);
break;
case 'toggle':
Font.togglePixel(target);
break;
case 'clean':
result = confirm("Delete the pixels ?");
if (!result) return;
pixels = currentContainer.getElementsByTagName('td');
for (let p = 0; p < pixels.length; p++) {
pixels[p].className = '';
}
break;
case 'copy':
let charNumber = parseInt(prompt("Source char #: "));
let chars = font.fontContainer.getElementsByTagName('table');
let tableOffset = charNumber - parseInt(document.getElementById('code').value);
let srcPixels = chars[tableOffset].getElementsByTagName('td');
let targetPixels = currentContainer.getElementsByTagName('td');
for(let i=0; i < srcPixels.length; i++) {
// First tds are in the th row, for editing actions. Skip them
if (targetPixels[i].parentNode.localName === 'th') continue; // In case we give them css at some point...
targetPixels[i].className = srcPixels[i].className;
}
break;
default:
// no.
}
});
function shiftUp(container) {
var pixels = container.getElementsByTagName('td');
for(p = 0; p < pixels.length; p++) {
if(p < font.width*(font.height -1)) {
pixels[p].className = pixels[p + font.width].className;
} else {
pixels[p].className = '';
}
}
}
document.getElementById('chars').addEventListener('mouseover', function(e) {
let target = e.target;
let action = target.getAttribute('action');
if (action !== 'toggle' || e.buttons !== 1) return;
Font.togglePixel(target);
});
document.getElementById('chars').addEventListener('dragstart', function() {
return false;
});
document.getElementById('create').addEventListener('click', function() {
font = new Font();
font.addChar();
});
// parse a char array declaration for an existing font.
// parsing heavily relies on comments.
document.getElementById('parse').addEventListener('click', function() {
if (document.getElementById('chars').childElementCount) {
let result = confirm("Confirm you want to overwrite the existing grids ?");
if (!result) return;
}
let lines = document.getElementById('inputText').value.split('\n');
let name = '';
let height = 0;
let width = 0;
let firstCharCode = 0;
let jumpTable = [];
let charData = [];
let readingJumpTable = false;
let readingData = false;
for(let l = 0 ; l < lines.length; l++) {
// TODO ? keep C compilation directives to copy them (not lowercased :)) to newly generated char array
let line = lines[l].trim();
//alert(line);
let fields;
// Declaration line: grab the name
if (line.indexOf('PROGMEM') > 0) {
fields = line.split(' ');
name = fields[2].replace(/[\[\]]/g, '');
continue;
}
line = line.toLowerCase();
// Todo: could rely on line order...
// width line: grab the width
if (line.indexOf('width') > 0) {
fields = line.split(',');
width = fields[0];
continue;
}
// height line: grab the height
if (line.indexOf('height') > 0) {
fields = line.split(',');
height = fields[0];
continue;
}
// first char code line: grab the first char code
if (line.indexOf('first char') > 0) {
fields = line.split(',');
firstCharCode = fields[0];
continue;
}
// End of array declaration
// TODO warn if more lines: next fonts are ignored
if (line.indexOf('};') === 0) {
break;
}
if (readingJumpTable || readingData) {
if (line.indexOf('#') !== 0 && line.length !== 0 && line.indexOf('//') !== 0) {
line = line.replace(/\/\/.*/, ''); // get rid of end of line comments
fields = line.split(',');
let newEntry = [];
for(let f=0; f < fields.length; f++) {
let value = parseInt(fields[f]);
if (isNaN(value)) continue;
if (readingData) {
charData.push(value);
}
if (readingJumpTable) {
newEntry.push(value);
}
}
if (readingJumpTable) {
jumpTable.push(newEntry);
}
}
}
// Begining of data
if (line.indexOf('font data') > 0) {
readingData = true;
readingJumpTable = false;
}
// Begining of jump table
if (line.indexOf('jump table') > 0) {
readingJumpTable = true;
}
}
if (!name || !height || !width || !firstCharCode) {
alert("Does not look like a parsable font. Try again.");
return;
}
Font.emptyChars();
Font.emptyOutput();
document.getElementById('name').value = name;
document.getElementById('height').value = parseInt(height);
document.getElementById('width').value = parseInt(width);
document.getElementById('code').value = parseInt(firstCharCode);
font = new Font();
for(let c = 0 ; c < jumpTable.length; c++) {
let jumpEntry = jumpTable[c];
let charEntry = [];
// displayable character
if (jumpEntry[0] !== 255 || jumpEntry[1] !== 255) {
charEntry = charData.splice(0, jumpEntry[2]);
}
font.addChar(jumpEntry, charEntry);
}
document.body.className = "started";
});
})();
</script>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,384 @@
/**
* The MIT License (MIT)
*
* Copyright (c) 2018 by ThingPulse, Daniel Eichhorn
* Copyright (c) 2018 by Fabrice Weinberg
* Copyright (c) 2019 by Helmut Tschemernjak - www.radioshuttle.de
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* ThingPulse invests considerable time and money to develop these open source libraries.
* Please support us by buying our products (and not the clones) from
* https://thingpulse.com
*
*/
#ifndef OLEDDISPLAY_h
#define OLEDDISPLAY_h
#ifdef ARDUINO
#include <Arduino.h>
#elif __MBED__
#define pgm_read_byte(addr) (*(const unsigned char *)(addr))
#include <mbed.h>
#define delay(x) wait_ms(x)
#define yield() void()
/*
* This is a little Arduino String emulation to keep the OLEDDisplay
* library code in common between Arduino and mbed-os
*/
class String {
public:
String(const char *s) { _str = s; };
int length() { return strlen(_str); };
const char *c_str() { return _str; };
void toCharArray(char *buf, unsigned int bufsize, unsigned int index = 0) const {
memcpy(buf, _str + index, std::min(bufsize, strlen(_str)));
};
private:
const char *_str;
};
#else
#error "Unkown operating system"
#endif
#include "OLEDDisplayFonts.h"
//#define DEBUG_OLEDDISPLAY(...) Serial.printf( __VA_ARGS__ )
//#define DEBUG_OLEDDISPLAY(...) dprintf("%s", __VA_ARGS__ )
#ifndef DEBUG_OLEDDISPLAY
#define DEBUG_OLEDDISPLAY(...)
#endif
// Use DOUBLE BUFFERING by default
#ifndef OLEDDISPLAY_REDUCE_MEMORY
#define OLEDDISPLAY_DOUBLE_BUFFER
#endif
// Header Values
#define JUMPTABLE_BYTES 4
#define JUMPTABLE_LSB 1
#define JUMPTABLE_SIZE 2
#define JUMPTABLE_WIDTH 3
#define JUMPTABLE_START 4
#define WIDTH_POS 0
#define HEIGHT_POS 1
#define FIRST_CHAR_POS 2
#define CHAR_NUM_POS 3
// Display commands
#define CHARGEPUMP 0x8D
#define COLUMNADDR 0x21
#define COMSCANDEC 0xC8
#define COMSCANINC 0xC0
#define DISPLAYALLON 0xA5
#define DISPLAYALLON_RESUME 0xA4
#define DISPLAYOFF 0xAE
#define DISPLAYON 0xAF
#define EXTERNALVCC 0x1
#define INVERTDISPLAY 0xA7
#define MEMORYMODE 0x20
#define NORMALDISPLAY 0xA6
#define PAGEADDR 0x22
#define SEGREMAP 0xA0
#define SETCOMPINS 0xDA
#define SETCONTRAST 0x81
#define SETDISPLAYCLOCKDIV 0xD5
#define SETDISPLAYOFFSET 0xD3
#define SETHIGHCOLUMN 0x10
#define SETLOWCOLUMN 0x00
#define SETMULTIPLEX 0xA8
#define SETPRECHARGE 0xD9
#define SETSEGMENTREMAP 0xA1
#define SETSTARTLINE 0x40
#define SETVCOMDETECT 0xDB
#define SWITCHCAPVCC 0x2
#ifndef _swap_int16_t
#define _swap_int16_t(a, b) { int16_t t = a; a = b; b = t; }
#endif
enum OLEDDISPLAY_COLOR {
BLACK = 0,
WHITE = 1,
INVERSE = 2
};
enum OLEDDISPLAY_TEXT_ALIGNMENT {
TEXT_ALIGN_LEFT = 0,
TEXT_ALIGN_RIGHT = 1,
TEXT_ALIGN_CENTER = 2,
TEXT_ALIGN_CENTER_BOTH = 3
};
enum OLEDDISPLAY_GEOMETRY {
GEOMETRY_128_64 = 0,
GEOMETRY_128_32 = 1,
GEOMETRY_64_48 = 2,
GEOMETRY_64_32 = 3,
GEOMETRY_RAWMODE = 4
};
enum HW_I2C {
I2C_ONE,
I2C_TWO
};
typedef char (*FontTableLookupFunction)(const uint8_t ch);
char DefaultFontTableLookup(const uint8_t ch);
#ifdef ARDUINO
class OLEDDisplay : public Print {
#elif __MBED__
class OLEDDisplay : public Stream {
#else
#error "Unkown operating system"
#endif
public:
OLEDDisplay();
virtual ~OLEDDisplay();
uint16_t width(void) const { return displayWidth; };
uint16_t height(void) const { return displayHeight; };
// Use this to resume after a deep sleep without resetting the display (what init() would do).
// Returns true if connection to the display was established and the buffer allocated, false otherwise.
bool allocateBuffer();
// Allocates the buffer and initializes the driver & display. Resets the display!
// Returns false if buffer allocation failed, true otherwise.
bool init();
// Free the memory used by the display
void end();
// Cycle through the initialization
void resetDisplay(void);
/* Drawing functions */
// Sets the color of all pixel operations
void setColor(OLEDDISPLAY_COLOR color);
// Returns the current color.
OLEDDISPLAY_COLOR getColor();
// Draw a pixel at given position
void setPixel(int16_t x, int16_t y);
// Draw a pixel at given position and color
void setPixelColor(int16_t x, int16_t y, OLEDDISPLAY_COLOR color);
// Clear a pixel at given position FIXME: INVERSE is untested with this function
void clearPixel(int16_t x, int16_t y);
// Draw a line from position 0 to position 1
void drawLine(int16_t x0, int16_t y0, int16_t x1, int16_t y1);
// Draw the border of a rectangle at the given location
void drawRect(int16_t x, int16_t y, int16_t width, int16_t height);
// Fill the rectangle
void fillRect(int16_t x, int16_t y, int16_t width, int16_t height);
// Draw the border of a circle
void drawCircle(int16_t x, int16_t y, int16_t radius);
// Draw all Quadrants specified in the quads bit mask
void drawCircleQuads(int16_t x0, int16_t y0, int16_t radius, uint8_t quads);
// Fill circle
void fillCircle(int16_t x, int16_t y, int16_t radius);
// Draw a line horizontally
void drawHorizontalLine(int16_t x, int16_t y, int16_t length);
// Draw a line vertically
void drawVerticalLine(int16_t x, int16_t y, int16_t length);
// Draws a rounded progress bar with the outer dimensions given by width and height. Progress is
// a unsigned byte value between 0 and 100
void drawProgressBar(uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint8_t progress);
// Draw a bitmap in the internal image format
void drawFastImage(int16_t x, int16_t y, int16_t width, int16_t height, const uint8_t *image);
// Draw a XBM
void drawXbm(int16_t x, int16_t y, int16_t width, int16_t height, const uint8_t *xbm);
// Draw icon 16x16 xbm format
void drawIco16x16(int16_t x, int16_t y, const char *ico, bool inverse = false);
/* Text functions */
// Draws a string at the given location
void drawString(int16_t x, int16_t y, String text);
// Draws a formatted string (like printf) at the given location
void drawStringf(int16_t x, int16_t y, char* buffer, String format, ... );
// Draws a String with a maximum width at the given location.
// If the given String is wider than the specified width
// The text will be wrapped to the next line at a space or dash
void drawStringMaxWidth(int16_t x, int16_t y, uint16_t maxLineWidth, String text);
// Returns the width of the const char* with the current
// font settings
uint16_t getStringWidth(const char* text, uint16_t length);
// Convencience method for the const char version
uint16_t getStringWidth(String text);
// Specifies relative to which anchor point
// the text is rendered. Available constants:
// TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER, TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER_BOTH
void setTextAlignment(OLEDDISPLAY_TEXT_ALIGNMENT textAlignment);
// Sets the current font. Available default fonts
// ArialMT_Plain_10, ArialMT_Plain_16, ArialMT_Plain_24
void setFont(const uint8_t *fontData);
// Set the function that will convert utf-8 to font table index
void setFontTableLookupFunction(FontTableLookupFunction function);
/* Display functions */
// Turn the display on
void displayOn(void);
// Turn the display offs
void displayOff(void);
// Inverted display mode
void invertDisplay(void);
// Normal display mode
void normalDisplay(void);
// Set display contrast
// really low brightness & contrast: contrast = 10, precharge = 5, comdetect = 0
// normal brightness & contrast: contrast = 100
void setContrast(uint8_t contrast, uint8_t precharge = 241, uint8_t comdetect = 64);
// Convenience method to access
void setBrightness(uint8_t);
// Reset display rotation or mirroring
void resetOrientation();
// Turn the display upside down
void flipScreenVertically();
// Mirror the display (to be used in a mirror or as a projector)
void mirrorScreen();
// Write the buffer to the display memory
virtual void display(void) = 0;
// Clear the local pixel buffer
void clear(void);
// Log buffer implementation
// This will define the lines and characters you can
// print to the screen. When you exeed the buffer size (lines * chars)
// the output may be truncated due to the size constraint.
bool setLogBuffer(uint16_t lines, uint16_t chars);
// Draw the log buffer at position (x, y)
void drawLogBuffer(uint16_t x, uint16_t y);
// Get screen geometry
uint16_t getWidth(void);
uint16_t getHeight(void);
// Implement needed function to be compatible with Print class
size_t write(uint8_t c);
size_t write(const char* s);
// Implement needed function to be compatible with Stream class
#ifdef __MBED__
int _putc(int c);
int _getc() { return -1; };
#endif
uint8_t *buffer;
#ifdef OLEDDISPLAY_DOUBLE_BUFFER
uint8_t *buffer_back;
#endif
protected:
OLEDDISPLAY_GEOMETRY geometry;
uint16_t displayWidth;
uint16_t displayHeight;
uint16_t displayBufferSize;
// Set the correct height, width and buffer for the geometry
void setGeometry(OLEDDISPLAY_GEOMETRY g, uint16_t width = 0, uint16_t height = 0);
OLEDDISPLAY_TEXT_ALIGNMENT textAlignment;
OLEDDISPLAY_COLOR color;
const uint8_t *fontData;
// State values for logBuffer
uint16_t logBufferSize;
uint16_t logBufferFilled;
uint16_t logBufferLine;
uint16_t logBufferMaxLines;
char *logBuffer;
// the header size of the buffer used, e.g. for the SPI command header
int BufferOffset;
virtual int getBufferOffset(void) = 0;
// Send a command to the display (low level function)
virtual void sendCommand(uint8_t com) {(void)com;};
// Connect to the display
virtual bool connect() { return false; };
// Send all the init commands
void sendInitCommands();
// converts utf8 characters to extended ascii
char* utf8ascii(String s);
void inline drawInternal(int16_t xMove, int16_t yMove, int16_t width, int16_t height, const uint8_t *data, uint16_t offset, uint16_t bytesInData) __attribute__((always_inline));
void drawStringInternal(int16_t xMove, int16_t yMove, char* text, uint16_t textLength, uint16_t textWidth);
FontTableLookupFunction fontTableLookupFunction;
};
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,471 @@
/**
* The MIT License (MIT)
*
* Copyright (c) 2018 by ThingPulse, Daniel Eichhorn
* Copyright (c) 2018 by Fabrice Weinberg
* Copyright (c) 2019 by Helmut Tschemernjak - www.radioshuttle.de
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* ThingPulse invests considerable time and money to develop these open source libraries.
* Please support us by buying our products (and not the clones) from
* https://thingpulse.com
*
*/
#include "OLEDDisplayUi.h"
void LoadingDrawDefault(OLEDDisplay *display, LoadingStage* stage, uint8_t progress) {
display->setTextAlignment(TEXT_ALIGN_CENTER);
display->setFont(ArialMT_Plain_10);
display->drawString(64, 18, stage->process);
display->drawProgressBar(4, 32, 120, 8, progress);
};
OLEDDisplayUi::OLEDDisplayUi(OLEDDisplay *display) {
this->display = display;
indicatorPosition = BOTTOM;
indicatorDirection = LEFT_RIGHT;
activeSymbol = ANIMATION_activeSymbol;
inactiveSymbol = ANIMATION_inactiveSymbol;
frameAnimationDirection = SLIDE_RIGHT;
lastTransitionDirection = 1;
frameCount = 0;
nextFrameNumber = -1;
overlayCount = 0;
indicatorDrawState = 1;
loadingDrawFunction = LoadingDrawDefault;
updateInterval = 33;
state.lastUpdate = 0;
state.ticksSinceLastStateSwitch = 0;
state.frameState = FIXED;
state.currentFrame = 0;
state.frameTransitionDirection = 1;
state.isIndicatorDrawen = true;
state.manuelControll = false;
state.userData = NULL;
shouldDrawIndicators = true;
autoTransition = true;
setTimePerFrame(5000);
setTimePerTransition(500);
}
void OLEDDisplayUi::init() {
this->display->init();
}
void OLEDDisplayUi::setTargetFPS(uint8_t fps){
this->updateInterval = ((float) 1.0 / (float) fps) * 1000;
this->ticksPerFrame = timePerFrame / updateInterval;
this->ticksPerTransition = timePerTransition / updateInterval;
}
// -/------ Automatic controll ------\-
void OLEDDisplayUi::enableAutoTransition(){
this->autoTransition = true;
}
void OLEDDisplayUi::disableAutoTransition(){
this->autoTransition = false;
}
void OLEDDisplayUi::setAutoTransitionForwards(){
this->state.frameTransitionDirection = 1;
this->lastTransitionDirection = 1;
}
void OLEDDisplayUi::setAutoTransitionBackwards(){
this->state.frameTransitionDirection = -1;
this->lastTransitionDirection = -1;
}
void OLEDDisplayUi::setTimePerFrame(uint16_t time){
this->timePerFrame = time;
this->ticksPerFrame = timePerFrame / updateInterval;
}
void OLEDDisplayUi::setTimePerTransition(uint16_t time){
this->timePerTransition = time;
this->ticksPerTransition = timePerTransition / updateInterval;
}
// -/------ Customize indicator position and style -------\-
void OLEDDisplayUi::enableIndicator(){
this->state.isIndicatorDrawen = true;
}
void OLEDDisplayUi::disableIndicator(){
this->state.isIndicatorDrawen = false;
}
void OLEDDisplayUi::enableAllIndicators(){
this->shouldDrawIndicators = true;
}
void OLEDDisplayUi::disableAllIndicators(){
this->shouldDrawIndicators = false;
}
void OLEDDisplayUi::setIndicatorPosition(IndicatorPosition pos) {
this->indicatorPosition = pos;
}
void OLEDDisplayUi::setIndicatorDirection(IndicatorDirection dir) {
this->indicatorDirection = dir;
}
void OLEDDisplayUi::setActiveSymbol(const uint8_t* symbol) {
this->activeSymbol = symbol;
}
void OLEDDisplayUi::setInactiveSymbol(const uint8_t* symbol) {
this->inactiveSymbol = symbol;
}
// -/----- Frame settings -----\-
void OLEDDisplayUi::setFrameAnimation(AnimationDirection dir) {
this->frameAnimationDirection = dir;
}
void OLEDDisplayUi::setFrames(FrameCallback* frameFunctions, uint8_t frameCount) {
this->frameFunctions = frameFunctions;
this->frameCount = frameCount;
this->resetState();
}
// -/----- Overlays ------\-
void OLEDDisplayUi::setOverlays(OverlayCallback* overlayFunctions, uint8_t overlayCount){
this->overlayFunctions = overlayFunctions;
this->overlayCount = overlayCount;
}
// -/----- Loading Process -----\-
void OLEDDisplayUi::setLoadingDrawFunction(LoadingDrawFunction loadingDrawFunction) {
this->loadingDrawFunction = loadingDrawFunction;
}
void OLEDDisplayUi::runLoadingProcess(LoadingStage* stages, uint8_t stagesCount) {
uint8_t progress = 0;
uint8_t increment = 100 / stagesCount;
for (uint8_t i = 0; i < stagesCount; i++) {
display->clear();
this->loadingDrawFunction(this->display, &stages[i], progress);
display->display();
stages[i].callback();
progress += increment;
yield();
}
display->clear();
this->loadingDrawFunction(this->display, &stages[stagesCount-1], progress);
display->display();
delay(150);
}
// -/----- Manuel control -----\-
void OLEDDisplayUi::nextFrame() {
if (this->state.frameState != IN_TRANSITION) {
this->state.manuelControll = true;
this->state.frameState = IN_TRANSITION;
this->state.ticksSinceLastStateSwitch = 0;
this->lastTransitionDirection = this->state.frameTransitionDirection;
this->state.frameTransitionDirection = 1;
}
}
void OLEDDisplayUi::previousFrame() {
if (this->state.frameState != IN_TRANSITION) {
this->state.manuelControll = true;
this->state.frameState = IN_TRANSITION;
this->state.ticksSinceLastStateSwitch = 0;
this->lastTransitionDirection = this->state.frameTransitionDirection;
this->state.frameTransitionDirection = -1;
}
}
void OLEDDisplayUi::switchToFrame(uint8_t frame) {
if (frame >= this->frameCount) return;
this->state.ticksSinceLastStateSwitch = 0;
if (frame == this->state.currentFrame) return;
this->state.frameState = FIXED;
this->state.currentFrame = frame;
this->state.isIndicatorDrawen = true;
}
void OLEDDisplayUi::transitionToFrame(uint8_t frame) {
if (frame >= this->frameCount) return;
this->state.ticksSinceLastStateSwitch = 0;
if (frame == this->state.currentFrame) return;
this->nextFrameNumber = frame;
this->lastTransitionDirection = this->state.frameTransitionDirection;
this->state.manuelControll = true;
this->state.frameState = IN_TRANSITION;
this->state.frameTransitionDirection = frame < this->state.currentFrame ? -1 : 1;
}
// -/----- State information -----\-
OLEDDisplayUiState* OLEDDisplayUi::getUiState(){
return &this->state;
}
int16_t OLEDDisplayUi::update(){
#ifdef ARDUINO
unsigned long frameStart = millis();
#elif __MBED__
Timer t;
t.start();
unsigned long frameStart = t.read_ms();
#else
#error "Unkown operating system"
#endif
int32_t timeBudget = this->updateInterval - (frameStart - this->state.lastUpdate);
if ( timeBudget <= 0) {
// Implement frame skipping to ensure time budget is keept
if (this->autoTransition && this->state.lastUpdate != 0) this->state.ticksSinceLastStateSwitch += ceil((double)-timeBudget / (double)this->updateInterval);
this->state.lastUpdate = frameStart;
this->tick();
}
#ifdef ARDUINO
return this->updateInterval - (millis() - frameStart);
#elif __MBED__
return this->updateInterval - (t.read_ms() - frameStart);
#else
#error "Unkown operating system"
#endif
}
void OLEDDisplayUi::tick() {
this->state.ticksSinceLastStateSwitch++;
switch (this->state.frameState) {
case IN_TRANSITION:
if (this->state.ticksSinceLastStateSwitch >= this->ticksPerTransition){
this->state.frameState = FIXED;
this->state.currentFrame = getNextFrameNumber();
this->state.ticksSinceLastStateSwitch = 0;
this->nextFrameNumber = -1;
}
break;
case FIXED:
// Revert manuelControll
if (this->state.manuelControll) {
this->state.frameTransitionDirection = this->lastTransitionDirection;
this->state.manuelControll = false;
}
if (this->state.ticksSinceLastStateSwitch >= this->ticksPerFrame){
if (this->autoTransition){
this->state.frameState = IN_TRANSITION;
}
this->state.ticksSinceLastStateSwitch = 0;
}
break;
}
this->display->clear();
this->drawFrame();
if (shouldDrawIndicators) {
this->drawIndicator();
}
this->drawOverlays();
this->display->display();
}
void OLEDDisplayUi::resetState() {
this->state.lastUpdate = 0;
this->state.ticksSinceLastStateSwitch = 0;
this->state.frameState = FIXED;
this->state.currentFrame = 0;
this->state.isIndicatorDrawen = true;
}
void OLEDDisplayUi::drawFrame(){
switch (this->state.frameState){
case IN_TRANSITION: {
float progress = 0.f;
if (this->ticksPerTransition > 0u) {
progress = (float) this->state.ticksSinceLastStateSwitch / (float) this->ticksPerTransition;
}
int16_t x = 0, y = 0, x1 = 0, y1 = 0;
switch(this->frameAnimationDirection){
case SLIDE_LEFT:
x = -this->display->width() * progress;
y = 0;
x1 = x + this->display->width();
y1 = 0;
break;
case SLIDE_RIGHT:
x = this->display->width() * progress;
y = 0;
x1 = x - this->display->width();
y1 = 0;
break;
case SLIDE_UP:
x = 0;
y = -this->display->height() * progress;
x1 = 0;
y1 = y + this->display->height();
break;
case SLIDE_DOWN:
default:
x = 0;
y = this->display->height() * progress;
x1 = 0;
y1 = y - this->display->height();
break;
}
// Invert animation if direction is reversed.
int8_t dir = this->state.frameTransitionDirection >= 0 ? 1 : -1;
x *= dir; y *= dir; x1 *= dir; y1 *= dir;
bool drawenCurrentFrame;
// Prope each frameFunction for the indicator Drawen state
this->enableIndicator();
(this->frameFunctions[this->state.currentFrame])(this->display, &this->state, x, y);
drawenCurrentFrame = this->state.isIndicatorDrawen;
this->enableIndicator();
(this->frameFunctions[this->getNextFrameNumber()])(this->display, &this->state, x1, y1);
// Build up the indicatorDrawState
if (drawenCurrentFrame && !this->state.isIndicatorDrawen) {
// Drawen now but not next
this->indicatorDrawState = 2;
} else if (!drawenCurrentFrame && this->state.isIndicatorDrawen) {
// Not drawen now but next
this->indicatorDrawState = 1;
} else if (!drawenCurrentFrame && !this->state.isIndicatorDrawen) {
// Not drawen in both frames
this->indicatorDrawState = 3;
}
// If the indicator isn't draw in the current frame
// reflect it in state.isIndicatorDrawen
if (!drawenCurrentFrame) this->state.isIndicatorDrawen = false;
break;
}
case FIXED:
// Always assume that the indicator is drawn!
// And set indicatorDrawState to "not known yet"
this->indicatorDrawState = 0;
this->enableIndicator();
(this->frameFunctions[this->state.currentFrame])(this->display, &this->state, 0, 0);
break;
}
}
void OLEDDisplayUi::drawIndicator() {
// Only draw if the indicator is invisible
// for both frames or
// the indiactor is shown and we are IN_TRANSITION
if (this->indicatorDrawState == 3 || (!this->state.isIndicatorDrawen && this->state.frameState != IN_TRANSITION)) {
return;
}
uint8_t posOfHighlightFrame = 0;
float indicatorFadeProgress = 0;
// if the indicator needs to be slided in we want to
// highlight the next frame in the transition
uint8_t frameToHighlight = this->indicatorDrawState == 1 ? this->getNextFrameNumber() : this->state.currentFrame;
// Calculate the frame that needs to be highlighted
// based on the Direction the indiactor is drawn
switch (this->indicatorDirection){
case LEFT_RIGHT:
posOfHighlightFrame = frameToHighlight;
break;
case RIGHT_LEFT:
default:
posOfHighlightFrame = this->frameCount - frameToHighlight;
break;
}
switch (this->indicatorDrawState) {
case 1: // Indicator was not drawn in this frame but will be in next
// Slide IN
indicatorFadeProgress = 1 - ((float) this->state.ticksSinceLastStateSwitch / (float) this->ticksPerTransition);
break;
case 2: // Indicator was drawn in this frame but not in next
// Slide OUT
indicatorFadeProgress = ((float) this->state.ticksSinceLastStateSwitch / (float) this->ticksPerTransition);
break;
}
//Space between indicators - reduce for small screen sizes
uint16_t indicatorSpacing = 12;
if (this->display->getHeight() < 64 && (this->indicatorPosition == RIGHT || this->indicatorPosition == LEFT)) {
indicatorSpacing = 6;
}
uint16_t frameStartPos = (indicatorSpacing * frameCount / 2);
const uint8_t *image;
uint16_t x = 0,y = 0;
for (uint8_t i = 0; i < this->frameCount; i++) {
switch (this->indicatorPosition){
case TOP:
y = 0 - (8 * indicatorFadeProgress);
x = (this->display->width() / 2) - frameStartPos + 12 * i;
break;
case BOTTOM:
y = (this->display->height() - 8) + (8 * indicatorFadeProgress);
x = (this->display->width() / 2) - frameStartPos + 12 * i;
break;
case RIGHT:
x = (this->display->width() - 8) + (8 * indicatorFadeProgress);
y = (this->display->height() / 2) - frameStartPos + 2 + 12 * i;
break;
case LEFT:
default:
x = 0 - (8 * indicatorFadeProgress);
y = (this->display->height() / 2) - frameStartPos + 2 + indicatorSpacing * i;
break;
}
if (posOfHighlightFrame == i) {
image = this->activeSymbol;
} else {
image = this->inactiveSymbol;
}
this->display->drawFastImage(x, y, 8, 8, image);
}
}
void OLEDDisplayUi::drawOverlays() {
for (uint8_t i=0;i<this->overlayCount;i++){
(this->overlayFunctions[i])(this->display, &this->state);
}
}
uint8_t OLEDDisplayUi::getNextFrameNumber(){
if (this->nextFrameNumber != -1) return this->nextFrameNumber;
return (this->state.currentFrame + this->frameCount + this->state.frameTransitionDirection) % this->frameCount;
}

View File

@@ -0,0 +1,315 @@
/**
* The MIT License (MIT)
*
* Copyright (c) 2018 by ThingPulse, Daniel Eichhorn
* Copyright (c) 2018 by Fabrice Weinberg
* Copyright (c) 2019 by Helmut Tschemernjak - www.radioshuttle.de
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* ThingPulse invests considerable time and money to develop these open source libraries.
* Please support us by buying our products (and not the clones) from
* https://thingpulse.com
*
*/
#ifndef OLEDDISPLAYUI_h
#define OLEDDISPLAYUI_h
#ifdef ARDUINO
#include <Arduino.h>
#elif __MBED__
#include <mbed.h>
#else
#error "Unkown operating system"
#endif
#include "OLEDDisplay.h"
//#define DEBUG_OLEDDISPLAYUI(...) Serial.printf( __VA_ARGS__ )
#ifndef DEBUG_OLEDDISPLAYUI
#define DEBUG_OLEDDISPLAYUI(...)
#endif
enum AnimationDirection {
SLIDE_UP,
SLIDE_DOWN,
SLIDE_LEFT,
SLIDE_RIGHT
};
enum IndicatorPosition {
TOP,
RIGHT,
BOTTOM,
LEFT
};
enum IndicatorDirection {
LEFT_RIGHT,
RIGHT_LEFT
};
enum FrameState {
IN_TRANSITION,
FIXED
};
const uint8_t ANIMATION_activeSymbol[] PROGMEM = {
0x00, 0x18, 0x3c, 0x7e, 0x7e, 0x3c, 0x18, 0x00
};
const uint8_t ANIMATION_inactiveSymbol[] PROGMEM = {
0x00, 0x0, 0x0, 0x18, 0x18, 0x0, 0x0, 0x00
};
// Structure of the UiState
struct OLEDDisplayUiState {
uint64_t lastUpdate;
uint16_t ticksSinceLastStateSwitch;
FrameState frameState;
uint8_t currentFrame;
bool isIndicatorDrawen;
// Normal = 1, Inverse = -1;
int8_t frameTransitionDirection;
bool manuelControll;
// Custom data that can be used by the user
void* userData;
};
struct LoadingStage {
const char* process;
void (*callback)();
};
typedef void (*FrameCallback)(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y);
typedef void (*OverlayCallback)(OLEDDisplay *display, OLEDDisplayUiState* state);
typedef void (*LoadingDrawFunction)(OLEDDisplay *display, LoadingStage* stage, uint8_t progress);
class OLEDDisplayUi {
private:
OLEDDisplay *display;
// Symbols for the Indicator
IndicatorPosition indicatorPosition;
IndicatorDirection indicatorDirection;
const uint8_t* activeSymbol;
const uint8_t* inactiveSymbol;
bool shouldDrawIndicators;
// Values for the Frames
AnimationDirection frameAnimationDirection;
int8_t lastTransitionDirection;
uint16_t ticksPerFrame; // ~ 5000ms at 30 FPS
uint16_t ticksPerTransition; // ~ 500ms at 30 FPS
bool autoTransition;
FrameCallback* frameFunctions;
uint8_t frameCount;
// Internally used to transition to a specific frame
int8_t nextFrameNumber;
// Values for Overlays
OverlayCallback* overlayFunctions;
uint8_t overlayCount;
// Will the Indicator be drawen
// 3 Not drawn in both frames
// 2 Drawn this frame but not next
// 1 Not drown this frame but next
// 0 Not known yet
uint8_t indicatorDrawState;
// Loading screen
LoadingDrawFunction loadingDrawFunction;
// UI State
OLEDDisplayUiState state;
// Bookeeping for update
uint16_t updateInterval = 33;
uint16_t timePerFrame;
uint16_t timePerTransition;
uint8_t getNextFrameNumber();
void drawIndicator();
void drawFrame();
void drawOverlays();
void tick();
void resetState();
public:
OLEDDisplayUi(OLEDDisplay *display);
/**
* Initialise the display
*/
void init();
/**
* Configure the internal used target FPS
*/
void setTargetFPS(uint8_t fps);
// Automatic Controll
/**
* Enable automatic transition to next frame after the some time can be configured with `setTimePerFrame` and `setTimePerTransition`.
*/
void enableAutoTransition();
/**
* Disable automatic transition to next frame.
*/
void disableAutoTransition();
/**
* Set the direction if the automatic transitioning
*/
void setAutoTransitionForwards();
void setAutoTransitionBackwards();
/**
* Set the approx. time a frame is displayed
*/
void setTimePerFrame(uint16_t time);
/**
* Set the approx. time a transition will take
*/
void setTimePerTransition(uint16_t time);
// Customize indicator position and style
/**
* Draw the indicator.
* This is the defaut state for all frames if
* the indicator was hidden on the previous frame
* it will be slided in.
*/
void enableIndicator();
/**
* Don't draw the indicator.
* This will slide out the indicator
* when transitioning to the next frame.
*/
void disableIndicator();
/**
* Enable drawing of indicators
*/
void enableAllIndicators();
/**
* Disable draw of indicators.
*/
void disableAllIndicators();
/**
* Set the position of the indicator bar.
*/
void setIndicatorPosition(IndicatorPosition pos);
/**
* Set the direction of the indicator bar. Defining the order of frames ASCENDING / DESCENDING
*/
void setIndicatorDirection(IndicatorDirection dir);
/**
* Set the symbol to indicate an active frame in the indicator bar.
*/
void setActiveSymbol(const uint8_t* symbol);
/**
* Set the symbol to indicate an inactive frame in the indicator bar.
*/
void setInactiveSymbol(const uint8_t* symbol);
// Frame settings
/**
* Configure what animation is used to transition from one frame to another
*/
void setFrameAnimation(AnimationDirection dir);
/**
* Add frame drawing functions
*/
void setFrames(FrameCallback* frameFunctions, uint8_t frameCount);
// Overlay
/**
* Add overlays drawing functions that are draw independent of the Frames
*/
void setOverlays(OverlayCallback* overlayFunctions, uint8_t overlayCount);
// Loading animation
/**
* Set the function that will draw each step
* in the loading animation
*/
void setLoadingDrawFunction(LoadingDrawFunction loadingFunction);
/**
* Run the loading process
*/
void runLoadingProcess(LoadingStage* stages, uint8_t stagesCount);
// Manual Control
void nextFrame();
void previousFrame();
/**
* Switch without transition to frame `frame`.
*/
void switchToFrame(uint8_t frame);
/**
* Transition to frame `frame`, when the `frame` number is bigger than the current
* frame the forward animation will be used, otherwise the backwards animation is used.
*/
void transitionToFrame(uint8_t frame);
// State Info
OLEDDisplayUiState* getUiState();
int16_t update();
};
#endif

View File

@@ -0,0 +1,39 @@
/**
* The MIT License (MIT)
*
* Copyright (c) 2018 by ThingPulse, Daniel Eichhorn
* Copyright (c) 2018 by Fabrice Weinberg
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* ThingPulse invests considerable time and money to develop these open source libraries.
* Please support us by buying our products (and not the clones) from
* https://thingpulse.com
*
*/
#ifndef SH1106_h
#define SH1106_h
#include "SH1106Wire.h"
// For make SH1106 an alias for SH1106Wire
typedef SH1106Wire SH1106;
#endif

View File

@@ -0,0 +1,141 @@
/**
* The MIT License (MIT)
*
* Copyright (c) 2018 by ThingPulse, Daniel Eichhorn
* Copyright (c) 2018 by Fabrice Weinberg
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* ThingPulse invests considerable time and money to develop these open source libraries.
* Please support us by buying our products (and not the clones) from
* https://thingpulse.com
*
*/
#ifndef SH1106Brzo_h
#define SH1106Brzo_h
#include "OLEDDisplay.h"
#include <brzo_i2c.h>
#if F_CPU == 160000000L
#define BRZO_I2C_SPEED 1000
#else
#define BRZO_I2C_SPEED 800
#endif
class SH1106Brzo : public OLEDDisplay {
private:
uint8_t _address;
uint8_t _sda;
uint8_t _scl;
public:
SH1106Brzo(uint8_t _address, uint8_t _sda, uint8_t _scl, OLEDDISPLAY_GEOMETRY g = GEOMETRY_128_64) {
setGeometry(g);
this->_address = _address;
this->_sda = _sda;
this->_scl = _scl;
}
bool connect(){
brzo_i2c_setup(_sda, _scl, 0);
return true;
}
void display(void) {
#ifdef OLEDDISPLAY_DOUBLE_BUFFER
uint8_t minBoundY = UINT8_MAX;
uint8_t maxBoundY = 0;
uint8_t minBoundX = UINT8_MAX;
uint8_t maxBoundX = 0;
uint8_t x, y;
// Calculate the Y bounding box of changes
// and copy buffer[pos] to buffer_back[pos];
for (y = 0; y < (displayHeight / 8); y++) {
for (x = 0; x < displayWidth; x++) {
uint16_t pos = x + y * displayWidth;
if (buffer[pos] != buffer_back[pos]) {
minBoundY = _min(minBoundY, y);
maxBoundY = _max(maxBoundY, y);
minBoundX = _min(minBoundX, x);
maxBoundX = _max(maxBoundX, x);
}
buffer_back[pos] = buffer[pos];
}
yield();
}
// If the minBoundY wasn't updated
// we can savely assume that buffer_back[pos] == buffer[pos]
// holdes true for all values of pos
if (minBoundY == UINT8_MAX) return;
byte k = 0;
uint8_t sendBuffer[17];
sendBuffer[0] = 0x40;
// Calculate the colum offset
uint8_t minBoundXp2H = (minBoundX + 2) & 0x0F;
uint8_t minBoundXp2L = 0x10 | ((minBoundX + 2) >> 4 );
brzo_i2c_start_transaction(this->_address, BRZO_I2C_SPEED);
for (y = minBoundY; y <= maxBoundY; y++) {
sendCommand(0xB0 + y);
sendCommand(minBoundXp2H);
sendCommand(minBoundXp2L);
for (x = minBoundX; x <= maxBoundX; x++) {
k++;
sendBuffer[k] = buffer[x + y * displayWidth];
if (k == 16) {
brzo_i2c_write(sendBuffer, 17, true);
k = 0;
}
}
if (k != 0) {
brzo_i2c_write(sendBuffer, k + 1, true);
k = 0;
}
yield();
}
if (k != 0) {
brzo_i2c_write(sendBuffer, k + 1, true);
}
brzo_i2c_end_transaction();
#else
#endif
}
private:
int getBufferOffset(void) {
return 0;
}
inline void sendCommand(uint8_t com) __attribute__((always_inline)){
uint8_t command[2] = {0x80 /* command mode */, com};
brzo_i2c_start_transaction(_address, BRZO_I2C_SPEED);
brzo_i2c_write(command, 2, true);
brzo_i2c_end_transaction();
}
};
#endif

View File

@@ -0,0 +1,135 @@
/**
* The MIT License (MIT)
*
* Copyright (c) 2018 by ThingPulse, Daniel Eichhorn
* Copyright (c) 2018 by Fabrice Weinberg
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* ThingPulse invests considerable time and money to develop these open source libraries.
* Please support us by buying our products (and not the clones) from
* https://thingpulse.com
*
*/
#ifndef SH1106Spi_h
#define SH1106Spi_h
#include "OLEDDisplay.h"
#include <SPI.h>
class SH1106Spi : public OLEDDisplay {
private:
uint8_t _rst;
uint8_t _dc;
public:
SH1106Spi(uint8_t _rst, uint8_t _dc, uint8_t _cs, OLEDDISPLAY_GEOMETRY g = GEOMETRY_128_64) {
setGeometry(g);
this->_rst = _rst;
this->_dc = _dc;
}
bool connect(){
pinMode(_dc, OUTPUT);
pinMode(_rst, OUTPUT);
SPI.begin ();
SPI.setClockDivider (SPI_CLOCK_DIV2);
// Pulse Reset low for 10ms
digitalWrite(_rst, HIGH);
delay(1);
digitalWrite(_rst, LOW);
delay(10);
digitalWrite(_rst, HIGH);
return true;
}
void display(void) {
#ifdef OLEDDISPLAY_DOUBLE_BUFFER
uint8_t minBoundY = UINT8_MAX;
uint8_t maxBoundY = 0;
uint8_t minBoundX = UINT8_MAX;
uint8_t maxBoundX = 0;
uint8_t x, y;
// Calculate the Y bounding box of changes
// and copy buffer[pos] to buffer_back[pos];
for (y = 0; y < (displayHeight / 8); y++) {
for (x = 0; x < displayWidth; x++) {
uint16_t pos = x + y * displayWidth;
if (buffer[pos] != buffer_back[pos]) {
minBoundY = _min(minBoundY, y);
maxBoundY = _max(maxBoundY, y);
minBoundX = _min(minBoundX, x);
maxBoundX = _max(maxBoundX, x);
}
buffer_back[pos] = buffer[pos];
}
yield();
}
// If the minBoundY wasn't updated
// we can savely assume that buffer_back[pos] == buffer[pos]
// holdes true for all values of pos
if (minBoundY == UINT8_MAX) return;
// Calculate the colum offset
uint8_t minBoundXp2H = (minBoundX + 2) & 0x0F;
uint8_t minBoundXp2L = 0x10 | ((minBoundX + 2) >> 4 );
for (y = minBoundY; y <= maxBoundY; y++) {
sendCommand(0xB0 + y);
sendCommand(minBoundXp2H);
sendCommand(minBoundXp2L);
digitalWrite(_dc, HIGH); // data mode
for (x = minBoundX; x <= maxBoundX; x++) {
SPI.transfer(buffer[x + y * displayWidth]);
}
yield();
}
#else
for (uint8_t y=0; y<displayHeight/8; y++) {
sendCommand(0xB0 + y);
sendCommand(0x02);
sendCommand(0x10);
digitalWrite(_dc, HIGH); // data mode
for( uint8_t x=0; x < displayWidth; x++) {
SPI.transfer(buffer[x + y * displayWidth]);
}
yield();
}
#endif
}
private:
int getBufferOffset(void) {
return 0;
}
inline void sendCommand(uint8_t com) __attribute__((always_inline)){
digitalWrite(_dc, LOW);
SPI.transfer(com);
}
};
#endif

Some files were not shown because too many files have changed in this diff Show More