mirror of
https://github.com/sascha-hemi/pycom-documentation.git
synced 2026-03-21 13:06:14 +01:00
GitBook: [master] 333 pages modified
This commit is contained in:
committed by
gitbook-bot
parent
260e81c8e8
commit
015fdc88ef
4
tutorials/all/README.md
Normal file
4
tutorials/all/README.md
Normal file
@@ -0,0 +1,4 @@
|
||||
# All Pycom Device Examples
|
||||
|
||||
This section contains generic examples that will work across all Pycom devices and Expansion Boards.
|
||||
|
||||
35
tutorials/all/adc.md
Normal file
35
tutorials/all/adc.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# ADC
|
||||
|
||||
This example is a simple ADC sample. For more information please see [`ADC`](../../firmwareapi/pycom/machine/adc.md).
|
||||
|
||||
```python
|
||||
from machine import ADC
|
||||
adc = ADC(0)
|
||||
adc_c = adc.channel(pin='P13')
|
||||
adc_c()
|
||||
adc_c.value()
|
||||
```
|
||||
|
||||
## Calibration
|
||||
|
||||
Currently the ESP32's ADC is not calibrated from the factory. This means it must be calibrated each time you wish to use it. To do this you must firstly measure the internal voltage reference. The following code will connect the 1.1v reference to `P22`
|
||||
|
||||
```python
|
||||
from machine import ADC
|
||||
adc = ADC()
|
||||
|
||||
# Output Vref of P22
|
||||
adc.vref_to_pin('P22')
|
||||
```
|
||||
|
||||
Now that the voltage reference is externally accessible you should measure it with the most accurate voltmeter you have access to. Note down the reading in millivolts, e.g. `1120`. To disconnect the 1.1v reference from `P22` please reset your module. You can now calibrate the ADC by telling it the true value of the internal reference. You should then check your calibration by connecting the ADC to a known voltage source.
|
||||
|
||||
```python
|
||||
# Set calibration - see note above
|
||||
adc.vref(1100)
|
||||
|
||||
# Check calibration by reading a known voltage
|
||||
adc_c = adc.channel(pin='P16', attn=ADC.ATTN_11DB)
|
||||
print(adc_c.voltage())
|
||||
```
|
||||
|
||||
198
tutorials/all/aws.md
Normal file
198
tutorials/all/aws.md
Normal file
@@ -0,0 +1,198 @@
|
||||
# AWS
|
||||
|
||||
The AWS IoT platform enables devices to connect to the Amazon cloud and lets applications in the cloud interact with Internet-connected things. Common IoT applications either collect and process telemetry from devices or enable users to control a device remotely. Things report their state by publishing messages, in JSON format, on MQTT topics.
|
||||
|
||||
For more information see this [PDF File](http://docs.aws.amazon.com/iot/latest/developerguide/iot-dg.pdf).
|
||||
|
||||
## Getting Started with AWS IoT
|
||||
|
||||
### Creating the message broker \(Amazon website\):
|
||||
|
||||
* Sign in to the [AWS Management Console](https://aws.amazon.com/console/)
|
||||
* Navigate to the IoT Console by clicking on the [AWS IoT link](https://github.com/pycom/pycom-docs/tree/37661883902849b1a931ee273a23ae8e0f3d773e/img/aws-1.png)
|
||||
* In the left navigation pane, choose [Register/Manage](https://github.com/pycom/pycom-docs/tree/37661883902849b1a931ee273a23ae8e0f3d773e/img/aws-2.png)
|
||||
* Click on the create button, give your [device a name and press create](https://github.com/pycom/pycom-docs/tree/37661883902849b1a931ee273a23ae8e0f3d773e/img/aws-3.png)
|
||||
* Click on the device that has been created
|
||||
* On the Details page, in the left navigation pane, choose [Security](https://github.com/pycom/pycom-docs/tree/37661883902849b1a931ee273a23ae8e0f3d773e/img/aws-4.png)
|
||||
* On the Certificates page, choose Create certificate
|
||||
* Download all the certificates, then press the Activate and the Attach a Policy buttons. [See image](https://github.com/pycom/pycom-docs/tree/37661883902849b1a931ee273a23ae8e0f3d773e/img/aws-5.png)
|
||||
* Click on the Create New Policy button
|
||||
* On the [Create Policy](https://github.com/pycom/pycom-docs/tree/37661883902849b1a931ee273a23ae8e0f3d773e/img/aws-6.png) page, choose a policy name and the actions to authorise.
|
||||
* Go to the certificates page, click on the three dots of your certificate and attach the policy to the certificate as shown in the [diagram](https://github.com/pycom/pycom-docs/tree/37661883902849b1a931ee273a23ae8e0f3d773e/img/aws-7.png)
|
||||
|
||||
### Setting up the device \(Pycom device\):
|
||||
|
||||
* Download the latest sample code from the Pycom [GitHub Repository](https://github.com/pycom/aws-pycom).
|
||||
* Connect to the device via FTP and put the root CA certificate, the client certificate \(`*.pem.crt`\) and the private key \(`*.private.pem.key`\) in the `/flash/cert` folder.
|
||||
* Update the config file with your WiFi settings, the [AWS Host](https://github.com/pycom/pycom-docs/tree/37661883902849b1a931ee273a23ae8e0f3d773e/img/aws-8.png) and the certificate paths.
|
||||
* Put the `config.py` and the `main.py` in the device flash
|
||||
|
||||
### Configuration \(`config.py`\):
|
||||
|
||||
This file contains the WiFi, certificate paths and application specific settings that need to be updated by the user.
|
||||
|
||||
```python
|
||||
# WiFi configuration
|
||||
WIFI_SSID = 'my_wifi_ssid'
|
||||
WIFI_PASS = 'my_wifi_password'
|
||||
|
||||
# AWS general configuration
|
||||
AWS_PORT = 8883
|
||||
AWS_HOST = 'aws_host_url'
|
||||
AWS_ROOT_CA = '/flash/cert/aws_root.ca'
|
||||
AWS_CLIENT_CERT = '/flash/cert/aws_client.cert'
|
||||
AWS_PRIVATE_KEY = '/flash/cert/aws_private.key'
|
||||
|
||||
################## Subscribe / Publish client #################
|
||||
CLIENT_ID = 'PycomPublishClient'
|
||||
TOPIC = 'PublishTopic'
|
||||
OFFLINE_QUEUE_SIZE = -1
|
||||
DRAINING_FREQ = 2
|
||||
CONN_DISCONN_TIMEOUT = 10
|
||||
MQTT_OPER_TIMEOUT = 5
|
||||
LAST_WILL_TOPIC = 'PublishTopic'
|
||||
LAST_WILL_MSG = 'To All: Last will message'
|
||||
|
||||
####################### Shadow updater ########################
|
||||
#THING_NAME = "my thing name"
|
||||
#CLIENT_ID = "ShadowUpdater"
|
||||
#CONN_DISCONN_TIMEOUT = 10
|
||||
#MQTT_OPER_TIMEOUT = 5
|
||||
|
||||
####################### Delta Listener ########################
|
||||
#THING_NAME = "my thing name"
|
||||
#CLIENT_ID = "DeltaListener"
|
||||
#CONN_DISCONN_TIMEOUT = 10
|
||||
#MQTT_OPER_TIMEOUT = 5
|
||||
|
||||
####################### Shadow Echo ########################
|
||||
#THING_NAME = "my thing name"
|
||||
#CLIENT_ID = "ShadowEcho"
|
||||
#CONN_DISCONN_TIMEOUT = 10
|
||||
#MQTT_OPER_TIMEOUT = 5
|
||||
```
|
||||
|
||||
### Subscibe / Publish \(`main.py`\)
|
||||
|
||||
To subscribe to a topic:
|
||||
|
||||
* Go to the AWS Iot page, click on manage and choose your device
|
||||
* From the left hand side, choose Activity and then click MQTT client.
|
||||
* Choose the [topic name](https://github.com/pycom/pycom-docs/tree/37661883902849b1a931ee273a23ae8e0f3d773e/img/aws-9.png) you entered in the configuration file.
|
||||
* Messages should be published as shown in the [diagram](https://github.com/pycom/pycom-docs/tree/37661883902849b1a931ee273a23ae8e0f3d773e/img/aws-10.png)
|
||||
|
||||
```python
|
||||
# user specified callback function
|
||||
def customCallback(client, userdata, message):
|
||||
print("Received a new message: ")
|
||||
print(message.payload)
|
||||
print("from topic: ")
|
||||
print(message.topic)
|
||||
print("--------------\n\n")
|
||||
|
||||
# configure the MQTT client
|
||||
pycomAwsMQTTClient = AWSIoTMQTTClient(config.CLIENT_ID)
|
||||
pycomAwsMQTTClient.configureEndpoint(config.AWS_HOST, config.AWS_PORT)
|
||||
pycomAwsMQTTClient.configureCredentials(config.AWS_ROOT_CA, config.AWS_PRIVATE_KEY, config.AWS_CLIENT_CERT)
|
||||
|
||||
pycomAwsMQTTClient.configureOfflinePublishQueueing(config.OFFLINE_QUEUE_SIZE)
|
||||
pycomAwsMQTTClient.configureDrainingFrequency(config.DRAINING_FREQ)
|
||||
pycomAwsMQTTClient.configureConnectDisconnectTimeout(config.CONN_DISCONN_TIMEOUT)
|
||||
pycomAwsMQTTClient.configureMQTTOperationTimeout(config.MQTT_OPER_TIMEOUT)
|
||||
pycomAwsMQTTClient.configureLastWill(config.LAST_WILL_TOPIC, config.LAST_WILL_MSG, 1)
|
||||
|
||||
#Connect to MQTT Host
|
||||
if pycomAwsMQTTClient.connect():
|
||||
print('AWS connection succeeded')
|
||||
|
||||
# Subscribe to topic
|
||||
pycomAwsMQTTClient.subscribe(config.TOPIC, 1, customCallback)
|
||||
time.sleep(2)
|
||||
|
||||
# Send message to host
|
||||
loopCount = 0
|
||||
while loopCount < 8:
|
||||
pycomAwsMQTTClient.publish(config.TOPIC, "New Message " + str(loopCount), 1)
|
||||
loopCount += 1
|
||||
time.sleep(5.0)
|
||||
```
|
||||
|
||||
### Shadow updater \(`main.py`\)
|
||||
|
||||
```python
|
||||
# user specified callback functions
|
||||
def customShadowCallback_Update(payload, responseStatus, token):
|
||||
if responseStatus == "timeout":
|
||||
print("Update request " + token + " time out!")
|
||||
if responseStatus == "accepted":
|
||||
payloadDict = json.loads(payload)
|
||||
print("Update request with token: " + token + " accepted!")
|
||||
print("property: " + str(payloadDict["state"]["desired"]["property"]))
|
||||
if responseStatus == "rejected":
|
||||
print("Update request " + token + " rejected!")
|
||||
|
||||
def customShadowCallback_Delete(payload, responseStatus, token):
|
||||
if responseStatus == "timeout":
|
||||
print("Delete request " + token + " time out!")
|
||||
if responseStatus == "accepted":
|
||||
print("Delete request with token: " + token + " accepted!")
|
||||
if responseStatus == "rejected":
|
||||
print("Delete request " + token + " rejected!")
|
||||
|
||||
# configure the MQTT client
|
||||
pycomAwsMQTTShadowClient = AWSIoTMQTTShadowClient(config.CLIENT_ID)
|
||||
pycomAwsMQTTShadowClient.configureEndpoint(config.AWS_HOST, config.AWS_PORT)
|
||||
pycomAwsMQTTShadowClient.configureCredentials(config.AWS_ROOT_CA, config.AWS_PRIVATE_KEY, config.AWS_CLIENT_CERT)
|
||||
|
||||
pycomAwsMQTTShadowClient.configureConnectDisconnectTimeout(config.CONN_DISCONN_TIMEOUT)
|
||||
pycomAwsMQTTShadowClient.configureMQTTOperationTimeout(config.MQTT_OPER_TIMEOUT)
|
||||
|
||||
# Connect to MQTT Host
|
||||
if pycomAwsMQTTShadowClient.connect():
|
||||
print('AWS connection succeeded')
|
||||
|
||||
deviceShadowHandler = pycomAwsMQTTShadowClient.createShadowHandlerWithName(config.THING_NAME, True)
|
||||
|
||||
# Delete shadow JSON doc
|
||||
deviceShadowHandler.shadowDelete(customShadowCallback_Delete, 5)
|
||||
|
||||
# Update shadow in a loop
|
||||
loopCount = 0
|
||||
while True:
|
||||
JSONPayload = '{"state":{"desired":{"property":' + str(loopCount) + '}}}'
|
||||
deviceShadowHandler.shadowUpdate(JSONPayload, customShadowCallback_Update, 5)
|
||||
loopCount += 1
|
||||
time.sleep(5)
|
||||
```
|
||||
|
||||
### Delta Listener \(`main.py`\)
|
||||
|
||||
```python
|
||||
# Custom Shadow callback
|
||||
def customShadowCallback_Delta(payload, responseStatus, token):
|
||||
payloadDict = json.loads(payload)
|
||||
print("property: " + str(payloadDict["state"]["property"]))
|
||||
print("version: " + str(payloadDict["version"]))
|
||||
|
||||
# configure the MQTT client
|
||||
pycomAwsMQTTShadowClient = AWSIoTMQTTShadowClient(config.CLIENT_ID)
|
||||
pycomAwsMQTTShadowClient.configureEndpoint(config.AWS_HOST, config.AWS_PORT)
|
||||
pycomAwsMQTTShadowClient.configureCredentials(config.AWS_ROOT_CA, config.AWS_PRIVATE_KEY, config.AWS_CLIENT_CERT)
|
||||
|
||||
pycomAwsMQTTShadowClient.configureConnectDisconnectTimeout(config.CONN_DISCONN_TIMEOUT)
|
||||
pycomAwsMQTTShadowClient.configureMQTTOperationTimeout(config.MQTT_OPER_TIMEOUT)
|
||||
|
||||
# Connect to MQTT Host
|
||||
if pycomAwsMQTTShadowClient.connect():
|
||||
print('AWS connection succeeded')
|
||||
|
||||
deviceShadowHandler = pycomAwsMQTTShadowClient.createShadowHandlerWithName(config.THING_NAME, True)
|
||||
|
||||
# Listen on deltas
|
||||
deviceShadowHandler.shadowRegisterDeltaCallback(customShadowCallback_Delta)
|
||||
|
||||
# Loop forever
|
||||
while True:
|
||||
time.sleep(1)
|
||||
```
|
||||
|
||||
111
tutorials/all/ble.md
Normal file
111
tutorials/all/ble.md
Normal file
@@ -0,0 +1,111 @@
|
||||
# Bluetooth
|
||||
|
||||
At present, basic BLE functionality is available. More features will be implemented in the near future, such as pairing. This page will be updated in line with these features.
|
||||
|
||||
Full info on `bluetooth` can be found within [Bluetooth page](../../firmwareapi/pycom/network/bluetooth/) of the Firmware API Reference.
|
||||
|
||||
## Scan for BLE Devices
|
||||
|
||||
Scan for all of the advertising devices within range of the scanning device.
|
||||
|
||||
```python
|
||||
bluetooth.start_scan(10) # starts scanning and stop after 10 seconds
|
||||
bluetooth.start_scan(-1) # starts scanning indefinitely until bluetooth.stop_scan() is called
|
||||
```
|
||||
|
||||
## Raw Data from a BLE Device
|
||||
|
||||
A quick usage example that scans and prints the raw data from advertisements.
|
||||
|
||||
```python
|
||||
from network import Bluetooth
|
||||
|
||||
bluetooth = Bluetooth()
|
||||
bluetooth.start_scan(-1) # start scanning with no timeout
|
||||
|
||||
while True:
|
||||
print(bluetooth.get_adv())
|
||||
```
|
||||
|
||||
## Connect to a BLE Device
|
||||
|
||||
Connecting to a device that is sending advertisements.
|
||||
|
||||
```python
|
||||
from network import Bluetooth
|
||||
import ubinascii
|
||||
bluetooth = Bluetooth()
|
||||
|
||||
# scan until we can connect to any BLE device around
|
||||
bluetooth.start_scan(-1)
|
||||
adv = None
|
||||
while True:
|
||||
adv = bluetooth.get_adv()
|
||||
if adv:
|
||||
try:
|
||||
bluetooth.connect(adv.mac)
|
||||
except:
|
||||
# start scanning again
|
||||
bluetooth.start_scan(-1)
|
||||
continue
|
||||
break
|
||||
print("Connected to device with addr = {}".format(ubinascii.hexlify(adv.mac)))
|
||||
```
|
||||
|
||||
## Connect to a BLE Device and Retrieve Data
|
||||
|
||||
Connecting to a device named 'Heart Rate' and receiving data from it’s services.
|
||||
|
||||
```python
|
||||
from network import Bluetooth
|
||||
import time
|
||||
bt = Bluetooth()
|
||||
bt.start_scan(-1)
|
||||
|
||||
while True:
|
||||
adv = bt.get_adv()
|
||||
if adv and bt.resolve_adv_data(adv.data, Bluetooth.ADV_NAME_CMPL) == 'Heart Rate':
|
||||
try:
|
||||
conn = bt.connect(adv.mac)
|
||||
services = conn.services()
|
||||
for service in services:
|
||||
time.sleep(0.050)
|
||||
if type(service.uuid()) == bytes:
|
||||
print('Reading chars from service = {}'.format(service.uuid()))
|
||||
else:
|
||||
print('Reading chars from service = %x' % service.uuid())
|
||||
chars = service.characteristics()
|
||||
for char in chars:
|
||||
if (char.properties() & Bluetooth.PROP_READ):
|
||||
print('char {} value = {}'.format(char.uuid(), char.read()))
|
||||
conn.disconnect()
|
||||
break
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
time.sleep(0.050)
|
||||
```
|
||||
|
||||
## Retrieve the Name & Manufacturer from a BLE Device
|
||||
|
||||
Using `resolve_adv_data()` to attempt to retrieve the name and manufacturer data from the advertiser.
|
||||
|
||||
```python
|
||||
import ubinascii
|
||||
from network import Bluetooth
|
||||
bluetooth = Bluetooth()
|
||||
|
||||
bluetooth.start_scan(20)
|
||||
while bluetooth.isscanning():
|
||||
adv = bluetooth.get_adv()
|
||||
if adv:
|
||||
# try to get the complete name
|
||||
print(bluetooth.resolve_adv_data(adv.data, Bluetooth.ADV_NAME_CMPL))
|
||||
|
||||
mfg_data = bluetooth.resolve_adv_data(adv.data, Bluetooth.ADV_MANUFACTURER_DATA)
|
||||
|
||||
if mfg_data:
|
||||
# try to get the manufacturer data (Apple's iBeacon data is sent here)
|
||||
print(ubinascii.hexlify(mfg_data))
|
||||
```
|
||||
|
||||
28
tutorials/all/https.md
Normal file
28
tutorials/all/https.md
Normal file
@@ -0,0 +1,28 @@
|
||||
# HTTPS
|
||||
|
||||
Basic connection using `ssl.wrap_socket()`.
|
||||
|
||||
```python
|
||||
import socket
|
||||
import ssl
|
||||
|
||||
s = socket.socket()
|
||||
ss = ssl.wrap_socket(s)
|
||||
ss.connect(socket.getaddrinfo('www.google.com', 443)[0][-1])
|
||||
```
|
||||
|
||||
Below is an example using certificates with the blynk cloud.
|
||||
|
||||
Certificate was downloaded from the blynk examples [folder](https://github.com/wipy/wipy/tree/master/examples/blynk) and placed in `/flash/cert/` on the device.
|
||||
|
||||
```python
|
||||
import socket
|
||||
import ssl
|
||||
|
||||
s = socket.socket()
|
||||
ss = ssl.wrap_socket(s, cert_reqs=ssl.CERT_REQUIRED, ca_certs='/flash/cert/ca.pem')
|
||||
ss.connect(socket.getaddrinfo('cloud.blynk.cc', 8441)[0][-1])
|
||||
```
|
||||
|
||||
For more info, check the [`ssl`](../../firmwareapi/micropython/ussl.md) module in the API reference.
|
||||
|
||||
94
tutorials/all/i2c.md
Normal file
94
tutorials/all/i2c.md
Normal file
@@ -0,0 +1,94 @@
|
||||
# I2C
|
||||
|
||||
The following example receives data from a light sensor using I2C. Sensor used is the BH1750FVI Digital Light Sensor.
|
||||
|
||||
```python
|
||||
import time
|
||||
from machine import I2C
|
||||
import bh1750fvi
|
||||
|
||||
i2c = I2C(0, I2C.MASTER, baudrate=100000)
|
||||
light_sensor = bh1750fvi.BH1750FVI(i2c, addr=i2c.scan()[0])
|
||||
|
||||
while(True):
|
||||
data = light_sensor.read()
|
||||
print(data)
|
||||
time.sleep(1)
|
||||
```
|
||||
|
||||
## Drivers for the BH1750FVI
|
||||
|
||||
Place this sample code into a file named `bh1750fvi.py`. This can then be imported as a library.
|
||||
|
||||
```python
|
||||
# Simple driver for the BH1750FVI digital light sensor
|
||||
|
||||
class BH1750FVI:
|
||||
MEASUREMENT_TIME = const(120)
|
||||
|
||||
def __init__(self, i2c, addr=0x23, period=150):
|
||||
self.i2c = i2c
|
||||
self.period = period
|
||||
self.addr = addr
|
||||
self.time = 0
|
||||
self.value = 0
|
||||
self.i2c.writeto(addr, bytes([0x10])) # start continuos 1 Lux readings every 120ms
|
||||
|
||||
def read(self):
|
||||
self.time += self.period
|
||||
if self.time >= MEASUREMENT_TIME:
|
||||
self.time = 0
|
||||
data = self.i2c.readfrom(self.addr, 2)
|
||||
self.value = (((data[0] << 8) + data[1]) * 1200) // 1000
|
||||
return self.value
|
||||
```
|
||||
|
||||
## Light sensor and LoRa
|
||||
|
||||
This is the same code, with added LoRa connectivity, sending the lux value from the light sensor to another LoRa enabled device.
|
||||
|
||||
```python
|
||||
import socket
|
||||
import time
|
||||
import pycom
|
||||
import struct
|
||||
from network import LoRa
|
||||
from machine import I2C
|
||||
import bh1750fvi
|
||||
|
||||
LORA_PKG_FORMAT = "!BH"
|
||||
LORA_CONFIRM_FORMAT = "!BB"
|
||||
|
||||
DEVICE_ID = 1
|
||||
|
||||
pycom.heartbeat(False)
|
||||
|
||||
lora = LoRa(mode=LoRa.LORA, tx_iq=True, region=LoRa.EU868)
|
||||
lora_sock = socket.socket(socket.AF_LORA, socket.SOCK_RAW)
|
||||
lora_sock.setblocking(False)
|
||||
|
||||
i2c = I2C(0, I2C.MASTER, baudrate=100000)
|
||||
light_sensor = bh1750fvi.BH1750FVI(i2c, addr=i2c.scan()[0])
|
||||
|
||||
while(True):
|
||||
msg = struct.pack(LORA_PKG_FORMAT, DEVICE_ID, light_sensor.read())
|
||||
lora_sock.send(msg)
|
||||
|
||||
pycom.rgbled(0x150000)
|
||||
|
||||
wait = 5
|
||||
while (wait > 0):
|
||||
wait = wait - 0.1
|
||||
time.sleep(0.1)
|
||||
recv_data = lora_sock.recv(64)
|
||||
|
||||
if (len (recv_data) >= 2):
|
||||
status, device_id = struct.unpack(LORA_CONFIRM_FORMAT, recv_data)
|
||||
|
||||
if (device_id == DEVICE_ID and status == 200):
|
||||
pycom.rgbled(0x001500)
|
||||
wait = 0
|
||||
|
||||
time.sleep(1)
|
||||
```
|
||||
|
||||
133
tutorials/all/modbus.md
Normal file
133
tutorials/all/modbus.md
Normal file
@@ -0,0 +1,133 @@
|
||||
# Modbus
|
||||
|
||||
Modbus is a messaging protocol that defines the packet structure for transferring data between devices in a master/slave architecture. The protocol is independent of the transmission medium and is usually transmitted over TCP \(MODBUS TCP\) or serial communication \(MODBUS RTU\). Modbus is intended as a request/reply protocol and delivers services specified by function codes. The function code in the request tells the addressed slave what kind of action to perform. The function codes most commonly supported by devices are listed below.
|
||||
|
||||
| Function Name | Function Code |
|
||||
| :--- | :--- |
|
||||
| Read Coils | 0x01 |
|
||||
| Read Discrete Inputs | 0x02 |
|
||||
| Read Holding Registers | 0x03 |
|
||||
| Read Input Registers | 0x04 |
|
||||
| Write Single Coil | 0x05 |
|
||||
| Write Single Register | 0x06 |
|
||||
| Write Multiple Coils | 0x0F |
|
||||
| Write Multiple Registers | 0x10 |
|
||||
|
||||
For more information on the MODBUS RTU see the following [PDF File](http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf). Information on the MODBUS TCP can be found [here](http://www.modbus.org/docs/Modbus_Messaging_Implementation_Guide_V1_0b.pdf).
|
||||
|
||||
## Pycom Modbus Library
|
||||
|
||||
Python libraries and sample code that support Modbus TCP and Modbus RTU are available at the following [GitHub Repository](https://github.com/pycom/pycom-modbus). To use this library, connect to the target Pycom device via ftp and upload the uModbus folder to `/flash`. A description of the supported function codes is found below.
|
||||
|
||||
### Read Coils
|
||||
|
||||
This function code requests the status \(ON/OFF\) of discrete coils on a remote device. The slave device address, the address of the first coil and the number of coils must be specified in the request. The address of the first coil is 0 and a maximum of 2000 contiguous coils can be read. Python sample code is shown below.
|
||||
|
||||
```python
|
||||
slave_addr=0x0A
|
||||
starting_address=0x00
|
||||
coil_quantity=100
|
||||
|
||||
coil_status = modbus_obj.read_coils(slave_addr, starting_address, coil_quantity)
|
||||
print('Coil status: ' + ' '.join('{:d}'.format(x) for x in coil_status))
|
||||
```
|
||||
|
||||
### Read Discrete Inputs
|
||||
|
||||
This command is used to read the status \(ON/OFF\) of discrete inputs on a remote device. The slave address, the address of the first input, and the quantity of inputs to be read must be specified. The address of the first input is 0 and a maximum of 2000 continuous inputs can be read. The Python sample code is shown below.
|
||||
|
||||
```python
|
||||
slave_addr=0x0A
|
||||
starting_address=0x0
|
||||
input_quantity=100
|
||||
|
||||
input_status = modbus_obj.read_discrete_inputs(slave_addr, starting_address, input_quantity)
|
||||
print('Input status: ' + ' '.join('{:d}'.format(x) for x in input_status))
|
||||
```
|
||||
|
||||
### Read Holding Registers
|
||||
|
||||
This function code is used to read the contents of analogue output holding registers. The slave address, the starting register address, the number of registers to read and the sign of the data must be specified. Register addresses start at 0 and a maximum of 125 continuous registers can be read.
|
||||
|
||||
```python
|
||||
slave_addr=0x0A
|
||||
starting_address=0x00
|
||||
register_quantity=100
|
||||
signed=True
|
||||
|
||||
register_value = modbus_obj.read_holding_registers(slave_addr, starting_address, register_quantity, signed)
|
||||
print('Holding register value: ' + ' '.join('{:d}'.format(x) for x in register_value))
|
||||
```
|
||||
|
||||
### Read Input Registers
|
||||
|
||||
This command is used to read up to 125 continuous input registers on a remote device. The slave address, the starting register address, the number of input registers and the sign of the data must be specified. The address of the first input registers is 0.
|
||||
|
||||
```python
|
||||
slave_addr=0x0A
|
||||
starting_address=0x00
|
||||
register_quantity=100
|
||||
signed=True
|
||||
|
||||
register_value = modbus_obj.read_input_registers(slave_addr, starting_address, register_quantity, signed)
|
||||
print('Input register value: ' + ' '.join('{:d}'.format(x) for x in register_value))
|
||||
```
|
||||
|
||||
### Write Single Coil
|
||||
|
||||
This function code is used to write the state of a discrete coil on a remote device. A value of `0xFF00` means the coil should be set to ON, while a value of `0x0000` means the coil should be set to OFF. The Python sample code to set the coil at address `0x00`, to an ON state is shown below.
|
||||
|
||||
```python
|
||||
slave_addr=0x0A
|
||||
output_address=0x00
|
||||
output_value=0xFF00
|
||||
|
||||
return_flag = modbus_obj.write_single_coil(slave_addr, output_address, output_value)
|
||||
output_flag = 'Success' if return_flag else 'Failure'
|
||||
print('Writing single coil status: ' + output_flag)
|
||||
```
|
||||
|
||||
### Write Single Register
|
||||
|
||||
This command is used to write the contents of an analog output holding register on a remote device. The slave address, the register address, the register value, and the signature of the data must be specified. As for all the other commands, the register addresses start from 0.
|
||||
|
||||
```python
|
||||
slave_addr=0x0A
|
||||
register_address=0x01
|
||||
register_value=-32768
|
||||
signed=True
|
||||
|
||||
return_flag = modbus_obj.write_single_register(slave_addr, register_address, register_value, signed)
|
||||
output_flag = 'Success' if return_flag else 'Failure'
|
||||
print('Writing single coil status: ' + output_flag)
|
||||
```
|
||||
|
||||
### Write Multiple Coils
|
||||
|
||||
This function code is used to set a continuous sequence of coils, in a remote device, to either ON or OFF. The slave address, the starting address of the coils and an array with the coil states must be specified.
|
||||
|
||||
```python
|
||||
slave_addr=0x0A
|
||||
starting_address=0x00
|
||||
output_values=[1,1,1,0,0,1,1,1,0,0,1,1,1]
|
||||
|
||||
return_flag = modbus_obj.write_multiple_coils(slave_addr, starting_address, output_values)
|
||||
output_flag = 'Success' if return_flag else 'Failure'
|
||||
print('Writing multiple coil status: ' + output_flag)
|
||||
```
|
||||
|
||||
### Write Multiple Registers
|
||||
|
||||
This command is used to write the contents of a continuous sequence of analogue registers on a remote device. The slave address, the starting register address, the register values, and the signature of the data must be specified. The address of the first register is 0 and a maximum of 125 register values can be written. The Python sample code is shown below.
|
||||
|
||||
```python
|
||||
slave_addr=0x0A
|
||||
register_address=0x01
|
||||
register_values=[2, -4, 6, -256, 1024]
|
||||
signed=True
|
||||
|
||||
return_flag = modbus_obj.write_multiple_registers(slave_addr, register_address, register_values, signed)
|
||||
output_flag = 'Success' if return_flag else 'Failure'
|
||||
print('Writing multiple register status: ' + output_flag)
|
||||
```
|
||||
|
||||
43
tutorials/all/mqtt.md
Normal file
43
tutorials/all/mqtt.md
Normal file
@@ -0,0 +1,43 @@
|
||||
# MQTT
|
||||
|
||||
MQTT is a lightweight messaging protocol that is ideal for sending small packets of data to and from IoT devices via WiFi.
|
||||
|
||||
The broker used in this example is the [IO Adafruit](https://io.adafruit.com) platform, which is free and allows for tinkering with MQTT.
|
||||
|
||||
Visit [IO Adafruit](https://io.adafruit.com) and create an account. You'll need to get hold of an API Key as well as your credentials. Visit this [guide](https://learn.adafruit.com/adafruit-io/mqtt-api) for more information about MQTT and how to use it with Adafruit's Broker.
|
||||
|
||||
This example will send a message to a topic on the Adafruit MQTT broker and then also subscribe to the same topic, in order to show how to use the subscribe functionality.
|
||||
|
||||
```python
|
||||
from mqtt import MQTTClient
|
||||
from network import WLAN
|
||||
import machine
|
||||
import time
|
||||
|
||||
def sub_cb(topic, msg):
|
||||
print(msg)
|
||||
|
||||
wlan = WLAN(mode=WLAN.STA)
|
||||
wlan.connect("yourwifinetwork", auth=(WLAN.WPA2, "wifipassword"), timeout=5000)
|
||||
|
||||
while not wlan.isconnected():
|
||||
machine.idle()
|
||||
print("Connected to WiFi\n")
|
||||
|
||||
client = MQTTClient("device_id", "io.adafruit.com",user="your_username", password="your_api_key", port=1883)
|
||||
|
||||
client.set_callback(sub_cb)
|
||||
client.connect()
|
||||
client.subscribe(topic="youraccount/feeds/lights")
|
||||
|
||||
while True:
|
||||
print("Sending ON")
|
||||
client.publish(topic="youraccount/feeds/lights", msg="ON")
|
||||
time.sleep(1)
|
||||
print("Sending OFF")
|
||||
client.publish(topic="youraccount/feeds/lights", msg="OFF")
|
||||
client.check_msg()
|
||||
|
||||
time.sleep(1)
|
||||
```
|
||||
|
||||
207
tutorials/all/ota.md
Normal file
207
tutorials/all/ota.md
Normal file
@@ -0,0 +1,207 @@
|
||||
# OTA update
|
||||
|
||||
## Overview
|
||||
|
||||
Pycom modules come with the ability to update the devices firmware, while it is still running, we call this an "over the air" \(OTA\) update. The [`pycom`](../../firmwareapi/pycom/pycom.md) library provides several functions to achieve this. This example will demonstrate how you could potentially use this functionality to update deployed devices. The full source code of this example can be found [here](https://github.com/pycom/pycom-libraries/tree/master/examples/OTA).
|
||||
|
||||
## Method
|
||||
|
||||
Here we will describe one possible update methodology you could use that is implemented by this example.
|
||||
|
||||
Imagine you a smart metering company and you wish to roll out an update for your Pycom based smart meter. These meters usually send data back via LoRa. Unfortunately LoRa downlink messages have a very limited size and several hundred if not thousand would be required to upload a complete firmware image. To get around this you can have your devices sending their regular data via LoRa and when they receive a special command via a downlink message, the devices will connect to a WiFi network. It is unfeasible to ask customers to allow your device to connect to their home network so instead this network could be provided by a vehicle. This vehicle will travel around a certain geographic area in which the devices have been sent the special downlink message to initiate the update. The devices will look for the WiFi network being broadcast by the vehicle and connect. The devices will then connect to a server running on this WiFi network. This server \(also shown in this example\) will generate manifest files that instruct the device on what it should update, and where to get the update data from.
|
||||
|
||||
## Server
|
||||
|
||||
Code available [here](https://github.com/pycom/pycom-libraries/blob/master/examples/OTA/OTA_server.py).
|
||||
|
||||
This script runs a HTTP server on port `8000` that provisions over the air \(OTA\) update manifests in JSON format as well as serving the update content. This script should be run in a directory that contains every version of the end devices code, in the following structure:
|
||||
|
||||
```text
|
||||
- server directory
|
||||
|- this_script.py
|
||||
|- 1.0.0
|
||||
| |- flash
|
||||
| | |- lib
|
||||
| | | |- lib_a.py
|
||||
| | |- main.py
|
||||
| | |- boot.py
|
||||
| |- sd
|
||||
| |- some_asset.txt
|
||||
| |- asset_that_will_be_removed.wav
|
||||
|- 1.0.1
|
||||
| |- flash
|
||||
| | |- lib
|
||||
| | | |- lib_a.py
|
||||
| | | |- new_lib.py
|
||||
| | |- main.py
|
||||
| | |- boot.py
|
||||
| |- sd
|
||||
| |- some_asset.txt
|
||||
|- firmware_1.0.0.bin
|
||||
|- firmware_1.0.1.bin
|
||||
```
|
||||
|
||||
The top level directory that contains this script can contain one of two things:
|
||||
|
||||
* Update directory: These should be named with a version number compatible
|
||||
|
||||
with the python LooseVersion versioning scheme
|
||||
|
||||
\([http://epydoc.sourceforge.net/stdlib/distutils.version.LooseVersion-class.html](http://epydoc.sourceforge.net/stdlib/distutils.version.LooseVersion-class.html)\).
|
||||
|
||||
They should contain the entire file system of the end device for the
|
||||
|
||||
corresponding version number.
|
||||
|
||||
* Firmware: These files should be named in the format `firmare_VERSION.bin`, where VERSION is a a version number compatible with the python LooseVersion versioning scheme \([http://epydoc.sourceforge.net/stdlib/distutils.version.LooseVersion-class.html](http://epydoc.sourceforge.net/stdlib/distutils.version.LooseVersion-class.html)\).
|
||||
|
||||
This file should be in the format of the `appimg.bin` created by the Pycom
|
||||
|
||||
firmware build scripts.
|
||||
|
||||
### How to use
|
||||
|
||||
Once the directory has been setup as described above you simply need to start this script using python3. Once started this script will run a HTTP server on port `8000` \(this can be changed by changing the PORT variable\). This server will serve all the files in directory as expected along with one additional special file, `manifest.json`. This file does not exist on the file system but is instead generated when requested and contains the required changes to bring the end device from its current version to the latest available version. You can see an example of this by pointing your web browser at:
|
||||
|
||||
`http://127.0.0.1:8000/manifest.json?current_ver=1.0.0`
|
||||
|
||||
The `current_ver` field at the end of the URL should be set to the current firmware version of the end device. The generated manifest will contain lists of which files are new, have changed or need to be deleted along with SHA1 hashes of the files. Below is an example of what such a manifest might look like:
|
||||
|
||||
```text
|
||||
{
|
||||
"delete": [
|
||||
"flash/old_file.py",
|
||||
"flash/other_old_file.py"
|
||||
],
|
||||
"firmware": {
|
||||
"URL": "http://192.168.1.144:8000/firmware_1.0.1b.bin",
|
||||
"hash": "ccc6914a457eb4af8855ec02f6909316526bdd08"
|
||||
},
|
||||
"new": [
|
||||
{
|
||||
"URL": "http://192.168.1.144:8000/1.0.1b/flash/lib/new_lib.py",
|
||||
"dst_path": "flash/lib/new_lib.py",
|
||||
"hash": "1095df8213aac2983efd68dba9420c8efc9c7c4a"
|
||||
}
|
||||
],
|
||||
"update": [
|
||||
{
|
||||
"URL": "http://192.168.1.144:8000/1.0.1b/flash/changed_file.py",
|
||||
"dst_path": "flash/changed_file.py",
|
||||
"hash": "1095df8213aac2983efd68dba9420c8efc9c7c4a"
|
||||
}
|
||||
],
|
||||
"version": "1.0.1b"
|
||||
}
|
||||
```
|
||||
|
||||
The manifest contains the following fields:
|
||||
|
||||
* `delete`: A list of paths to files which are no longer needed
|
||||
* `firmware`: The URL and SHA1 hash of the firmware image
|
||||
* `new`: the URL, path on end device and SHA1 hash of all new files
|
||||
* `update`: the URL, path on end device and SHA1 hash of all files which
|
||||
|
||||
existed before but have changed.
|
||||
|
||||
* `version`: The version number that this manifest will update the client to
|
||||
* `previous_version`: The version the client is currently on before applying
|
||||
|
||||
this update
|
||||
|
||||
_Note_: The version number of the files might not be the same as the firmware. The highest available version number, higher than the current client version is used for both firmware and files. This may differ between the two.
|
||||
|
||||
In order for the URL's to be properly formatted you are required to send a "host" header along with your HTTP get request e.g:
|
||||
|
||||
```text
|
||||
GET /manifest.json?current_ver=1.0.0 HTTP/1.0\r\nHost: 192.168.1.144:8000\r\n\r\n
|
||||
```
|
||||
|
||||
## Client Library
|
||||
|
||||
A MicroPyton library for interfacing with the server described above is available [here](https://github.com/pycom/pycom-libraries/blob/master/examples/OTA/1.0.0/flash/lib/OTA.py).
|
||||
|
||||
This library is split into two layers. The top level `OTA` class implements all the high level functionality such as parsing the JSON file, making back copies of files being updated incase the update fails, etc. The layer of the library is agnostic to your chosen transport method. Below this is the `WiFiOTA` class. This class implements the actual transport mechanism of how the device fetches the files and update manifest \(via WiFi as the class name suggests\). The reason for this split is so that the high level functionality can be reused regardless of what transport mechanism you end up using. This could be implemented on top of Bluetooth for example, or the sever changed from HTTP to FTP.
|
||||
|
||||
{% hint style="danger" %}
|
||||
Although the above code is functional, it is provided only as an example of how an end user might implement a OTA update mechanism. It is not 100% feature complete e.g. even though it does backup previous versions of files, the roll back procedure is not implemented. This is left of the end user to do.
|
||||
{% endhint %}
|
||||
|
||||
## Example
|
||||
|
||||
Below is am example implementing the methodology previously explained in this tutorial to initiate an OTA update.
|
||||
|
||||
{% hint style="info" %}
|
||||
The example below will only work on a Pycom device with LoRa capabilities. If want to test it out on a device without LoRa functionality then simply comment out any code relating to LoRa. Leaving just the `WiFiOTA` initialisation and they `ota.connect()` and `ota.update()`
|
||||
{% endhint %}
|
||||
|
||||
```python
|
||||
from network import LoRa, WLAN
|
||||
import socket
|
||||
import time
|
||||
from OTA import WiFiOTA
|
||||
from time import sleep
|
||||
import pycom
|
||||
import ubinascii
|
||||
|
||||
from config import WIFI_SSID, WIFI_PW, SERVER_IP
|
||||
|
||||
# Turn on GREEN LED
|
||||
pycom.heartbeat(False)
|
||||
pycom.rgbled(0xff00)
|
||||
|
||||
# Setup OTA
|
||||
ota = WiFiOTA(WIFI_SSID,
|
||||
WIFI_PW,
|
||||
SERVER_IP, # Update server address
|
||||
8000) # Update server port
|
||||
|
||||
# Turn off WiFi to save power
|
||||
w = WLAN()
|
||||
w.deinit()
|
||||
|
||||
# Initialise LoRa in LORAWAN mode.
|
||||
lora = LoRa(mode=LoRa.LORAWAN, region=LoRa.EU868)
|
||||
|
||||
app_eui = ubinascii.unhexlify('70B3D57ED0008CD6')
|
||||
app_key = ubinascii.unhexlify('B57F36D88691CEC5EE8659320169A61C')
|
||||
|
||||
# join a network using OTAA (Over the Air Activation)
|
||||
lora.join(activation=LoRa.OTAA, auth=(app_eui, app_key), timeout=0)
|
||||
|
||||
# wait until the module has joined the network
|
||||
while not lora.has_joined():
|
||||
time.sleep(2.5)
|
||||
print('Not yet joined...')
|
||||
|
||||
# create a LoRa socket
|
||||
s = socket.socket(socket.AF_LORA, socket.SOCK_RAW)
|
||||
|
||||
# set the LoRaWAN data rate
|
||||
s.setsockopt(socket.SOL_LORA, socket.SO_DR, 5)
|
||||
|
||||
# make the socket blocking
|
||||
# (waits for the data to be sent and for the 2 receive windows to expire)
|
||||
s.setblocking(True)
|
||||
|
||||
while True:
|
||||
# send some data
|
||||
s.send(bytes([0x04, 0x05, 0x06]))
|
||||
|
||||
# make the socket non-blocking
|
||||
# (because if there's no data received it will block forever...)
|
||||
s.setblocking(False)
|
||||
|
||||
# get any data received (if any...)
|
||||
data = s.recv(64)
|
||||
|
||||
# Some sort of OTA trigger
|
||||
if data == bytes([0x01, 0x02, 0x03]):
|
||||
print("Performing OTA")
|
||||
# Perform OTA
|
||||
ota.connect()
|
||||
ota.update()
|
||||
|
||||
sleep(5)
|
||||
```
|
||||
|
||||
246
tutorials/all/owd.md
Normal file
246
tutorials/all/owd.md
Normal file
@@ -0,0 +1,246 @@
|
||||
# Onewire Driver
|
||||
|
||||
This tutorial explains how to connect and read data from a DS18x20 temperature sensor. The onewire library is also available at the [pycom-libraries](https://github.com/pycom/pycom-libraries/tree/master/lib/onewire) GitHub Repository.
|
||||
|
||||
## Basic usage
|
||||
|
||||
```python
|
||||
import time
|
||||
from machine import Pin
|
||||
from onewire import DS18X20
|
||||
from onewire import OneWire
|
||||
|
||||
# DS18B20 data line connected to pin P10
|
||||
ow = OneWire(Pin('P10'))
|
||||
temp = DS18X20(ow)
|
||||
|
||||
while True:
|
||||
print(temp.read_temp_async())
|
||||
time.sleep(1)
|
||||
temp.start_conversion()
|
||||
time.sleep(1)
|
||||
```
|
||||
|
||||
## Library
|
||||
|
||||
```python
|
||||
#!/usr/bin/env python3
|
||||
|
||||
"""
|
||||
OneWire library for MicroPython
|
||||
"""
|
||||
|
||||
import time
|
||||
import machine
|
||||
|
||||
class OneWire:
|
||||
CMD_SEARCHROM = const(0xf0)
|
||||
CMD_READROM = const(0x33)
|
||||
CMD_MATCHROM = const(0x55)
|
||||
CMD_SKIPROM = const(0xcc)
|
||||
|
||||
def __init__(self, pin):
|
||||
self.pin = pin
|
||||
self.pin.init(pin.OPEN_DRAIN, pin.PULL_UP)
|
||||
|
||||
def reset(self):
|
||||
"""
|
||||
Perform the onewire reset function.
|
||||
Returns True if a device asserted a presence pulse, False otherwise.
|
||||
"""
|
||||
sleep_us = time.sleep_us
|
||||
disable_irq = machine.disable_irq
|
||||
enable_irq = machine.enable_irq
|
||||
pin = self.pin
|
||||
|
||||
pin(0)
|
||||
sleep_us(480)
|
||||
i = disable_irq()
|
||||
pin(1)
|
||||
sleep_us(60)
|
||||
status = not pin()
|
||||
enable_irq(i)
|
||||
sleep_us(420)
|
||||
return status
|
||||
|
||||
def read_bit(self):
|
||||
sleep_us = time.sleep_us
|
||||
enable_irq = machine.enable_irq
|
||||
pin = self.pin
|
||||
|
||||
pin(1) # half of the devices don't match CRC without this line
|
||||
i = machine.disable_irq()
|
||||
pin(0)
|
||||
sleep_us(1)
|
||||
pin(1)
|
||||
sleep_us(1)
|
||||
value = pin()
|
||||
enable_irq(i)
|
||||
sleep_us(40)
|
||||
return value
|
||||
|
||||
def read_byte(self):
|
||||
value = 0
|
||||
for i in range(8):
|
||||
value |= self.read_bit() << i
|
||||
return value
|
||||
|
||||
def read_bytes(self, count):
|
||||
buf = bytearray(count)
|
||||
for i in range(count):
|
||||
buf[i] = self.read_byte()
|
||||
return buf
|
||||
|
||||
def write_bit(self, value):
|
||||
sleep_us = time.sleep_us
|
||||
pin = self.pin
|
||||
|
||||
i = machine.disable_irq()
|
||||
pin(0)
|
||||
sleep_us(1)
|
||||
pin(value)
|
||||
sleep_us(60)
|
||||
pin(1)
|
||||
sleep_us(1)
|
||||
machine.enable_irq(i)
|
||||
|
||||
def write_byte(self, value):
|
||||
for i in range(8):
|
||||
self.write_bit(value & 1)
|
||||
value >>= 1
|
||||
|
||||
def write_bytes(self, buf):
|
||||
for b in buf:
|
||||
self.write_byte(b)
|
||||
|
||||
def select_rom(self, rom):
|
||||
"""
|
||||
Select a specific device to talk to. Pass in rom as a bytearray (8 bytes).
|
||||
"""
|
||||
self.reset()
|
||||
self.write_byte(CMD_MATCHROM)
|
||||
self.write_bytes(rom)
|
||||
|
||||
def crc8(self, data):
|
||||
"""
|
||||
Compute CRC
|
||||
"""
|
||||
crc = 0
|
||||
for i in range(len(data)):
|
||||
byte = data[i]
|
||||
for b in range(8):
|
||||
fb_bit = (crc ^ byte) & 0x01
|
||||
if fb_bit == 0x01:
|
||||
crc = crc ^ 0x18
|
||||
crc = (crc >> 1) & 0x7f
|
||||
if fb_bit == 0x01:
|
||||
crc = crc | 0x80
|
||||
byte = byte >> 1
|
||||
return crc
|
||||
|
||||
def scan(self):
|
||||
"""
|
||||
Return a list of ROMs for all attached devices.
|
||||
Each ROM is returned as a bytes object of 8 bytes.
|
||||
"""
|
||||
devices = []
|
||||
diff = 65
|
||||
rom = False
|
||||
for i in range(0xff):
|
||||
rom, diff = self._search_rom(rom, diff)
|
||||
if rom:
|
||||
devices += [rom]
|
||||
if diff == 0:
|
||||
break
|
||||
return devices
|
||||
|
||||
def _search_rom(self, l_rom, diff):
|
||||
if not self.reset():
|
||||
return None, 0
|
||||
self.write_byte(CMD_SEARCHROM)
|
||||
if not l_rom:
|
||||
l_rom = bytearray(8)
|
||||
rom = bytearray(8)
|
||||
next_diff = 0
|
||||
i = 64
|
||||
for byte in range(8):
|
||||
r_b = 0
|
||||
for bit in range(8):
|
||||
b = self.read_bit()
|
||||
if self.read_bit():
|
||||
if b: # there are no devices or there is an error on the bus
|
||||
return None, 0
|
||||
else:
|
||||
if not b: # collision, two devices with different bit meaning
|
||||
if diff > i or ((l_rom[byte] & (1 << bit)) and diff != i):
|
||||
b = 1
|
||||
next_diff = i
|
||||
self.write_bit(b)
|
||||
if b:
|
||||
r_b |= 1 << bit
|
||||
i -= 1
|
||||
rom[byte] = r_b
|
||||
return rom, next_diff
|
||||
|
||||
class DS18X20(object):
|
||||
def __init__(self, onewire):
|
||||
self.ow = onewire
|
||||
self.roms = [rom for rom in self.ow.scan() if rom[0] == 0x10 or rom[0] == 0x28]
|
||||
|
||||
def isbusy(self):
|
||||
"""
|
||||
Checks wether one of the DS18x20 devices on the bus is busy
|
||||
performing a temperature conversion
|
||||
"""
|
||||
return not self.ow.read_bit()
|
||||
|
||||
def start_conversion(self, rom=None):
|
||||
"""
|
||||
Start the temp conversion on one DS18x20 device.
|
||||
Pass the 8-byte bytes object with the ROM of the specific device you want to read.
|
||||
If only one DS18x20 device is attached to the bus you may omit the rom parameter.
|
||||
"""
|
||||
rom = rom or self.roms[0]
|
||||
ow = self.ow
|
||||
ow.reset()
|
||||
ow.select_rom(rom)
|
||||
ow.write_byte(0x44) # Convert Temp
|
||||
|
||||
def read_temp_async(self, rom=None):
|
||||
"""
|
||||
Read the temperature of one DS18x20 device if the conversion is complete,
|
||||
otherwise return None.
|
||||
"""
|
||||
if self.isbusy():
|
||||
return None
|
||||
rom = rom or self.roms[0]
|
||||
ow = self.ow
|
||||
ow.reset()
|
||||
ow.select_rom(rom)
|
||||
ow.write_byte(0xbe) # Read scratch
|
||||
data = ow.read_bytes(9)
|
||||
return self.convert_temp(rom[0], data)
|
||||
|
||||
def convert_temp(self, rom0, data):
|
||||
"""
|
||||
Convert the raw temperature data into degrees celsius and return as a fixed point with 2 decimal places.
|
||||
"""
|
||||
temp_lsb = data[0]
|
||||
temp_msb = data[1]
|
||||
if rom0 == 0x10:
|
||||
if temp_msb != 0:
|
||||
# convert negative number
|
||||
temp_read = temp_lsb >> 1 | 0x80 # truncate bit 0 by shifting, fill high bit with 1.
|
||||
temp_read = -((~temp_read + 1) & 0xff) # now convert from two's complement
|
||||
else:
|
||||
temp_read = temp_lsb >> 1 # truncate bit 0 by shifting
|
||||
count_remain = data[6]
|
||||
count_per_c = data[7]
|
||||
temp = 100 * temp_read - 25 + (count_per_c - count_remain) // count_per_c
|
||||
return temp
|
||||
elif rom0 == 0x28:
|
||||
return (temp_msb << 8 | temp_lsb) * 100 // 16
|
||||
else:
|
||||
assert False
|
||||
```
|
||||
|
||||
125
tutorials/all/pir.md
Normal file
125
tutorials/all/pir.md
Normal file
@@ -0,0 +1,125 @@
|
||||
# PIR Sensor
|
||||
|
||||
This code reads PIR sensor triggers from this simple [PIR sensor](https://www.kiwi-electronics.nl/PIR-Motion-Sensor) and sends an HTTP request for every trigger, in this case to a [Domoticz](https://domoticz.com/) installation. When motion is constantly detected, this PIR sensor keeps the pin high, in which case this code will keep sending HTTP requests every 10 seconds \(configurable with the hold\_time variable\).
|
||||
|
||||
## Main \(`main.py`\)
|
||||
|
||||
```python
|
||||
import time
|
||||
from network import WLAN
|
||||
from machine import Pin
|
||||
from domoticz import Domoticz
|
||||
|
||||
wl = WLAN(WLAN.STA)
|
||||
d = Domoticz("<ip>", 8080 ,"<hash>")
|
||||
|
||||
#config
|
||||
hold_time_sec = 10
|
||||
|
||||
#flags
|
||||
last_trigger = -10
|
||||
|
||||
pir = Pin('G4',mode=Pin.IN, pull=Pin.PULL_UP)
|
||||
|
||||
# main loop
|
||||
print("Starting main loop")
|
||||
while True:
|
||||
if pir() == 1:
|
||||
if time.time() - last_trigger > hold_time_sec:
|
||||
last_trigger = time.time()
|
||||
print("Presence detected, sending HTTP request")
|
||||
try:
|
||||
return_code = d.setVariable('Presence:LivingRoom','1')
|
||||
print("Request result: "+str(return_code))
|
||||
except Exception as e:
|
||||
print("Request failed")
|
||||
print(e)
|
||||
else:
|
||||
last_trigger = 0
|
||||
print("No presence")
|
||||
|
||||
time.sleep_ms(500)
|
||||
|
||||
print("Exited main loop")
|
||||
```
|
||||
|
||||
## Boot \(`boot.py`\)
|
||||
|
||||
For more WiFi scripts, see the wlan step by step tutorial.
|
||||
|
||||
```python
|
||||
import os
|
||||
import machine
|
||||
|
||||
uart = machine.UART(0, 115200)
|
||||
os.dupterm(uart)
|
||||
|
||||
known_nets = {
|
||||
'NetworkID': {'pwd': '<password>', 'wlan_config': ('10.0.0.8', '255.255.0.0', '10.0.0.1', '10.0.0.1')},
|
||||
}
|
||||
|
||||
from network import WLAN
|
||||
wl = WLAN()
|
||||
|
||||
|
||||
if machine.reset_cause() != machine.SOFT_RESET:
|
||||
|
||||
wl.mode(WLAN.STA)
|
||||
original_ssid = wl.ssid()
|
||||
original_auth = wl.auth()
|
||||
|
||||
print("Scanning for known wifi nets")
|
||||
available_nets = wl.scan()
|
||||
nets = frozenset([e.ssid for e in available_nets])
|
||||
|
||||
known_nets_names = frozenset([key for key in known_nets])
|
||||
net_to_use = list(nets & known_nets_names)
|
||||
try:
|
||||
net_to_use = net_to_use[0]
|
||||
net_properties = known_nets[net_to_use]
|
||||
pwd = net_properties['pwd']
|
||||
sec = [e.sec for e in available_nets if e.ssid == net_to_use][0]
|
||||
if 'wlan_config' in net_properties:
|
||||
wl.ifconfig(config=net_properties['wlan_config'])
|
||||
wl.connect(net_to_use, (sec, pwd), timeout=10000)
|
||||
while not wl.isconnected():
|
||||
machine.idle() # save power while waiting
|
||||
print("Connected to "+net_to_use+" with IP address:" + wl.ifconfig()[0])
|
||||
|
||||
except Exception as e:
|
||||
print("Failed to connect to any known network, going into AP mode")
|
||||
wl.init(mode=WLAN.AP, ssid=original_ssid, auth=original_auth, channel=6, antenna=WLAN.INT_ANT)
|
||||
```
|
||||
|
||||
## Domoticz Wrapper \(`domoticz.py`\)
|
||||
|
||||
```python
|
||||
import socket
|
||||
class Domoticz:
|
||||
|
||||
def __init__(self, ip, port, basic):
|
||||
self.basic = basic
|
||||
self.ip = ip
|
||||
self.port = port
|
||||
|
||||
def setLight(self, idx, command):
|
||||
return self.sendRequest("type=command¶m=switchlight&idx="+idx+"&switchcmd="+command)
|
||||
|
||||
def setVariable(self, name, value):
|
||||
return self.sendRequest("type=command¶m=updateuservariable&vtype=0&vname="+name+"&vvalue="+value)
|
||||
|
||||
def sendRequest(self, path):
|
||||
try:
|
||||
s = socket.socket()
|
||||
s.connect((self.ip,self.port))
|
||||
s.send(b"GET /json.htm?"+path+" HTTP/1.1\r\nHost: pycom.io\r\nAuthorization: Basic "+self.basic+"\r\n\r\n")
|
||||
status = str(s.readline(), 'utf8')
|
||||
code = status.split(" ")[1]
|
||||
s.close()
|
||||
return code
|
||||
|
||||
except Exception:
|
||||
print("HTTP request failed")
|
||||
return 0
|
||||
```
|
||||
|
||||
45
tutorials/all/repl.md
Normal file
45
tutorials/all/repl.md
Normal file
@@ -0,0 +1,45 @@
|
||||
# REPL
|
||||
|
||||
Using the Pymakr Plugin, open and connect a device or use serial terminal \(PuTTY, screen, picocom, etc\). Upon connecting, there should be a blank screen with a flashing cursor. Press Enter and a MicroPython prompt should appear, i.e. `>>>`. Let’s make sure it is working with the obligatory test:
|
||||
|
||||
```python
|
||||
>>> print("Hello LoPy!")
|
||||
Hello LoPy!
|
||||
```
|
||||
|
||||
In the example above, the `>>>` characters should not be typed. They are there to indicate that the text should be placed after the prompt. Once the text has been entered `print("Hello LoPy!")` and pressed `Enter`, the output should appear on screen, identical to the example above.
|
||||
|
||||
Basic Python commands can be tested out in a similar fashion.
|
||||
|
||||
If this is not working, try either a hard reset or a soft reset; see below.
|
||||
|
||||
Here are some other example, utilising the device's hardware features:
|
||||
|
||||
```python
|
||||
>>> from machine import Pin
|
||||
>>> led = Pin('G16', mode=Pin.OUT, value=1)
|
||||
>>> led(0)
|
||||
>>> led(1)
|
||||
>>> led.toggle()
|
||||
>>> 1 + 2
|
||||
3
|
||||
>>> 5 / 2
|
||||
2.5
|
||||
>>> 20 * 'py'
|
||||
'pypypypypypypypypypypypypypypypypypypypy'
|
||||
```
|
||||
|
||||
## Resetting the Device
|
||||
|
||||
If something goes wrong, the device can be reset with two methods. The first is to press `CTRL-D` at the MicroPython prompt, which will perform a soft reset. A message, as following, will appear:
|
||||
|
||||
```python
|
||||
>>>
|
||||
PYB: soft reboot
|
||||
MicroPython v1.4.6-146-g1d8b5e5 on 2016-10-21; LoPy with ESP32
|
||||
Type "help()" for more information.
|
||||
>>>
|
||||
```
|
||||
|
||||
If that still isn’t working a hard reset can be performed \(power-off/on\) by pressing the `RST` switch \(the small black button next to the RGB LED\). Using telnet, this will end the session, disconnecting the program that was used to connect to the Pycom Device.
|
||||
|
||||
33
tutorials/all/rgbled.md
Normal file
33
tutorials/all/rgbled.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# RGB LED
|
||||
|
||||
By default the heartbeat LED flashes in blue colour once every 4s to signal that the system is alive. This can be overridden through the `pycom` module.
|
||||
|
||||
```python
|
||||
import pycom
|
||||
|
||||
pycom.heartbeat(False)
|
||||
pycom.rgbled(0xff00) # turn on the RGB LED in green colour
|
||||
```
|
||||
|
||||
The heartbeat LED is also used to indicate that an error was detected.
|
||||
|
||||
The following piece of code uses the RGB LED to make a traffic light that runs for 10 cycles.
|
||||
|
||||
```python
|
||||
import pycom
|
||||
import time
|
||||
|
||||
pycom.heartbeat(False)
|
||||
for cycles in range(10): # stop after 10 cycles
|
||||
pycom.rgbled(0x007f00) # green
|
||||
time.sleep(5)
|
||||
pycom.rgbled(0x7f7f00) # yellow
|
||||
time.sleep(1.5)
|
||||
pycom.rgbled(0x7f0000) # red
|
||||
time.sleep(4)
|
||||
```
|
||||
|
||||
Here is the expected result:
|
||||
|
||||

|
||||
|
||||
141
tutorials/all/rmt.md
Normal file
141
tutorials/all/rmt.md
Normal file
@@ -0,0 +1,141 @@
|
||||
# RMT
|
||||
|
||||
Detailed information about this class can be found in [`RMT`](../../firmwareapi/pycom/machine/rmt.md).
|
||||
|
||||
The RMT \(Remote Control\) peripheral of the ESP32 is primarily designed to send and receive infrared remote control signals that use on-off-keying of a carrier frequency, but due to its design it can be used to generate various types of signals, this class will allow you to do this.
|
||||
|
||||
The RMT has 7 channels, of which 5 are available and can be mapped to any GPIO pin \(_Note:_ Pins `P13` -`P18` can only be used as inputs\).
|
||||
|
||||
| Channel | Resolution | Maximum Pulse Width |
|
||||
| :--- | :--- | :--- |
|
||||
| 0 | Used by on-board LED | |
|
||||
| 1 | Used by `pycom.pulses_get()` | |
|
||||
| 2 | 100nS | 3.2768 ms |
|
||||
| 3 | 100nS | 3.2768 ms |
|
||||
| 4 | 1000nS | 32.768 ms |
|
||||
| 5 | 1000nS | 32.768 ms |
|
||||
| 6 | 3125nS | 102.4 ms |
|
||||
| 7 | 3125nS | 102.4 ms |
|
||||
|
||||
## Transmitting
|
||||
|
||||
The following examples create an RMT object on channel 4, configure it for transmission and send some data in various forms. The resolution of channel 4 is 1000 nano seconds, the given values are interpreted accordingly.
|
||||
|
||||
In this first example, we define the signal as a tuple of binary values that define the shape of the desired signal along with the duration of a bit.
|
||||
|
||||
```python
|
||||
from machine import RMT
|
||||
# Map RMT channel 4 to P21, when the RMT is idle, it will output LOW
|
||||
rmt = RMT(channel=4, gpio="P21", tx_idle_level=RMT.LOW)
|
||||
|
||||
# Produces the pattern shown in data, where each bit lasts
|
||||
# duration * channel resolution = 10000 * 1000ns = 10ms
|
||||
data = (1,0,1,1,1,0,1,0,1)
|
||||
duration = 10000
|
||||
rmt.pulses_send(duration, data)
|
||||
```
|
||||
|
||||

|
||||
|
||||
In this example we define the signal by a tuple of durations and what state the signal starts in.
|
||||
|
||||
```python
|
||||
from machine import RMT
|
||||
# Map RMT channel 4 to P21, when the RMT is idle, it will output LOW
|
||||
rmt = RMT(channel=4, gpio="P21", tx_idle_level=RMT.LOW)
|
||||
|
||||
# The list of durations for each pulse to be, these are in units of the channels
|
||||
# resolution:
|
||||
# duration = Desired pulse length / Channel Resolution
|
||||
duration = (8000,11000,8000,11000,6000,13000,6000,3000,8000)
|
||||
|
||||
# `start_level` defines if the signal starts off as LOW or HIGH, it will then
|
||||
# toggle state between each duration
|
||||
rmt.pulses_send(duration, start_level=RMT.HIGH)
|
||||
```
|
||||
|
||||

|
||||
|
||||
This third example, is a combination of the above two styles of defining a signal. Each pulse has a defined duration as well as a state. This is useful if you don't always want the signal to toggle state.
|
||||
|
||||
```python
|
||||
from machine import RMT
|
||||
# Map RMT channel 4 to P21, when the RMT is idle, it will output LOW
|
||||
rmt = RMT(channel=4, gpio="P21", tx_idle_level=RMT.LOW)
|
||||
|
||||
# Produces the pattern shown in data, where each bit lasts
|
||||
# duration[i] * channel resolution = duration[i] * 1000ns
|
||||
data = (1,0,1,1,0,1)
|
||||
duration = (400,200,100,300,200,400)
|
||||
rmt.pulses_send(duration, data)
|
||||
```
|
||||
|
||||

|
||||
|
||||
The following example creates an RMT object on channel 4 and configures it for transmission with carrier modulation.
|
||||
|
||||
```python
|
||||
from machine import RMT
|
||||
rmt = RMT(channel=4,
|
||||
gpio="P21",
|
||||
tx_idle_level=RMT.LOW,
|
||||
# Carrier = 100Hz, 80% duty, modules HIGH signals
|
||||
tx_carrier = (100, 70, RMT.HIGH))
|
||||
data = (1,0,1)
|
||||
duration = 10000
|
||||
rmt.pulses_send(duration, data)
|
||||
```
|
||||
|
||||

|
||||
|
||||
The following example creates an RMT object on channel 2, configures it for receiving, then waits for the first, undefined number of pulses without timeout
|
||||
|
||||
```python
|
||||
from machine import RMT
|
||||
rmt = machine.RMT(channel=2)
|
||||
rmt.init(gpio="P21", rx_idle_threshold=1000)
|
||||
|
||||
data = rmt.pulses_get()
|
||||
```
|
||||
|
||||
{% hint style="danger" %}
|
||||
If `tx_idle_level` is not set to the opposite of the third value in the `tx_carrier` tuple, the carrier wave will continue to be generated when the RMT channel is idle.
|
||||
{% endhint %}
|
||||
|
||||
## Receiving
|
||||
|
||||
The following example creates an RMT object on channel 2, configures it for receiving a undefined number of pulses, then waits maximum of 1000us for the first pulse.
|
||||
|
||||
```python
|
||||
from machine import RMT
|
||||
# Sets RMT channel 2 to P21 and sets the maximum length of a valid pulse to
|
||||
# 1000*channel resolution = 1000 * 100ns = 100us
|
||||
rmt = machine.RMT(channel=2, gpio="P21", rx_idle_threshold=1000)
|
||||
rmt.init()
|
||||
|
||||
# Get a undefined number of pulses, waiting a maximum of 500us for the first
|
||||
# pulse (unlike other places where the absolute duration was based on the RMT
|
||||
# channels resolution, this value is in us) until a pulse longer than
|
||||
# rx_idle_threshold occurs.
|
||||
data = rmt.pulses_get(timeout=500)
|
||||
```
|
||||
|
||||
The following example creates an RMT object on channel 2, configures it for receiving, filters out pulses with width < 20\*100 nano seconds, then waits for 100 pulses
|
||||
|
||||
```python
|
||||
from machine import RMT
|
||||
|
||||
rmt = machine.RMT(channel=2, # Resolution = 100ns
|
||||
gpio="P21",
|
||||
# Longest valid pulse = 1000*100ns = 100us
|
||||
rx_idle_threshold=1000,
|
||||
# Filter out pulses shorter than 20*100ns = 2us
|
||||
rx_filter_threshold=20)
|
||||
|
||||
# Receive 100 pulses, pulses shorter than 2us or longer than 100us will be
|
||||
# ignored. That means if it receives 80 valid pulses but then the signal
|
||||
# doesn't change for 10 hours and then 20 more pulses occur, this function
|
||||
# will wait for 10h
|
||||
data = rmt.pulses_get(pulses=100)
|
||||
```
|
||||
|
||||
28
tutorials/all/threading.md
Normal file
28
tutorials/all/threading.md
Normal file
@@ -0,0 +1,28 @@
|
||||
# Threading
|
||||
|
||||
MicroPython supports spawning threads by the `_thread` module. The following example demonstrates the use of this module. A thread is simply defined as a function that can receive any number of parameters. Below 3 threads are started, each one perform a print at a different interval.
|
||||
|
||||
```python
|
||||
import _thread
|
||||
import time
|
||||
|
||||
def th_func(delay, id):
|
||||
while True:
|
||||
time.sleep(delay)
|
||||
print('Running thread %d' % id)
|
||||
|
||||
for i in range(3):
|
||||
_thread.start_new_thread(th_func, (i + 1, i))
|
||||
```
|
||||
|
||||
## Using Locks:
|
||||
|
||||
```python
|
||||
import _thread
|
||||
|
||||
a_lock = _thread.allocate_lock()
|
||||
|
||||
with a_lock:
|
||||
print("a_lock is locked while this executes")
|
||||
```
|
||||
|
||||
53
tutorials/all/timers.md
Normal file
53
tutorials/all/timers.md
Normal file
@@ -0,0 +1,53 @@
|
||||
# Timers
|
||||
|
||||
Detailed information about this class can be found in [`Timer`](../../firmwareapi/pycom/machine/timer.md).
|
||||
|
||||
## Chronometer
|
||||
|
||||
The Chronometer can be used to measure how much time has elapsed in a block of code. The following example uses a simple stopwatch.
|
||||
|
||||
```python
|
||||
from machine import Timer
|
||||
import time
|
||||
|
||||
chrono = Timer.Chrono()
|
||||
|
||||
chrono.start()
|
||||
time.sleep(1.25) # simulate the first lap took 1.25 seconds
|
||||
lap = chrono.read() # read elapsed time without stopping
|
||||
time.sleep(1.5)
|
||||
chrono.stop()
|
||||
total = chrono.read()
|
||||
|
||||
print()
|
||||
print("\nthe racer took %f seconds to finish the race" % total)
|
||||
print(" %f seconds in the first lap" % lap)
|
||||
print(" %f seconds in the last lap" % (total - lap))
|
||||
```
|
||||
|
||||
## Alarm
|
||||
|
||||
The Alarm can be used to get interrupts at a specific interval. The following code executes a callback every second for 10 seconds.
|
||||
|
||||
```python
|
||||
from machine import Timer
|
||||
|
||||
class Clock:
|
||||
|
||||
def __init__(self):
|
||||
self.seconds = 0
|
||||
self.__alarm = Timer.Alarm(self._seconds_handler, 1, periodic=True)
|
||||
|
||||
def _seconds_handler(self, alarm):
|
||||
self.seconds += 1
|
||||
print("%02d seconds have passed" % self.seconds)
|
||||
if self.seconds == 10:
|
||||
alarm.callback(None) # stop counting after 10 seconds
|
||||
|
||||
clock = Clock()
|
||||
```
|
||||
|
||||
{% hint style="info" %}
|
||||
There are no restrictions to what can be done in an interrupt. For example, it is possible to even do network requests with an interrupt. However, it is important to keep in mind that interrupts are handled sequentially, so it’s good practice to keep them short. More information can be found in [`Interrupt Handling`](../../firmwareapi/notes.md#interrupt-handling).
|
||||
{% endhint %}
|
||||
|
||||
144
tutorials/all/wlan.md
Normal file
144
tutorials/all/wlan.md
Normal file
@@ -0,0 +1,144 @@
|
||||
# WLAN
|
||||
|
||||
The WLAN is a system feature of all Pycom devices, therefore it is enabled by default.
|
||||
|
||||
In order to retrieve the current WLAN instance, run:
|
||||
|
||||
```python
|
||||
>>> from network import WLAN
|
||||
>>> wlan = WLAN() # we call the constructor without params
|
||||
```
|
||||
|
||||
The current mode \(`WLAN.AP` after power up\) may be checked by running:
|
||||
|
||||
```python
|
||||
>>> wlan.mode()
|
||||
```
|
||||
|
||||
{% hint style="danger" %}
|
||||
|
||||
When changing the WLAN mode, if following the instructions below, the WLAN connection to the Pycom device will be broken. This means commands will not run interactively over WiFi.
|
||||
|
||||
**There are two ways around this:**
|
||||
|
||||
1. Put this setup code into the `boot.py` file of the Pycom device so that it gets executed automatically after reset.
|
||||
2. Duplicate the REPL on UART. This way commands can be run via Serial USB.
|
||||
|
||||
## Connecting to a Router
|
||||
|
||||
The WLAN network class always boots in `WLAN.AP` mode; to connect it to an existing network, the WiFi class must be configured as a station:
|
||||
|
||||
```python
|
||||
from network import WLAN
|
||||
wlan = WLAN(mode=WLAN.STA)
|
||||
```
|
||||
|
||||
Now the device may proceed to scan for networks:
|
||||
|
||||
```python
|
||||
nets = wlan.scan()
|
||||
for net in nets:
|
||||
if net.ssid == 'mywifi':
|
||||
print('Network found!')
|
||||
wlan.connect(net.ssid, auth=(net.sec, 'mywifikey'), timeout=5000)
|
||||
while not wlan.isconnected():
|
||||
machine.idle() # save power while waiting
|
||||
print('WLAN connection succeeded!')
|
||||
break
|
||||
```
|
||||
|
||||
## Assigning a Static IP Address at Boot Up
|
||||
|
||||
If the users wants their device to connect to a home router upon boot up, using with a fixed IP address, use the following script as `/flash/boot.py`:
|
||||
|
||||
```python
|
||||
import machine
|
||||
from network import WLAN
|
||||
wlan = WLAN() # get current object, without changing the mode
|
||||
|
||||
if machine.reset_cause() != machine.SOFT_RESET:
|
||||
wlan.init(mode=WLAN.STA)
|
||||
# configuration below MUST match your home router settings!!
|
||||
wlan.ifconfig(config=('192.168.178.107', '255.255.255.0', '192.168.178.1', '8.8.8.8'))
|
||||
|
||||
if not wlan.isconnected():
|
||||
# change the line below to match your network ssid, security and password
|
||||
wlan.connect('mywifi', auth=(WLAN.WPA2, 'mywifikey'), timeout=5000)
|
||||
while not wlan.isconnected():
|
||||
machine.idle() # save power while waiting
|
||||
```
|
||||
|
||||
{% hint style="info" %}
|
||||
Notice how we check for the reset cause and the connection status, this is crucial in order to be able to soft reset the LoPy during a telnet session without breaking the connection.
|
||||
{% endhint %}
|
||||
|
||||
## Multiple Networks using a Static IP Address
|
||||
|
||||
The following script holds a list with nets and an optional list of `wlan_config` to set a fixed IP
|
||||
|
||||
```python
|
||||
import os
|
||||
import machine
|
||||
|
||||
uart = machine.UART(0, 115200)
|
||||
os.dupterm(uart)
|
||||
|
||||
known_nets = {
|
||||
'<net>': {'pwd': '<password>'},
|
||||
'<net>': {'pwd': '<password>', 'wlan_config': ('10.0.0.114', '255.255.0.0', '10.0.0.1', '10.0.0.1')}, # (ip, subnet_mask, gateway, DNS_server)
|
||||
}
|
||||
|
||||
if machine.reset_cause() != machine.SOFT_RESET:
|
||||
from network import WLAN
|
||||
wl = WLAN()
|
||||
wl.mode(WLAN.STA)
|
||||
original_ssid = wl.ssid()
|
||||
original_auth = wl.auth()
|
||||
|
||||
print("Scanning for known wifi nets")
|
||||
available_nets = wl.scan()
|
||||
nets = frozenset([e.ssid for e in available_nets])
|
||||
|
||||
known_nets_names = frozenset([key for key in known_nets])
|
||||
net_to_use = list(nets & known_nets_names)
|
||||
try:
|
||||
net_to_use = net_to_use[0]
|
||||
net_properties = known_nets[net_to_use]
|
||||
pwd = net_properties['pwd']
|
||||
sec = [e.sec for e in available_nets if e.ssid == net_to_use][0]
|
||||
if 'wlan_config' in net_properties:
|
||||
wl.ifconfig(config=net_properties['wlan_config'])
|
||||
wl.connect(net_to_use, (sec, pwd), timeout=10000)
|
||||
while not wl.isconnected():
|
||||
machine.idle() # save power while waiting
|
||||
print("Connected to "+net_to_use+" with IP address:" + wl.ifconfig()[0])
|
||||
|
||||
except Exception as e:
|
||||
print("Failed to connect to any known network, going into AP mode")
|
||||
wl.init(mode=WLAN.AP, ssid=original_ssid, auth=original_auth, channel=6, antenna=WLAN.INT_ANT)
|
||||
```
|
||||
|
||||
## Connecting to a WPA2-Enterprise network
|
||||
|
||||
### Connecting with EAP-TLS:
|
||||
|
||||
Before connecting, obtain and copy the public and private keys to the device, e.g. under location `/flash/cert`. If it is required to validate the server’s public key, an appropriate CA certificate \(chain\) must also be provided.
|
||||
|
||||
```python
|
||||
from network import WLAN
|
||||
|
||||
wlan = WLAN(mode=WLAN.STA)
|
||||
wlan.connect(ssid='mywifi', auth=(WLAN.WPA2_ENT,), identity='myidentity', ca_certs='/flash/cert/ca.pem', keyfile='/flash/cert/client.key', certfile='/flash/cert/client.crt')
|
||||
```
|
||||
|
||||
### Connecting with EAP-PEAP or EAP-TTLS:
|
||||
|
||||
In case of EAP-PEAP \(or EAP-TTLS\), the client key and certificate are not necessary, only a username and password pair. If it is required to validate the server’s public key, an appropriate CA certificate \(chain\) must also be provided.
|
||||
|
||||
```python
|
||||
from network import WLAN
|
||||
|
||||
wlan = WLAN(mode=WLAN.STA)
|
||||
wlan.connect(ssid='mywifi', auth=(WLAN.WPA2_ENT, 'username', 'password'), identity='myidentity', ca_certs='/flash/cert/ca.pem')
|
||||
```
|
||||
|
||||
14
tutorials/introduction.md
Normal file
14
tutorials/introduction.md
Normal file
@@ -0,0 +1,14 @@
|
||||
# Introduction
|
||||
|
||||

|
||||
|
||||
## Tutorials and Examples
|
||||
|
||||
This section contains tutorials and examples for use with Pycom modules and Expansion boards.
|
||||
|
||||
General Pycom tutorials contains tutorials that may be run on any Pycom device, such as connecting to a WiFi network, Bluetooth, controlling I/O pins etc. Later sections are specific to the LoPy and SiPy devices such as setting up a LoRa node or connecting to the Sigfox network. The final sections are related to examples using the Pytrack and Pysense.
|
||||
|
||||
Before starting, ensure that any Pycom devices are running the latest firmware; for instructions see [Firmware Updates](../gettingstarted/installation/firmwaretool.md).
|
||||
|
||||
The source code for these tutorials, along with the required libraries can be found in in the [pycom-libraries](https://github.com/pycom/pycom-libraries) repository.
|
||||
|
||||
8
tutorials/lora/README.md
Normal file
8
tutorials/lora/README.md
Normal file
@@ -0,0 +1,8 @@
|
||||
# LoRa Examples
|
||||
|
||||
The following tutorials demonstrate the use of the LoRa functionality on the LoPy. LoRa can work in 2 different modes; **LoRa-MAC** \(which we also call Raw-LoRa\) and **LoRaWAN** mode.
|
||||
|
||||
LoRa-MAC mode basically accesses de radio directly and packets are sent using the LoRa modulation on the selected frequency without any headers, addressing information or encryption. Only a CRC is added at the tail of the packet and this is removed before the received frame is passed on to the application. This mode can be used to build any higher level protocol that can benefit from the long range features of the LoRa modulation. Typical uses cases include LoPy to LoPy direct communication and a LoRa packet forwarder.
|
||||
|
||||
LoRaWAN mode implements the full LoRaWAN stack for a class A device. It supports both OTAA and ABP connection methods, as well as advanced features like adding and removing custom channels to support "special" frequencies plans like the those used in New Zealand.
|
||||
|
||||
106
tutorials/lora/lora-mac-nano-gateway.md
Normal file
106
tutorials/lora/lora-mac-nano-gateway.md
Normal file
@@ -0,0 +1,106 @@
|
||||
# LoRa-MAC Nano-Gateway
|
||||
|
||||
This example allows a raw LoRa connection between two LoPys \(nodes\) to a single LoPy acting as a Nano-Gateway.
|
||||
|
||||
For more information and discussions about this code, see this forum [post](https://forum.pycom.io/topic/236/lopy-nano-gateway).
|
||||
|
||||
## Gateway Code
|
||||
|
||||
```python
|
||||
import socket
|
||||
import struct
|
||||
from network import LoRa
|
||||
|
||||
# A basic package header, B: 1 byte for the deviceId, B: 1 byte for the pkg size, %ds: Formatted string for string
|
||||
_LORA_PKG_FORMAT = "!BB%ds"
|
||||
# A basic ack package, B: 1 byte for the deviceId, B: 1 byte for the pkg size, B: 1 byte for the Ok (200) or error messages
|
||||
_LORA_PKG_ACK_FORMAT = "BBB"
|
||||
|
||||
# Open a LoRa Socket, use rx_iq to avoid listening to our own messages
|
||||
# Please pick the region that matches where you are using the device:
|
||||
# Asia = LoRa.AS923
|
||||
# Australia = LoRa.AU915
|
||||
# Europe = LoRa.EU868
|
||||
# United States = LoRa.US915
|
||||
lora = LoRa(mode=LoRa.LORA, rx_iq=True, region=LoRa.EU868)
|
||||
lora_sock = socket.socket(socket.AF_LORA, socket.SOCK_RAW)
|
||||
lora_sock.setblocking(False)
|
||||
|
||||
while (True):
|
||||
recv_pkg = lora_sock.recv(512)
|
||||
if (len(recv_pkg) > 2):
|
||||
recv_pkg_len = recv_pkg[1]
|
||||
|
||||
device_id, pkg_len, msg = struct.unpack(_LORA_PKG_FORMAT % recv_pkg_len, recv_pkg)
|
||||
|
||||
# If the uart = machine.UART(0, 115200) and os.dupterm(uart) are set in the boot.py this print should appear in the serial port
|
||||
print('Device: %d - Pkg: %s' % (device_id, msg))
|
||||
|
||||
ack_pkg = struct.pack(_LORA_PKG_ACK_FORMAT, device_id, 1, 200)
|
||||
lora_sock.send(ack_pkg)
|
||||
```
|
||||
|
||||
The `_LORA_PKG_FORMAT` is used as a method of identifying the different devices within a network. The `_LORA_PKG_ACK_FORMAT` is a simple `ack` package as a response to the nodes package.
|
||||
|
||||
## Node
|
||||
|
||||
```python
|
||||
import os
|
||||
import socket
|
||||
import time
|
||||
import struct
|
||||
from network import LoRa
|
||||
|
||||
# A basic package header, B: 1 byte for the deviceId, B: 1 byte for the pkg size
|
||||
_LORA_PKG_FORMAT = "BB%ds"
|
||||
_LORA_PKG_ACK_FORMAT = "BBB"
|
||||
DEVICE_ID = 0x01
|
||||
|
||||
|
||||
# Open a Lora Socket, use tx_iq to avoid listening to our own messages
|
||||
# Please pick the region that matches where you are using the device:
|
||||
# Asia = LoRa.AS923
|
||||
# Australia = LoRa.AU915
|
||||
# Europe = LoRa.EU868
|
||||
# United States = LoRa.US915
|
||||
lora = LoRa(mode=LoRa.LORA, tx_iq=True, region=LoRa.EU868)
|
||||
lora_sock = socket.socket(socket.AF_LORA, socket.SOCK_RAW)
|
||||
lora_sock.setblocking(False)
|
||||
|
||||
while(True):
|
||||
# Package send containing a simple string
|
||||
msg = "Device 1 Here"
|
||||
pkg = struct.pack(_LORA_PKG_FORMAT % len(msg), DEVICE_ID, len(msg), msg)
|
||||
lora_sock.send(pkg)
|
||||
|
||||
# Wait for the response from the gateway. NOTE: For this demo the device does an infinite loop for while waiting the response. Introduce a max_time_waiting for you application
|
||||
waiting_ack = True
|
||||
while(waiting_ack):
|
||||
recv_ack = lora_sock.recv(256)
|
||||
|
||||
if (len(recv_ack) > 0):
|
||||
device_id, pkg_len, ack = struct.unpack(_LORA_PKG_ACK_FORMAT, recv_ack)
|
||||
if (device_id == DEVICE_ID):
|
||||
if (ack == 200):
|
||||
waiting_ack = False
|
||||
# If the uart = machine.UART(0, 115200) and os.dupterm(uart) are set in the boot.py this print should appear in the serial port
|
||||
print("ACK")
|
||||
else:
|
||||
waiting_ack = False
|
||||
# If the uart = machine.UART(0, 115200) and os.dupterm(uart) are set in the boot.py this print should appear in the serial port
|
||||
print("Message Failed")
|
||||
|
||||
time.sleep(5)
|
||||
```
|
||||
|
||||
The node is always sending packages and waiting for the `ack` from the gateway.
|
||||
|
||||
{% hint style="info" %}
|
||||
To adapt this code to user specific needs:
|
||||
|
||||
* Put a max waiting time for the `ack` to arrive and resend the package or mark it as invalid
|
||||
* Increase the package size changing the `_LORA_PKG_FORMAT` to `BH%ds`. The `H` will allow the keeping of 2 bytes for size \(for more information about [struct format](https://docs.python.org/2/library/struct.html#format-characters)\)
|
||||
* Reduce the package size with bitwise manipulation
|
||||
* Reduce the message size \(for this demo, a string\) to something more useful for specific development
|
||||
{% endhint %}
|
||||
|
||||
38
tutorials/lora/lora-mac.md
Normal file
38
tutorials/lora/lora-mac.md
Normal file
@@ -0,0 +1,38 @@
|
||||
# LoRa-MAC \(Raw LoRa\)
|
||||
|
||||
Basic LoRa connection example, sending and receiving data. In LoRa-MAC mode the LoRaWAN layer is bypassed and the radio is used directly. The data sent is not formatted or encrypted in any way, and no addressing information is added to the frame.
|
||||
|
||||
For the example below, you will need two LoPys. A `while` loop with a random delay time is used to minimise the chances of the 2 LoPy’s transmitting at the same time. Run the code below on the 2 LoPy modules and you will see the word 'Hello' being received on both sides.
|
||||
|
||||
```python
|
||||
from network import LoRa
|
||||
import socket
|
||||
import machine
|
||||
import time
|
||||
|
||||
# initialise LoRa in LORA mode
|
||||
# Please pick the region that matches where you are using the device:
|
||||
# Asia = LoRa.AS923
|
||||
# Australia = LoRa.AU915
|
||||
# Europe = LoRa.EU868
|
||||
# United States = LoRa.US915
|
||||
# more params can also be given, like frequency, tx power and spreading factor
|
||||
lora = LoRa(mode=LoRa.LORA, region=LoRa.EU868)
|
||||
|
||||
# create a raw LoRa socket
|
||||
s = socket.socket(socket.AF_LORA, socket.SOCK_RAW)
|
||||
|
||||
while True:
|
||||
# send some data
|
||||
s.setblocking(True)
|
||||
s.send('Hello')
|
||||
|
||||
# get any data received...
|
||||
s.setblocking(False)
|
||||
data = s.recv(64)
|
||||
print(data)
|
||||
|
||||
# wait a random amount of time
|
||||
time.sleep(machine.rng() & 0x0F)
|
||||
```
|
||||
|
||||
50
tutorials/lora/lorawan-abp.md
Normal file
50
tutorials/lora/lorawan-abp.md
Normal file
@@ -0,0 +1,50 @@
|
||||
# LoRaWAN with ABP
|
||||
|
||||
ABP stands for Authentication By Personalisation. It means that the encryption keys are configured manually on the device and can start sending frames to the Gateway without needing a 'handshake' procedure to exchange the keys \(such as the one performed during an OTAA join procedure\).
|
||||
|
||||
The example below attempts to get any data received after sending the frame. Keep in mind that the Gateway might not be sending any data back, therefore we make the socket non-blocking before attempting to receive, in order to prevent getting stuck waiting for a packet that will never arrive.
|
||||
|
||||
```python
|
||||
from network import LoRa
|
||||
import socket
|
||||
import ubinascii
|
||||
import struct
|
||||
|
||||
# Initialise LoRa in LORAWAN mode.
|
||||
# Please pick the region that matches where you are using the device:
|
||||
# Asia = LoRa.AS923
|
||||
# Australia = LoRa.AU915
|
||||
# Europe = LoRa.EU868
|
||||
# United States = LoRa.US915
|
||||
lora = LoRa(mode=LoRa.LORAWAN, region=LoRa.EU868)
|
||||
|
||||
# create an ABP authentication params
|
||||
dev_addr = struct.unpack(">l", ubinascii.unhexlify('00000005'))[0]
|
||||
nwk_swkey = ubinascii.unhexlify('2B7E151628AED2A6ABF7158809CF4F3C')
|
||||
app_swkey = ubinascii.unhexlify('2B7E151628AED2A6ABF7158809CF4F3C')
|
||||
|
||||
# join a network using ABP (Activation By Personalization)
|
||||
lora.join(activation=LoRa.ABP, auth=(dev_addr, nwk_swkey, app_swkey))
|
||||
|
||||
# create a LoRa socket
|
||||
s = socket.socket(socket.AF_LORA, socket.SOCK_RAW)
|
||||
|
||||
# set the LoRaWAN data rate
|
||||
s.setsockopt(socket.SOL_LORA, socket.SO_DR, 5)
|
||||
|
||||
# make the socket blocking
|
||||
# (waits for the data to be sent and for the 2 receive windows to expire)
|
||||
s.setblocking(True)
|
||||
|
||||
# send some data
|
||||
s.send(bytes([0x01, 0x02, 0x03]))
|
||||
|
||||
# make the socket non-blocking
|
||||
# (because if there's no data received it will block forever...)
|
||||
s.setblocking(False)
|
||||
|
||||
# get any data received (if any...)
|
||||
data = s.recv(64)
|
||||
print(data)
|
||||
```
|
||||
|
||||
549
tutorials/lora/lorawan-nano-gateway.md
Normal file
549
tutorials/lora/lorawan-nano-gateway.md
Normal file
@@ -0,0 +1,549 @@
|
||||
# LoRaWAN Nano-Gateway
|
||||
|
||||
This example allows to connect a LoPy to a LoRaWAN network such as The Things Network \(TTN\) or Loriot to be used as a nano-gateway.
|
||||
|
||||
This example uses settings specifically for connecting to The Things Network within the European 868 MHz region. For another usage, please see the `config.py` file for relevant sections that need changing.
|
||||
|
||||
Up to date versions of these snippets can be found at the following [GitHub Repository](https://github.com/pycom/pycom-libraries/tree/master/examples/lorawan-nano-gateway). For more information and discussion about this code, see this forum [post](https://forum.pycom.io/topic/810/new-firmware-release-1-6-7-b1-lorawan-nano-gateway-with-ttn-example).
|
||||
|
||||
## Nano-Gateway
|
||||
|
||||
The Nano-Gateway code is split into 3 files, `main.py`, `config.py` and `nanogateway.py`. These are used to configure and specify how the gateway will connect to a preferred network and how it can act as packet forwarder.
|
||||
|
||||
### Gateway ID
|
||||
|
||||
Most LoRaWAN network servers expect a Gateway ID in the form of a unique 64-bit hexadecimal number \(called a EUI-64\). The recommended practice is to produce this ID from your board by expanding the WiFi MAC address \(a 48-bit number, called MAC-48\). You can obtain that by running this code prior to configuration:
|
||||
|
||||
```python
|
||||
from network import WLAN
|
||||
import ubinascii
|
||||
wl = WLAN()
|
||||
ubinascii.hexlify(wl.mac())[:6] + 'FFFE' + ubinascii.hexlify(wl.mac())[6:]
|
||||
```
|
||||
|
||||
The result will by something like `b'240ac4FFFE008d88'` where `240ac4FFFE008d88` is your Gateway ID to be used in your network provider configuration.
|
||||
|
||||
## Main \(`main.py`\)
|
||||
|
||||
This file runs at boot and calls the library and `config.py` files to initialise the nano-gateway. Once configuration is set, the nano-gateway is then started.
|
||||
|
||||
```python
|
||||
""" LoPy LoRaWAN Nano Gateway example usage """
|
||||
|
||||
import config
|
||||
from nanogateway import NanoGateway
|
||||
|
||||
if __name__ == '__main__':
|
||||
nanogw = NanoGateway(
|
||||
id=config.GATEWAY_ID,
|
||||
frequency=config.LORA_FREQUENCY,
|
||||
datarate=config.LORA_GW_DR,
|
||||
ssid=config.WIFI_SSID,
|
||||
password=config.WIFI_PASS,
|
||||
server=config.SERVER,
|
||||
port=config.PORT,
|
||||
ntp_server=config.NTP,
|
||||
ntp_period=config.NTP_PERIOD_S
|
||||
)
|
||||
|
||||
nanogw.start()
|
||||
nanogw._log('You may now press ENTER to enter the REPL')
|
||||
input()
|
||||
```
|
||||
|
||||
## Configuration \(`config.py`\)
|
||||
|
||||
This file contains settings for the server and network it is connecting to. Depending on the nano-gateway region and provider \(TTN, Loriot, etc.\) these will vary. The provided example will work with The Things Network \(TTN\) in the European, 868Mhz, region.
|
||||
|
||||
The Gateway ID is generated in the script using the process described above.
|
||||
|
||||
**Please change the WIFI\_SSID and WIFI\_PASS variables to match your desired WiFi network**
|
||||
|
||||
```python
|
||||
""" LoPy LoRaWAN Nano Gateway configuration options """
|
||||
|
||||
import machine
|
||||
import ubinascii
|
||||
|
||||
WIFI_MAC = ubinascii.hexlify(machine.unique_id()).upper()
|
||||
# Set the Gateway ID to be the first 3 bytes of MAC address + 'FFFE' + last 3 bytes of MAC address
|
||||
GATEWAY_ID = WIFI_MAC[:6] + "FFFE" + WIFI_MAC[6:12]
|
||||
|
||||
SERVER = 'router.eu.thethings.network'
|
||||
PORT = 1700
|
||||
|
||||
NTP = "pool.ntp.org"
|
||||
NTP_PERIOD_S = 3600
|
||||
|
||||
WIFI_SSID = 'my-wifi'
|
||||
WIFI_PASS = 'my-wifi-password'
|
||||
|
||||
# for EU868
|
||||
LORA_FREQUENCY = 868100000
|
||||
LORA_GW_DR = "SF7BW125" # DR_5
|
||||
LORA_NODE_DR = 5
|
||||
|
||||
# for US915
|
||||
# LORA_FREQUENCY = 903900000
|
||||
# LORA_GW_DR = "SF7BW125" # DR_3
|
||||
# LORA_NODE_DR = 3
|
||||
```
|
||||
|
||||
## Library \(`nanogateway.py`\)
|
||||
|
||||
The nano-gateway library controls all of the packet generation and forwarding for the LoRa data. This does not require any user configuration and the latest version of this code should be downloaded from the Pycom [GitHub Repository](https://github.com/pycom/pycom-libraries/tree/master/examples/lorawan-nano-gateway).
|
||||
|
||||
```python
|
||||
""" LoPy Nano Gateway class """
|
||||
|
||||
from network import WLAN
|
||||
from network import LoRa
|
||||
from machine import Timer
|
||||
import os
|
||||
import ubinascii
|
||||
import machine
|
||||
import json
|
||||
import time
|
||||
import errno
|
||||
import _thread
|
||||
import socket
|
||||
|
||||
|
||||
PROTOCOL_VERSION = const(2)
|
||||
|
||||
PUSH_DATA = const(0)
|
||||
PUSH_ACK = const(1)
|
||||
PULL_DATA = const(2)
|
||||
PULL_ACK = const(4)
|
||||
PULL_RESP = const(3)
|
||||
|
||||
TX_ERR_NONE = "NONE"
|
||||
TX_ERR_TOO_LATE = "TOO_LATE"
|
||||
TX_ERR_TOO_EARLY = "TOO_EARLY"
|
||||
TX_ERR_COLLISION_PACKET = "COLLISION_PACKET"
|
||||
TX_ERR_COLLISION_BEACON = "COLLISION_BEACON"
|
||||
TX_ERR_TX_FREQ = "TX_FREQ"
|
||||
TX_ERR_TX_POWER = "TX_POWER"
|
||||
TX_ERR_GPS_UNLOCKED = "GPS_UNLOCKED"
|
||||
|
||||
STAT_PK = {"stat": {"time": "", "lati": 0,
|
||||
"long": 0, "alti": 0,
|
||||
"rxnb": 0, "rxok": 0,
|
||||
"rxfw": 0, "ackr": 100.0,
|
||||
"dwnb": 0, "txnb": 0}}
|
||||
|
||||
RX_PK = {"rxpk": [{"time": "", "tmst": 0,
|
||||
"chan": 0, "rfch": 0,
|
||||
"freq": 868.1, "stat": 1,
|
||||
"modu": "LORA", "datr": "SF7BW125",
|
||||
"codr": "4/5", "rssi": 0,
|
||||
"lsnr": 0, "size": 0,
|
||||
"data": ""}]}
|
||||
|
||||
TX_ACK_PK = {"txpk_ack":{"error":""}}
|
||||
|
||||
|
||||
class NanoGateway:
|
||||
|
||||
def __init__(self, id, frequency, datarate, ssid, password, server, port, ntp='pool.ntp.org', ntp_period=3600):
|
||||
self.id = id
|
||||
self.frequency = frequency
|
||||
self.sf = self._dr_to_sf(datarate)
|
||||
self.ssid = ssid
|
||||
self.password = password
|
||||
self.server = server
|
||||
self.port = port
|
||||
self.ntp = ntp
|
||||
self.ntp_period = ntp_period
|
||||
|
||||
self.rxnb = 0
|
||||
self.rxok = 0
|
||||
self.rxfw = 0
|
||||
self.dwnb = 0
|
||||
self.txnb = 0
|
||||
|
||||
self.stat_alarm = None
|
||||
self.pull_alarm = None
|
||||
self.uplink_alarm = None
|
||||
|
||||
self.udp_lock = _thread.allocate_lock()
|
||||
|
||||
self.lora = None
|
||||
self.lora_sock = None
|
||||
|
||||
def start(self):
|
||||
# Change WiFi to STA mode and connect
|
||||
self.wlan = WLAN(mode=WLAN.STA)
|
||||
self._connect_to_wifi()
|
||||
|
||||
# Get a time Sync
|
||||
self.rtc = machine.RTC()
|
||||
self.rtc.ntp_sync(self.ntp, update_period=self.ntp_period)
|
||||
|
||||
# Get the server IP and create an UDP socket
|
||||
self.server_ip = socket.getaddrinfo(self.server, self.port)[0][-1]
|
||||
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
|
||||
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
self.sock.setblocking(False)
|
||||
|
||||
# Push the first time immediately
|
||||
self._push_data(self._make_stat_packet())
|
||||
|
||||
# Create the alarms
|
||||
self.stat_alarm = Timer.Alarm(handler=lambda t: self._push_data(self._make_stat_packet()), s=60, periodic=True)
|
||||
self.pull_alarm = Timer.Alarm(handler=lambda u: self._pull_data(), s=25, periodic=True)
|
||||
|
||||
# Start the UDP receive thread
|
||||
_thread.start_new_thread(self._udp_thread, ())
|
||||
|
||||
# Initialize LoRa in LORA mode
|
||||
self.lora = LoRa(mode=LoRa.LORA, frequency=self.frequency, bandwidth=LoRa.BW_125KHZ, sf=self.sf,
|
||||
preamble=8, coding_rate=LoRa.CODING_4_5, tx_iq=True)
|
||||
# Create a raw LoRa socket
|
||||
self.lora_sock = socket.socket(socket.AF_LORA, socket.SOCK_RAW)
|
||||
self.lora_sock.setblocking(False)
|
||||
self.lora_tx_done = False
|
||||
|
||||
self.lora.callback(trigger=(LoRa.RX_PACKET_EVENT | LoRa.TX_PACKET_EVENT), handler=self._lora_cb)
|
||||
|
||||
def stop(self):
|
||||
# TODO: Check how to stop the NTP sync
|
||||
# TODO: Create a cancel method for the alarm
|
||||
# TODO: kill the UDP thread
|
||||
self.sock.close()
|
||||
|
||||
def _connect_to_wifi(self):
|
||||
self.wlan.connect(self.ssid, auth=(None, self.password))
|
||||
while not self.wlan.isconnected():
|
||||
time.sleep(0.5)
|
||||
print("WiFi connected!")
|
||||
|
||||
def _dr_to_sf(self, dr):
|
||||
sf = dr[2:4]
|
||||
if sf[1] not in '0123456789':
|
||||
sf = sf[:1]
|
||||
return int(sf)
|
||||
|
||||
def _sf_to_dr(self, sf):
|
||||
return "SF7BW125"
|
||||
|
||||
def _make_stat_packet(self):
|
||||
now = self.rtc.now()
|
||||
STAT_PK["stat"]["time"] = "%d-%02d-%02d %02d:%02d:%02d GMT" % (now[0], now[1], now[2], now[3], now[4], now[5])
|
||||
STAT_PK["stat"]["rxnb"] = self.rxnb
|
||||
STAT_PK["stat"]["rxok"] = self.rxok
|
||||
STAT_PK["stat"]["rxfw"] = self.rxfw
|
||||
STAT_PK["stat"]["dwnb"] = self.dwnb
|
||||
STAT_PK["stat"]["txnb"] = self.txnb
|
||||
return json.dumps(STAT_PK)
|
||||
|
||||
def _make_node_packet(self, rx_data, rx_time, tmst, sf, rssi, snr):
|
||||
RX_PK["rxpk"][0]["time"] = "%d-%02d-%02dT%02d:%02d:%02d.%dZ" % (rx_time[0], rx_time[1], rx_time[2], rx_time[3], rx_time[4], rx_time[5], rx_time[6])
|
||||
RX_PK["rxpk"][0]["tmst"] = tmst
|
||||
RX_PK["rxpk"][0]["datr"] = self._sf_to_dr(sf)
|
||||
RX_PK["rxpk"][0]["rssi"] = rssi
|
||||
RX_PK["rxpk"][0]["lsnr"] = float(snr)
|
||||
RX_PK["rxpk"][0]["data"] = ubinascii.b2a_base64(rx_data)[:-1]
|
||||
RX_PK["rxpk"][0]["size"] = len(rx_data)
|
||||
return json.dumps(RX_PK)
|
||||
|
||||
def _push_data(self, data):
|
||||
token = os.urandom(2)
|
||||
packet = bytes([PROTOCOL_VERSION]) + token + bytes([PUSH_DATA]) + ubinascii.unhexlify(self.id) + data
|
||||
with self.udp_lock:
|
||||
try:
|
||||
self.sock.sendto(packet, self.server_ip)
|
||||
except Exception:
|
||||
print("PUSH exception")
|
||||
|
||||
def _pull_data(self):
|
||||
token = os.urandom(2)
|
||||
packet = bytes([PROTOCOL_VERSION]) + token + bytes([PULL_DATA]) + ubinascii.unhexlify(self.id)
|
||||
with self.udp_lock:
|
||||
try:
|
||||
self.sock.sendto(packet, self.server_ip)
|
||||
except Exception:
|
||||
print("PULL exception")
|
||||
|
||||
def _ack_pull_rsp(self, token, error):
|
||||
TX_ACK_PK["txpk_ack"]["error"] = error
|
||||
resp = json.dumps(TX_ACK_PK)
|
||||
packet = bytes([PROTOCOL_VERSION]) + token + bytes([PULL_ACK]) + ubinascii.unhexlify(self.id) + resp
|
||||
with self.udp_lock:
|
||||
try:
|
||||
self.sock.sendto(packet, self.server_ip)
|
||||
except Exception:
|
||||
print("PULL RSP ACK exception")
|
||||
|
||||
def _lora_cb(self, lora):
|
||||
events = lora.events()
|
||||
if events & LoRa.RX_PACKET_EVENT:
|
||||
self.rxnb += 1
|
||||
self.rxok += 1
|
||||
rx_data = self.lora_sock.recv(256)
|
||||
stats = lora.stats()
|
||||
self._push_data(self._make_node_packet(rx_data, self.rtc.now(), stats.timestamp, stats.sf, stats.rssi, stats.snr))
|
||||
self.rxfw += 1
|
||||
if events & LoRa.TX_PACKET_EVENT:
|
||||
self.txnb += 1
|
||||
lora.init(mode=LoRa.LORA, frequency=self.frequency, bandwidth=LoRa.BW_125KHZ,
|
||||
sf=self.sf, preamble=8, coding_rate=LoRa.CODING_4_5, tx_iq=True)
|
||||
|
||||
def _send_down_link(self, data, tmst, datarate, frequency):
|
||||
self.lora.init(mode=LoRa.LORA, frequency=frequency, bandwidth=LoRa.BW_125KHZ,
|
||||
sf=self._dr_to_sf(datarate), preamble=8, coding_rate=LoRa.CODING_4_5,
|
||||
tx_iq=True)
|
||||
while time.ticks_us() < tmst:
|
||||
pass
|
||||
self.lora_sock.send(data)
|
||||
|
||||
def _udp_thread(self):
|
||||
while True:
|
||||
try:
|
||||
data, src = self.sock.recvfrom(1024)
|
||||
_token = data[1:3]
|
||||
_type = data[3]
|
||||
if _type == PUSH_ACK:
|
||||
print("Push ack")
|
||||
elif _type == PULL_ACK:
|
||||
print("Pull ack")
|
||||
elif _type == PULL_RESP:
|
||||
self.dwnb += 1
|
||||
ack_error = TX_ERR_NONE
|
||||
tx_pk = json.loads(data[4:])
|
||||
tmst = tx_pk["txpk"]["tmst"]
|
||||
t_us = tmst - time.ticks_us() - 5000
|
||||
if t_us < 0:
|
||||
t_us += 0xFFFFFFFF
|
||||
if t_us < 20000000:
|
||||
self.uplink_alarm = Timer.Alarm(handler=lambda x: self._send_down_link(ubinascii.a2b_base64(tx_pk["txpk"]["data"]),
|
||||
tx_pk["txpk"]["tmst"] - 10, tx_pk["txpk"]["datr"],
|
||||
int(tx_pk["txpk"]["freq"] * 1000000)), us=t_us)
|
||||
else:
|
||||
ack_error = TX_ERR_TOO_LATE
|
||||
print("Downlink timestamp error!, t_us:", t_us)
|
||||
self._ack_pull_rsp(_token, ack_error)
|
||||
print("Pull rsp")
|
||||
except socket.timeout:
|
||||
pass
|
||||
except OSError as e:
|
||||
if e.errno == errno.EAGAIN:
|
||||
pass
|
||||
else:
|
||||
print("UDP recv OSError Exception")
|
||||
except Exception:
|
||||
print("UDP recv Exception")
|
||||
# Wait before trying to receive again
|
||||
time.sleep(0.025)
|
||||
```
|
||||
|
||||
## Registering with TTN
|
||||
|
||||
To set up the gateway with The Things Network \(TTN\), navigate to their website and create/register an account. Enter a username and an email address to verify with their platform.
|
||||
|
||||

|
||||
|
||||
Once an account has been registered, the nano-gateway can then be registered. To do this, navigate to the TTN Console web page.
|
||||
|
||||
## Registering the Gateway
|
||||
|
||||
Inside the TTN Console, there are two options, `applications` and `gateways`. Select `gateways` and then click on `register gateway`. This will allow for the set up and registration of a new nano-gateway.
|
||||
|
||||

|
||||
|
||||
On the Register Gateway page, you will need to set the following settings:
|
||||
|
||||
 These are unique to each gateway, location and country specific frequency. Please verify that correct settings are selected otherwise the gateway will not connect to TTN.
|
||||
|
||||
**You need to tick the "I'm using the legacy packet forwarder" to enable the right settings.** This is because the Nano-Gateway uses the 'de facto' standard Semtech UDP protocol.
|
||||
|
||||
| Option | Value |
|
||||
| :--- | :--- |
|
||||
| Protocol | Packet Forwarder |
|
||||
| Gateway EUI | User Defined \(must match `config.py`\) |
|
||||
| Description | User Defined |
|
||||
| Frequency Plan | Select Country \(e.g. EU - 868 MHz\) |
|
||||
| Location | User Defined |
|
||||
| Antenna Placement | Indoor or Outdoor |
|
||||
|
||||
The Gateway EUI should match your Gateway ID from the `config.py` file. We suggest you follow the procedure described near the top of this document to create your own unique Gateway ID.
|
||||
|
||||
Once these settings have been applied, click `Register Gateway`. A Gateway Overview page will appear, with the configuration settings showing. Next click on the `Gateway Settings` and configure the Router address to match that of the gateway \(default: `router.eu.thethings.network`\).
|
||||
|
||||

|
||||
|
||||
The `Gateway` should now be configured. Next, one or more nodes can now be configured to use the nano-gateway and TTN applications may be built.
|
||||
|
||||
## LoPy Node
|
||||
|
||||
There are two methods of connecting LoPy devices to the nano-gateway, Over the Air Activation \(OTAA\) and Activation By Personalisation \(ABP\). The code and instructions for registering these methods are shown below, followed by instruction for how to connect them to an application on TTN.
|
||||
|
||||
{% hint style="info" %}
|
||||
It’s important that the following code examples \(also on GitHub\) are used to connect to the nano-gateway as it only supports single channel connections.
|
||||
{% endhint %}
|
||||
|
||||
### OTAA \(Over The Air Activation\)
|
||||
|
||||
When the LoPy connects an application \(via TTN\) using OTAA, the network configuration is derived automatically during a handshake between the LoPy and network server. Note that the network keys derived using the OTAA methodology are specific to the device and are used to encrypt and verify transmissions at the network level.
|
||||
|
||||
```python
|
||||
""" OTAA Node example compatible with the LoPy Nano Gateway """
|
||||
|
||||
from network import LoRa
|
||||
import socket
|
||||
import ubinascii
|
||||
import struct
|
||||
import time
|
||||
|
||||
# Initialize LoRa in LORAWAN mode.
|
||||
lora = LoRa(mode=LoRa.LORAWAN)
|
||||
|
||||
# create an OTA authentication params
|
||||
dev_eui = ubinascii.unhexlify('AABBCCDDEEFF7778') # these settings can be found from TTN
|
||||
app_eui = ubinascii.unhexlify('70B3D57EF0003BFD') # these settings can be found from TTN
|
||||
app_key = ubinascii.unhexlify('36AB7625FE77776881683B495300FFD6') # these settings can be found from TTN
|
||||
|
||||
# set the 3 default channels to the same frequency (must be before sending the OTAA join request)
|
||||
lora.add_channel(0, frequency=868100000, dr_min=0, dr_max=5)
|
||||
lora.add_channel(1, frequency=868100000, dr_min=0, dr_max=5)
|
||||
lora.add_channel(2, frequency=868100000, dr_min=0, dr_max=5)
|
||||
|
||||
# join a network using OTAA
|
||||
lora.join(activation=LoRa.OTAA, auth=(dev_eui, app_eui, app_key), timeout=0)
|
||||
|
||||
# wait until the module has joined the network
|
||||
while not lora.has_joined():
|
||||
time.sleep(2.5)
|
||||
print('Not joined yet...')
|
||||
|
||||
# remove all the non-default channels
|
||||
for i in range(3, 16):
|
||||
lora.remove_channel(i)
|
||||
|
||||
# create a LoRa socket
|
||||
s = socket.socket(socket.AF_LORA, socket.SOCK_RAW)
|
||||
|
||||
# set the LoRaWAN data rate
|
||||
s.setsockopt(socket.SOL_LORA, socket.SO_DR, 5)
|
||||
|
||||
# make the socket non-blocking
|
||||
s.setblocking(False)
|
||||
|
||||
time.sleep(5.0)
|
||||
|
||||
""" Your own code can be written below! """
|
||||
|
||||
for i in range (200):
|
||||
s.send(b'PKT #' + bytes([i]))
|
||||
time.sleep(4)
|
||||
rx = s.recv(256)
|
||||
if rx:
|
||||
print(rx)
|
||||
time.sleep(6)
|
||||
```
|
||||
|
||||
### ABP \(Activation By Personalisation\)
|
||||
|
||||
Using ABP join mode requires the user to define the following values and input them into both the LoPy and the TTN Application:
|
||||
|
||||
* Device Address
|
||||
* Application Session Key
|
||||
* Network Session Key
|
||||
|
||||
```python
|
||||
""" ABP Node example compatible with the LoPy Nano Gateway """
|
||||
|
||||
from network import LoRa
|
||||
import socket
|
||||
import ubinascii
|
||||
import struct
|
||||
import time
|
||||
|
||||
# Initialise LoRa in LORAWAN mode.
|
||||
lora = LoRa(mode=LoRa.LORAWAN)
|
||||
|
||||
# create an ABP authentication params
|
||||
dev_addr = struct.unpack(">l", ubinascii.unhexlify('2601147D'))[0] # these settings can be found from TTN
|
||||
nwk_swkey = ubinascii.unhexlify('3C74F4F40CAE2221303BC24284FCF3AF') # these settings can be found from TTN
|
||||
app_swkey = ubinascii.unhexlify('0FFA7072CC6FF69A102A0F39BEB0880F') # these settings can be found from TTN
|
||||
|
||||
# join a network using ABP (Activation By Personalisation)
|
||||
lora.join(activation=LoRa.ABP, auth=(dev_addr, nwk_swkey, app_swkey))
|
||||
|
||||
# remove all the non-default channels
|
||||
for i in range(3, 16):
|
||||
lora.remove_channel(i)
|
||||
|
||||
# set the 3 default channels to the same frequency
|
||||
lora.add_channel(0, frequency=868100000, dr_min=0, dr_max=5)
|
||||
lora.add_channel(1, frequency=868100000, dr_min=0, dr_max=5)
|
||||
lora.add_channel(2, frequency=868100000, dr_min=0, dr_max=5)
|
||||
|
||||
# create a LoRa socket
|
||||
s = socket.socket(socket.AF_LORA, socket.SOCK_RAW)
|
||||
|
||||
# set the LoRaWAN data rate
|
||||
s.setsockopt(socket.SOL_LORA, socket.SO_DR, 5)
|
||||
|
||||
# make the socket non-blocking
|
||||
s.setblocking(False)
|
||||
|
||||
""" Your own code can be written below! """
|
||||
|
||||
for i in range (200):
|
||||
s.send(b'PKT #' + bytes([i]))
|
||||
time.sleep(4)
|
||||
rx = s.recv(256)
|
||||
if rx:
|
||||
print(rx)
|
||||
time.sleep(6)
|
||||
```
|
||||
|
||||
## TTN Applications
|
||||
|
||||
Now that the gateway & nodes have been setup, a TTN Application can be built; i.e. what happens to the LoRa data once it is received by TTN. There are a number of different setups/systems that can be used, however the following example demonstrates the HTTP request integration.
|
||||
|
||||
## Registering an Application
|
||||
|
||||
Selecting the `Applications` tab at the top of the TTN console, will bring up a screen for registering applications. Click register and a new page, similar to the one below, will open.
|
||||
|
||||

|
||||
|
||||
Enter a unique `Application ID` as well as a Description & Handler Registration.
|
||||
|
||||
Now the LoPy nodes must be registered to send data up to the new Application.
|
||||
|
||||
### Registering Devices \(LoPy\)
|
||||
|
||||
To connect nodes to the nano-gateway, devices need to be added to the application. To do this, navigate to the `Devices` tab on the `Application` home page and click the `Register Device` button.
|
||||
|
||||

|
||||
|
||||
In the `Register Device` panel, complete the forms for the `Device ID` and the `Device EUI`. The `Device ID` is user specified and is unique to the device in this application. The `Device EUI` is also user specified but must consist of exactly 8 bytes, given in hexadecimal.
|
||||
|
||||
Once the device has been added, change the `Activation Method` between `OTAA` and `ABP` depending on user preference. This option can be found under the Settings tab.
|
||||
|
||||
### Adding Application Integrations
|
||||
|
||||
Now that the data is arriving on the TTN Backend, TTN can be managed as to where data should be delivered to. To do this, use the `Integrations` tab within the new Application’s settings.
|
||||
|
||||

|
||||
|
||||
Upon clicking `add integration`, a screen with 4 different options will appear. These have various functionality and more information about them can be found on the TTN website/documentation.
|
||||
|
||||
For this example, use the `HTTP Integration` to forward the LoRaWAN Packets to a remote server/address.
|
||||
|
||||

|
||||
|
||||
Click `HTTP Integration` to connect up an endpoint that can receive the data.
|
||||
|
||||
For testing, a website called [RequestBin](https://requestb.in/) may be used to receive the data that TTN forwards \(via POST Request\). To set this up, navigate to [RequestBin](https://requestb.in/) and click the `Create a RequestBin`.
|
||||
|
||||

|
||||
|
||||
Copy the URL that is generated and past this into the `URL` form under the `Application Settings`.
|
||||
|
||||

|
||||
|
||||
This is the address that TTN will forward data onto. As soon as a LoPy starts sending messages, TTN will forward these onto `RequestBin` and they will appear at the unique `RequestBin URL`.
|
||||
|
||||
54
tutorials/lora/lorawan-otaa.md
Normal file
54
tutorials/lora/lorawan-otaa.md
Normal file
@@ -0,0 +1,54 @@
|
||||
# LoRaWAN with OTAA
|
||||
|
||||
OTAA stands for Over The Air Authentication. With this method the LoPy sends a Join request to the LoRaWAN Gateway using the `APPEUI` and `APPKEY` provided. If the keys are correct the Gateway will reply to the LoPy with a join accept message and from that point on the LoPy is able to send and receive packets to/from the Gateway. If the keys are incorrect no response will be received and the `has_joined()` method will always return `False`.
|
||||
|
||||
The example below attempts to get any data received after sending the frame. Keep in mind that the Gateway might not be sending any data back, therefore we make the socket non-blocking before attempting to receive, in order to prevent getting stuck waiting for a packet that will never arrive.
|
||||
|
||||
```python
|
||||
from network import LoRa
|
||||
import socket
|
||||
import time
|
||||
import ubinascii
|
||||
|
||||
# Initialise LoRa in LORAWAN mode.
|
||||
# Please pick the region that matches where you are using the device:
|
||||
# Asia = LoRa.AS923
|
||||
# Australia = LoRa.AU915
|
||||
# Europe = LoRa.EU868
|
||||
# United States = LoRa.US915
|
||||
lora = LoRa(mode=LoRa.LORAWAN, region=LoRa.EU868)
|
||||
|
||||
# create an OTAA authentication parameters
|
||||
app_eui = ubinascii.unhexlify('ADA4DAE3AC12676B')
|
||||
app_key = ubinascii.unhexlify('11B0282A189B75B0B4D2D8C7FA38548B')
|
||||
|
||||
# join a network using OTAA (Over the Air Activation)
|
||||
lora.join(activation=LoRa.OTAA, auth=(app_eui, app_key), timeout=0)
|
||||
|
||||
# wait until the module has joined the network
|
||||
while not lora.has_joined():
|
||||
time.sleep(2.5)
|
||||
print('Not yet joined...')
|
||||
|
||||
# create a LoRa socket
|
||||
s = socket.socket(socket.AF_LORA, socket.SOCK_RAW)
|
||||
|
||||
# set the LoRaWAN data rate
|
||||
s.setsockopt(socket.SOL_LORA, socket.SO_DR, 5)
|
||||
|
||||
# make the socket blocking
|
||||
# (waits for the data to be sent and for the 2 receive windows to expire)
|
||||
s.setblocking(True)
|
||||
|
||||
# send some data
|
||||
s.send(bytes([0x01, 0x02, 0x03]))
|
||||
|
||||
# make the socket non-blocking
|
||||
# (because if there's no data received it will block forever...)
|
||||
s.setblocking(False)
|
||||
|
||||
# get any data received (if any...)
|
||||
data = s.recv(64)
|
||||
print(data)
|
||||
```
|
||||
|
||||
46
tutorials/lora/module-module.md
Normal file
46
tutorials/lora/module-module.md
Normal file
@@ -0,0 +1,46 @@
|
||||
# LoPy to LoPy
|
||||
|
||||
This example shows how to connect two Pycode LoRa capable modules \(nodes\) via raw LoRa.
|
||||
|
||||
## Node A
|
||||
|
||||
```python
|
||||
from network import LoRa
|
||||
import socket
|
||||
import time
|
||||
|
||||
# Please pick the region that matches where you are using the device:
|
||||
# Asia = LoRa.AS923
|
||||
# Australia = LoRa.AU915
|
||||
# Europe = LoRa.EU868
|
||||
# United States = LoRa.US915
|
||||
lora = LoRa(mode=LoRa.LORA, region=LoRa.EU868)
|
||||
s = socket.socket(socket.AF_LORA, socket.SOCK_RAW)
|
||||
s.setblocking(False)
|
||||
|
||||
while True:
|
||||
if s.recv(64) == b'Ping':
|
||||
s.send('Pong')
|
||||
time.sleep(5)
|
||||
```
|
||||
|
||||
## Node B
|
||||
|
||||
```python
|
||||
from network import LoRa
|
||||
import socket
|
||||
import time
|
||||
|
||||
# Please pick the region that matches where you are using the device:
|
||||
# Asia = LoRa.AS923
|
||||
# Australia = LoRa.AU915
|
||||
# Europe = LoRa.EU868
|
||||
# United States = LoRa.US915
|
||||
lora = LoRa(mode=LoRa.LORA, region=LoRa.EU868)
|
||||
s = socket.socket(socket.AF_LORA, socket.SOCK_RAW)
|
||||
s.setblocking(False)
|
||||
while True:
|
||||
s.send('Ping')
|
||||
time.sleep(5)
|
||||
```
|
||||
|
||||
39
tutorials/lora/rn2483-to-lopy.md
Normal file
39
tutorials/lora/rn2483-to-lopy.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# RN2483 to LoPy
|
||||
|
||||
This example shows how to send data between a Microchip RN2483 and a LoPy via raw LoRa.
|
||||
|
||||
## RN2483
|
||||
|
||||
```text
|
||||
mac pause
|
||||
radio set freq 868000000
|
||||
|
||||
radio set mod lora
|
||||
radio set bw 250
|
||||
radio set sf sf7
|
||||
radio set cr 4/5
|
||||
radio set bw 125
|
||||
radio set sync 12
|
||||
radio set prlen 8
|
||||
|
||||
# Transmit via radio tx:
|
||||
radio tx 48656c6C6F #(should send ‘Hello’)
|
||||
```
|
||||
|
||||
## LoPy
|
||||
|
||||
```python
|
||||
from network import LoRa
|
||||
import socket
|
||||
|
||||
lora = LoRa(mode=LoRa.LORA, frequency= 868000000, bandwidth=LoRa.BW_125KHZ, sf=7, preamble=8,
|
||||
coding_rate=LoRa.CODING_4_5, power_mode=LoRa.ALWAYS_ON,
|
||||
tx_iq=False, rx_iq=False, public=False)
|
||||
|
||||
s = socket.socket(socket.AF_LORA, socket.SOCK_RAW)
|
||||
|
||||
# This keeps listening for data "forever".
|
||||
while(True):
|
||||
s.recv(64)
|
||||
```
|
||||
|
||||
6
tutorials/lte/README.md
Normal file
6
tutorials/lte/README.md
Normal file
@@ -0,0 +1,6 @@
|
||||
# LTE Examples
|
||||
|
||||
The following tutorials demonstrate the use of the LTE CAT-M1 and NB-IoT functionality on cellular enabled Pycom modules.
|
||||
|
||||
Our cellular modules support both LTE CAT-M1 and NB-IoT, these are new lower power, long range, cellular protocols. These are not the same as the full version of 2G/3G/LTE supported by cell phones, and require your local carriers to support them. At the time of writing, CAT-M1 and NB-IoT connectivity is not widely available so be sure to check with local carriers if support is available where you are.
|
||||
|
||||
43
tutorials/lte/cat-m1.md
Normal file
43
tutorials/lte/cat-m1.md
Normal file
@@ -0,0 +1,43 @@
|
||||
# CAT-M1
|
||||
|
||||
{% hint style="info" %}
|
||||
Please ensure you have the latest Sequans modem firmware for the best network compatibility. Instructions for this can be found [here](firmware.md).
|
||||
{% endhint %}
|
||||
|
||||
The LTE Cat M1 service gives full IP access through the cellular modem.
|
||||
|
||||
Once the `lte.connect()` function has completed all the IP socket functions - including SSL - will be routed through this connection. This mean any code using WLAN can be adapted to Cat M1 by simply adding the connection setup step first and disconnect after.
|
||||
|
||||
For example to connect over LTE Cat M1 to Google's web server over secure SSL:
|
||||
|
||||
```python
|
||||
import socket
|
||||
import ssl
|
||||
import time
|
||||
from network import LTE
|
||||
|
||||
lte = LTE() # instantiate the LTE object
|
||||
lte.attach() # attach the cellular modem to a base station
|
||||
while not lte.isattached():
|
||||
time.sleep(0.25)
|
||||
lte.connect() # start a data session and obtain an IP address
|
||||
while not lte.isconnected():
|
||||
time.sleep(0.25)
|
||||
|
||||
s = socket.socket()
|
||||
s = ssl.wrap_socket(s)
|
||||
s.connect(socket.getaddrinfo('www.google.com', 443)[0][-1])
|
||||
s.send(b"GET / HTTP/1.0\r\n\r\n")
|
||||
print(s.recv(4096))
|
||||
s.close()
|
||||
|
||||
lte.disconnect()
|
||||
lte.dettach()
|
||||
```
|
||||
|
||||
This also applies to our MQTT and AWS examples.
|
||||
|
||||
**IMPORTANT:** Once the LTE radio is initialised, it must be de-initialised before going to deepsleep in order to ensure minimum power consumption. This is required due to the LTE radio being powered independently and allowing use cases which require the system to be taken out from deepsleep by an event from the LTE network \(data or SMS received for instance\).
|
||||
|
||||
When using the expansion board and the FiPy together, the RTS/CTS jumpers **MUST** be removed as those pins are being used by the LTE radio. Keeping those jumpers in place will lead to erratic operation and higher current consumption specially while in deepsleep.
|
||||
|
||||
179
tutorials/lte/firmware.md
Normal file
179
tutorials/lte/firmware.md
Normal file
@@ -0,0 +1,179 @@
|
||||
# Modem Firmware Update
|
||||
|
||||
{% hint style="info" %}
|
||||
This article is only related to GPy, FiPy, and G01 boards
|
||||
{% endhint %}
|
||||
|
||||
{% hint style="danger" %}
|
||||
**Important**: When upgrading your modem for the first time, even if you have updated it in the past with the old firmware update method, you **MUST** use the "recovery" upgrade method described below. Otherwise you will risk breaking your module
|
||||
{% endhint %}
|
||||
|
||||
Please read the following instructions carefully as there are some significant changes compared to the previous updater version.
|
||||
|
||||
Most importantly, the updater is now integrated in the latest stable firmware release \(we will also publish a new development and pybytes firmware in the coming days\), so you no longer need to upload any scripts to your module. The built-in updater will take precedence over any scripts uploaded.
|
||||
|
||||
Please start with the following steps:
|
||||
|
||||
1. Upgrade the Pycom Firmware Updater tool to latest version
|
||||
2. Select Firmware Type `stable` in the communication window to upgrade to version `v1.18.1.r1`
|
||||
|
||||
You can find the different versions of firmwares available here: [https://software.pycom.io/downloads/sequans2.html](https://software.pycom.io/downloads/sequans2.html)
|
||||
|
||||
There are two packages available, one for the latest CAT-M1 firmware, and another for the latest NB-IoT firmware.
|
||||
|
||||
After unpacking the zip archive, you will find each firmware packages contains two files, one being the firmware file \(`CATM1-38638.dup` or `NB1-37781.dup`\) and the `updater.elf` file, which is required when using the "recovery" firmware update method or if a previous upgrade failed and the modem is in recovery mode.
|
||||
|
||||
Please note that the `updater.elf` file is only around 300K so you can also store it inside the flash file system of the module. The firmware dup files will NOT fit into the available `/flash` file system on the module, so you either need to use an SD card or upload it directly from your computer.
|
||||
|
||||
## Via SD card
|
||||
|
||||
To transfer the firmware files onto the SD card you have two options:
|
||||
|
||||
1. Format your SD card as with the FAT file system and then copy the files onto the card using your computer
|
||||
2. Make sure your SD card has an MBR and a single primary partition, the format it directly on the module and mount it.
|
||||
3. Transfer the firmware files onto the SD card using FTP. Please ensure the transfer is successful and that the file on the module has the same size as the original file.
|
||||
|
||||
```python
|
||||
from machine import SD
|
||||
|
||||
sd = SD()
|
||||
os.mkfs(sd) # format SD card
|
||||
os.mount(sd, '/sd') # mount it
|
||||
os.listdir('/sd') # list its content
|
||||
```
|
||||
|
||||
Once you copied/uploaded the firmware files on to the SD card you can flash the LTE modem using the following command:
|
||||
|
||||
To flash the CAT-M1 firmware onto your device:
|
||||
|
||||
```python
|
||||
import sqnsupgrade
|
||||
sqnsupgrade.run('/sd/CATM1-38638.dup', '/sd/updater.elf')
|
||||
```
|
||||
|
||||
To flash the NB-IoT firmware onto your device:
|
||||
|
||||
```python
|
||||
import sqnsupgrade
|
||||
sqnsupgrade.run('/sd/NB1-37781.dup', '/sd/updater.elf')
|
||||
```
|
||||
|
||||
Please note you can directly flash the desired firmware onto your module, it is not necessary to upgrade to the latest CAT-M1 firmware before switching to NB-IoT.
|
||||
|
||||
If you have already mounted the SD card, please use the path you used when mounting it. Otherwise, if an absolute path other than `/flash` is specified, the script will automatically mount the SD card using the path specified.
|
||||
|
||||
Once update is finished successfully you will have a summary of new updated versions. The full output from the upgrade will looks similar to this:
|
||||
|
||||
```text
|
||||
<<< Welcome to the SQN3330 firmware updater >>>
|
||||
Attempting AT wakeup...
|
||||
Starting STP (DO NOT DISCONNECT POWER!!!)
|
||||
Session opened: version 1, max transfer 8192 bytes
|
||||
Sending 54854 bytes: [########################################] 100%
|
||||
Bootrom updated successfully, switching to upgrade mode
|
||||
Attempting AT auto-negotiation...
|
||||
Session opened: version 1, max transfer 2048 bytes
|
||||
Sending 306076 bytes: [########################################] 100%
|
||||
Attempting AT wakeup...
|
||||
Upgrader loaded successfully, modem is in upgrade mode
|
||||
Attempting AT wakeup...
|
||||
Starting STP ON_THE_FLY
|
||||
Session opened: version 1, max transfer 8192 bytes
|
||||
Sending 5996938 bytes: [########################################] 100%
|
||||
Code download done, returning to user mode
|
||||
Resetting (DO NOT DISCONNECT POWER!!!)................
|
||||
Upgrade completed!
|
||||
Here's the current firmware version:
|
||||
|
||||
SYSTEM VERSION
|
||||
==============
|
||||
FIRMWARE VERSION
|
||||
Bootloader0 : 5.1.1.0 [33080]
|
||||
Bootloader1 : 5.1.1.0 [38638]
|
||||
Bootloader2* : 5.1.1.0 [38638]
|
||||
NV Info : 1.1,0,0
|
||||
Software : 5.1.1.0 [38638] by robot-soft at 2018-08-20 09:51:46
|
||||
UE : 5.0.0.0d
|
||||
COMPONENTS
|
||||
ZSP0 : 1.0.99-13604
|
||||
ZSP1 : 1.0.99-12341
|
||||
```
|
||||
|
||||
{% hint style="info" %}
|
||||
|
||||
After you have updated your modem once using the recovery method, you can now flash your modem again using just the `CATM1-38638.dup` or `NB1-37781.dup` file without specifying the `updater.elf` file. However, should the upgrade fail, your modem may end up in recovery mode and you will need the `updater.elf` file again. The updater will check for this and prompt you if using the `updater.elf` file is necessary.
|
||||
|
||||
Example output using just the firmware file:
|
||||
|
||||
```text
|
||||
<<< Welcome to the SQN3330 firmware updater >>>
|
||||
Attempting AT wakeup...
|
||||
|
||||
Starting STP ON_THE_FLY
|
||||
Session opened: version 1, max transfer 8192 bytes
|
||||
Sending 5996938 bytes: [########################################] 100%
|
||||
Code download done, returning to user mode
|
||||
Resetting (DO NOT DISCONNECT POWER!!!)............................................................................
|
||||
Upgrade completed!
|
||||
Here's the current firmware version:
|
||||
|
||||
SYSTEM VERSION
|
||||
==============
|
||||
FIRMWARE VERSION
|
||||
Bootloader0 : 5.1.1.0 [33080]
|
||||
Bootloader1* : 5.1.1.0 [38638]
|
||||
Bootloader2 : 5.1.1.0 [38638]
|
||||
NV Info : 1.1,0,0
|
||||
Software : 5.1.1.0 [38638] by robot-soft at 2018-08-20 09:51:46
|
||||
UE : 5.0.0.0d
|
||||
COMPONENTS
|
||||
ZSP0 : 1.0.99-13604
|
||||
ZSP1 : 1.0.99-12341
|
||||
```
|
||||
|
||||
## Via UART Serial Interface
|
||||
|
||||
If you can't use an SD card to hold the firmware images, you can use the existing UART interface you have with the board to load these firmware files from your Computer.
|
||||
|
||||
You will need the following software installed on your computer:
|
||||
|
||||
1. [Python 3](https://www.python.org/downloads), if it's not directly available through your OS distributor
|
||||
2. [PySerial](https://pythonhosted.org/pyserial/pyserial.html#installation)
|
||||
|
||||
You will also need to download the following Python scripts: [https://github.com/pycom/pycom-libraries/tree/master/lib/sqnsupgrade](https://github.com/pycom/pycom-libraries/tree/master/lib/sqnsupgrade)
|
||||
|
||||
First, you need to prepare your modem for upgrade mode by using the following commands:
|
||||
|
||||
### **Commands to run on the Pycom module**
|
||||
|
||||
```python
|
||||
import sqnsupgrade
|
||||
sqnsupgrade.uart(True)
|
||||
```
|
||||
|
||||
After this command is executed a message will be displayed asking you to close the port.
|
||||
|
||||
```text
|
||||
Going into MIRROR mode... please close this terminal to resume the upgrade via UART
|
||||
```
|
||||
|
||||
### **Commands to be run on your computer**
|
||||
|
||||
You must close the terminal/Atom or Visual Studio Code console to run the following commands from your computer:
|
||||
|
||||
Go to the directory where you saved the `sqnsupgrade` scripts run the following commands in terminal
|
||||
|
||||
```python
|
||||
$ python3
|
||||
Python 3.6.5 (default, Apr 25 2018, 14:23:58)
|
||||
[GCC 4.2.1 Compatible Apple LLVM 9.1.0 (clang-902.0.39.1)] on darwin
|
||||
Type "help", "copyright", "credits" or "license" for more information.
|
||||
>>>
|
||||
>>> import sqnsupgrade
|
||||
>>> sqnsupgrade.run('Serial_Port', '/path/to/CATM1-38638.dup', '/path/to/updater.elf')
|
||||
```
|
||||
|
||||
## Retrying process
|
||||
|
||||
In case of any failure or interruption to the process of LTE modem upgrade you can repeat the same steps **after doing a hard reset to the board \(i.e disconnecting and reconnecting power\), pressing the reset button is not enough.**
|
||||
|
||||
20
tutorials/lte/imei.md
Normal file
20
tutorials/lte/imei.md
Normal file
@@ -0,0 +1,20 @@
|
||||
# Module IMEI
|
||||
|
||||
In order to retrieve the IMEI of your cellular enabled Pycom module you will firstly need to make sure you are on firmware version `1.17.0.b1` or higher. You can check your firmware version by running the following code on you device via the interactive REPL.
|
||||
|
||||
```python
|
||||
>>> import os
|
||||
>>> os.uname()
|
||||
(sysname='GPy', nodename='GPy', release='1.17.0.b1', version='v1.8.6-849-d0dc708 on 2018-02-27', machine='GPy with ESP32')
|
||||
```
|
||||
|
||||
Once you have a compatible firmware, you can run the following code to get your modules IMEI number:
|
||||
|
||||
```python
|
||||
from network import LTE
|
||||
lte = LTE()
|
||||
lte.send_at_cmd('AT+CGSN=1')
|
||||
```
|
||||
|
||||
You’ll get a return string like this `\r\n+CGSN: "354347xxxxxxxxx"\r\n\r\nOK`. The value between the double quotes is your IMEI.
|
||||
|
||||
41
tutorials/lte/nb-iot.md
Normal file
41
tutorials/lte/nb-iot.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# NB-IoT
|
||||
|
||||
## LTE class for Narrow Band IoT
|
||||
|
||||
{% hint style="info" %}
|
||||
As shipped, Pycom modules only support CAT-M1, in order to use NB-IoT you need to flash a different firmware to the Sequans modem. Instructions for this can be found [here](firmware.md).
|
||||
{% endhint %}
|
||||
|
||||
### Current NB-IoT limitations
|
||||
|
||||
At the moment the NB-IoT firmware supplied by Sequans only support Ericsson base stations configured for In-Band mode. Standalone and guard-band modes will be supported in a later release. Support for Huawei base stations is also limited and only lab testing with Huawei eNodeB is recommended at the moment. Full support for Huawei is planned for early Q2 2018.
|
||||
|
||||
## NB-IoT usage
|
||||
|
||||
Example with Vodafone:
|
||||
|
||||
```python
|
||||
from network import LTE
|
||||
|
||||
lte = LTE()
|
||||
lte.send_at_cmd('AT+CFUN=0')
|
||||
lte.send_at_cmd('AT!="clearscanconfig"')
|
||||
lte.send_at_cmd('AT!="addscanfreq band=20 dl-earfcn=6300"')
|
||||
lte.send_at_cmd('AT!="zsp0:npc 1"')
|
||||
lte.send_at_cmd('AT+CGDCONT=1,"IP","nb.inetd.gdsp"')
|
||||
lte.send_at_cmd('AT+CFUN=1')
|
||||
|
||||
while not lte.isattached():
|
||||
pass
|
||||
|
||||
lte.connect()
|
||||
while not lte.isconnected():
|
||||
pass
|
||||
|
||||
# now use socket as usual...
|
||||
```
|
||||
|
||||
**IMPORTANT:** Once the LTE radio is initialised, it must be de-initialised before going to deepsleep in order to ensure minimum power consumption. This is required due to the LTE radio being powered independently and allowing use cases which require the system to be taken out from deepsleep by an event from the LTE network \(data or SMS received for instance\).
|
||||
|
||||
When using the expansion board and the FiPy together, the RTS/CTS jumpers **MUST** be removed as those pins are being used by the LTE radio. Keeping those jumpers in place will lead to erratic operation and higher current consumption specially while in deepsleep.
|
||||
|
||||
58
tutorials/pyscan.md
Normal file
58
tutorials/pyscan.md
Normal file
@@ -0,0 +1,58 @@
|
||||
# Pyscan Examples
|
||||
|
||||
This basic example shows how to read an NFC card and authenticate it using a pre-defined access list.
|
||||
|
||||
```python
|
||||
from pyscan import Pyscan
|
||||
from MFRC630 import MFRC630
|
||||
import time
|
||||
import pycom
|
||||
import _thread
|
||||
|
||||
VALID_CARDS = [[0x43, 0x95, 0xDD, 0xF8],
|
||||
[0x43, 0x95, 0xDD, 0xF9]]
|
||||
|
||||
py = Pyscan()
|
||||
nfc = MFRC630(py)
|
||||
|
||||
RGB_BRIGHTNESS = 0x8
|
||||
|
||||
RGB_RED = (RGB_BRIGHTNESS << 16)
|
||||
RGB_GREEN = (RGB_BRIGHTNESS << 8)
|
||||
RGB_BLUE = (RGB_BRIGHTNESS)
|
||||
|
||||
# Make sure heartbeat is disabled before setting RGB LED
|
||||
pycom.heartbeat(False)
|
||||
|
||||
# Initialise the MFRC630 with some settings
|
||||
nfc.mfrc630_cmd_init()
|
||||
|
||||
def check_uid(uid, len):
|
||||
return VALID_CARDS.count(uid[:len])
|
||||
|
||||
def discovery_loop(nfc, id):
|
||||
while True:
|
||||
# Send REQA for ISO14443A card type
|
||||
atqa = nfc.mfrc630_iso14443a_WUPA_REQA(nfc.MFRC630_ISO14443_CMD_REQA)
|
||||
if (atqa != 0):
|
||||
# A card has been detected, read UID
|
||||
uid = bytearray(10)
|
||||
uid_len = nfc.mfrc630_iso14443a_select(uid)
|
||||
if (uid_len > 0):
|
||||
if (check_uid(list(uid), uid_len)) > 0:
|
||||
pycom.rgbled(RGB_GREEN)
|
||||
else:
|
||||
pycom.rgbled(RGB_RED)
|
||||
else:
|
||||
# No card detected
|
||||
pycom.rgbled(RGB_BLUE)
|
||||
nfc.mfrc630_cmd_reset()
|
||||
time.sleep(.5)
|
||||
nfc.mfrc630_cmd_init()
|
||||
|
||||
# This is the start of our main execution... start the thread
|
||||
_thread.start_new_thread(discovery_loop, (nfc, 0))
|
||||
```
|
||||
|
||||
You can find this, and all the other examples in our [pycom-libraries GitHub repository](https://github.com/pycom/pycom-libraries)
|
||||
|
||||
23
tutorials/pysense.md
Normal file
23
tutorials/pysense.md
Normal file
@@ -0,0 +1,23 @@
|
||||
# Pysense Examples
|
||||
|
||||
## Accelerometer
|
||||
|
||||
This basic example shows how to read pitch and roll from the on-board accelerometer and output it in comma separated value \(CSV\) format over serial.
|
||||
|
||||
```python
|
||||
from LIS2HH12 import LIS2HH12
|
||||
from pytrack import Pytrack
|
||||
py = Pytrack()
|
||||
acc = LIS2HH12()
|
||||
|
||||
while True:
|
||||
pitch = acc.pitch()
|
||||
roll = acc.roll()
|
||||
print('{},{}'.format(pitch, roll))
|
||||
time.sleep_ms(100)
|
||||
```
|
||||
|
||||

|
||||
|
||||
If you want to visualise the data output by this script a Processing sketch is available [here](https://github.com/pycom/pycom-libraries/tree/master/examples/pytrack_pysense_accelerometer) that will show the board orientation in 3D.
|
||||
|
||||
6
tutorials/pytrack.md
Normal file
6
tutorials/pytrack.md
Normal file
@@ -0,0 +1,6 @@
|
||||
# Pytrack Examples
|
||||
|
||||
Both the Pysense and Pytrack use the same accelerometer. Please see the [Pysense Examples](pysense.md) to see how to use the accelerometer.
|
||||
|
||||
{% page-ref page="pysense.md" %}
|
||||
|
||||
26
tutorials/sigfox.md
Normal file
26
tutorials/sigfox.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# Sigfox Examples
|
||||
|
||||
Before you start, make sure that your device was registered with [Sigfox](../gettingstarted/registration/sigfox.md).
|
||||
|
||||
The following tutorials demonstrate how to register and get started with the SiPy. The SiPy can be configured for operation in various countries based upon specified RCZ zones \(see the `Sigfox` class for more info\). The SiPy supports both uplink and downlink `Sigfox` messages as well as device to device communication via its FSK Mode `Sigfox`.
|
||||
|
||||
## Disengage Sequence Number
|
||||
|
||||
If your are experiencing issues with Sigfox connectivity, this could be due to the sequence number being out of sync. To prevent replay on the network, the Sigfox protocol uses sequence numbers. If there is a large difference between the sequence number sent by the device and the one expected by the backend, your message is dropped by the network.
|
||||
|
||||
You can use the `Disengage sequence number` button on the device information or on the device type information page of the Sigfox backend to reset the number expected by the backend. If the sequence number of your next message is different from the last trashed sequence number, the message will be accepted.
|
||||
|
||||
Issues with the sequence number can occur when a lot of messages are sent when outside of Sigfox coverage for instance.
|
||||
|
||||
Firstly you will need to log into the [Sigfox Backend](https://backend.sigfox.com), navigate to device, and click on the Sigfox ID of the affected SiPy.
|
||||
|
||||

|
||||
|
||||
You should now see the Information page with an entry `Device Type:` followed by a link. Please follow the link
|
||||
|
||||

|
||||
|
||||
Finally, on this page click on `Disengage sequence number` button in the upper right corner.
|
||||
|
||||

|
||||
|
||||
Reference in New Issue
Block a user