mirror of
https://github.com/richonguzman/LoRa_APRS_iGate.git
synced 2026-03-20 04:05:20 +01:00
commit
4b72fb3f3d
63
.github/workflows/build.yml
vendored
Normal file
63
.github/workflows/build.yml
vendored
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
name: Build Release
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target:
|
||||
- "ttgo-lora32-v21"
|
||||
- "heltec-lora32-v2"
|
||||
- "heltec_wifi_lora_32_V3"
|
||||
- "ESP32_DIY_LoRa"
|
||||
- "ESP32_DIY_1W_LoRa"
|
||||
- "ttgo-t-beam-v1_2"
|
||||
- "ttgo-t-beam-v1"
|
||||
- "ttgo-t-beam-v1_SX1268"
|
||||
- "ttgo-t-beam-v1_2_SX1262"
|
||||
# - "heltec_wireless_stick_lite" # NOT FULLY TESTED
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.9"
|
||||
|
||||
- name: Install PlatformIO Core
|
||||
run: pip install --upgrade platformio
|
||||
|
||||
- name: Build target
|
||||
run: pio run -e ${{ matrix.target }}
|
||||
|
||||
- name: Build FS
|
||||
run: pio run --target buildfs -e ${{ matrix.target }}
|
||||
|
||||
- name: Move Files
|
||||
run: |
|
||||
mkdir -p installer/firmware
|
||||
cp .pio/build/${{ matrix.target }}/firmware.bin installer/firmware/
|
||||
cp .pio/build/${{ matrix.target }}/bootloader.bin installer/firmware/
|
||||
cp .pio/build/${{ matrix.target }}/partitions.bin installer/firmware/
|
||||
cp .pio/build/${{ matrix.target }}/spiffs.bin installer/firmware/
|
||||
cp ~/.platformio/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin installer/firmware/
|
||||
|
||||
- name: Install Zip
|
||||
run: sudo apt-get install zip
|
||||
|
||||
- name: Archive Files
|
||||
run: zip -r installer.zip installer/
|
||||
|
||||
- name: Upload Release Asset
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ github.event.release.upload_url }}
|
||||
asset_path: ./installer.zip
|
||||
asset_name: ${{ matrix.target }}.zip
|
||||
asset_content_type: application/zip
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -4,3 +4,7 @@
|
|||
.vscode/launch.json
|
||||
.vscode/ipch
|
||||
.DS_Store
|
||||
/data_embed/*.gz
|
||||
installer/firmware
|
||||
installer/*.bin
|
||||
**/__pycache__/
|
||||
4
.vscode/settings.json
vendored
Normal file
4
.vscode/settings.json
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"editor.tabSize": 4,
|
||||
"editor.formatOnSave": true
|
||||
}
|
||||
|
|
@ -3,23 +3,29 @@
|
|||
"stationMode": 2,
|
||||
"iGateComment": "LoRa_APRS_iGate",
|
||||
"wifi": {
|
||||
"autoAP": {
|
||||
"password": "1234567890",
|
||||
"powerOff": 10
|
||||
},
|
||||
"AP": [
|
||||
{ "ssid": "WIFI_1",
|
||||
{
|
||||
"ssid": "WIFI_1",
|
||||
"password": "WIFI_1_password",
|
||||
"latitude": 0.0000000,
|
||||
"longitude": 0.0000000
|
||||
"latitude": 0.0,
|
||||
"longitude": 0.0
|
||||
},
|
||||
{ "ssid": "WIFI_2",
|
||||
{
|
||||
"ssid": "WIFI_2",
|
||||
"password": "WIFI_2_password",
|
||||
"latitude": 0.0000000,
|
||||
"longitude": 0.0000000
|
||||
"latitude": 0.0,
|
||||
"longitude": 0.0
|
||||
}
|
||||
]
|
||||
},
|
||||
"digi": {
|
||||
"comment": "LoRa_APRS_Digirepeater",
|
||||
"latitude": 0.0000000,
|
||||
"longitude": 0.0000000
|
||||
"latitude": 0.0,
|
||||
"longitude": 0.0
|
||||
},
|
||||
"aprs_is": {
|
||||
"passcode": "XYZVW",
|
||||
|
|
@ -39,7 +45,7 @@
|
|||
"display": {
|
||||
"alwaysOn": true,
|
||||
"timeout": 4,
|
||||
"turn180" : false
|
||||
"turn180": false
|
||||
},
|
||||
"syslog": {
|
||||
"active": false,
|
||||
|
|
@ -50,7 +56,7 @@
|
|||
"active": false
|
||||
},
|
||||
"ota": {
|
||||
"username":"",
|
||||
"username": "",
|
||||
"password": ""
|
||||
},
|
||||
"other": {
|
||||
|
|
@ -59,7 +65,7 @@
|
|||
"igateRepeatsLoRaPackets": false,
|
||||
"rememberStationTime": 30,
|
||||
"sendBatteryVoltage": false,
|
||||
"externalVoltageMeasurement" : false,
|
||||
"externalVoltageMeasurement": false,
|
||||
"externalVoltagePin": 34
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +0,0 @@
|
|||
<?php
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$data = [
|
||||
'name' => $_POST['name'],
|
||||
'email' => $_POST['email']
|
||||
];
|
||||
|
||||
// Convert the data array to JSON format
|
||||
$json_data = json_encode($data);
|
||||
|
||||
// Specify the path where you want to save the JSON file
|
||||
$file_path = 'userdata.json';
|
||||
|
||||
// Save the JSON data to the file
|
||||
file_put_contents($file_path, $json_data);
|
||||
|
||||
echo "Data saved as JSON successfully.";
|
||||
}
|
||||
?>
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
// Retrieve JSON data from the JSON file in SPIFFS
|
||||
fetch('igate_conf.json')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
// Process the JSON data
|
||||
//displayJSONData(data);
|
||||
|
||||
const jsonDataDiv = document.getElementById('json-container');
|
||||
jsonDataDiv.innerHTML = ''; // Clear the previous content
|
||||
|
||||
if (Array.isArray(data)) {
|
||||
// If data is an array, iterate through it
|
||||
data.forEach(item => {
|
||||
const itemDiv = document.createElement('div');
|
||||
itemDiv.textContent = JSON.stringify(item, null, 2);
|
||||
jsonDataDiv.appendChild(itemDiv);
|
||||
});
|
||||
} else {
|
||||
// If data is not an array, display it directly
|
||||
const itemDiv = document.createElement('div');
|
||||
itemDiv.textContent = JSON.stringify(data, null, 2);
|
||||
jsonDataDiv.appendChild(itemDiv);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('There was a problem fetching the JSON data:', error);
|
||||
});
|
||||
|
||||
// Display JSON data in the HTML
|
||||
function displayJSONData(data) {
|
||||
//const jsonContainer = document.getElementById('json-container');
|
||||
//jsonContainer.innerHTML = JSON.stringify(data, null, 2);
|
||||
|
||||
}
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Save Form Data to JSON</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Save Form Data to JSON</h1>
|
||||
|
||||
<form method="POST" action="process_form.php">
|
||||
<label for="name">Name:</label>
|
||||
<input type="text" id="name" name="name" required><br>
|
||||
|
||||
<label for="email">Email:</label>
|
||||
<input type="email" id="email" name="email" required><br>
|
||||
|
||||
<input type="submit" value="Save as JSON">
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>JSON Data Example</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="json-container"></div>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
12200
data_embed/bootstrap.css
vendored
Normal file
12200
data_embed/bootstrap.css
vendored
Normal file
File diff suppressed because it is too large
Load diff
4467
data_embed/bootstrap.js
vendored
Normal file
4467
data_embed/bootstrap.js
vendored
Normal file
File diff suppressed because it is too large
Load diff
1082
data_embed/index.html
Normal file
1082
data_embed/index.html
Normal file
File diff suppressed because it is too large
Load diff
388
data_embed/script.js
Normal file
388
data_embed/script.js
Normal file
|
|
@ -0,0 +1,388 @@
|
|||
// Custom scripts
|
||||
|
||||
let currentSettings = null;
|
||||
|
||||
function backupSettings() {
|
||||
const data =
|
||||
"data:text/json;charset=utf-8," +
|
||||
encodeURIComponent(JSON.stringify(currentSettings));
|
||||
const a = document.createElement("a");
|
||||
a.setAttribute("href", data);
|
||||
a.setAttribute("download", "iGateConfigurationBackup.json");
|
||||
a.click();
|
||||
}
|
||||
|
||||
document.getElementById("backup").onclick = backupSettings;
|
||||
|
||||
document.getElementById("restore").onclick = function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
document.querySelector("input[type=file]").click();
|
||||
};
|
||||
|
||||
document.querySelector("input[type=file]").onchange = function () {
|
||||
const files = document.querySelector("input[type=file]").files;
|
||||
|
||||
if (!files.length) return;
|
||||
|
||||
const file = files.item(0);
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.readAsText(file);
|
||||
reader.onload = () => {
|
||||
const data = JSON.parse(reader.result);
|
||||
|
||||
loadSettings(data);
|
||||
};
|
||||
};
|
||||
|
||||
function fetchSettings() {
|
||||
fetch("/configuration.json")
|
||||
.then((response) => response.json())
|
||||
.then((settings) => {
|
||||
loadSettings(settings);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
|
||||
alert(`Failed to load configuration`);
|
||||
});
|
||||
}
|
||||
|
||||
const alwaysOnCheckbox = document.querySelector(
|
||||
'input[name="display.alwaysOn"]'
|
||||
);
|
||||
const timeoutInput = document.querySelector('input[name="display.timeout"]');
|
||||
|
||||
alwaysOnCheckbox.addEventListener("change", function () {
|
||||
timeoutInput.disabled = this.checked;
|
||||
});
|
||||
|
||||
// timeoutInput.addEventListener("change", function () {
|
||||
// alwaysOnCheckbox.disabled = this.value !== "";
|
||||
// });
|
||||
|
||||
const logCheckbox = document.querySelector('input[name="syslog.active"]');
|
||||
const serverField = document.querySelector('input[name="syslog.server"]');
|
||||
const portField = document.querySelector('input[name="syslog.port"]');
|
||||
|
||||
logCheckbox.addEventListener("change", function () {
|
||||
serverField.disabled = !this.checked;
|
||||
portField.disabled = !this.checked;
|
||||
});
|
||||
|
||||
function loadSettings(settings) {
|
||||
currentSettings = settings;
|
||||
// General
|
||||
document.getElementById("callsign").value = settings.callsign;
|
||||
document.getElementById("stationMode").value = settings.stationMode;
|
||||
document.getElementById("bme.active").checked = settings.bme.active;
|
||||
document.getElementById("iGateComment").value = settings.iGateComment;
|
||||
|
||||
document.querySelector(".list-networks").innerHTML = "";
|
||||
|
||||
// Networks
|
||||
const wifiNetworks = settings.wifi.AP;
|
||||
const networksContainer = document.querySelector(".list-networks");
|
||||
|
||||
let networkCount = 0;
|
||||
|
||||
wifiNetworks.forEach((network) => {
|
||||
const networkElement = document.createElement("div");
|
||||
networkElement.classList.add("row", "network", "border-bottom", "py-2");
|
||||
|
||||
// Increment the name, id, and for attributes
|
||||
const attributeName = `wifi.AP.${networkCount}`;
|
||||
networkElement.innerHTML = `
|
||||
<div class="form-floating col-6 col-md-3 px-1 mb-2">
|
||||
<input type="text" class="form-control form-control-sm" name="${attributeName}.ssid" id="${attributeName}.ssid" value="${network.ssid}">
|
||||
<label for="${attributeName}.ssid">SSID</label>
|
||||
</div>
|
||||
<div class="form-floating col-6 col-md-3 px-1 mb-2">
|
||||
<input type="text" class="form-control form-control-sm" name="${attributeName}.password" id="${attributeName}.password" value="${network.password}">
|
||||
<label for="${attributeName}.password">Passphrase</label>
|
||||
</div>
|
||||
<div class="col-4 col-md-2 form-floating px-1 mb-2">
|
||||
<input type="text" class="form-control form-control-sm latitude" name="${attributeName}.latitude" id="${attributeName}.latitude" value="${network.latitude}">
|
||||
<label for="${attributeName}.latitude">Latitude</label>
|
||||
</div>
|
||||
<div class="col-4 col-md-2 form-floating px-1 mb-2">
|
||||
<input type="text" class="form-control form-control-sm longitude" name="${attributeName}.longitude" id="${attributeName}.longitude" value="${network.longitude}">
|
||||
<label for="${attributeName}.longitude">Longitude</label>
|
||||
</div>
|
||||
<div class="col-4 col-md-2 d-flex align-items-center justify-content-end">
|
||||
<div class="btn-group" role="group">
|
||||
<button type="button" class="btn btn-sm btn-danger" title="Delete" onclick="return this.parentNode.parentNode.parentNode.remove();"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-trash3-fill" viewBox="0 0 16 16">
|
||||
<path d="M11 1.5v1h3.5a.5.5 0 0 1 0 1h-.538l-.853 10.66A2 2 0 0 1 11.115 16h-6.23a2 2 0 0 1-1.994-1.84L2.038 3.5H1.5a.5.5 0 0 1 0-1H5v-1A1.5 1.5 0 0 1 6.5 0h3A1.5 1.5 0 0 1 11 1.5m-5 0v1h4v-1a.5.5 0 0 0-.5-.5h-3a.5.5 0 0 0-.5.5M4.5 5.029l.5 8.5a.5.5 0 1 0 .998-.06l-.5-8.5a.5.5 0 1 0-.998.06m6.53-.528a.5.5 0 0 0-.528.47l-.5 8.5a.5.5 0 0 0 .998.058l.5-8.5a.5.5 0 0 0-.47-.528M8 4.5a.5.5 0 0 0-.5.5v8.5a.5.5 0 0 0 1 0V5a.5.5 0 0 0-.5-.5"/>
|
||||
</svg><span class="visually-hidden">Delete</span></button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
networksContainer.appendChild(networkElement);
|
||||
networkCount++;
|
||||
});
|
||||
|
||||
// APRS-IS
|
||||
document.getElementById("aprs_is.server").value = settings.aprs_is.server;
|
||||
document.getElementById("aprs_is.port").value = settings.aprs_is.port;
|
||||
document.getElementById("aprs_is.reportingDistance").value =
|
||||
settings.aprs_is.reportingDistance;
|
||||
document.getElementById("aprs_is.passcode").value =
|
||||
settings.aprs_is.passcode;
|
||||
|
||||
// Display
|
||||
document.getElementById("display.alwaysOn").checked =
|
||||
settings.display.alwaysOn;
|
||||
document.getElementById("display.turn180").checked =
|
||||
settings.display.turn180;
|
||||
document.getElementById("display.timeout").value = settings.display.timeout;
|
||||
|
||||
if (settings.display.alwaysOn) {
|
||||
timeoutInput.disabled = true;
|
||||
}
|
||||
|
||||
// WiFi Auto AP
|
||||
document.getElementById("wifi.autoAP.password").value =
|
||||
settings.wifi.autoAP.password;
|
||||
document.getElementById("wifi.autoAP.powerOff").value =
|
||||
settings.wifi.autoAP.powerOff;
|
||||
|
||||
// Digi
|
||||
document.getElementById("digi.comment").value = settings.digi.comment;
|
||||
document.getElementById("digi.latitude").value = settings.digi.latitude;
|
||||
document.getElementById("digi.longitude").value = settings.digi.longitude;
|
||||
|
||||
// OTA
|
||||
document.getElementById("ota.username").value = settings.ota.username;
|
||||
document.getElementById("ota.password").value = settings.ota.password;
|
||||
|
||||
// Beacon
|
||||
document.getElementById("other.beaconInterval").value =
|
||||
settings.other.beaconInterval;
|
||||
document.getElementById("other.rememberStationTime").value =
|
||||
settings.other.rememberStationTime;
|
||||
document.getElementById("other.sendBatteryVoltage").checked =
|
||||
settings.other.sendBatteryVoltage;
|
||||
document.getElementById("other.externalVoltageMeasurement").checked =
|
||||
settings.other.externalVoltageMeasurement;
|
||||
document.getElementById("other.externalVoltagePin").value =
|
||||
settings.other.externalVoltagePin;
|
||||
document.getElementById("other.igateSendsLoRaBeacon").value =
|
||||
settings.other.igateSendsLoRaBeacon;
|
||||
document.getElementById("other.igateRepeatLoRaPackets").value =
|
||||
settings.other.igateRepeatLoRaPackets;
|
||||
|
||||
// Syslog
|
||||
document.getElementById("syslog.active").checked = settings.syslog.active;
|
||||
document.getElementById("syslog.server").value = settings.syslog.server;
|
||||
document.getElementById("syslog.port").value = settings.syslog.port;
|
||||
|
||||
if (settings.syslog.active) {
|
||||
serverField.disabled = false;
|
||||
portField.disabled = false;
|
||||
}
|
||||
|
||||
// LoRa
|
||||
document.getElementById("lora.digirepeaterTxFreq").value =
|
||||
settings.lora.digirepeaterTxFreq;
|
||||
document.getElementById("lora.iGateFreq").value = settings.lora.iGateFreq;
|
||||
document.getElementById("lora.digirepeaterRxFreq").value =
|
||||
settings.lora.digirepeaterRxFreq;
|
||||
document.getElementById("lora.spreadingFactor").value =
|
||||
settings.lora.spreadingFactor;
|
||||
document.getElementById("lora.signalBandwidth").value =
|
||||
settings.lora.signalBandwidth;
|
||||
document.getElementById("lora.codingRate4").value =
|
||||
settings.lora.codingRate4;
|
||||
document.getElementById("lora.power").value = settings.lora.power;
|
||||
|
||||
updateImage();
|
||||
}
|
||||
|
||||
const bmeCheckbox = document.querySelector("input[name='bme.active']");
|
||||
|
||||
const stationModeSelect = document.querySelector("select[name='stationMode']");
|
||||
|
||||
const digirepeaterTxFreqInput = document.querySelector(
|
||||
"input[name='lora.digirepeaterRxFreq']"
|
||||
);
|
||||
|
||||
function updateImage() {
|
||||
const image = document.querySelector("img");
|
||||
|
||||
const isChecked = bmeCheckbox.checked;
|
||||
|
||||
if (isChecked) {
|
||||
image.src =
|
||||
"data:image/png;base64,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";
|
||||
} else {
|
||||
const selectedValue = stationModeSelect.value;
|
||||
|
||||
if (selectedValue === "1") {
|
||||
if (bmeCheckbox.checked) {
|
||||
image.src =
|
||||
"data:image/png;base64,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";
|
||||
} else {
|
||||
image.src =
|
||||
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHgAAAB4CAYAAAA5ZDbSAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAQ6SURBVHgB7d0/axRBGMfx50IMWPoKkpA02gUsLfZdqOCLOPBFWAliHbCUVNpaxQNrIY0WVjZaJlUsIsZ5uDu9bGb/zMwzs/PM/L7wIG4Ocflwl7ndvVsihBBCmTajunpk5uHG33+aOSGkvhdmLs1cW+bKzDEhle2a+UZ22Pacrx6PlMRYXc/arrkkIKvIBxfISgrBBXLmSeACOdMkcYGcWTFwgZxJMXGBPHEpcIE8USlxgSzcbGD2KD0ukIXKGRfIAuWOC+TANOACOSAtuEAemQ10a2P2KV9cIA+0ZZltMzurOaT8cYHcUx+wJlwgd9QFrBEXyJZswAekFxfIrdq4GhZUQO7Itlq+QzoXVEC21AdcIm51yF3AJeNWhWwDLmFBBeRVmg4/Atmj2nGLR/bCbZrm2qWx/+7Ekwx5i9LGO/XFzF2qO97/r5QAOSUwcG+WBDkVMHDtRUdOAcz/ed4J4NqLihwbGLjjioYcExi4bkVBjgUMXL/EkWMAAzcsUWRpYODKJIYsCQxc2USQpYCBG6dgZAlg4MYtCDkUGLhp8kYOAQZu2ryQfYGT4S7PAKJVzsg+wEmfubNZbd+XOpgTsiswXpbzaDSyCzBw82oU8lhg4ObZIPIYYODmXS/yEDBwdcQ+72w/6AMGrq6OaHnLghv1AQNXX6/bG7qAPxNwNbbf3mAD5qf5EaEisgG/IVRMNuA9QsVkA94mpLWP7Q2pP5uE4vayvQHA5bQw86m90Qb8i5C22Kyx/cAG/IGQphj3ftcPbcBzM78JaWiN+73rATZgfvAzWn4SHeXbIC7Xtcg6MfOUgJxro3C5vlU0kPNsNC439DYJyHnlhMuNeR8M5DxyxuXGHugA8rR54XIuR7KAPE3euJzroUogpy0Il/M5Fg3kNAXjcr4nG4AcNxFcLuTc78nqz7e0/A7KLDo9PbV+nkn6Q2xnZ2c0n88pQmK4XOjJ/eyQm6YhxYnichLng/FyLZM4Lid1wh/IYUXB5SSv6ACyX9FwOelLdoDsVlRcLsY1WUAeV3RcLtZFd0Duj3EfUGRcLuZVlUC2lwyXi32Re/D75IuLC1osFtYDFXxAo73dts21MV/8wgc6PEqKm7LHZv5QnndASTV8p5U9unl7oaJ6QvUi23CLA+YdqhGZcQ/Jfmu/olrvVE3Ia9wdqgB4sxp+JzPuPv2/+XV1lYzcxq32Q34lvlzbcKsF5h0vCZlxD+g2btXApSCvF1TbBOB/ba4oNSOvn7lr3KpWzC5pXHgVf4RKOk3P5CqOUEmn5eW6CxfAA2n4ndyHC+AR5YwMXOFyWngx7i4h8XJABm7kpkQGbqKmQAZu4lIiA3eiUiADd+JiIgM3k2IgAzezJJGBm2kSyMDNvBBk4CrJBxm4ynJB/kHAVRnf3OucumGvzBxTBZV+eouhn5u5t7HtvZlXhBBCCCEUsb+qGDWF0d8l5AAAAABJRU5ErkJggg==";
|
||||
}
|
||||
digirepeaterTxFreqInput.disabled = true;
|
||||
} else if (selectedValue === "2") {
|
||||
if (bmeCheckbox.checked) {
|
||||
image.src =
|
||||
"data:image/png;base64,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";
|
||||
} else {
|
||||
image.src =
|
||||
"data:image/png;base64,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";
|
||||
}
|
||||
digirepeaterTxFreqInput.disabled = true;
|
||||
} else if (selectedValue === "3" || selectedValue === "4") {
|
||||
image.src =
|
||||
"data:image/png;base64,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";
|
||||
digirepeaterTxFreqInput.disabled = selectedValue === "3";
|
||||
} else if (selectedValue === "5") {
|
||||
if (bmeCheckbox.checked) {
|
||||
image.src =
|
||||
"data:image/png;base64,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";
|
||||
} else {
|
||||
image.src =
|
||||
"data:image/png;base64,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";
|
||||
}
|
||||
digirepeaterTxFreqInput.disabled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stationModeSelect.addEventListener("change", updateImage);
|
||||
bmeCheckbox.addEventListener("change", updateImage);
|
||||
|
||||
function toggleFields() {
|
||||
const sendBatteryVoltageCheckbox = document.querySelector(
|
||||
'input[name="other.sendBatteryVoltage"]'
|
||||
);
|
||||
const externalVoltageMeasurementCheckbox = document.querySelector(
|
||||
'input[name="other.externalVoltageMeasurement"]'
|
||||
);
|
||||
const externalVoltagePinInput = document.querySelector(
|
||||
'input[name="other.externalVoltagePin"]'
|
||||
);
|
||||
|
||||
externalVoltageMeasurementCheckbox.disabled =
|
||||
!sendBatteryVoltageCheckbox.checked;
|
||||
externalVoltagePinInput.disabled =
|
||||
!externalVoltageMeasurementCheckbox.checked;
|
||||
}
|
||||
|
||||
const sendBatteryVoltageCheckbox = document.querySelector(
|
||||
'input[name="other.sendBatteryVoltage"]'
|
||||
);
|
||||
const externalVoltageMeasurementCheckbox = document.querySelector(
|
||||
'input[name="other.externalVoltageMeasurement"]'
|
||||
);
|
||||
const externalVoltagePinInput = document.querySelector(
|
||||
'input[name="other.externalVoltagePin"]'
|
||||
);
|
||||
|
||||
sendBatteryVoltageCheckbox.addEventListener("change", function () {
|
||||
externalVoltageMeasurementCheckbox.disabled = !this.checked;
|
||||
externalVoltagePinInput.disabled =
|
||||
!externalVoltageMeasurementCheckbox.checked;
|
||||
});
|
||||
|
||||
document.querySelector(".new button").addEventListener("click", function () {
|
||||
const networksContainer = document.querySelector(".list-networks");
|
||||
|
||||
let networkCount = document.querySelectorAll(".network").length;
|
||||
|
||||
const networkElement = document.createElement("div");
|
||||
|
||||
networkElement.classList.add("row", "network", "border-bottom", "py-2");
|
||||
|
||||
// Increment the name, id, and for attributes
|
||||
const attributeName = `wifi.AP.${networkCount}`;
|
||||
networkElement.innerHTML = `
|
||||
<div class="form-floating col-6 col-md-3 px-1 mb-2">
|
||||
<input type="text" class="form-control form-control-sm" name="${attributeName}.ssid" id="${attributeName}.ssid" placeholder="" >
|
||||
<label for="${attributeName}.ssid">SSID</label>
|
||||
</div>
|
||||
<div class="form-floating col-6 col-md-3 px-1 mb-2">
|
||||
<input type="text" class="form-control form-control-sm" name="${attributeName}.password" id="${attributeName}.password" placeholder="">
|
||||
<label for="${attributeName}.password">Passphrase</label>
|
||||
</div>
|
||||
<div class="col-4 col-md-2 form-floating px-1 mb-2">
|
||||
<input type="text" class="form-control form-control-sm latitude" name="${attributeName}.latitude" id="${attributeName}.latitude" placeholder="">
|
||||
<label for="${attributeName}.latitude">Latitude</label>
|
||||
</div>
|
||||
<div class="col-4 col-md-2 form-floating px-1 mb-2">
|
||||
<input type="text" class="form-control form-control-sm longitude" name="${attributeName}.longitude" id="${attributeName}.longitude" placeholder="">
|
||||
<label for="${attributeName}.longitude">Longitude</label>
|
||||
</div>
|
||||
<div class="col-4 col-md-2 d-flex align-items-center justify-content-end">
|
||||
<div class="btn-group" role="group">
|
||||
<button type="button" class="btn btn-sm btn-danger" title="Delete" onclick="return this.parentNode.parentNode.parentNode.remove();"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-trash3-fill" viewBox="0 0 16 16">
|
||||
<path d="M11 1.5v1h3.5a.5.5 0 0 1 0 1h-.538l-.853 10.66A2 2 0 0 1 11.115 16h-6.23a2 2 0 0 1-1.994-1.84L2.038 3.5H1.5a.5.5 0 0 1 0-1H5v-1A1.5 1.5 0 0 1 6.5 0h3A1.5 1.5 0 0 1 11 1.5m-5 0v1h4v-1a.5.5 0 0 0-.5-.5h-3a.5.5 0 0 0-.5.5M4.5 5.029l.5 8.5a.5.5 0 1 0 .998-.06l-.5-8.5a.5.5 0 1 0-.998.06m6.53-.528a.5.5 0 0 0-.528.47l-.5 8.5a.5.5 0 0 0 .998.058l.5-8.5a.5.5 0 0 0-.47-.528M8 4.5a.5.5 0 0 0-.5.5v8.5a.5.5 0 0 0 1 0V5a.5.5 0 0 0-.5-.5"/>
|
||||
</svg><span class="visually-hidden">Delete</span></button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
networksContainer.appendChild(networkElement);
|
||||
|
||||
networkCount++;
|
||||
|
||||
// Add the new network element to the end of the document
|
||||
document.querySelector(".new").before(networkElement);
|
||||
});
|
||||
|
||||
toggleFields();
|
||||
|
||||
const form = document.querySelector("form");
|
||||
|
||||
const saveModal = new bootstrap.Modal(document.getElementById("saveModal"), {
|
||||
backdrop: "static",
|
||||
keyboard: false,
|
||||
});
|
||||
|
||||
const savedModal = new bootstrap.Modal(
|
||||
document.getElementById("savedModal"),
|
||||
{}
|
||||
);
|
||||
|
||||
function checkConnection() {
|
||||
const controller = new AbortController();
|
||||
|
||||
setTimeout(() => controller.abort(), 2000);
|
||||
|
||||
fetch("/status?_t=" + Date.now(), { signal: controller.signal })
|
||||
.then(() => {
|
||||
saveModal.hide();
|
||||
|
||||
savedModal.show();
|
||||
|
||||
setTimeout(function () {
|
||||
savedModal.hide();
|
||||
}, 3000);
|
||||
|
||||
fetchSettings();
|
||||
})
|
||||
.catch((err) => {
|
||||
setTimeout(checkConnection, 0);
|
||||
});
|
||||
}
|
||||
|
||||
form.addEventListener("submit", async (event) => {
|
||||
event.preventDefault();
|
||||
|
||||
document.getElementById("wifi.APs").value =
|
||||
document.querySelectorAll(".network").length;
|
||||
|
||||
fetch(form.action, {
|
||||
method: form.method,
|
||||
body: new FormData(form),
|
||||
});
|
||||
|
||||
saveModal.show();
|
||||
|
||||
setTimeout(checkConnection, 0);
|
||||
});
|
||||
|
||||
fetchSettings();
|
||||
0
data_embed/style.css
Normal file
0
data_embed/style.css
Normal file
34
installer/bin/esptool/esptool.py
Normal file
34
installer/bin/esptool/esptool.py
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
#!/usr/bin/env python
|
||||
#
|
||||
# SPDX-FileCopyrightText: 2014-2022 Fredrik Ahlberg, Angus Gratton,
|
||||
# Espressif Systems (Shanghai) CO LTD, other contributors as noted.
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
# This executable script is a thin wrapper around the main functionality
|
||||
# in the esptool Python package
|
||||
|
||||
# When updating this script, please also update espefuse.py and espsecure.py
|
||||
|
||||
import contextlib
|
||||
import os
|
||||
import sys
|
||||
|
||||
if os.name != "nt":
|
||||
# Linux/macOS: remove current script directory to avoid importing this file
|
||||
# as a module; we want to import the installed esptool module instead
|
||||
with contextlib.suppress(ValueError):
|
||||
if sys.path[0].endswith("/bin"):
|
||||
sys.path.pop(0)
|
||||
sys.path.remove(os.path.dirname(sys.executable))
|
||||
|
||||
# Linux/macOS: delete imported module entry to force Python to load
|
||||
# the module from scratch; this enables importing esptool module in
|
||||
# other Python scripts
|
||||
with contextlib.suppress(KeyError):
|
||||
del sys.modules["esptool"]
|
||||
|
||||
import esptool
|
||||
|
||||
if __name__ == "__main__":
|
||||
esptool._main()
|
||||
1055
installer/bin/esptool/esptool/__init__.py
Normal file
1055
installer/bin/esptool/esptool/__init__.py
Normal file
File diff suppressed because it is too large
Load diff
9
installer/bin/esptool/esptool/__main__.py
Normal file
9
installer/bin/esptool/esptool/__main__.py
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
# SPDX-FileCopyrightText: 2014-2022 Fredrik Ahlberg, Angus Gratton,
|
||||
# Espressif Systems (Shanghai) CO LTD, other contributors as noted.
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import esptool
|
||||
|
||||
if __name__ == "__main__":
|
||||
esptool._main()
|
||||
1239
installer/bin/esptool/esptool/bin_image.py
Normal file
1239
installer/bin/esptool/esptool/bin_image.py
Normal file
File diff suppressed because it is too large
Load diff
1198
installer/bin/esptool/esptool/cmds.py
Normal file
1198
installer/bin/esptool/esptool/cmds.py
Normal file
File diff suppressed because it is too large
Load diff
92
installer/bin/esptool/esptool/config.py
Normal file
92
installer/bin/esptool/esptool/config.py
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
# SPDX-FileCopyrightText: 2014-2023 Espressif Systems (Shanghai) CO LTD,
|
||||
# other contributors as noted.
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import configparser
|
||||
import os
|
||||
|
||||
CONFIG_OPTIONS = [
|
||||
"timeout",
|
||||
"chip_erase_timeout",
|
||||
"max_timeout",
|
||||
"sync_timeout",
|
||||
"md5_timeout_per_mb",
|
||||
"erase_region_timeout_per_mb",
|
||||
"erase_write_timeout_per_mb",
|
||||
"mem_end_rom_timeout",
|
||||
"serial_write_timeout",
|
||||
"connect_attempts",
|
||||
"write_block_attempts",
|
||||
"reset_delay",
|
||||
"custom_reset_sequence",
|
||||
]
|
||||
|
||||
|
||||
def _validate_config_file(file_path, verbose=False):
|
||||
if not os.path.exists(file_path):
|
||||
return False
|
||||
|
||||
cfg = configparser.RawConfigParser()
|
||||
try:
|
||||
cfg.read(file_path, encoding="UTF-8")
|
||||
# Only consider it a valid config file if it contains [esptool] section
|
||||
if cfg.has_section("esptool"):
|
||||
if verbose:
|
||||
unknown_opts = list(set(cfg.options("esptool")) - set(CONFIG_OPTIONS))
|
||||
unknown_opts.sort()
|
||||
no_of_unknown_opts = len(unknown_opts)
|
||||
if no_of_unknown_opts > 0:
|
||||
suffix = "s" if no_of_unknown_opts > 1 else ""
|
||||
print(
|
||||
"Ignoring unknown config file option{}: {}".format(
|
||||
suffix, ", ".join(unknown_opts)
|
||||
)
|
||||
)
|
||||
return True
|
||||
except (UnicodeDecodeError, configparser.Error) as e:
|
||||
if verbose:
|
||||
print(f"Ignoring invalid config file {file_path}: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def _find_config_file(dir_path, verbose=False):
|
||||
for candidate in ("esptool.cfg", "setup.cfg", "tox.ini"):
|
||||
cfg_path = os.path.join(dir_path, candidate)
|
||||
if _validate_config_file(cfg_path, verbose):
|
||||
return cfg_path
|
||||
return None
|
||||
|
||||
|
||||
def load_config_file(verbose=False):
|
||||
set_with_env_var = False
|
||||
env_var_path = os.environ.get("ESPTOOL_CFGFILE")
|
||||
if env_var_path is not None and _validate_config_file(env_var_path):
|
||||
cfg_file_path = env_var_path
|
||||
set_with_env_var = True
|
||||
else:
|
||||
home_dir = os.path.expanduser("~")
|
||||
os_config_dir = (
|
||||
f"{home_dir}/.config/esptool"
|
||||
if os.name == "posix"
|
||||
else f"{home_dir}/AppData/Local/esptool/"
|
||||
)
|
||||
# Search priority: 1) current dir, 2) OS specific config dir, 3) home dir
|
||||
for dir_path in (os.getcwd(), os_config_dir, home_dir):
|
||||
cfg_file_path = _find_config_file(dir_path, verbose)
|
||||
if cfg_file_path:
|
||||
break
|
||||
|
||||
cfg = configparser.ConfigParser()
|
||||
cfg["esptool"] = {} # Create an empty esptool config for when no file is found
|
||||
|
||||
if cfg_file_path is not None:
|
||||
# If config file is found and validated, read and parse it
|
||||
cfg.read(cfg_file_path)
|
||||
if verbose:
|
||||
msg = " (set with ESPTOOL_CFGFILE)" if set_with_env_var else ""
|
||||
print(
|
||||
f"Loaded custom configuration from "
|
||||
f"{os.path.abspath(cfg_file_path)}{msg}"
|
||||
)
|
||||
return cfg, cfg_file_path
|
||||
1608
installer/bin/esptool/esptool/loader.py
Normal file
1608
installer/bin/esptool/esptool/loader.py
Normal file
File diff suppressed because it is too large
Load diff
178
installer/bin/esptool/esptool/reset.py
Normal file
178
installer/bin/esptool/esptool/reset.py
Normal file
|
|
@ -0,0 +1,178 @@
|
|||
# SPDX-FileCopyrightText: 2014-2023 Fredrik Ahlberg, Angus Gratton,
|
||||
# Espressif Systems (Shanghai) CO LTD, other contributors as noted.
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import os
|
||||
import struct
|
||||
import time
|
||||
|
||||
from .util import FatalError
|
||||
|
||||
# Used for resetting into bootloader on Unix-like systems
|
||||
if os.name != "nt":
|
||||
import fcntl
|
||||
import termios
|
||||
|
||||
# Constants used for terminal status lines reading/setting.
|
||||
# Taken from pySerial's backend for IO:
|
||||
# https://github.com/pyserial/pyserial/blob/master/serial/serialposix.py
|
||||
TIOCMSET = getattr(termios, "TIOCMSET", 0x5418)
|
||||
TIOCMGET = getattr(termios, "TIOCMGET", 0x5415)
|
||||
TIOCM_DTR = getattr(termios, "TIOCM_DTR", 0x002)
|
||||
TIOCM_RTS = getattr(termios, "TIOCM_RTS", 0x004)
|
||||
|
||||
DEFAULT_RESET_DELAY = 0.05 # default time to wait before releasing boot pin after reset
|
||||
|
||||
|
||||
class ResetStrategy(object):
|
||||
def __init__(self, port, reset_delay=DEFAULT_RESET_DELAY):
|
||||
self.port = port
|
||||
self.reset_delay = reset_delay
|
||||
|
||||
def __call__():
|
||||
pass
|
||||
|
||||
def _setDTR(self, state):
|
||||
self.port.setDTR(state)
|
||||
|
||||
def _setRTS(self, state):
|
||||
self.port.setRTS(state)
|
||||
# Work-around for adapters on Windows using the usbser.sys driver:
|
||||
# generate a dummy change to DTR so that the set-control-line-state
|
||||
# request is sent with the updated RTS state and the same DTR state
|
||||
self.port.setDTR(self.port.dtr)
|
||||
|
||||
def _setDTRandRTS(self, dtr=False, rts=False):
|
||||
status = struct.unpack(
|
||||
"I", fcntl.ioctl(self.port.fileno(), TIOCMGET, struct.pack("I", 0))
|
||||
)[0]
|
||||
if dtr:
|
||||
status |= TIOCM_DTR
|
||||
else:
|
||||
status &= ~TIOCM_DTR
|
||||
if rts:
|
||||
status |= TIOCM_RTS
|
||||
else:
|
||||
status &= ~TIOCM_RTS
|
||||
fcntl.ioctl(self.port.fileno(), TIOCMSET, struct.pack("I", status))
|
||||
|
||||
|
||||
class ClassicReset(ResetStrategy):
|
||||
"""
|
||||
Classic reset sequence, sets DTR and RTS lines sequentially.
|
||||
"""
|
||||
|
||||
def __call__(self):
|
||||
self._setDTR(False) # IO0=HIGH
|
||||
self._setRTS(True) # EN=LOW, chip in reset
|
||||
time.sleep(0.1)
|
||||
self._setDTR(True) # IO0=LOW
|
||||
self._setRTS(False) # EN=HIGH, chip out of reset
|
||||
time.sleep(self.reset_delay)
|
||||
self._setDTR(False) # IO0=HIGH, done
|
||||
|
||||
|
||||
class UnixTightReset(ResetStrategy):
|
||||
"""
|
||||
UNIX-only reset sequence with custom implementation,
|
||||
which allows setting DTR and RTS lines at the same time.
|
||||
"""
|
||||
|
||||
def __call__(self):
|
||||
self._setDTRandRTS(False, False)
|
||||
self._setDTRandRTS(True, True)
|
||||
self._setDTRandRTS(False, True) # IO0=HIGH & EN=LOW, chip in reset
|
||||
time.sleep(0.1)
|
||||
self._setDTRandRTS(True, False) # IO0=LOW & EN=HIGH, chip out of reset
|
||||
time.sleep(self.reset_delay)
|
||||
self._setDTRandRTS(False, False) # IO0=HIGH, done
|
||||
self._setDTR(False) # Needed in some environments to ensure IO0=HIGH
|
||||
|
||||
|
||||
class USBJTAGSerialReset(ResetStrategy):
|
||||
"""
|
||||
Custom reset sequence, which is required when the device
|
||||
is connecting via its USB-JTAG-Serial peripheral.
|
||||
"""
|
||||
|
||||
def __call__(self):
|
||||
self._setRTS(False)
|
||||
self._setDTR(False) # Idle
|
||||
time.sleep(0.1)
|
||||
self._setDTR(True) # Set IO0
|
||||
self._setRTS(False)
|
||||
time.sleep(0.1)
|
||||
self._setRTS(True) # Reset. Calls inverted to go through (1,1) instead of (0,0)
|
||||
self._setDTR(False)
|
||||
self._setRTS(True) # RTS set as Windows only propagates DTR on RTS setting
|
||||
time.sleep(0.1)
|
||||
self._setDTR(False)
|
||||
self._setRTS(False) # Chip out of reset
|
||||
|
||||
|
||||
class HardReset(ResetStrategy):
|
||||
"""
|
||||
Reset sequence for hard resetting the chip.
|
||||
Can be used to reset out of the bootloader or to restart a running app.
|
||||
"""
|
||||
|
||||
def __init__(self, port, uses_usb_otg=False):
|
||||
super().__init__(port)
|
||||
self.uses_usb_otg = uses_usb_otg
|
||||
|
||||
def __call__(self):
|
||||
self._setRTS(True) # EN->LOW
|
||||
if self.uses_usb_otg:
|
||||
# Give the chip some time to come out of reset,
|
||||
# to be able to handle further DTR/RTS transitions
|
||||
time.sleep(0.2)
|
||||
self._setRTS(False)
|
||||
time.sleep(0.2)
|
||||
else:
|
||||
time.sleep(0.1)
|
||||
self._setRTS(False)
|
||||
|
||||
|
||||
class CustomReset(ResetStrategy):
|
||||
"""
|
||||
Custom reset strategy defined with a string.
|
||||
|
||||
CustomReset object is created as "rst = CustomReset(port, seq_str)"
|
||||
and can be later executed simply with "rst()"
|
||||
|
||||
The seq_str input string consists of individual commands divided by "|".
|
||||
Commands (e.g. R0) are defined by a code (R) and an argument (0).
|
||||
|
||||
The commands are:
|
||||
D: setDTR - 1=True / 0=False
|
||||
R: setRTS - 1=True / 0=False
|
||||
U: setDTRandRTS (Unix-only) - 0,0 / 0,1 / 1,0 / or 1,1
|
||||
W: Wait (time delay) - positive float number
|
||||
|
||||
e.g.
|
||||
"D0|R1|W0.1|D1|R0|W0.05|D0" represents the ClassicReset strategy
|
||||
"U1,1|U0,1|W0.1|U1,0|W0.05|U0,0" represents the UnixTightReset strategy
|
||||
"""
|
||||
|
||||
format_dict = {
|
||||
"D": "self.port.setDTR({})",
|
||||
"R": "self.port.setRTS({})",
|
||||
"W": "time.sleep({})",
|
||||
"U": "self._setDTRandRTS({})",
|
||||
}
|
||||
|
||||
def __call__(self):
|
||||
exec(self.constructed_strategy)
|
||||
|
||||
def __init__(self, port, seq_str):
|
||||
super().__init__(port)
|
||||
self.constructed_strategy = self._parse_string_to_seq(seq_str)
|
||||
|
||||
def _parse_string_to_seq(self, seq_str):
|
||||
try:
|
||||
cmds = seq_str.split("|")
|
||||
fn_calls_list = [self.format_dict[cmd[0]].format(cmd[1:]) for cmd in cmds]
|
||||
except Exception as e:
|
||||
raise FatalError(f'Invalid "custom_reset_sequence" option format: {e}')
|
||||
return "\n".join(fn_calls_list)
|
||||
31
installer/bin/esptool/esptool/targets/__init__.py
Normal file
31
installer/bin/esptool/esptool/targets/__init__.py
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
from .esp32 import ESP32ROM
|
||||
from .esp32c2 import ESP32C2ROM
|
||||
from .esp32c3 import ESP32C3ROM
|
||||
from .esp32c6 import ESP32C6ROM
|
||||
from .esp32c6beta import ESP32C6BETAROM
|
||||
from .esp32h2 import ESP32H2ROM
|
||||
from .esp32h2beta1 import ESP32H2BETA1ROM
|
||||
from .esp32h2beta2 import ESP32H2BETA2ROM
|
||||
from .esp32s2 import ESP32S2ROM
|
||||
from .esp32s3 import ESP32S3ROM
|
||||
from .esp32s3beta2 import ESP32S3BETA2ROM
|
||||
from .esp8266 import ESP8266ROM
|
||||
|
||||
|
||||
CHIP_DEFS = {
|
||||
"esp8266": ESP8266ROM,
|
||||
"esp32": ESP32ROM,
|
||||
"esp32s2": ESP32S2ROM,
|
||||
"esp32s3beta2": ESP32S3BETA2ROM,
|
||||
"esp32s3": ESP32S3ROM,
|
||||
"esp32c3": ESP32C3ROM,
|
||||
"esp32c6beta": ESP32C6BETAROM,
|
||||
"esp32h2beta1": ESP32H2BETA1ROM,
|
||||
"esp32h2beta2": ESP32H2BETA2ROM,
|
||||
"esp32c2": ESP32C2ROM,
|
||||
"esp32c6": ESP32C6ROM,
|
||||
"esp32h2": ESP32H2ROM,
|
||||
}
|
||||
|
||||
CHIP_LIST = list(CHIP_DEFS.keys())
|
||||
ROM_LIST = list(CHIP_DEFS.values())
|
||||
393
installer/bin/esptool/esptool/targets/esp32.py
Normal file
393
installer/bin/esptool/esptool/targets/esp32.py
Normal file
|
|
@ -0,0 +1,393 @@
|
|||
# SPDX-FileCopyrightText: 2014-2022 Fredrik Ahlberg, Angus Gratton,
|
||||
# Espressif Systems (Shanghai) CO LTD, other contributors as noted.
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import struct
|
||||
import time
|
||||
|
||||
from ..loader import ESPLoader
|
||||
from ..util import FatalError, NotSupportedError
|
||||
|
||||
|
||||
class ESP32ROM(ESPLoader):
|
||||
"""Access class for ESP32 ROM bootloader"""
|
||||
|
||||
CHIP_NAME = "ESP32"
|
||||
IMAGE_CHIP_ID = 0
|
||||
IS_STUB = False
|
||||
|
||||
FPGA_SLOW_BOOT = True
|
||||
|
||||
CHIP_DETECT_MAGIC_VALUE = [0x00F01D83]
|
||||
|
||||
IROM_MAP_START = 0x400D0000
|
||||
IROM_MAP_END = 0x40400000
|
||||
|
||||
DROM_MAP_START = 0x3F400000
|
||||
DROM_MAP_END = 0x3F800000
|
||||
|
||||
# ESP32 uses a 4 byte status reply
|
||||
STATUS_BYTES_LENGTH = 4
|
||||
|
||||
SPI_REG_BASE = 0x3FF42000
|
||||
SPI_USR_OFFS = 0x1C
|
||||
SPI_USR1_OFFS = 0x20
|
||||
SPI_USR2_OFFS = 0x24
|
||||
SPI_MOSI_DLEN_OFFS = 0x28
|
||||
SPI_MISO_DLEN_OFFS = 0x2C
|
||||
EFUSE_RD_REG_BASE = 0x3FF5A000
|
||||
|
||||
EFUSE_DIS_DOWNLOAD_MANUAL_ENCRYPT_REG = EFUSE_RD_REG_BASE + 0x18
|
||||
EFUSE_DIS_DOWNLOAD_MANUAL_ENCRYPT = 1 << 7 # EFUSE_RD_DISABLE_DL_ENCRYPT
|
||||
|
||||
EFUSE_SPI_BOOT_CRYPT_CNT_REG = EFUSE_RD_REG_BASE # EFUSE_BLK0_WDATA0_REG
|
||||
EFUSE_SPI_BOOT_CRYPT_CNT_MASK = 0x7F << 20 # EFUSE_FLASH_CRYPT_CNT
|
||||
|
||||
EFUSE_RD_ABS_DONE_REG = EFUSE_RD_REG_BASE + 0x018
|
||||
EFUSE_RD_ABS_DONE_0_MASK = 1 << 4
|
||||
EFUSE_RD_ABS_DONE_1_MASK = 1 << 5
|
||||
|
||||
DR_REG_SYSCON_BASE = 0x3FF66000
|
||||
APB_CTL_DATE_ADDR = DR_REG_SYSCON_BASE + 0x7C
|
||||
APB_CTL_DATE_V = 0x1
|
||||
APB_CTL_DATE_S = 31
|
||||
|
||||
SPI_W0_OFFS = 0x80
|
||||
|
||||
UART_CLKDIV_REG = 0x3FF40014
|
||||
|
||||
XTAL_CLK_DIVIDER = 1
|
||||
|
||||
RTCCALICFG1 = 0x3FF5F06C
|
||||
TIMERS_RTC_CALI_VALUE = 0x01FFFFFF
|
||||
TIMERS_RTC_CALI_VALUE_S = 7
|
||||
|
||||
FLASH_SIZES = {
|
||||
"1MB": 0x00,
|
||||
"2MB": 0x10,
|
||||
"4MB": 0x20,
|
||||
"8MB": 0x30,
|
||||
"16MB": 0x40,
|
||||
"32MB": 0x50,
|
||||
"64MB": 0x60,
|
||||
"128MB": 0x70,
|
||||
}
|
||||
|
||||
FLASH_FREQUENCY = {
|
||||
"80m": 0xF,
|
||||
"40m": 0x0,
|
||||
"26m": 0x1,
|
||||
"20m": 0x2,
|
||||
}
|
||||
|
||||
BOOTLOADER_FLASH_OFFSET = 0x1000
|
||||
|
||||
OVERRIDE_VDDSDIO_CHOICES = ["1.8V", "1.9V", "OFF"]
|
||||
|
||||
MEMORY_MAP = [
|
||||
[0x00000000, 0x00010000, "PADDING"],
|
||||
[0x3F400000, 0x3F800000, "DROM"],
|
||||
[0x3F800000, 0x3FC00000, "EXTRAM_DATA"],
|
||||
[0x3FF80000, 0x3FF82000, "RTC_DRAM"],
|
||||
[0x3FF90000, 0x40000000, "BYTE_ACCESSIBLE"],
|
||||
[0x3FFAE000, 0x40000000, "DRAM"],
|
||||
[0x3FFE0000, 0x3FFFFFFC, "DIRAM_DRAM"],
|
||||
[0x40000000, 0x40070000, "IROM"],
|
||||
[0x40070000, 0x40078000, "CACHE_PRO"],
|
||||
[0x40078000, 0x40080000, "CACHE_APP"],
|
||||
[0x40080000, 0x400A0000, "IRAM"],
|
||||
[0x400A0000, 0x400BFFFC, "DIRAM_IRAM"],
|
||||
[0x400C0000, 0x400C2000, "RTC_IRAM"],
|
||||
[0x400D0000, 0x40400000, "IROM"],
|
||||
[0x50000000, 0x50002000, "RTC_DATA"],
|
||||
]
|
||||
|
||||
FLASH_ENCRYPTED_WRITE_ALIGN = 32
|
||||
|
||||
""" Try to read the BLOCK1 (encryption key) and check if it is valid """
|
||||
|
||||
def is_flash_encryption_key_valid(self):
|
||||
"""Bit 0 of efuse_rd_disable[3:0] is mapped to BLOCK1
|
||||
this bit is at position 16 in EFUSE_BLK0_RDATA0_REG"""
|
||||
word0 = self.read_efuse(0)
|
||||
rd_disable = (word0 >> 16) & 0x1
|
||||
|
||||
# reading of BLOCK1 is NOT ALLOWED so we assume valid key is programmed
|
||||
if rd_disable:
|
||||
return True
|
||||
else:
|
||||
# reading of BLOCK1 is ALLOWED so we will read and verify for non-zero.
|
||||
# When ESP32 has not generated AES/encryption key in BLOCK1,
|
||||
# the contents will be readable and 0.
|
||||
# If the flash encryption is enabled it is expected to have a valid
|
||||
# non-zero key. We break out on first occurance of non-zero value
|
||||
key_word = [0] * 7
|
||||
for i in range(len(key_word)):
|
||||
key_word[i] = self.read_efuse(14 + i)
|
||||
# key is non-zero so break & return
|
||||
if key_word[i] != 0:
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_flash_crypt_config(self):
|
||||
"""For flash encryption related commands we need to make sure
|
||||
user has programmed all the relevant efuse correctly so before
|
||||
writing encrypted write_flash_encrypt esptool will verify the values
|
||||
of flash_crypt_config to be non zero if they are not read
|
||||
protected. If the values are zero a warning will be printed
|
||||
|
||||
bit 3 in efuse_rd_disable[3:0] is mapped to flash_crypt_config
|
||||
this bit is at position 19 in EFUSE_BLK0_RDATA0_REG"""
|
||||
word0 = self.read_efuse(0)
|
||||
rd_disable = (word0 >> 19) & 0x1
|
||||
|
||||
if rd_disable == 0:
|
||||
"""we can read the flash_crypt_config efuse value
|
||||
so go & read it (EFUSE_BLK0_RDATA5_REG[31:28])"""
|
||||
word5 = self.read_efuse(5)
|
||||
word5 = (word5 >> 28) & 0xF
|
||||
return word5
|
||||
else:
|
||||
# if read of the efuse is disabled we assume it is set correctly
|
||||
return 0xF
|
||||
|
||||
def get_encrypted_download_disabled(self):
|
||||
return (
|
||||
self.read_reg(self.EFUSE_DIS_DOWNLOAD_MANUAL_ENCRYPT_REG)
|
||||
& self.EFUSE_DIS_DOWNLOAD_MANUAL_ENCRYPT
|
||||
)
|
||||
|
||||
def get_flash_encryption_enabled(self):
|
||||
flash_crypt_cnt = (
|
||||
self.read_reg(self.EFUSE_SPI_BOOT_CRYPT_CNT_REG)
|
||||
& self.EFUSE_SPI_BOOT_CRYPT_CNT_MASK
|
||||
)
|
||||
# Flash encryption enabled when odd number of bits are set
|
||||
return bin(flash_crypt_cnt).count("1") & 1 != 0
|
||||
|
||||
def get_secure_boot_enabled(self):
|
||||
efuses = self.read_reg(self.EFUSE_RD_ABS_DONE_REG)
|
||||
rev = self.get_chip_revision()
|
||||
return efuses & self.EFUSE_RD_ABS_DONE_0_MASK or (
|
||||
rev >= 300 and efuses & self.EFUSE_RD_ABS_DONE_1_MASK
|
||||
)
|
||||
|
||||
def get_pkg_version(self):
|
||||
word3 = self.read_efuse(3)
|
||||
pkg_version = (word3 >> 9) & 0x07
|
||||
pkg_version += ((word3 >> 2) & 0x1) << 3
|
||||
return pkg_version
|
||||
|
||||
def get_chip_revision(self):
|
||||
return self.get_major_chip_version() * 100 + self.get_minor_chip_version()
|
||||
|
||||
def get_minor_chip_version(self):
|
||||
return (self.read_efuse(5) >> 24) & 0x3
|
||||
|
||||
def get_major_chip_version(self):
|
||||
rev_bit0 = (self.read_efuse(3) >> 15) & 0x1
|
||||
rev_bit1 = (self.read_efuse(5) >> 20) & 0x1
|
||||
apb_ctl_date = self.read_reg(self.APB_CTL_DATE_ADDR)
|
||||
rev_bit2 = (apb_ctl_date >> self.APB_CTL_DATE_S) & self.APB_CTL_DATE_V
|
||||
combine_value = (rev_bit2 << 2) | (rev_bit1 << 1) | rev_bit0
|
||||
|
||||
revision = {
|
||||
0: 0,
|
||||
1: 1,
|
||||
3: 2,
|
||||
7: 3,
|
||||
}.get(combine_value, 0)
|
||||
return revision
|
||||
|
||||
def get_chip_description(self):
|
||||
pkg_version = self.get_pkg_version()
|
||||
major_rev = self.get_major_chip_version()
|
||||
minor_rev = self.get_minor_chip_version()
|
||||
rev3 = major_rev == 3
|
||||
single_core = self.read_efuse(3) & (1 << 0) # CHIP_VER DIS_APP_CPU
|
||||
|
||||
chip_name = {
|
||||
0: "ESP32-S0WDQ6" if single_core else "ESP32-D0WDQ6",
|
||||
1: "ESP32-S0WD" if single_core else "ESP32-D0WD",
|
||||
2: "ESP32-D2WD",
|
||||
4: "ESP32-U4WDH",
|
||||
5: "ESP32-PICO-V3" if rev3 else "ESP32-PICO-D4",
|
||||
6: "ESP32-PICO-V3-02",
|
||||
7: "ESP32-D0WDR2-V3",
|
||||
}.get(pkg_version, "unknown ESP32")
|
||||
|
||||
# ESP32-D0WD-V3, ESP32-D0WDQ6-V3
|
||||
if chip_name.startswith("ESP32-D0WD") and rev3:
|
||||
chip_name += "-V3"
|
||||
|
||||
return f"{chip_name} (revision v{major_rev}.{minor_rev})"
|
||||
|
||||
def get_chip_features(self):
|
||||
features = ["WiFi"]
|
||||
word3 = self.read_efuse(3)
|
||||
|
||||
# names of variables in this section are lowercase
|
||||
# versions of EFUSE names as documented in TRM and
|
||||
# ESP-IDF efuse_reg.h
|
||||
|
||||
chip_ver_dis_bt = word3 & (1 << 1)
|
||||
if chip_ver_dis_bt == 0:
|
||||
features += ["BT"]
|
||||
|
||||
chip_ver_dis_app_cpu = word3 & (1 << 0)
|
||||
if chip_ver_dis_app_cpu:
|
||||
features += ["Single Core"]
|
||||
else:
|
||||
features += ["Dual Core"]
|
||||
|
||||
chip_cpu_freq_rated = word3 & (1 << 13)
|
||||
if chip_cpu_freq_rated:
|
||||
chip_cpu_freq_low = word3 & (1 << 12)
|
||||
if chip_cpu_freq_low:
|
||||
features += ["160MHz"]
|
||||
else:
|
||||
features += ["240MHz"]
|
||||
|
||||
pkg_version = self.get_pkg_version()
|
||||
if pkg_version in [2, 4, 5, 6]:
|
||||
features += ["Embedded Flash"]
|
||||
|
||||
if pkg_version == 6:
|
||||
features += ["Embedded PSRAM"]
|
||||
|
||||
word4 = self.read_efuse(4)
|
||||
adc_vref = (word4 >> 8) & 0x1F
|
||||
if adc_vref:
|
||||
features += ["VRef calibration in efuse"]
|
||||
|
||||
blk3_part_res = word3 >> 14 & 0x1
|
||||
if blk3_part_res:
|
||||
features += ["BLK3 partially reserved"]
|
||||
|
||||
word6 = self.read_efuse(6)
|
||||
coding_scheme = word6 & 0x3
|
||||
features += [
|
||||
"Coding Scheme %s"
|
||||
% {0: "None", 1: "3/4", 2: "Repeat (UNSUPPORTED)", 3: "Invalid"}[
|
||||
coding_scheme
|
||||
]
|
||||
]
|
||||
|
||||
return features
|
||||
|
||||
def read_efuse(self, n):
|
||||
"""Read the nth word of the ESP3x EFUSE region."""
|
||||
return self.read_reg(self.EFUSE_RD_REG_BASE + (4 * n))
|
||||
|
||||
def chip_id(self):
|
||||
raise NotSupportedError(self, "chip_id")
|
||||
|
||||
def read_mac(self):
|
||||
"""Read MAC from EFUSE region"""
|
||||
words = [self.read_efuse(2), self.read_efuse(1)]
|
||||
bitstring = struct.pack(">II", *words)
|
||||
bitstring = bitstring[2:8] # trim the 2 byte CRC
|
||||
return tuple(bitstring)
|
||||
|
||||
def get_erase_size(self, offset, size):
|
||||
return size
|
||||
|
||||
def override_vddsdio(self, new_voltage):
|
||||
new_voltage = new_voltage.upper()
|
||||
if new_voltage not in self.OVERRIDE_VDDSDIO_CHOICES:
|
||||
raise FatalError(
|
||||
"The only accepted VDDSDIO overrides are '1.8V', '1.9V' and 'OFF'"
|
||||
)
|
||||
RTC_CNTL_SDIO_CONF_REG = 0x3FF48074
|
||||
RTC_CNTL_XPD_SDIO_REG = 1 << 31
|
||||
RTC_CNTL_DREFH_SDIO_M = 3 << 29
|
||||
RTC_CNTL_DREFM_SDIO_M = 3 << 27
|
||||
RTC_CNTL_DREFL_SDIO_M = 3 << 25
|
||||
# RTC_CNTL_SDIO_TIEH = (1 << 23)
|
||||
# not used here, setting TIEH=1 would set 3.3V output,
|
||||
# not safe for esptool.py to do
|
||||
RTC_CNTL_SDIO_FORCE = 1 << 22
|
||||
RTC_CNTL_SDIO_PD_EN = 1 << 21
|
||||
|
||||
reg_val = RTC_CNTL_SDIO_FORCE # override efuse setting
|
||||
reg_val |= RTC_CNTL_SDIO_PD_EN
|
||||
if new_voltage != "OFF":
|
||||
reg_val |= RTC_CNTL_XPD_SDIO_REG # enable internal LDO
|
||||
if new_voltage == "1.9V":
|
||||
reg_val |= (
|
||||
RTC_CNTL_DREFH_SDIO_M | RTC_CNTL_DREFM_SDIO_M | RTC_CNTL_DREFL_SDIO_M
|
||||
) # boost voltage
|
||||
self.write_reg(RTC_CNTL_SDIO_CONF_REG, reg_val)
|
||||
print("VDDSDIO regulator set to %s" % new_voltage)
|
||||
|
||||
def read_flash_slow(self, offset, length, progress_fn):
|
||||
BLOCK_LEN = 64 # ROM read limit per command (this limit is why it's so slow)
|
||||
|
||||
data = b""
|
||||
while len(data) < length:
|
||||
block_len = min(BLOCK_LEN, length - len(data))
|
||||
r = self.check_command(
|
||||
"read flash block",
|
||||
self.ESP_READ_FLASH_SLOW,
|
||||
struct.pack("<II", offset + len(data), block_len),
|
||||
)
|
||||
if len(r) < block_len:
|
||||
raise FatalError(
|
||||
"Expected %d byte block, got %d bytes. Serial errors?"
|
||||
% (block_len, len(r))
|
||||
)
|
||||
# command always returns 64 byte buffer,
|
||||
# regardless of how many bytes were actually read from flash
|
||||
data += r[:block_len]
|
||||
if progress_fn and (len(data) % 1024 == 0 or len(data) == length):
|
||||
progress_fn(len(data), length)
|
||||
return data
|
||||
|
||||
def get_rom_cal_crystal_freq(self):
|
||||
"""
|
||||
Get the crystal frequency calculated by the ROM
|
||||
"""
|
||||
# - Simulate the calculation in the ROM to get the XTAL frequency
|
||||
# calculated by the ROM
|
||||
|
||||
cali_val = (
|
||||
self.read_reg(self.RTCCALICFG1) >> self.TIMERS_RTC_CALI_VALUE_S
|
||||
) & self.TIMERS_RTC_CALI_VALUE
|
||||
clk_8M_freq = self.read_efuse(4) & (0xFF) # EFUSE_RD_CK8M_FREQ
|
||||
rom_calculated_freq = cali_val * 15625 * clk_8M_freq / 40
|
||||
return rom_calculated_freq
|
||||
|
||||
def change_baud(self, baud):
|
||||
# It's a workaround to avoid esp32 CK_8M frequency drift.
|
||||
rom_calculated_freq = self.get_rom_cal_crystal_freq()
|
||||
valid_freq = 40000000 if rom_calculated_freq > 33000000 else 26000000
|
||||
false_rom_baud = int(baud * rom_calculated_freq // valid_freq)
|
||||
|
||||
print(f"Changing baud rate to {baud}")
|
||||
self.command(self.ESP_CHANGE_BAUDRATE, struct.pack("<II", false_rom_baud, 0))
|
||||
print("Changed.")
|
||||
self._set_port_baudrate(baud)
|
||||
time.sleep(0.05) # get rid of garbage sent during baud rate change
|
||||
self.flush_input()
|
||||
|
||||
|
||||
class ESP32StubLoader(ESP32ROM):
|
||||
"""Access class for ESP32 stub loader, runs on top of ROM."""
|
||||
|
||||
FLASH_WRITE_SIZE = 0x4000 # matches MAX_WRITE_BLOCK in stub_loader.c
|
||||
STATUS_BYTES_LENGTH = 2 # same as ESP8266, different to ESP32 ROM
|
||||
IS_STUB = True
|
||||
|
||||
def __init__(self, rom_loader):
|
||||
self.secure_download_mode = rom_loader.secure_download_mode
|
||||
self._port = rom_loader._port
|
||||
self._trace_enabled = rom_loader._trace_enabled
|
||||
self.cache = rom_loader.cache
|
||||
self.flush_input() # resets _slip_reader
|
||||
|
||||
def change_baud(self, baud):
|
||||
ESPLoader.change_baud(self, baud)
|
||||
|
||||
|
||||
ESP32ROM.STUB_CLASS = ESP32StubLoader
|
||||
165
installer/bin/esptool/esptool/targets/esp32c2.py
Normal file
165
installer/bin/esptool/esptool/targets/esp32c2.py
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
# SPDX-FileCopyrightText: 2014-2022 Fredrik Ahlberg, Angus Gratton,
|
||||
# Espressif Systems (Shanghai) CO LTD, other contributors as noted.
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import struct
|
||||
import time
|
||||
|
||||
from .esp32c3 import ESP32C3ROM
|
||||
from ..loader import ESPLoader
|
||||
|
||||
|
||||
class ESP32C2ROM(ESP32C3ROM):
|
||||
CHIP_NAME = "ESP32-C2"
|
||||
IMAGE_CHIP_ID = 12
|
||||
|
||||
IROM_MAP_START = 0x42000000
|
||||
IROM_MAP_END = 0x42400000
|
||||
DROM_MAP_START = 0x3C000000
|
||||
DROM_MAP_END = 0x3C400000
|
||||
|
||||
# Magic value for ESP32C2 ECO0 and ECO1 respectively
|
||||
CHIP_DETECT_MAGIC_VALUE = [0x6F51306F, 0x7C41A06F]
|
||||
|
||||
EFUSE_BASE = 0x60008800
|
||||
EFUSE_BLOCK2_ADDR = EFUSE_BASE + 0x040
|
||||
MAC_EFUSE_REG = EFUSE_BASE + 0x040
|
||||
|
||||
EFUSE_SECURE_BOOT_EN_REG = EFUSE_BASE + 0x30
|
||||
EFUSE_SECURE_BOOT_EN_MASK = 1 << 21
|
||||
|
||||
EFUSE_SPI_BOOT_CRYPT_CNT_REG = EFUSE_BASE + 0x30
|
||||
EFUSE_SPI_BOOT_CRYPT_CNT_MASK = 0x7 << 18
|
||||
|
||||
EFUSE_DIS_DOWNLOAD_MANUAL_ENCRYPT_REG = EFUSE_BASE + 0x30
|
||||
EFUSE_DIS_DOWNLOAD_MANUAL_ENCRYPT = 1 << 6
|
||||
|
||||
EFUSE_XTS_KEY_LENGTH_256_REG = EFUSE_BASE + 0x30
|
||||
EFUSE_XTS_KEY_LENGTH_256 = 1 << 10
|
||||
|
||||
EFUSE_BLOCK_KEY0_REG = EFUSE_BASE + 0x60
|
||||
|
||||
EFUSE_RD_DIS_REG = EFUSE_BASE + 0x30
|
||||
EFUSE_RD_DIS = 3
|
||||
|
||||
FLASH_FREQUENCY = {
|
||||
"60m": 0xF,
|
||||
"30m": 0x0,
|
||||
"20m": 0x1,
|
||||
"15m": 0x2,
|
||||
}
|
||||
|
||||
MEMORY_MAP = [
|
||||
[0x00000000, 0x00010000, "PADDING"],
|
||||
[0x3C000000, 0x3C400000, "DROM"],
|
||||
[0x3FCA0000, 0x3FCE0000, "DRAM"],
|
||||
[0x3FC88000, 0x3FD00000, "BYTE_ACCESSIBLE"],
|
||||
[0x3FF00000, 0x3FF50000, "DROM_MASK"],
|
||||
[0x40000000, 0x40090000, "IROM_MASK"],
|
||||
[0x42000000, 0x42400000, "IROM"],
|
||||
[0x4037C000, 0x403C0000, "IRAM"],
|
||||
]
|
||||
|
||||
def get_pkg_version(self):
|
||||
num_word = 1
|
||||
return (self.read_reg(self.EFUSE_BLOCK2_ADDR + (4 * num_word)) >> 22) & 0x07
|
||||
|
||||
def get_chip_description(self):
|
||||
chip_name = {
|
||||
0: "ESP32-C2",
|
||||
1: "ESP32-C2",
|
||||
}.get(self.get_pkg_version(), "unknown ESP32-C2")
|
||||
major_rev = self.get_major_chip_version()
|
||||
minor_rev = self.get_minor_chip_version()
|
||||
return f"{chip_name} (revision v{major_rev}.{minor_rev})"
|
||||
|
||||
def get_minor_chip_version(self):
|
||||
num_word = 1
|
||||
return (self.read_reg(self.EFUSE_BLOCK2_ADDR + (4 * num_word)) >> 16) & 0xF
|
||||
|
||||
def get_major_chip_version(self):
|
||||
num_word = 1
|
||||
return (self.read_reg(self.EFUSE_BLOCK2_ADDR + (4 * num_word)) >> 20) & 0x3
|
||||
|
||||
def get_crystal_freq(self):
|
||||
# The crystal detection algorithm of ESP32/ESP8266 works for ESP32-C2 as well.
|
||||
return ESPLoader.get_crystal_freq(self)
|
||||
|
||||
def change_baud(self, baud):
|
||||
rom_with_26M_XTAL = not self.IS_STUB and self.get_crystal_freq() == 26
|
||||
if rom_with_26M_XTAL:
|
||||
# The code is copied over from ESPLoader.change_baud().
|
||||
# Probably this is just a temporary solution until the next chip revision.
|
||||
|
||||
# The ROM code thinks it uses a 40 MHz XTAL. Recompute the baud rate
|
||||
# in order to trick the ROM code to set the correct baud rate for
|
||||
# a 26 MHz XTAL.
|
||||
false_rom_baud = baud * 40 // 26
|
||||
|
||||
print(f"Changing baud rate to {baud}")
|
||||
self.command(
|
||||
self.ESP_CHANGE_BAUDRATE, struct.pack("<II", false_rom_baud, 0)
|
||||
)
|
||||
print("Changed.")
|
||||
self._set_port_baudrate(baud)
|
||||
time.sleep(0.05) # get rid of garbage sent during baud rate change
|
||||
self.flush_input()
|
||||
else:
|
||||
ESPLoader.change_baud(self, baud)
|
||||
|
||||
def _post_connect(self):
|
||||
# ESP32C2 ECO0 is no longer supported by the flasher stub
|
||||
if self.get_chip_revision() == 0:
|
||||
self.stub_is_disabled = True
|
||||
self.IS_STUB = False
|
||||
|
||||
""" Try to read (encryption key) and check if it is valid """
|
||||
|
||||
def is_flash_encryption_key_valid(self):
|
||||
key_len_256 = (
|
||||
self.read_reg(self.EFUSE_XTS_KEY_LENGTH_256_REG)
|
||||
& self.EFUSE_XTS_KEY_LENGTH_256
|
||||
)
|
||||
|
||||
word0 = self.read_reg(self.EFUSE_RD_DIS_REG) & self.EFUSE_RD_DIS
|
||||
rd_disable = word0 == 3 if key_len_256 else word0 == 1
|
||||
|
||||
# reading of BLOCK3 is NOT ALLOWED so we assume valid key is programmed
|
||||
if rd_disable:
|
||||
return True
|
||||
else:
|
||||
# reading of BLOCK3 is ALLOWED so we will read and verify for non-zero.
|
||||
# When chip has not generated AES/encryption key in BLOCK3,
|
||||
# the contents will be readable and 0.
|
||||
# If the flash encryption is enabled it is expected to have a valid
|
||||
# non-zero key. We break out on first occurance of non-zero value
|
||||
key_word = [0] * 7 if key_len_256 else [0] * 3
|
||||
for i in range(len(key_word)):
|
||||
key_word[i] = self.read_reg(self.EFUSE_BLOCK_KEY0_REG + i * 4)
|
||||
# key is non-zero so break & return
|
||||
if key_word[i] != 0:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class ESP32C2StubLoader(ESP32C2ROM):
|
||||
"""Access class for ESP32C2 stub loader, runs on top of ROM.
|
||||
|
||||
(Basically the same as ESP32StubLoader, but different base class.
|
||||
Can possibly be made into a mixin.)
|
||||
"""
|
||||
|
||||
FLASH_WRITE_SIZE = 0x4000 # matches MAX_WRITE_BLOCK in stub_loader.c
|
||||
STATUS_BYTES_LENGTH = 2 # same as ESP8266, different to ESP32 ROM
|
||||
IS_STUB = True
|
||||
|
||||
def __init__(self, rom_loader):
|
||||
self.secure_download_mode = rom_loader.secure_download_mode
|
||||
self._port = rom_loader._port
|
||||
self._trace_enabled = rom_loader._trace_enabled
|
||||
self.cache = rom_loader.cache
|
||||
self.flush_input() # resets _slip_reader
|
||||
|
||||
|
||||
ESP32C2ROM.STUB_CLASS = ESP32C2StubLoader
|
||||
210
installer/bin/esptool/esptool/targets/esp32c3.py
Normal file
210
installer/bin/esptool/esptool/targets/esp32c3.py
Normal file
|
|
@ -0,0 +1,210 @@
|
|||
# SPDX-FileCopyrightText: 2014-2022 Fredrik Ahlberg, Angus Gratton,
|
||||
# Espressif Systems (Shanghai) CO LTD, other contributors as noted.
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import struct
|
||||
|
||||
from .esp32 import ESP32ROM
|
||||
from ..loader import ESPLoader
|
||||
from ..util import FatalError, NotImplementedInROMError
|
||||
|
||||
|
||||
class ESP32C3ROM(ESP32ROM):
|
||||
CHIP_NAME = "ESP32-C3"
|
||||
IMAGE_CHIP_ID = 5
|
||||
|
||||
FPGA_SLOW_BOOT = False
|
||||
|
||||
IROM_MAP_START = 0x42000000
|
||||
IROM_MAP_END = 0x42800000
|
||||
DROM_MAP_START = 0x3C000000
|
||||
DROM_MAP_END = 0x3C800000
|
||||
|
||||
SPI_REG_BASE = 0x60002000
|
||||
SPI_USR_OFFS = 0x18
|
||||
SPI_USR1_OFFS = 0x1C
|
||||
SPI_USR2_OFFS = 0x20
|
||||
SPI_MOSI_DLEN_OFFS = 0x24
|
||||
SPI_MISO_DLEN_OFFS = 0x28
|
||||
SPI_W0_OFFS = 0x58
|
||||
|
||||
BOOTLOADER_FLASH_OFFSET = 0x0
|
||||
|
||||
# Magic value for ESP32C3 eco 1+2 and ESP32C3 eco3 respectivly
|
||||
CHIP_DETECT_MAGIC_VALUE = [0x6921506F, 0x1B31506F]
|
||||
|
||||
UART_DATE_REG_ADDR = 0x60000000 + 0x7C
|
||||
|
||||
UART_CLKDIV_REG = 0x60000014
|
||||
|
||||
EFUSE_BASE = 0x60008800
|
||||
EFUSE_BLOCK1_ADDR = EFUSE_BASE + 0x044
|
||||
MAC_EFUSE_REG = EFUSE_BASE + 0x044
|
||||
|
||||
EFUSE_RD_REG_BASE = EFUSE_BASE + 0x030 # BLOCK0 read base address
|
||||
|
||||
EFUSE_PURPOSE_KEY0_REG = EFUSE_BASE + 0x34
|
||||
EFUSE_PURPOSE_KEY0_SHIFT = 24
|
||||
EFUSE_PURPOSE_KEY1_REG = EFUSE_BASE + 0x34
|
||||
EFUSE_PURPOSE_KEY1_SHIFT = 28
|
||||
EFUSE_PURPOSE_KEY2_REG = EFUSE_BASE + 0x38
|
||||
EFUSE_PURPOSE_KEY2_SHIFT = 0
|
||||
EFUSE_PURPOSE_KEY3_REG = EFUSE_BASE + 0x38
|
||||
EFUSE_PURPOSE_KEY3_SHIFT = 4
|
||||
EFUSE_PURPOSE_KEY4_REG = EFUSE_BASE + 0x38
|
||||
EFUSE_PURPOSE_KEY4_SHIFT = 8
|
||||
EFUSE_PURPOSE_KEY5_REG = EFUSE_BASE + 0x38
|
||||
EFUSE_PURPOSE_KEY5_SHIFT = 12
|
||||
|
||||
EFUSE_DIS_DOWNLOAD_MANUAL_ENCRYPT_REG = EFUSE_RD_REG_BASE
|
||||
EFUSE_DIS_DOWNLOAD_MANUAL_ENCRYPT = 1 << 20
|
||||
|
||||
EFUSE_SPI_BOOT_CRYPT_CNT_REG = EFUSE_BASE + 0x034
|
||||
EFUSE_SPI_BOOT_CRYPT_CNT_MASK = 0x7 << 18
|
||||
|
||||
EFUSE_SECURE_BOOT_EN_REG = EFUSE_BASE + 0x038
|
||||
EFUSE_SECURE_BOOT_EN_MASK = 1 << 20
|
||||
|
||||
PURPOSE_VAL_XTS_AES128_KEY = 4
|
||||
|
||||
SUPPORTS_ENCRYPTED_FLASH = True
|
||||
|
||||
FLASH_ENCRYPTED_WRITE_ALIGN = 16
|
||||
|
||||
UARTDEV_BUF_NO = 0x3FCDF07C # Variable in ROM .bss which indicates the port in use
|
||||
UARTDEV_BUF_NO_USB_JTAG_SERIAL = 3 # The above var when USB-JTAG/Serial is used
|
||||
|
||||
RTC_CNTL_WDT_WKEY = 0x50D83AA1
|
||||
RTCCNTL_BASE_REG = 0x60008000
|
||||
RTC_CNTL_WDTCONFIG0_REG = RTCCNTL_BASE_REG + 0x0090
|
||||
RTC_CNTL_WDTWPROTECT_REG = RTCCNTL_BASE_REG + 0x00A8
|
||||
|
||||
MEMORY_MAP = [
|
||||
[0x00000000, 0x00010000, "PADDING"],
|
||||
[0x3C000000, 0x3C800000, "DROM"],
|
||||
[0x3FC80000, 0x3FCE0000, "DRAM"],
|
||||
[0x3FC88000, 0x3FD00000, "BYTE_ACCESSIBLE"],
|
||||
[0x3FF00000, 0x3FF20000, "DROM_MASK"],
|
||||
[0x40000000, 0x40060000, "IROM_MASK"],
|
||||
[0x42000000, 0x42800000, "IROM"],
|
||||
[0x4037C000, 0x403E0000, "IRAM"],
|
||||
[0x50000000, 0x50002000, "RTC_IRAM"],
|
||||
[0x50000000, 0x50002000, "RTC_DRAM"],
|
||||
[0x600FE000, 0x60100000, "MEM_INTERNAL2"],
|
||||
]
|
||||
|
||||
def get_pkg_version(self):
|
||||
num_word = 3
|
||||
return (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * num_word)) >> 21) & 0x07
|
||||
|
||||
def get_minor_chip_version(self):
|
||||
hi_num_word = 5
|
||||
hi = (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * hi_num_word)) >> 23) & 0x01
|
||||
low_num_word = 3
|
||||
low = (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * low_num_word)) >> 18) & 0x07
|
||||
return (hi << 3) + low
|
||||
|
||||
def get_major_chip_version(self):
|
||||
num_word = 5
|
||||
return (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * num_word)) >> 24) & 0x03
|
||||
|
||||
def get_chip_description(self):
|
||||
chip_name = {
|
||||
0: "ESP32-C3",
|
||||
}.get(self.get_pkg_version(), "unknown ESP32-C3")
|
||||
major_rev = self.get_major_chip_version()
|
||||
minor_rev = self.get_minor_chip_version()
|
||||
return f"{chip_name} (revision v{major_rev}.{minor_rev})"
|
||||
|
||||
def get_chip_features(self):
|
||||
return ["WiFi", "BLE"]
|
||||
|
||||
def get_crystal_freq(self):
|
||||
# ESP32C3 XTAL is fixed to 40MHz
|
||||
return 40
|
||||
|
||||
def override_vddsdio(self, new_voltage):
|
||||
raise NotImplementedInROMError(
|
||||
"VDD_SDIO overrides are not supported for ESP32-C3"
|
||||
)
|
||||
|
||||
def read_mac(self):
|
||||
mac0 = self.read_reg(self.MAC_EFUSE_REG)
|
||||
mac1 = self.read_reg(self.MAC_EFUSE_REG + 4) # only bottom 16 bits are MAC
|
||||
bitstring = struct.pack(">II", mac1, mac0)[2:]
|
||||
return tuple(bitstring)
|
||||
|
||||
def get_flash_crypt_config(self):
|
||||
return None # doesn't exist on ESP32-C3
|
||||
|
||||
def get_secure_boot_enabled(self):
|
||||
return (
|
||||
self.read_reg(self.EFUSE_SECURE_BOOT_EN_REG)
|
||||
& self.EFUSE_SECURE_BOOT_EN_MASK
|
||||
)
|
||||
|
||||
def get_key_block_purpose(self, key_block):
|
||||
if key_block < 0 or key_block > 5:
|
||||
raise FatalError("Valid key block numbers must be in range 0-5")
|
||||
|
||||
reg, shift = [
|
||||
(self.EFUSE_PURPOSE_KEY0_REG, self.EFUSE_PURPOSE_KEY0_SHIFT),
|
||||
(self.EFUSE_PURPOSE_KEY1_REG, self.EFUSE_PURPOSE_KEY1_SHIFT),
|
||||
(self.EFUSE_PURPOSE_KEY2_REG, self.EFUSE_PURPOSE_KEY2_SHIFT),
|
||||
(self.EFUSE_PURPOSE_KEY3_REG, self.EFUSE_PURPOSE_KEY3_SHIFT),
|
||||
(self.EFUSE_PURPOSE_KEY4_REG, self.EFUSE_PURPOSE_KEY4_SHIFT),
|
||||
(self.EFUSE_PURPOSE_KEY5_REG, self.EFUSE_PURPOSE_KEY5_SHIFT),
|
||||
][key_block]
|
||||
return (self.read_reg(reg) >> shift) & 0xF
|
||||
|
||||
def is_flash_encryption_key_valid(self):
|
||||
# Need to see an AES-128 key
|
||||
purposes = [self.get_key_block_purpose(b) for b in range(6)]
|
||||
|
||||
return any(p == self.PURPOSE_VAL_XTS_AES128_KEY for p in purposes)
|
||||
|
||||
def change_baud(self, baud):
|
||||
ESPLoader.change_baud(self, baud)
|
||||
|
||||
def uses_usb_jtag_serial(self):
|
||||
"""
|
||||
Check the UARTDEV_BUF_NO register to see if USB-JTAG/Serial is being used
|
||||
"""
|
||||
if self.secure_download_mode:
|
||||
return False # Can't detect USB-JTAG/Serial in secure download mode
|
||||
return self.get_uart_no() == self.UARTDEV_BUF_NO_USB_JTAG_SERIAL
|
||||
|
||||
def disable_rtc_watchdog(self):
|
||||
# When USB-JTAG/Serial is used, the RTC watchdog is not reset
|
||||
# and can then reset the board during flashing. Disable it.
|
||||
if self.uses_usb_jtag_serial():
|
||||
self.write_reg(self.RTC_CNTL_WDTWPROTECT_REG, self.RTC_CNTL_WDT_WKEY)
|
||||
self.write_reg(self.RTC_CNTL_WDTCONFIG0_REG, 0)
|
||||
self.write_reg(self.RTC_CNTL_WDTWPROTECT_REG, 0)
|
||||
|
||||
def _post_connect(self):
|
||||
if not self.sync_stub_detected: # Don't run if stub is reused
|
||||
self.disable_rtc_watchdog()
|
||||
|
||||
|
||||
class ESP32C3StubLoader(ESP32C3ROM):
|
||||
"""Access class for ESP32C3 stub loader, runs on top of ROM.
|
||||
|
||||
(Basically the same as ESP32StubLoader, but different base class.
|
||||
Can possibly be made into a mixin.)
|
||||
"""
|
||||
|
||||
FLASH_WRITE_SIZE = 0x4000 # matches MAX_WRITE_BLOCK in stub_loader.c
|
||||
STATUS_BYTES_LENGTH = 2 # same as ESP8266, different to ESP32 ROM
|
||||
IS_STUB = True
|
||||
|
||||
def __init__(self, rom_loader):
|
||||
self.secure_download_mode = rom_loader.secure_download_mode
|
||||
self._port = rom_loader._port
|
||||
self._trace_enabled = rom_loader._trace_enabled
|
||||
self.cache = rom_loader.cache
|
||||
self.flush_input() # resets _slip_reader
|
||||
|
||||
|
||||
ESP32C3ROM.STUB_CLASS = ESP32C3StubLoader
|
||||
189
installer/bin/esptool/esptool/targets/esp32c6.py
Normal file
189
installer/bin/esptool/esptool/targets/esp32c6.py
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
# SPDX-FileCopyrightText: 2022 Fredrik Ahlberg, Angus Gratton,
|
||||
# Espressif Systems (Shanghai) CO LTD, other contributors as noted.
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import struct
|
||||
|
||||
from .esp32c3 import ESP32C3ROM
|
||||
from ..util import FatalError, NotImplementedInROMError
|
||||
|
||||
|
||||
class ESP32C6ROM(ESP32C3ROM):
|
||||
CHIP_NAME = "ESP32-C6"
|
||||
IMAGE_CHIP_ID = 13
|
||||
|
||||
FPGA_SLOW_BOOT = False
|
||||
|
||||
IROM_MAP_START = 0x42000000
|
||||
IROM_MAP_END = 0x42800000
|
||||
DROM_MAP_START = 0x42800000
|
||||
DROM_MAP_END = 0x43000000
|
||||
|
||||
BOOTLOADER_FLASH_OFFSET = 0x0
|
||||
|
||||
# Magic value for ESP32C6
|
||||
CHIP_DETECT_MAGIC_VALUE = [0x2CE0806F]
|
||||
|
||||
SPI_REG_BASE = 0x60003000
|
||||
SPI_USR_OFFS = 0x18
|
||||
SPI_USR1_OFFS = 0x1C
|
||||
SPI_USR2_OFFS = 0x20
|
||||
SPI_MOSI_DLEN_OFFS = 0x24
|
||||
SPI_MISO_DLEN_OFFS = 0x28
|
||||
SPI_W0_OFFS = 0x58
|
||||
|
||||
UART_DATE_REG_ADDR = 0x60000000 + 0x7C
|
||||
|
||||
EFUSE_BASE = 0x600B0800
|
||||
EFUSE_BLOCK1_ADDR = EFUSE_BASE + 0x044
|
||||
MAC_EFUSE_REG = EFUSE_BASE + 0x044
|
||||
|
||||
EFUSE_RD_REG_BASE = EFUSE_BASE + 0x030 # BLOCK0 read base address
|
||||
|
||||
EFUSE_PURPOSE_KEY0_REG = EFUSE_BASE + 0x34
|
||||
EFUSE_PURPOSE_KEY0_SHIFT = 24
|
||||
EFUSE_PURPOSE_KEY1_REG = EFUSE_BASE + 0x34
|
||||
EFUSE_PURPOSE_KEY1_SHIFT = 28
|
||||
EFUSE_PURPOSE_KEY2_REG = EFUSE_BASE + 0x38
|
||||
EFUSE_PURPOSE_KEY2_SHIFT = 0
|
||||
EFUSE_PURPOSE_KEY3_REG = EFUSE_BASE + 0x38
|
||||
EFUSE_PURPOSE_KEY3_SHIFT = 4
|
||||
EFUSE_PURPOSE_KEY4_REG = EFUSE_BASE + 0x38
|
||||
EFUSE_PURPOSE_KEY4_SHIFT = 8
|
||||
EFUSE_PURPOSE_KEY5_REG = EFUSE_BASE + 0x38
|
||||
EFUSE_PURPOSE_KEY5_SHIFT = 12
|
||||
|
||||
EFUSE_DIS_DOWNLOAD_MANUAL_ENCRYPT_REG = EFUSE_RD_REG_BASE
|
||||
EFUSE_DIS_DOWNLOAD_MANUAL_ENCRYPT = 1 << 20
|
||||
|
||||
EFUSE_SPI_BOOT_CRYPT_CNT_REG = EFUSE_BASE + 0x034
|
||||
EFUSE_SPI_BOOT_CRYPT_CNT_MASK = 0x7 << 18
|
||||
|
||||
EFUSE_SECURE_BOOT_EN_REG = EFUSE_BASE + 0x038
|
||||
EFUSE_SECURE_BOOT_EN_MASK = 1 << 20
|
||||
|
||||
PURPOSE_VAL_XTS_AES128_KEY = 4
|
||||
|
||||
SUPPORTS_ENCRYPTED_FLASH = True
|
||||
|
||||
FLASH_ENCRYPTED_WRITE_ALIGN = 16
|
||||
|
||||
UARTDEV_BUF_NO = 0x4087F580 # Variable in ROM .bss which indicates the port in use
|
||||
UARTDEV_BUF_NO_USB_JTAG_SERIAL = 3 # The above var when USB-JTAG/Serial is used
|
||||
|
||||
DR_REG_LP_WDT_BASE = 0x600B1C00
|
||||
RTC_CNTL_WDTCONFIG0_REG = DR_REG_LP_WDT_BASE + 0x0 # LP_WDT_RWDT_CONFIG0_REG
|
||||
RTC_CNTL_WDTWPROTECT_REG = DR_REG_LP_WDT_BASE + 0x0018 # LP_WDT_RWDT_WPROTECT_REG
|
||||
|
||||
FLASH_FREQUENCY = {
|
||||
"80m": 0x0, # workaround for wrong mspi HS div value in ROM
|
||||
"40m": 0x0,
|
||||
"20m": 0x2,
|
||||
}
|
||||
|
||||
MEMORY_MAP = [
|
||||
[0x00000000, 0x00010000, "PADDING"],
|
||||
[0x42800000, 0x43000000, "DROM"],
|
||||
[0x40800000, 0x40880000, "DRAM"],
|
||||
[0x40800000, 0x40880000, "BYTE_ACCESSIBLE"],
|
||||
[0x4004AC00, 0x40050000, "DROM_MASK"],
|
||||
[0x40000000, 0x4004AC00, "IROM_MASK"],
|
||||
[0x42000000, 0x42800000, "IROM"],
|
||||
[0x40800000, 0x40880000, "IRAM"],
|
||||
[0x50000000, 0x50004000, "RTC_IRAM"],
|
||||
[0x50000000, 0x50004000, "RTC_DRAM"],
|
||||
[0x600FE000, 0x60100000, "MEM_INTERNAL2"],
|
||||
]
|
||||
|
||||
def get_pkg_version(self):
|
||||
num_word = 3
|
||||
return (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * num_word)) >> 21) & 0x07
|
||||
|
||||
def get_minor_chip_version(self):
|
||||
hi_num_word = 5
|
||||
hi = (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * hi_num_word)) >> 23) & 0x01
|
||||
low_num_word = 3
|
||||
low = (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * low_num_word)) >> 18) & 0x07
|
||||
return (hi << 3) + low
|
||||
|
||||
def get_major_chip_version(self):
|
||||
num_word = 5
|
||||
return (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * num_word)) >> 24) & 0x03
|
||||
|
||||
def get_chip_description(self):
|
||||
chip_name = {
|
||||
0: "ESP32-C6",
|
||||
}.get(self.get_pkg_version(), "unknown ESP32-C6")
|
||||
major_rev = self.get_major_chip_version()
|
||||
minor_rev = self.get_minor_chip_version()
|
||||
return f"{chip_name} (revision v{major_rev}.{minor_rev})"
|
||||
|
||||
def get_chip_features(self):
|
||||
return ["WiFi 6", "BT 5", "IEEE802.15.4"]
|
||||
|
||||
def get_crystal_freq(self):
|
||||
# ESP32C6 XTAL is fixed to 40MHz
|
||||
return 40
|
||||
|
||||
def override_vddsdio(self, new_voltage):
|
||||
raise NotImplementedInROMError(
|
||||
"VDD_SDIO overrides are not supported for ESP32-C6"
|
||||
)
|
||||
|
||||
def read_mac(self):
|
||||
mac0 = self.read_reg(self.MAC_EFUSE_REG)
|
||||
mac1 = self.read_reg(self.MAC_EFUSE_REG + 4) # only bottom 16 bits are MAC
|
||||
bitstring = struct.pack(">II", mac1, mac0)[2:]
|
||||
return tuple(bitstring)
|
||||
|
||||
def get_flash_crypt_config(self):
|
||||
return None # doesn't exist on ESP32-C6
|
||||
|
||||
def get_secure_boot_enabled(self):
|
||||
return (
|
||||
self.read_reg(self.EFUSE_SECURE_BOOT_EN_REG)
|
||||
& self.EFUSE_SECURE_BOOT_EN_MASK
|
||||
)
|
||||
|
||||
def get_key_block_purpose(self, key_block):
|
||||
if key_block < 0 or key_block > 5:
|
||||
raise FatalError("Valid key block numbers must be in range 0-5")
|
||||
|
||||
reg, shift = [
|
||||
(self.EFUSE_PURPOSE_KEY0_REG, self.EFUSE_PURPOSE_KEY0_SHIFT),
|
||||
(self.EFUSE_PURPOSE_KEY1_REG, self.EFUSE_PURPOSE_KEY1_SHIFT),
|
||||
(self.EFUSE_PURPOSE_KEY2_REG, self.EFUSE_PURPOSE_KEY2_SHIFT),
|
||||
(self.EFUSE_PURPOSE_KEY3_REG, self.EFUSE_PURPOSE_KEY3_SHIFT),
|
||||
(self.EFUSE_PURPOSE_KEY4_REG, self.EFUSE_PURPOSE_KEY4_SHIFT),
|
||||
(self.EFUSE_PURPOSE_KEY5_REG, self.EFUSE_PURPOSE_KEY5_SHIFT),
|
||||
][key_block]
|
||||
return (self.read_reg(reg) >> shift) & 0xF
|
||||
|
||||
def is_flash_encryption_key_valid(self):
|
||||
# Need to see an AES-128 key
|
||||
purposes = [self.get_key_block_purpose(b) for b in range(6)]
|
||||
|
||||
return any(p == self.PURPOSE_VAL_XTS_AES128_KEY for p in purposes)
|
||||
|
||||
|
||||
class ESP32C6StubLoader(ESP32C6ROM):
|
||||
"""Access class for ESP32C6 stub loader, runs on top of ROM.
|
||||
|
||||
(Basically the same as ESP32StubLoader, but different base class.
|
||||
Can possibly be made into a mixin.)
|
||||
"""
|
||||
|
||||
FLASH_WRITE_SIZE = 0x4000 # matches MAX_WRITE_BLOCK in stub_loader.c
|
||||
STATUS_BYTES_LENGTH = 2 # same as ESP8266, different to ESP32 ROM
|
||||
IS_STUB = True
|
||||
|
||||
def __init__(self, rom_loader):
|
||||
self.secure_download_mode = rom_loader.secure_download_mode
|
||||
self._port = rom_loader._port
|
||||
self._trace_enabled = rom_loader._trace_enabled
|
||||
self.cache = rom_loader.cache
|
||||
self.flush_input() # resets _slip_reader
|
||||
|
||||
|
||||
ESP32C6ROM.STUB_CLASS = ESP32C6StubLoader
|
||||
26
installer/bin/esptool/esptool/targets/esp32c6beta.py
Normal file
26
installer/bin/esptool/esptool/targets/esp32c6beta.py
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
# SPDX-FileCopyrightText: 2014-2022 Fredrik Ahlberg, Angus Gratton,
|
||||
# Espressif Systems (Shanghai) CO LTD, other contributors as noted.
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
from .esp32c3 import ESP32C3ROM
|
||||
|
||||
|
||||
class ESP32C6BETAROM(ESP32C3ROM):
|
||||
CHIP_NAME = "ESP32-C6(beta)"
|
||||
IMAGE_CHIP_ID = 7
|
||||
|
||||
CHIP_DETECT_MAGIC_VALUE = [0x0DA1806F]
|
||||
|
||||
UART_DATE_REG_ADDR = 0x00000500
|
||||
|
||||
def get_chip_description(self):
|
||||
chip_name = {
|
||||
0: "ESP32-C6",
|
||||
}.get(self.get_pkg_version(), "unknown ESP32-C6")
|
||||
major_rev = self.get_major_chip_version()
|
||||
minor_rev = self.get_minor_chip_version()
|
||||
return f"{chip_name} (revision v{major_rev}.{minor_rev})"
|
||||
|
||||
def _post_connect(self):
|
||||
pass
|
||||
65
installer/bin/esptool/esptool/targets/esp32h2.py
Normal file
65
installer/bin/esptool/esptool/targets/esp32h2.py
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
# SPDX-FileCopyrightText: 2022 Fredrik Ahlberg, Angus Gratton,
|
||||
# Espressif Systems (Shanghai) CO LTD, other contributors as noted.
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
from .esp32c6 import ESP32C6ROM
|
||||
|
||||
|
||||
class ESP32H2ROM(ESP32C6ROM):
|
||||
CHIP_NAME = "ESP32-H2"
|
||||
IMAGE_CHIP_ID = 16
|
||||
|
||||
# Magic value for ESP32H2
|
||||
CHIP_DETECT_MAGIC_VALUE = [0xD7B73E80]
|
||||
|
||||
FLASH_FREQUENCY = {
|
||||
"48m": 0xF,
|
||||
"24m": 0x0,
|
||||
"16m": 0x1,
|
||||
"12m": 0x2,
|
||||
}
|
||||
|
||||
def get_pkg_version(self):
|
||||
num_word = 3
|
||||
block1_addr = self.EFUSE_BASE + 0x044
|
||||
word3 = self.read_reg(block1_addr + (4 * num_word))
|
||||
pkg_version = (word3 >> 21) & 0x0F
|
||||
return pkg_version
|
||||
|
||||
def get_chip_description(self):
|
||||
chip_name = {
|
||||
0: "ESP32-H2",
|
||||
}.get(self.get_pkg_version(), "unknown ESP32-H2")
|
||||
major_rev = self.get_major_chip_version()
|
||||
minor_rev = self.get_minor_chip_version()
|
||||
return f"{chip_name} (revision v{major_rev}.{minor_rev})"
|
||||
|
||||
def get_chip_features(self):
|
||||
return ["BLE"]
|
||||
|
||||
def get_crystal_freq(self):
|
||||
# ESP32H2 XTAL is fixed to 32MHz
|
||||
return 32
|
||||
|
||||
|
||||
class ESP32H2StubLoader(ESP32H2ROM):
|
||||
"""Access class for ESP32H2 stub loader, runs on top of ROM.
|
||||
|
||||
(Basically the same as ESP32StubLoader, but different base class.
|
||||
Can possibly be made into a mixin.)
|
||||
"""
|
||||
|
||||
FLASH_WRITE_SIZE = 0x4000 # matches MAX_WRITE_BLOCK in stub_loader.c
|
||||
STATUS_BYTES_LENGTH = 2 # same as ESP8266, different to ESP32 ROM
|
||||
IS_STUB = True
|
||||
|
||||
def __init__(self, rom_loader):
|
||||
self.secure_download_mode = rom_loader.secure_download_mode
|
||||
self._port = rom_loader._port
|
||||
self._trace_enabled = rom_loader._trace_enabled
|
||||
self.cache = rom_loader.cache
|
||||
self.flush_input() # resets _slip_reader
|
||||
|
||||
|
||||
ESP32H2ROM.STUB_CLASS = ESP32H2StubLoader
|
||||
164
installer/bin/esptool/esptool/targets/esp32h2beta1.py
Normal file
164
installer/bin/esptool/esptool/targets/esp32h2beta1.py
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
# SPDX-FileCopyrightText: 2014-2022 Fredrik Ahlberg, Angus Gratton,
|
||||
# Espressif Systems (Shanghai) CO LTD, other contributors as noted.
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import struct
|
||||
|
||||
from .esp32c3 import ESP32C3ROM
|
||||
from ..util import FatalError, NotImplementedInROMError
|
||||
|
||||
|
||||
class ESP32H2BETA1ROM(ESP32C3ROM):
|
||||
CHIP_NAME = "ESP32-H2(beta1)"
|
||||
IMAGE_CHIP_ID = 10
|
||||
|
||||
IROM_MAP_START = 0x42000000
|
||||
IROM_MAP_END = 0x42800000
|
||||
DROM_MAP_START = 0x3C000000
|
||||
DROM_MAP_END = 0x3C800000
|
||||
|
||||
SPI_REG_BASE = 0x60002000
|
||||
SPI_USR_OFFS = 0x18
|
||||
SPI_USR1_OFFS = 0x1C
|
||||
SPI_USR2_OFFS = 0x20
|
||||
SPI_MOSI_DLEN_OFFS = 0x24
|
||||
SPI_MISO_DLEN_OFFS = 0x28
|
||||
SPI_W0_OFFS = 0x58
|
||||
|
||||
BOOTLOADER_FLASH_OFFSET = 0x0
|
||||
|
||||
CHIP_DETECT_MAGIC_VALUE = [0xCA26CC22]
|
||||
|
||||
UART_DATE_REG_ADDR = 0x60000000 + 0x7C
|
||||
|
||||
EFUSE_BASE = 0x6001A000
|
||||
EFUSE_BLOCK1_ADDR = EFUSE_BASE + 0x044
|
||||
MAC_EFUSE_REG = EFUSE_BASE + 0x044
|
||||
|
||||
EFUSE_RD_REG_BASE = EFUSE_BASE + 0x030 # BLOCK0 read base address
|
||||
|
||||
EFUSE_PURPOSE_KEY0_REG = EFUSE_BASE + 0x34
|
||||
EFUSE_PURPOSE_KEY0_SHIFT = 24
|
||||
EFUSE_PURPOSE_KEY1_REG = EFUSE_BASE + 0x34
|
||||
EFUSE_PURPOSE_KEY1_SHIFT = 28
|
||||
EFUSE_PURPOSE_KEY2_REG = EFUSE_BASE + 0x38
|
||||
EFUSE_PURPOSE_KEY2_SHIFT = 0
|
||||
EFUSE_PURPOSE_KEY3_REG = EFUSE_BASE + 0x38
|
||||
EFUSE_PURPOSE_KEY3_SHIFT = 4
|
||||
EFUSE_PURPOSE_KEY4_REG = EFUSE_BASE + 0x38
|
||||
EFUSE_PURPOSE_KEY4_SHIFT = 8
|
||||
EFUSE_PURPOSE_KEY5_REG = EFUSE_BASE + 0x38
|
||||
EFUSE_PURPOSE_KEY5_SHIFT = 12
|
||||
|
||||
EFUSE_DIS_DOWNLOAD_MANUAL_ENCRYPT_REG = EFUSE_RD_REG_BASE
|
||||
EFUSE_DIS_DOWNLOAD_MANUAL_ENCRYPT = 1 << 20
|
||||
|
||||
EFUSE_SPI_BOOT_CRYPT_CNT_REG = EFUSE_BASE + 0x034
|
||||
EFUSE_SPI_BOOT_CRYPT_CNT_MASK = 0x7 << 18
|
||||
|
||||
EFUSE_SECURE_BOOT_EN_REG = EFUSE_BASE + 0x038
|
||||
EFUSE_SECURE_BOOT_EN_MASK = 1 << 20
|
||||
|
||||
PURPOSE_VAL_XTS_AES128_KEY = 4
|
||||
|
||||
SUPPORTS_ENCRYPTED_FLASH = True
|
||||
|
||||
FLASH_ENCRYPTED_WRITE_ALIGN = 16
|
||||
|
||||
MEMORY_MAP = []
|
||||
|
||||
FLASH_FREQUENCY = {
|
||||
"48m": 0xF,
|
||||
"24m": 0x0,
|
||||
"16m": 0x1,
|
||||
"12m": 0x2,
|
||||
}
|
||||
|
||||
def get_pkg_version(self):
|
||||
num_word = 3
|
||||
return (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * num_word)) >> 21) & 0x0F
|
||||
|
||||
def get_minor_chip_version(self):
|
||||
hi_num_word = 5
|
||||
hi = (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * hi_num_word)) >> 23) & 0x01
|
||||
low_num_word = 3
|
||||
low = (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * low_num_word)) >> 18) & 0x07
|
||||
return (hi << 3) + low
|
||||
|
||||
def get_major_chip_version(self):
|
||||
num_word = 5
|
||||
return (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * num_word)) >> 24) & 0x03
|
||||
|
||||
def get_chip_description(self):
|
||||
chip_name = {
|
||||
0: "ESP32-H2",
|
||||
}.get(self.get_pkg_version(), "unknown ESP32-H2")
|
||||
major_rev = self.get_major_chip_version()
|
||||
minor_rev = self.get_minor_chip_version()
|
||||
return f"{chip_name} (revision v{major_rev}.{minor_rev})"
|
||||
|
||||
def get_chip_features(self):
|
||||
return ["BLE", "IEEE802.15.4"]
|
||||
|
||||
def get_crystal_freq(self):
|
||||
return 32
|
||||
|
||||
def override_vddsdio(self, new_voltage):
|
||||
raise NotImplementedInROMError(
|
||||
"VDD_SDIO overrides are not supported for ESP32-H2"
|
||||
)
|
||||
|
||||
def read_mac(self):
|
||||
mac0 = self.read_reg(self.MAC_EFUSE_REG)
|
||||
mac1 = self.read_reg(self.MAC_EFUSE_REG + 4) # only bottom 16 bits are MAC
|
||||
bitstring = struct.pack(">II", mac1, mac0)[2:]
|
||||
return tuple(bitstring)
|
||||
|
||||
def get_flash_crypt_config(self):
|
||||
return None # doesn't exist on ESP32-H2
|
||||
|
||||
def get_key_block_purpose(self, key_block):
|
||||
if key_block < 0 or key_block > 5:
|
||||
raise FatalError("Valid key block numbers must be in range 0-5")
|
||||
|
||||
reg, shift = [
|
||||
(self.EFUSE_PURPOSE_KEY0_REG, self.EFUSE_PURPOSE_KEY0_SHIFT),
|
||||
(self.EFUSE_PURPOSE_KEY1_REG, self.EFUSE_PURPOSE_KEY1_SHIFT),
|
||||
(self.EFUSE_PURPOSE_KEY2_REG, self.EFUSE_PURPOSE_KEY2_SHIFT),
|
||||
(self.EFUSE_PURPOSE_KEY3_REG, self.EFUSE_PURPOSE_KEY3_SHIFT),
|
||||
(self.EFUSE_PURPOSE_KEY4_REG, self.EFUSE_PURPOSE_KEY4_SHIFT),
|
||||
(self.EFUSE_PURPOSE_KEY5_REG, self.EFUSE_PURPOSE_KEY5_SHIFT),
|
||||
][key_block]
|
||||
return (self.read_reg(reg) >> shift) & 0xF
|
||||
|
||||
def is_flash_encryption_key_valid(self):
|
||||
# Need to see an AES-128 key
|
||||
purposes = [self.get_key_block_purpose(b) for b in range(6)]
|
||||
|
||||
return any(p == self.PURPOSE_VAL_XTS_AES128_KEY for p in purposes)
|
||||
|
||||
def _post_connect(self):
|
||||
pass
|
||||
|
||||
|
||||
class ESP32H2BETA1StubLoader(ESP32H2BETA1ROM):
|
||||
"""Access class for ESP32H2BETA1 stub loader, runs on top of ROM.
|
||||
|
||||
(Basically the same as ESP32StubLoader, but different base class.
|
||||
Can possibly be made into a mixin.)
|
||||
"""
|
||||
|
||||
FLASH_WRITE_SIZE = 0x4000 # matches MAX_WRITE_BLOCK in stub_loader.c
|
||||
STATUS_BYTES_LENGTH = 2 # same as ESP8266, different to ESP32 ROM
|
||||
IS_STUB = True
|
||||
|
||||
def __init__(self, rom_loader):
|
||||
self.secure_download_mode = rom_loader.secure_download_mode
|
||||
self._port = rom_loader._port
|
||||
self._trace_enabled = rom_loader._trace_enabled
|
||||
self.cache = rom_loader.cache
|
||||
self.flush_input() # resets _slip_reader
|
||||
|
||||
|
||||
ESP32H2BETA1ROM.STUB_CLASS = ESP32H2BETA1StubLoader
|
||||
43
installer/bin/esptool/esptool/targets/esp32h2beta2.py
Normal file
43
installer/bin/esptool/esptool/targets/esp32h2beta2.py
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
# SPDX-FileCopyrightText: 2014-2022 Fredrik Ahlberg, Angus Gratton,
|
||||
# Espressif Systems (Shanghai) CO LTD, other contributors as noted.
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
from .esp32h2beta1 import ESP32H2BETA1ROM
|
||||
|
||||
|
||||
class ESP32H2BETA2ROM(ESP32H2BETA1ROM):
|
||||
CHIP_NAME = "ESP32-H2(beta2)"
|
||||
IMAGE_CHIP_ID = 14
|
||||
|
||||
CHIP_DETECT_MAGIC_VALUE = [0x6881B06F]
|
||||
|
||||
def get_chip_description(self):
|
||||
chip_name = {
|
||||
1: "ESP32-H2(beta2)",
|
||||
}.get(self.get_pkg_version(), "unknown ESP32-H2")
|
||||
major_rev = self.get_major_chip_version()
|
||||
minor_rev = self.get_minor_chip_version()
|
||||
return f"{chip_name} (revision v{major_rev}.{minor_rev})"
|
||||
|
||||
|
||||
class ESP32H2BETA2StubLoader(ESP32H2BETA2ROM):
|
||||
"""Access class for ESP32H2BETA2 stub loader, runs on top of ROM.
|
||||
|
||||
(Basically the same as ESP32StubLoader, but different base class.
|
||||
Can possibly be made into a mixin.)
|
||||
"""
|
||||
|
||||
FLASH_WRITE_SIZE = 0x4000 # matches MAX_WRITE_BLOCK in stub_loader.c
|
||||
STATUS_BYTES_LENGTH = 2 # same as ESP8266, different to ESP32 ROM
|
||||
IS_STUB = True
|
||||
|
||||
def __init__(self, rom_loader):
|
||||
self.secure_download_mode = rom_loader.secure_download_mode
|
||||
self._port = rom_loader._port
|
||||
self._trace_enabled = rom_loader._trace_enabled
|
||||
self.cache = rom_loader.cache
|
||||
self.flush_input() # resets _slip_reader
|
||||
|
||||
|
||||
ESP32H2BETA2ROM.STUB_CLASS = ESP32H2BETA2StubLoader
|
||||
305
installer/bin/esptool/esptool/targets/esp32s2.py
Normal file
305
installer/bin/esptool/esptool/targets/esp32s2.py
Normal file
|
|
@ -0,0 +1,305 @@
|
|||
# SPDX-FileCopyrightText: 2014-2023 Fredrik Ahlberg, Angus Gratton,
|
||||
# Espressif Systems (Shanghai) CO LTD, other contributors as noted.
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import os
|
||||
import struct
|
||||
|
||||
from .esp32 import ESP32ROM
|
||||
from ..loader import ESPLoader
|
||||
from ..reset import HardReset
|
||||
from ..util import FatalError, NotImplementedInROMError
|
||||
|
||||
|
||||
class ESP32S2ROM(ESP32ROM):
|
||||
CHIP_NAME = "ESP32-S2"
|
||||
IMAGE_CHIP_ID = 2
|
||||
|
||||
FPGA_SLOW_BOOT = False
|
||||
|
||||
IROM_MAP_START = 0x40080000
|
||||
IROM_MAP_END = 0x40B80000
|
||||
DROM_MAP_START = 0x3F000000
|
||||
DROM_MAP_END = 0x3F3F0000
|
||||
|
||||
CHIP_DETECT_MAGIC_VALUE = [0x000007C6]
|
||||
|
||||
SPI_REG_BASE = 0x3F402000
|
||||
SPI_USR_OFFS = 0x18
|
||||
SPI_USR1_OFFS = 0x1C
|
||||
SPI_USR2_OFFS = 0x20
|
||||
SPI_MOSI_DLEN_OFFS = 0x24
|
||||
SPI_MISO_DLEN_OFFS = 0x28
|
||||
SPI_W0_OFFS = 0x58
|
||||
|
||||
MAC_EFUSE_REG = 0x3F41A044 # ESP32-S2 has special block for MAC efuses
|
||||
|
||||
UART_CLKDIV_REG = 0x3F400014
|
||||
|
||||
SUPPORTS_ENCRYPTED_FLASH = True
|
||||
|
||||
FLASH_ENCRYPTED_WRITE_ALIGN = 16
|
||||
|
||||
# todo: use espefuse APIs to get this info
|
||||
EFUSE_BASE = 0x3F41A000
|
||||
EFUSE_RD_REG_BASE = EFUSE_BASE + 0x030 # BLOCK0 read base address
|
||||
EFUSE_BLOCK1_ADDR = EFUSE_BASE + 0x044
|
||||
EFUSE_BLOCK2_ADDR = EFUSE_BASE + 0x05C
|
||||
|
||||
EFUSE_PURPOSE_KEY0_REG = EFUSE_BASE + 0x34
|
||||
EFUSE_PURPOSE_KEY0_SHIFT = 24
|
||||
EFUSE_PURPOSE_KEY1_REG = EFUSE_BASE + 0x34
|
||||
EFUSE_PURPOSE_KEY1_SHIFT = 28
|
||||
EFUSE_PURPOSE_KEY2_REG = EFUSE_BASE + 0x38
|
||||
EFUSE_PURPOSE_KEY2_SHIFT = 0
|
||||
EFUSE_PURPOSE_KEY3_REG = EFUSE_BASE + 0x38
|
||||
EFUSE_PURPOSE_KEY3_SHIFT = 4
|
||||
EFUSE_PURPOSE_KEY4_REG = EFUSE_BASE + 0x38
|
||||
EFUSE_PURPOSE_KEY4_SHIFT = 8
|
||||
EFUSE_PURPOSE_KEY5_REG = EFUSE_BASE + 0x38
|
||||
EFUSE_PURPOSE_KEY5_SHIFT = 12
|
||||
|
||||
EFUSE_DIS_DOWNLOAD_MANUAL_ENCRYPT_REG = EFUSE_RD_REG_BASE
|
||||
EFUSE_DIS_DOWNLOAD_MANUAL_ENCRYPT = 1 << 19
|
||||
|
||||
EFUSE_SPI_BOOT_CRYPT_CNT_REG = EFUSE_BASE + 0x034
|
||||
EFUSE_SPI_BOOT_CRYPT_CNT_MASK = 0x7 << 18
|
||||
|
||||
EFUSE_SECURE_BOOT_EN_REG = EFUSE_BASE + 0x038
|
||||
EFUSE_SECURE_BOOT_EN_MASK = 1 << 20
|
||||
|
||||
EFUSE_RD_REPEAT_DATA3_REG = EFUSE_BASE + 0x3C
|
||||
EFUSE_RD_REPEAT_DATA3_REG_FLASH_TYPE_MASK = 1 << 9
|
||||
|
||||
PURPOSE_VAL_XTS_AES256_KEY_1 = 2
|
||||
PURPOSE_VAL_XTS_AES256_KEY_2 = 3
|
||||
PURPOSE_VAL_XTS_AES128_KEY = 4
|
||||
|
||||
UARTDEV_BUF_NO = 0x3FFFFD14 # Variable in ROM .bss which indicates the port in use
|
||||
UARTDEV_BUF_NO_USB_OTG = 2 # Value of the above indicating that USB-OTG is in use
|
||||
|
||||
USB_RAM_BLOCK = 0x800 # Max block size USB-OTG is used
|
||||
|
||||
GPIO_STRAP_REG = 0x3F404038
|
||||
GPIO_STRAP_SPI_BOOT_MASK = 0x8 # Not download mode
|
||||
RTC_CNTL_OPTION1_REG = 0x3F408128
|
||||
RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK = 0x1 # Is download mode forced over USB?
|
||||
|
||||
MEMORY_MAP = [
|
||||
[0x00000000, 0x00010000, "PADDING"],
|
||||
[0x3F000000, 0x3FF80000, "DROM"],
|
||||
[0x3F500000, 0x3FF80000, "EXTRAM_DATA"],
|
||||
[0x3FF9E000, 0x3FFA0000, "RTC_DRAM"],
|
||||
[0x3FF9E000, 0x40000000, "BYTE_ACCESSIBLE"],
|
||||
[0x3FF9E000, 0x40072000, "MEM_INTERNAL"],
|
||||
[0x3FFB0000, 0x40000000, "DRAM"],
|
||||
[0x40000000, 0x4001A100, "IROM_MASK"],
|
||||
[0x40020000, 0x40070000, "IRAM"],
|
||||
[0x40070000, 0x40072000, "RTC_IRAM"],
|
||||
[0x40080000, 0x40800000, "IROM"],
|
||||
[0x50000000, 0x50002000, "RTC_DATA"],
|
||||
]
|
||||
|
||||
def get_pkg_version(self):
|
||||
num_word = 4
|
||||
return (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * num_word)) >> 0) & 0x0F
|
||||
|
||||
def get_minor_chip_version(self):
|
||||
hi_num_word = 3
|
||||
hi = (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * hi_num_word)) >> 20) & 0x01
|
||||
low_num_word = 4
|
||||
low = (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * low_num_word)) >> 4) & 0x07
|
||||
return (hi << 3) + low
|
||||
|
||||
def get_major_chip_version(self):
|
||||
num_word = 3
|
||||
return (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * num_word)) >> 18) & 0x03
|
||||
|
||||
def get_flash_version(self):
|
||||
num_word = 3
|
||||
return (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * num_word)) >> 21) & 0x0F
|
||||
|
||||
def get_psram_version(self):
|
||||
num_word = 3
|
||||
return (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * num_word)) >> 28) & 0x0F
|
||||
|
||||
def get_block2_version(self):
|
||||
# BLK_VERSION_MINOR
|
||||
num_word = 4
|
||||
return (self.read_reg(self.EFUSE_BLOCK2_ADDR + (4 * num_word)) >> 4) & 0x07
|
||||
|
||||
def get_chip_description(self):
|
||||
chip_name = {
|
||||
0: "ESP32-S2",
|
||||
1: "ESP32-S2FH2",
|
||||
2: "ESP32-S2FH4",
|
||||
102: "ESP32-S2FNR2",
|
||||
100: "ESP32-S2R2",
|
||||
}.get(
|
||||
self.get_flash_version() + self.get_psram_version() * 100,
|
||||
"unknown ESP32-S2",
|
||||
)
|
||||
major_rev = self.get_major_chip_version()
|
||||
minor_rev = self.get_minor_chip_version()
|
||||
return f"{chip_name} (revision v{major_rev}.{minor_rev})"
|
||||
|
||||
def get_chip_features(self):
|
||||
features = ["WiFi"]
|
||||
|
||||
if self.secure_download_mode:
|
||||
features += ["Secure Download Mode Enabled"]
|
||||
|
||||
flash_version = {
|
||||
0: "No Embedded Flash",
|
||||
1: "Embedded Flash 2MB",
|
||||
2: "Embedded Flash 4MB",
|
||||
}.get(self.get_flash_version(), "Unknown Embedded Flash")
|
||||
features += [flash_version]
|
||||
|
||||
psram_version = {
|
||||
0: "No Embedded PSRAM",
|
||||
1: "Embedded PSRAM 2MB",
|
||||
2: "Embedded PSRAM 4MB",
|
||||
}.get(self.get_psram_version(), "Unknown Embedded PSRAM")
|
||||
features += [psram_version]
|
||||
|
||||
block2_version = {
|
||||
0: "No calibration in BLK2 of efuse",
|
||||
1: "ADC and temperature sensor calibration in BLK2 of efuse V1",
|
||||
2: "ADC and temperature sensor calibration in BLK2 of efuse V2",
|
||||
}.get(self.get_block2_version(), "Unknown Calibration in BLK2")
|
||||
features += [block2_version]
|
||||
|
||||
return features
|
||||
|
||||
def get_crystal_freq(self):
|
||||
# ESP32-S2 XTAL is fixed to 40MHz
|
||||
return 40
|
||||
|
||||
def override_vddsdio(self, new_voltage):
|
||||
raise NotImplementedInROMError(
|
||||
"VDD_SDIO overrides are not supported for ESP32-S2"
|
||||
)
|
||||
|
||||
def read_mac(self):
|
||||
mac0 = self.read_reg(self.MAC_EFUSE_REG)
|
||||
mac1 = self.read_reg(self.MAC_EFUSE_REG + 4) # only bottom 16 bits are MAC
|
||||
bitstring = struct.pack(">II", mac1, mac0)[2:]
|
||||
return tuple(bitstring)
|
||||
|
||||
def flash_type(self):
|
||||
return (
|
||||
1
|
||||
if self.read_reg(self.EFUSE_RD_REPEAT_DATA3_REG)
|
||||
& self.EFUSE_RD_REPEAT_DATA3_REG_FLASH_TYPE_MASK
|
||||
else 0
|
||||
)
|
||||
|
||||
def get_flash_crypt_config(self):
|
||||
return None # doesn't exist on ESP32-S2
|
||||
|
||||
def get_secure_boot_enabled(self):
|
||||
return (
|
||||
self.read_reg(self.EFUSE_SECURE_BOOT_EN_REG)
|
||||
& self.EFUSE_SECURE_BOOT_EN_MASK
|
||||
)
|
||||
|
||||
def get_key_block_purpose(self, key_block):
|
||||
if key_block < 0 or key_block > 5:
|
||||
raise FatalError("Valid key block numbers must be in range 0-5")
|
||||
|
||||
reg, shift = [
|
||||
(self.EFUSE_PURPOSE_KEY0_REG, self.EFUSE_PURPOSE_KEY0_SHIFT),
|
||||
(self.EFUSE_PURPOSE_KEY1_REG, self.EFUSE_PURPOSE_KEY1_SHIFT),
|
||||
(self.EFUSE_PURPOSE_KEY2_REG, self.EFUSE_PURPOSE_KEY2_SHIFT),
|
||||
(self.EFUSE_PURPOSE_KEY3_REG, self.EFUSE_PURPOSE_KEY3_SHIFT),
|
||||
(self.EFUSE_PURPOSE_KEY4_REG, self.EFUSE_PURPOSE_KEY4_SHIFT),
|
||||
(self.EFUSE_PURPOSE_KEY5_REG, self.EFUSE_PURPOSE_KEY5_SHIFT),
|
||||
][key_block]
|
||||
return (self.read_reg(reg) >> shift) & 0xF
|
||||
|
||||
def is_flash_encryption_key_valid(self):
|
||||
# Need to see either an AES-128 key or two AES-256 keys
|
||||
purposes = [self.get_key_block_purpose(b) for b in range(6)]
|
||||
|
||||
if any(p == self.PURPOSE_VAL_XTS_AES128_KEY for p in purposes):
|
||||
return True
|
||||
|
||||
return any(p == self.PURPOSE_VAL_XTS_AES256_KEY_1 for p in purposes) and any(
|
||||
p == self.PURPOSE_VAL_XTS_AES256_KEY_2 for p in purposes
|
||||
)
|
||||
|
||||
def uses_usb_otg(self):
|
||||
"""
|
||||
Check the UARTDEV_BUF_NO register to see if USB-OTG console is being used
|
||||
"""
|
||||
if self.secure_download_mode:
|
||||
return False # can't detect native USB in secure download mode
|
||||
return self.get_uart_no() == self.UARTDEV_BUF_NO_USB_OTG
|
||||
|
||||
def _post_connect(self):
|
||||
if self.uses_usb_otg():
|
||||
self.ESP_RAM_BLOCK = self.USB_RAM_BLOCK
|
||||
|
||||
def _check_if_can_reset(self):
|
||||
"""
|
||||
Check the strapping register to see if we can reset out of download mode.
|
||||
"""
|
||||
if os.getenv("ESPTOOL_TESTING") is not None:
|
||||
print("ESPTOOL_TESTING is set, ignoring strapping mode check")
|
||||
# Esptool tests over USB-OTG run with GPIO0 strapped low,
|
||||
# don't complain in this case.
|
||||
return
|
||||
strap_reg = self.read_reg(self.GPIO_STRAP_REG)
|
||||
force_dl_reg = self.read_reg(self.RTC_CNTL_OPTION1_REG)
|
||||
if (
|
||||
strap_reg & self.GPIO_STRAP_SPI_BOOT_MASK == 0
|
||||
and force_dl_reg & self.RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK == 0
|
||||
):
|
||||
print(
|
||||
"WARNING: {} chip was placed into download mode using GPIO0.\n"
|
||||
"esptool.py can not exit the download mode over USB. "
|
||||
"To run the app, reset the chip manually.\n"
|
||||
"To suppress this note, set --after option to 'no_reset'.".format(
|
||||
self.get_chip_description()
|
||||
)
|
||||
)
|
||||
raise SystemExit(1)
|
||||
|
||||
def hard_reset(self):
|
||||
uses_usb_otg = self.uses_usb_otg()
|
||||
if uses_usb_otg:
|
||||
self._check_if_can_reset()
|
||||
|
||||
print("Hard resetting via RTS pin...")
|
||||
HardReset(self._port, uses_usb_otg)()
|
||||
|
||||
def change_baud(self, baud):
|
||||
ESPLoader.change_baud(self, baud)
|
||||
|
||||
|
||||
class ESP32S2StubLoader(ESP32S2ROM):
|
||||
"""Access class for ESP32-S2 stub loader, runs on top of ROM.
|
||||
|
||||
(Basically the same as ESP32StubLoader, but different base class.
|
||||
Can possibly be made into a mixin.)
|
||||
"""
|
||||
|
||||
FLASH_WRITE_SIZE = 0x4000 # matches MAX_WRITE_BLOCK in stub_loader.c
|
||||
STATUS_BYTES_LENGTH = 2 # same as ESP8266, different to ESP32 ROM
|
||||
IS_STUB = True
|
||||
|
||||
def __init__(self, rom_loader):
|
||||
self.secure_download_mode = rom_loader.secure_download_mode
|
||||
self._port = rom_loader._port
|
||||
self._trace_enabled = rom_loader._trace_enabled
|
||||
self.cache = rom_loader.cache
|
||||
self.flush_input() # resets _slip_reader
|
||||
|
||||
if rom_loader.uses_usb_otg():
|
||||
self.ESP_RAM_BLOCK = self.USB_RAM_BLOCK
|
||||
self.FLASH_WRITE_SIZE = self.USB_RAM_BLOCK
|
||||
|
||||
|
||||
ESP32S2ROM.STUB_CLASS = ESP32S2StubLoader
|
||||
315
installer/bin/esptool/esptool/targets/esp32s3.py
Normal file
315
installer/bin/esptool/esptool/targets/esp32s3.py
Normal file
|
|
@ -0,0 +1,315 @@
|
|||
# SPDX-FileCopyrightText: 2014-2023 Fredrik Ahlberg, Angus Gratton,
|
||||
# Espressif Systems (Shanghai) CO LTD, other contributors as noted.
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import os
|
||||
import struct
|
||||
|
||||
from .esp32 import ESP32ROM
|
||||
from ..loader import ESPLoader
|
||||
from ..reset import HardReset
|
||||
from ..util import FatalError, NotImplementedInROMError
|
||||
|
||||
|
||||
class ESP32S3ROM(ESP32ROM):
|
||||
CHIP_NAME = "ESP32-S3"
|
||||
|
||||
IMAGE_CHIP_ID = 9
|
||||
|
||||
CHIP_DETECT_MAGIC_VALUE = [0x9]
|
||||
|
||||
FPGA_SLOW_BOOT = False
|
||||
|
||||
IROM_MAP_START = 0x42000000
|
||||
IROM_MAP_END = 0x44000000
|
||||
DROM_MAP_START = 0x3C000000
|
||||
DROM_MAP_END = 0x3E000000
|
||||
|
||||
UART_DATE_REG_ADDR = 0x60000080
|
||||
|
||||
SPI_REG_BASE = 0x60002000
|
||||
SPI_USR_OFFS = 0x18
|
||||
SPI_USR1_OFFS = 0x1C
|
||||
SPI_USR2_OFFS = 0x20
|
||||
SPI_MOSI_DLEN_OFFS = 0x24
|
||||
SPI_MISO_DLEN_OFFS = 0x28
|
||||
SPI_W0_OFFS = 0x58
|
||||
|
||||
BOOTLOADER_FLASH_OFFSET = 0x0
|
||||
|
||||
SUPPORTS_ENCRYPTED_FLASH = True
|
||||
|
||||
FLASH_ENCRYPTED_WRITE_ALIGN = 16
|
||||
|
||||
# todo: use espefuse APIs to get this info
|
||||
EFUSE_BASE = 0x60007000 # BLOCK0 read base address
|
||||
EFUSE_BLOCK1_ADDR = EFUSE_BASE + 0x44
|
||||
EFUSE_BLOCK2_ADDR = EFUSE_BASE + 0x5C
|
||||
MAC_EFUSE_REG = EFUSE_BASE + 0x044
|
||||
|
||||
EFUSE_RD_REG_BASE = EFUSE_BASE + 0x030 # BLOCK0 read base address
|
||||
|
||||
EFUSE_PURPOSE_KEY0_REG = EFUSE_BASE + 0x34
|
||||
EFUSE_PURPOSE_KEY0_SHIFT = 24
|
||||
EFUSE_PURPOSE_KEY1_REG = EFUSE_BASE + 0x34
|
||||
EFUSE_PURPOSE_KEY1_SHIFT = 28
|
||||
EFUSE_PURPOSE_KEY2_REG = EFUSE_BASE + 0x38
|
||||
EFUSE_PURPOSE_KEY2_SHIFT = 0
|
||||
EFUSE_PURPOSE_KEY3_REG = EFUSE_BASE + 0x38
|
||||
EFUSE_PURPOSE_KEY3_SHIFT = 4
|
||||
EFUSE_PURPOSE_KEY4_REG = EFUSE_BASE + 0x38
|
||||
EFUSE_PURPOSE_KEY4_SHIFT = 8
|
||||
EFUSE_PURPOSE_KEY5_REG = EFUSE_BASE + 0x38
|
||||
EFUSE_PURPOSE_KEY5_SHIFT = 12
|
||||
|
||||
EFUSE_DIS_DOWNLOAD_MANUAL_ENCRYPT_REG = EFUSE_RD_REG_BASE
|
||||
EFUSE_DIS_DOWNLOAD_MANUAL_ENCRYPT = 1 << 20
|
||||
|
||||
EFUSE_SPI_BOOT_CRYPT_CNT_REG = EFUSE_BASE + 0x034
|
||||
EFUSE_SPI_BOOT_CRYPT_CNT_MASK = 0x7 << 18
|
||||
|
||||
EFUSE_SECURE_BOOT_EN_REG = EFUSE_BASE + 0x038
|
||||
EFUSE_SECURE_BOOT_EN_MASK = 1 << 20
|
||||
|
||||
EFUSE_RD_REPEAT_DATA3_REG = EFUSE_BASE + 0x3C
|
||||
EFUSE_RD_REPEAT_DATA3_REG_FLASH_TYPE_MASK = 1 << 9
|
||||
|
||||
PURPOSE_VAL_XTS_AES256_KEY_1 = 2
|
||||
PURPOSE_VAL_XTS_AES256_KEY_2 = 3
|
||||
PURPOSE_VAL_XTS_AES128_KEY = 4
|
||||
|
||||
UARTDEV_BUF_NO = 0x3FCEF14C # Variable in ROM .bss which indicates the port in use
|
||||
UARTDEV_BUF_NO_USB_OTG = 3 # The above var when USB-OTG is used
|
||||
UARTDEV_BUF_NO_USB_JTAG_SERIAL = 4 # The above var when USB-JTAG/Serial is used
|
||||
|
||||
RTC_CNTL_WDT_WKEY = 0x50D83AA1
|
||||
RTCCNTL_BASE_REG = 0x60008000
|
||||
RTC_CNTL_WDTCONFIG0_REG = RTCCNTL_BASE_REG + 0x0090
|
||||
RTC_CNTL_WDTWPROTECT_REG = RTCCNTL_BASE_REG + 0x00B0
|
||||
|
||||
USB_RAM_BLOCK = 0x800 # Max block size USB-OTG is used
|
||||
|
||||
GPIO_STRAP_REG = 0x60004038
|
||||
GPIO_STRAP_SPI_BOOT_MASK = 0x8 # Not download mode
|
||||
RTC_CNTL_OPTION1_REG = 0x6000812C
|
||||
RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK = 0x1 # Is download mode forced over USB?
|
||||
|
||||
UART_CLKDIV_REG = 0x60000014
|
||||
|
||||
MEMORY_MAP = [
|
||||
[0x00000000, 0x00010000, "PADDING"],
|
||||
[0x3C000000, 0x3D000000, "DROM"],
|
||||
[0x3D000000, 0x3E000000, "EXTRAM_DATA"],
|
||||
[0x600FE000, 0x60100000, "RTC_DRAM"],
|
||||
[0x3FC88000, 0x3FD00000, "BYTE_ACCESSIBLE"],
|
||||
[0x3FC88000, 0x403E2000, "MEM_INTERNAL"],
|
||||
[0x3FC88000, 0x3FD00000, "DRAM"],
|
||||
[0x40000000, 0x4001A100, "IROM_MASK"],
|
||||
[0x40370000, 0x403E0000, "IRAM"],
|
||||
[0x600FE000, 0x60100000, "RTC_IRAM"],
|
||||
[0x42000000, 0x42800000, "IROM"],
|
||||
[0x50000000, 0x50002000, "RTC_DATA"],
|
||||
]
|
||||
|
||||
def get_pkg_version(self):
|
||||
num_word = 3
|
||||
return (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * num_word)) >> 21) & 0x07
|
||||
|
||||
def is_eco0(self, minor_raw):
|
||||
# Workaround: The major version field was allocated to other purposes
|
||||
# when block version is v1.1.
|
||||
# Luckily only chip v0.0 have this kind of block version and efuse usage.
|
||||
return (
|
||||
(minor_raw & 0x7) == 0
|
||||
and self.get_blk_version_major() == 1
|
||||
and self.get_blk_version_minor() == 1
|
||||
)
|
||||
|
||||
def get_minor_chip_version(self):
|
||||
minor_raw = self.get_raw_minor_chip_version()
|
||||
if self.is_eco0(minor_raw):
|
||||
return 0
|
||||
return minor_raw
|
||||
|
||||
def get_raw_minor_chip_version(self):
|
||||
hi_num_word = 5
|
||||
hi = (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * hi_num_word)) >> 23) & 0x01
|
||||
low_num_word = 3
|
||||
low = (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * low_num_word)) >> 18) & 0x07
|
||||
return (hi << 3) + low
|
||||
|
||||
def get_blk_version_major(self):
|
||||
num_word = 4
|
||||
return (self.read_reg(self.EFUSE_BLOCK2_ADDR + (4 * num_word)) >> 0) & 0x03
|
||||
|
||||
def get_blk_version_minor(self):
|
||||
num_word = 3
|
||||
return (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * num_word)) >> 24) & 0x07
|
||||
|
||||
def get_major_chip_version(self):
|
||||
minor_raw = self.get_raw_minor_chip_version()
|
||||
if self.is_eco0(minor_raw):
|
||||
return 0
|
||||
return self.get_raw_major_chip_version()
|
||||
|
||||
def get_raw_major_chip_version(self):
|
||||
num_word = 5
|
||||
return (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * num_word)) >> 24) & 0x03
|
||||
|
||||
def get_chip_description(self):
|
||||
major_rev = self.get_major_chip_version()
|
||||
minor_rev = self.get_minor_chip_version()
|
||||
return f"{self.CHIP_NAME} (revision v{major_rev}.{minor_rev})"
|
||||
|
||||
def get_chip_features(self):
|
||||
return ["WiFi", "BLE"]
|
||||
|
||||
def get_crystal_freq(self):
|
||||
# ESP32S3 XTAL is fixed to 40MHz
|
||||
return 40
|
||||
|
||||
def get_flash_crypt_config(self):
|
||||
return None # doesn't exist on ESP32-S3
|
||||
|
||||
def get_key_block_purpose(self, key_block):
|
||||
if key_block < 0 or key_block > 5:
|
||||
raise FatalError("Valid key block numbers must be in range 0-5")
|
||||
|
||||
reg, shift = [
|
||||
(self.EFUSE_PURPOSE_KEY0_REG, self.EFUSE_PURPOSE_KEY0_SHIFT),
|
||||
(self.EFUSE_PURPOSE_KEY1_REG, self.EFUSE_PURPOSE_KEY1_SHIFT),
|
||||
(self.EFUSE_PURPOSE_KEY2_REG, self.EFUSE_PURPOSE_KEY2_SHIFT),
|
||||
(self.EFUSE_PURPOSE_KEY3_REG, self.EFUSE_PURPOSE_KEY3_SHIFT),
|
||||
(self.EFUSE_PURPOSE_KEY4_REG, self.EFUSE_PURPOSE_KEY4_SHIFT),
|
||||
(self.EFUSE_PURPOSE_KEY5_REG, self.EFUSE_PURPOSE_KEY5_SHIFT),
|
||||
][key_block]
|
||||
return (self.read_reg(reg) >> shift) & 0xF
|
||||
|
||||
def is_flash_encryption_key_valid(self):
|
||||
# Need to see either an AES-128 key or two AES-256 keys
|
||||
purposes = [self.get_key_block_purpose(b) for b in range(6)]
|
||||
|
||||
if any(p == self.PURPOSE_VAL_XTS_AES128_KEY for p in purposes):
|
||||
return True
|
||||
|
||||
return any(p == self.PURPOSE_VAL_XTS_AES256_KEY_1 for p in purposes) and any(
|
||||
p == self.PURPOSE_VAL_XTS_AES256_KEY_2 for p in purposes
|
||||
)
|
||||
|
||||
def get_secure_boot_enabled(self):
|
||||
return (
|
||||
self.read_reg(self.EFUSE_SECURE_BOOT_EN_REG)
|
||||
& self.EFUSE_SECURE_BOOT_EN_MASK
|
||||
)
|
||||
|
||||
def override_vddsdio(self, new_voltage):
|
||||
raise NotImplementedInROMError(
|
||||
"VDD_SDIO overrides are not supported for ESP32-S3"
|
||||
)
|
||||
|
||||
def read_mac(self):
|
||||
mac0 = self.read_reg(self.MAC_EFUSE_REG)
|
||||
mac1 = self.read_reg(self.MAC_EFUSE_REG + 4) # only bottom 16 bits are MAC
|
||||
bitstring = struct.pack(">II", mac1, mac0)[2:]
|
||||
return tuple(bitstring)
|
||||
|
||||
def flash_type(self):
|
||||
return (
|
||||
1
|
||||
if self.read_reg(self.EFUSE_RD_REPEAT_DATA3_REG)
|
||||
& self.EFUSE_RD_REPEAT_DATA3_REG_FLASH_TYPE_MASK
|
||||
else 0
|
||||
)
|
||||
|
||||
def uses_usb_otg(self):
|
||||
"""
|
||||
Check the UARTDEV_BUF_NO register to see if USB-OTG console is being used
|
||||
"""
|
||||
if self.secure_download_mode:
|
||||
return False # can't detect native USB in secure download mode
|
||||
return self.get_uart_no() == self.UARTDEV_BUF_NO_USB_OTG
|
||||
|
||||
def uses_usb_jtag_serial(self):
|
||||
"""
|
||||
Check the UARTDEV_BUF_NO register to see if USB-JTAG/Serial is being used
|
||||
"""
|
||||
if self.secure_download_mode:
|
||||
return False # can't detect USB-JTAG/Serial in secure download mode
|
||||
return self.get_uart_no() == self.UARTDEV_BUF_NO_USB_JTAG_SERIAL
|
||||
|
||||
def disable_rtc_watchdog(self):
|
||||
# When USB-JTAG/Serial is used, the RTC watchdog is not reset
|
||||
# and can then reset the board during flashing. Disable it.
|
||||
if self.uses_usb_jtag_serial():
|
||||
self.write_reg(self.RTC_CNTL_WDTWPROTECT_REG, self.RTC_CNTL_WDT_WKEY)
|
||||
self.write_reg(self.RTC_CNTL_WDTCONFIG0_REG, 0)
|
||||
self.write_reg(self.RTC_CNTL_WDTWPROTECT_REG, 0)
|
||||
|
||||
def _post_connect(self):
|
||||
if self.uses_usb_otg():
|
||||
self.ESP_RAM_BLOCK = self.USB_RAM_BLOCK
|
||||
if not self.sync_stub_detected: # Don't run if stub is reused
|
||||
self.disable_rtc_watchdog()
|
||||
|
||||
def _check_if_can_reset(self):
|
||||
"""
|
||||
Check the strapping register to see if we can reset out of download mode.
|
||||
"""
|
||||
if os.getenv("ESPTOOL_TESTING") is not None:
|
||||
print("ESPTOOL_TESTING is set, ignoring strapping mode check")
|
||||
# Esptool tests over USB-OTG run with GPIO0 strapped low,
|
||||
# don't complain in this case.
|
||||
return
|
||||
strap_reg = self.read_reg(self.GPIO_STRAP_REG)
|
||||
force_dl_reg = self.read_reg(self.RTC_CNTL_OPTION1_REG)
|
||||
if (
|
||||
strap_reg & self.GPIO_STRAP_SPI_BOOT_MASK == 0
|
||||
and force_dl_reg & self.RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK == 0
|
||||
):
|
||||
print(
|
||||
"WARNING: {} chip was placed into download mode using GPIO0.\n"
|
||||
"esptool.py can not exit the download mode over USB. "
|
||||
"To run the app, reset the chip manually.\n"
|
||||
"To suppress this note, set --after option to 'no_reset'.".format(
|
||||
self.get_chip_description()
|
||||
)
|
||||
)
|
||||
raise SystemExit(1)
|
||||
|
||||
def hard_reset(self):
|
||||
uses_usb_otg = self.uses_usb_otg()
|
||||
if uses_usb_otg:
|
||||
self._check_if_can_reset()
|
||||
|
||||
print("Hard resetting via RTS pin...")
|
||||
HardReset(self._port, uses_usb_otg)()
|
||||
|
||||
def change_baud(self, baud):
|
||||
ESPLoader.change_baud(self, baud)
|
||||
|
||||
|
||||
class ESP32S3StubLoader(ESP32S3ROM):
|
||||
"""Access class for ESP32S3 stub loader, runs on top of ROM.
|
||||
|
||||
(Basically the same as ESP32StubLoader, but different base class.
|
||||
Can possibly be made into a mixin.)
|
||||
"""
|
||||
|
||||
FLASH_WRITE_SIZE = 0x4000 # matches MAX_WRITE_BLOCK in stub_loader.c
|
||||
STATUS_BYTES_LENGTH = 2 # same as ESP8266, different to ESP32 ROM
|
||||
IS_STUB = True
|
||||
|
||||
def __init__(self, rom_loader):
|
||||
self.secure_download_mode = rom_loader.secure_download_mode
|
||||
self._port = rom_loader._port
|
||||
self._trace_enabled = rom_loader._trace_enabled
|
||||
self.cache = rom_loader.cache
|
||||
self.flush_input() # resets _slip_reader
|
||||
|
||||
if rom_loader.uses_usb_otg():
|
||||
self.ESP_RAM_BLOCK = self.USB_RAM_BLOCK
|
||||
self.FLASH_WRITE_SIZE = self.USB_RAM_BLOCK
|
||||
|
||||
|
||||
ESP32S3ROM.STUB_CLASS = ESP32S3StubLoader
|
||||
42
installer/bin/esptool/esptool/targets/esp32s3beta2.py
Normal file
42
installer/bin/esptool/esptool/targets/esp32s3beta2.py
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
# SPDX-FileCopyrightText: 2014-2022 Fredrik Ahlberg, Angus Gratton,
|
||||
# Espressif Systems (Shanghai) CO LTD, other contributors as noted.
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
from .esp32s3 import ESP32S3ROM
|
||||
|
||||
|
||||
class ESP32S3BETA2ROM(ESP32S3ROM):
|
||||
CHIP_NAME = "ESP32-S3(beta2)"
|
||||
IMAGE_CHIP_ID = 4
|
||||
|
||||
CHIP_DETECT_MAGIC_VALUE = [0xEB004136]
|
||||
|
||||
EFUSE_BASE = 0x6001A000 # BLOCK0 read base address
|
||||
|
||||
def get_chip_description(self):
|
||||
major_rev = self.get_major_chip_version()
|
||||
minor_rev = self.get_minor_chip_version()
|
||||
return f"{self.CHIP_NAME} (revision v{major_rev}.{minor_rev})"
|
||||
|
||||
|
||||
class ESP32S3BETA2StubLoader(ESP32S3BETA2ROM):
|
||||
"""Access class for ESP32S3 stub loader, runs on top of ROM.
|
||||
|
||||
(Basically the same as ESP32StubLoader, but different base class.
|
||||
Can possibly be made into a mixin.)
|
||||
"""
|
||||
|
||||
FLASH_WRITE_SIZE = 0x4000 # matches MAX_WRITE_BLOCK in stub_loader.c
|
||||
STATUS_BYTES_LENGTH = 2 # same as ESP8266, different to ESP32 ROM
|
||||
IS_STUB = True
|
||||
|
||||
def __init__(self, rom_loader):
|
||||
self.secure_download_mode = rom_loader.secure_download_mode
|
||||
self._port = rom_loader._port
|
||||
self._trace_enabled = rom_loader._trace_enabled
|
||||
self.cache = rom_loader.cache
|
||||
self.flush_input() # resets _slip_reader
|
||||
|
||||
|
||||
ESP32S3BETA2ROM.STUB_CLASS = ESP32S3BETA2StubLoader
|
||||
191
installer/bin/esptool/esptool/targets/esp8266.py
Normal file
191
installer/bin/esptool/esptool/targets/esp8266.py
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
# SPDX-FileCopyrightText: 2014-2022 Fredrik Ahlberg, Angus Gratton,
|
||||
# Espressif Systems (Shanghai) CO LTD, other contributors as noted.
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
from ..loader import ESPLoader
|
||||
from ..util import FatalError, NotImplementedInROMError
|
||||
|
||||
|
||||
class ESP8266ROM(ESPLoader):
|
||||
"""Access class for ESP8266 ROM bootloader"""
|
||||
|
||||
CHIP_NAME = "ESP8266"
|
||||
IS_STUB = False
|
||||
|
||||
CHIP_DETECT_MAGIC_VALUE = [0xFFF0C101]
|
||||
|
||||
# OTP ROM addresses
|
||||
ESP_OTP_MAC0 = 0x3FF00050
|
||||
ESP_OTP_MAC1 = 0x3FF00054
|
||||
ESP_OTP_MAC3 = 0x3FF0005C
|
||||
|
||||
SPI_REG_BASE = 0x60000200
|
||||
SPI_USR_OFFS = 0x1C
|
||||
SPI_USR1_OFFS = 0x20
|
||||
SPI_USR2_OFFS = 0x24
|
||||
SPI_MOSI_DLEN_OFFS = None
|
||||
SPI_MISO_DLEN_OFFS = None
|
||||
SPI_W0_OFFS = 0x40
|
||||
|
||||
UART_CLKDIV_REG = 0x60000014
|
||||
|
||||
XTAL_CLK_DIVIDER = 2
|
||||
|
||||
FLASH_SIZES = {
|
||||
"512KB": 0x00,
|
||||
"256KB": 0x10,
|
||||
"1MB": 0x20,
|
||||
"2MB": 0x30,
|
||||
"4MB": 0x40,
|
||||
"2MB-c1": 0x50,
|
||||
"4MB-c1": 0x60,
|
||||
"8MB": 0x80,
|
||||
"16MB": 0x90,
|
||||
}
|
||||
|
||||
FLASH_FREQUENCY = {
|
||||
"80m": 0xF,
|
||||
"40m": 0x0,
|
||||
"26m": 0x1,
|
||||
"20m": 0x2,
|
||||
}
|
||||
|
||||
BOOTLOADER_FLASH_OFFSET = 0
|
||||
|
||||
MEMORY_MAP = [
|
||||
[0x3FF00000, 0x3FF00010, "DPORT"],
|
||||
[0x3FFE8000, 0x40000000, "DRAM"],
|
||||
[0x40100000, 0x40108000, "IRAM"],
|
||||
[0x40201010, 0x402E1010, "IROM"],
|
||||
]
|
||||
|
||||
def get_efuses(self):
|
||||
# Return the 128 bits of ESP8266 efuse as a single Python integer
|
||||
result = self.read_reg(0x3FF0005C) << 96
|
||||
result |= self.read_reg(0x3FF00058) << 64
|
||||
result |= self.read_reg(0x3FF00054) << 32
|
||||
result |= self.read_reg(0x3FF00050)
|
||||
return result
|
||||
|
||||
def _get_flash_size(self, efuses):
|
||||
# rX_Y = EFUSE_DATA_OUTX[Y]
|
||||
r0_4 = (efuses & (1 << 4)) != 0
|
||||
r3_25 = (efuses & (1 << 121)) != 0
|
||||
r3_26 = (efuses & (1 << 122)) != 0
|
||||
r3_27 = (efuses & (1 << 123)) != 0
|
||||
|
||||
if r0_4 and not r3_25:
|
||||
if not r3_27 and not r3_26:
|
||||
return 1
|
||||
elif not r3_27 and r3_26:
|
||||
return 2
|
||||
if not r0_4 and r3_25:
|
||||
if not r3_27 and not r3_26:
|
||||
return 2
|
||||
elif not r3_27 and r3_26:
|
||||
return 4
|
||||
return -1
|
||||
|
||||
def get_chip_description(self):
|
||||
efuses = self.get_efuses()
|
||||
is_8285 = (
|
||||
efuses & ((1 << 4) | 1 << 80)
|
||||
) != 0 # One or the other efuse bit is set for ESP8285
|
||||
if is_8285:
|
||||
flash_size = self._get_flash_size(efuses)
|
||||
max_temp = (
|
||||
efuses & (1 << 5)
|
||||
) != 0 # This efuse bit identifies the max flash temperature
|
||||
chip_name = {
|
||||
1: "ESP8285H08" if max_temp else "ESP8285N08",
|
||||
2: "ESP8285H16" if max_temp else "ESP8285N16",
|
||||
}.get(flash_size, "ESP8285")
|
||||
return chip_name
|
||||
return "ESP8266EX"
|
||||
|
||||
def get_chip_features(self):
|
||||
features = ["WiFi"]
|
||||
if "ESP8285" in self.get_chip_description():
|
||||
features += ["Embedded Flash"]
|
||||
return features
|
||||
|
||||
def flash_spi_attach(self, hspi_arg):
|
||||
if self.IS_STUB:
|
||||
super(ESP8266ROM, self).flash_spi_attach(hspi_arg)
|
||||
else:
|
||||
# ESP8266 ROM has no flash_spi_attach command in serial protocol,
|
||||
# but flash_begin will do it
|
||||
self.flash_begin(0, 0)
|
||||
|
||||
def flash_set_parameters(self, size):
|
||||
# not implemented in ROM, but OK to silently skip for ROM
|
||||
if self.IS_STUB:
|
||||
super(ESP8266ROM, self).flash_set_parameters(size)
|
||||
|
||||
def chip_id(self):
|
||||
"""
|
||||
Read Chip ID from efuse - the equivalent of the SDK system_get_chip_id() func
|
||||
"""
|
||||
id0 = self.read_reg(self.ESP_OTP_MAC0)
|
||||
id1 = self.read_reg(self.ESP_OTP_MAC1)
|
||||
return (id0 >> 24) | ((id1 & 0xFFFFFF) << 8)
|
||||
|
||||
def read_mac(self):
|
||||
"""Read MAC from OTP ROM"""
|
||||
mac0 = self.read_reg(self.ESP_OTP_MAC0)
|
||||
mac1 = self.read_reg(self.ESP_OTP_MAC1)
|
||||
mac3 = self.read_reg(self.ESP_OTP_MAC3)
|
||||
if mac3 != 0:
|
||||
oui = ((mac3 >> 16) & 0xFF, (mac3 >> 8) & 0xFF, mac3 & 0xFF)
|
||||
elif ((mac1 >> 16) & 0xFF) == 0:
|
||||
oui = (0x18, 0xFE, 0x34)
|
||||
elif ((mac1 >> 16) & 0xFF) == 1:
|
||||
oui = (0xAC, 0xD0, 0x74)
|
||||
else:
|
||||
raise FatalError("Unknown OUI")
|
||||
return oui + ((mac1 >> 8) & 0xFF, mac1 & 0xFF, (mac0 >> 24) & 0xFF)
|
||||
|
||||
def get_erase_size(self, offset, size):
|
||||
"""Calculate an erase size given a specific size in bytes.
|
||||
|
||||
Provides a workaround for the bootloader erase bug."""
|
||||
|
||||
sectors_per_block = 16
|
||||
sector_size = self.FLASH_SECTOR_SIZE
|
||||
num_sectors = (size + sector_size - 1) // sector_size
|
||||
start_sector = offset // sector_size
|
||||
|
||||
head_sectors = sectors_per_block - (start_sector % sectors_per_block)
|
||||
if num_sectors < head_sectors:
|
||||
head_sectors = num_sectors
|
||||
|
||||
if num_sectors < 2 * head_sectors:
|
||||
return (num_sectors + 1) // 2 * sector_size
|
||||
else:
|
||||
return (num_sectors - head_sectors) * sector_size
|
||||
|
||||
def override_vddsdio(self, new_voltage):
|
||||
raise NotImplementedInROMError(
|
||||
"Overriding VDDSDIO setting only applies to ESP32"
|
||||
)
|
||||
|
||||
|
||||
class ESP8266StubLoader(ESP8266ROM):
|
||||
"""Access class for ESP8266 stub loader, runs on top of ROM."""
|
||||
|
||||
FLASH_WRITE_SIZE = 0x4000 # matches MAX_WRITE_BLOCK in stub_loader.c
|
||||
IS_STUB = True
|
||||
|
||||
def __init__(self, rom_loader):
|
||||
self.secure_download_mode = rom_loader.secure_download_mode
|
||||
self._port = rom_loader._port
|
||||
self._trace_enabled = rom_loader._trace_enabled
|
||||
self.cache = rom_loader.cache
|
||||
self.flush_input() # resets _slip_reader
|
||||
|
||||
def get_erase_size(self, offset, size):
|
||||
return size # stub doesn't have same size bug as ROM loader
|
||||
|
||||
|
||||
ESP8266ROM.STUB_CLASS = ESP8266StubLoader
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"entry": 1074521560,
|
||||
"text": "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",
|
||||
"text_start": 1074520064,
|
||||
"data": "GOv8P9jnC0Bx6AtA8+wLQO3oC0CP6AtA7egLQEnpC0AG6gtAeOoLQCHqC0CB5wtAo+kLQPjpC0Bn6QtAmuoLQI7pC0Ca6gtAXegLQLPoC0Dt6AtASekLQHfoC0BM6wtAs+wLQKXmC0DX7AtApeYLQKXmC0Cl5gtApeYLQKXmC0Cl5gtApeYLQKXmC0Dz6gtApeYLQM3rC0Cz7AtA",
|
||||
"data_start": 1073605544
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"entry": 1077413304,
|
||||
"text": "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",
|
||||
"text_start": 1077411840,
|
||||
"data": "GGvKP+AIOEAsCThAhAk4QCgKOECUCjhAQgo4QKgHOEDkCThAJAo4QJgJOEBYBzhAzAk4QFgHOEC6CDhA/gg4QCwJOECECThAzAg4QBIIOEBCCDhAyAg4QOYMOEAsCThArAs4QJoMOECkBjhAxAw4QKQGOECkBjhApAY4QKQGOECkBjhApAY4QKQGOECkBjhASAs4QKQGOEDICzhAmgw4QA==",
|
||||
"data_start": 1070295976
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"entry": 1077413532,
|
||||
"text": "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",
|
||||
"text_start": 1077411840,
|
||||
"data": "IGvIP1YKOECmCjhA/go4QKILOEAODDhAvAs4QCIJOEBeCzhAngs4QBILOEDSCDhARgs4QNIIOEAwCjhAdgo4QKYKOED+CjhAQgo4QIYJOEC2CThAPgo4QGAOOECmCjhAJg04QBgOOEASCDhAQA44QBIIOEASCDhAEgg4QBIIOEASCDhAEgg4QBIIOEASCDhAwgw4QBIIOEBEDThAGA44QA==",
|
||||
"data_start": 1070164912
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"entry": 1082132112,
|
||||
"text": "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",
|
||||
"text_start": 1082130432,
|
||||
"data": "HCuEQCoKgEB6CoBA0gqAQHYLgEDiC4BAkAuAQPYIgEAyC4BAcguAQOYKgECmCIBAGguAQKYIgEAECoBASgqAQHoKgEDSCoBAFgqAQFoJgECKCYBAEgqAQDQOgEB6CoBA+gyAQOwNgEDmB4BAFA6AQOYHgEDmB4BA5geAQOYHgEDmB4BA5geAQOYHgEDmB4BAlgyAQOYHgEAYDYBA7A2AQA==",
|
||||
"data_start": 1082469292
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"entry": 1077413318,
|
||||
"text": "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",
|
||||
"text_start": 1077411840,
|
||||
"data": "GGvIP/gIOEBECThAnAk4QEAKOECsCjhAWgo4QMAHOED8CThAPAo4QLAJOEBwBzhA5Ak4QHAHOEDSCDhAFgk4QEQJOECcCThA5Ag4QCoIOEBaCDhA4Ag4QP4MOEBECThAxAs4QLIMOEC8BjhA3Aw4QLwGOEC8BjhAvAY4QLwGOEC8BjhAvAY4QLwGOEC8BjhAYAs4QLwGOEDgCzhAsgw4QA==",
|
||||
"data_start": 1070164904
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"entry": 1082132112,
|
||||
"text": "QREixCbCBsa39wBgEUc3BINA2Mu39ABgEwQEANxAkYuR57JAIkSSREEBgoCIQBxAE3X1D4KX3bcBEbcHAGBOxoOphwBKyDcJg0AmylLEBs4izLcEAGB9WhMJCQDATBN09A8N4PJAYkQjqDQBQknSRLJJIkoFYYKAiECDJwkAE3X1D4KXfRTjGUT/yb8TBwAMlEGqh2MY5QCFR4XGI6AFAHlVgoAFR2OH5gAJRmONxgB9VYKAQgUTB7ANQYVjlecCiUecwfW3kwbADWMW1QCYwRMFAAyCgJMG0A19VWOV1wCYwRMFsA2CgLc1hEBBEZOFRboGxmE/Y0UFBrc3hECTh8exA6cHCAPWRwgTdfUPkwYWAMIGwYIjktcIMpcjAKcAA9dHCJFnk4cHBGMe9wI3t4NAEwfHsaFnupcDpgcIt/aDQLc3hECTh8exk4bGtWMf5gAjpscII6DXCCOSBwghoPlX4wb1/LJAQQGCgCOm1wgjoOcI3bc3NwBgfEudi/X/NycAYHxLnYv1/4KAQREGxt03tzcAYCOmBwI3BwAImMOYQ33/yFeyQBNF9f8FiUEBgoBBEQbG2T993TcHAEC3NwBgmMM3NwBgHEP9/7JAQQGCgEERIsQ3BINAkwcEAUrAA6kHAQbGJsJjCgkERTc5xb1HEwQEAYFEY9YnAQREvYiTtBQAfTeFPxxENwaAABOXxwCZ4DcGAAG39v8AdY+3NgBg2MKQwphCff9BR5HgBUczCelAupcjKCQBHMSyQCJEkkQCSUEBgoABEQbOIswlNzcEhUBsABMFBP+XAID/54Ag8qqHBUWV57JHk/cHID7GiTc3NwBgHEe3BkAAEwUE/9WPHMeyRZcAgP/ngKDvMzWgAPJAYkQFYYKAQRG3B4NABsaThwcBBUcjgOcAE9fFAJjHBWd9F8zDyMf5jTqVqpWxgYzLI6oHAEE3GcETBVAMskBBAYKAAREizDcEg0CTBwQBJsrER07GBs5KyKqJEwQEAWPzlQCuhKnAAylEACaZE1nJABxIY1XwABxEY175ArU9fd1IQCaGzoWXAID/54Cg4hN19Q8BxZMHQAxcyFxAppdcwFxEhY9cxPJAYkTSREJJskkFYYKAaTVtv0ERBsaXAID/54BA1gNFhQGyQHUVEzUVAEEBgoBBEQbGxTcNxbcHg0CThwcA1EOZzjdnCWATB8cQHEM3Bv3/fRbxjzcGAwDxjtWPHMOyQEEBgoBBEQbGbTcRwQ1FskBBARcDgP9nAIPMQREGxpcAgP/ngEDKcTcBxbJAQQHZv7JAQQGCgEERBsYTBwAMYxrlABMFsA3RPxMFwA2yQEEB6bcTB7AN4xvl/sE3EwXQDfW3QREixCbCBsYqhLMEtQBjF5QAskAiRJJEQQGCgANFBAAFBE0/7bc1cSbLTsf9coVp/XQizUrJUsVWwwbPk4SE+haRk4cJB6aXGAizhOcAKokmhS6ElwCA/+eAgCyThwkHGAgFarqXs4pHQTHkBWd9dZMFhfqTBwcHEwWF+RQIqpczhdcAkwcHB66Xs4XXACrGlwCA/+eAQCkyRcFFlTcBRYViFpH6QGpE2kRKSbpJKkqaSg1hgoCiiWNzigCFaU6G1oVKhZcAgP/ngIDIE3X1DwHtTobWhSaFlwCA/+eAgCROmTMENEFRtxMFMAZVvxMFAAzZtTFx/XIFZ07XUtVW017PBt8i3SbbStla0WLNZstqyW7H/XcWkRMHBwc+lxwIupc+xiOqB/iqiS6Ksoq2iwU1kwcAAhnBtwcCAD6FlwCA/+eAIB2FZ2PlVxMFZH15EwmJ+pMHBAfKlxgIM4nnAEqFlwCA/+eAoBt9exMMO/mTDIv5EwcEB5MHBAcUCGKX5peBRDMM1wCzjNcAUk1jfE0JY/GkA0GomT+ihQgBjTW5NyKGDAFKhZcAgP/ngIAXopmilGP1RAOzh6RBY/F3AzMEmkBj84oAVoQihgwBToWXAID/54DAtxN19Q9V3QLMAUR5XY1NowkBAGKFlwCA/+eAgKd9+QNFMQHmhVE8Y08FAOPijf6FZ5OHBweilxgIupfalyOKp/gFBPG34xWl/ZFH4wX09gVnfXWTBwcHkwWF+hMFhfkUCKqXM4XXAJMHBweul7OF1wAqxpcAgP/ngKANcT0yRcFFZTNRPdU5twcCABnhkwcAAj6FlwCA/+eAoAqFYhaR+lBqVNpUSlm6WSpamloKW/pLakzaTEpNuk0pYYKAt1dBSRlxk4f3hAFFht6i3KbaytjO1tLU1tLa0N7O4szmyurI7sY+zpcAgP/ngMCgcTENwTdnCWATB8cQHEO3BoNAI6L2ALcG/f/9FvWPwWbVjxzDpTEFwbcnC2A3R9hQk4aHwRMHF6qYwpOHB8AjoAcAI6AGALcHg0A3N4RAk4cHABMHx7ohoCOgBwCRB+Pt5/7hM5FFaAjROcEzt7eDQJOHx7EhZz6XIyD3CLcHgEA3CYNAk4eHDiMg+QC3OYRA9T4TCQkAk4nJsWMHBRC3BwFgRUcjqucIhUVFRZcAgP/ngID3twWAQAFGk4UFAEVFlwCA/+eAgPg39wBgHEs3BQIAk+dHABzLlwCA/+eAgPe3FwlgiF+BRbcEg0BxiWEVEzUVAJcAgP/ngACiwWf9FxMHABCFZkFmtwUAAQFFk4QEAQ1qtzqDQJcAgP/ngACYJpoTi8qxg6fJCPXfg6vJCIVHI6YJCCMC8QKDxxsACUcjE+ECowLxAgLUTUdjgecIUUdjj+cGKUdjn+cAg8c7AAPHKwCiB9mPEUdjlucAg6eLAJxDPtRVOaFFSBDBNoPHOwADxysAogfZjxFnQQdjdPcEEwWwDbk+EwXADaE+EwXgDok+WTFBt7cFgEABRpOFhQMVRZcAgP/ngEDpNwcAYFxHEwUAApPnFxBcxzG3yUcjE/ECTbcDxxsA0UZj5+YChUZj5uYAAUwTBPAPhah5FxN39w/JRuPo5v63NoRACgeThga7NpcYQwKHkwYHA5P29g8RRuNp1vwTB/cCE3f3D41GY+vmCLc2hEAKB5OGxr82lxhDAocTB0ACY5jnEALUHUQBRUU8AUXhNMU2+T6hRUgQfRTlNHX0AUwBRBN19A9FPBN1/A9tNMk24x4E6oPHGwBJR2Nj9y4JR+N29+r1F5P39w89R+Ng9+o3N4RAigcTB8fAupecQ4KHBUSd63AQgUUBRZfwf//ngIB1HeHRRWgQaTQBRDGoBUSB75fwf//ngAB6MzSgACmgIUdjhecABUQBTGG3A6yLAAOkywCzZ4wA0gf19+/wP4p98cFsIpz9HH19MwWMQFXcs3eVAZXjwWwzBYxAY+aMAv18MwWMQFXQMYGX8H//54CAdlX5ZpT1tzGBl/B//+eAgHVV8WqU0bdBgZfwf//ngMB0UfkzBJRBwbchR+OJ5/ABTBMEAAwxt0FHzb9BRwVE45zn9oOlywADpYsA9Tqxv0FHBUTjkuf2A6cLAZFnY+XnHIOlSwEDpYsA7/B/hTW/QUcFROOS5/SDpwsBEWdjZfcaA6fLAIOlSwEDpYsAM4TnAu/w/4IjrAQAIySKsDG3A8cEAGMOBxADp4sAwRcTBAAMYxP3AMBIAUeTBvAOY0b3AoPHWwADx0sAAUyiB9mPA8drAEIHXY+Dx3sA4gfZj+OB9uYTBBAMqb0zhusAA0aGAQUHsY7ht4PHBADxw9xEY5gHEsBII4AEAH21YUdjlucCg6fLAQOniwGDpksBA6YLAYOlywADpYsAl/B//+eAQGUqjDM0oAAptQFMBUQRtRFHBUTjmufmA6WLAIFFl/B//+eAwGqRtRP39wDjGgfsk9xHABOEiwABTH1d43mc3UhEl/B//+eAQE8YRFRAEED5jmMHpwEcQhNH9/99j9mOFMIFDEEE2b8RR0m9QUcFROOc5+CDp4sAA6dLASMm+QAjJOkA3bODJYkAwReR5YnPAUwTBGAMtbsDJ8kAY2b3BhP3NwDjHgfkAyjJAAFGAUczBehAs4blAGNp9wDjCQbUIyapACMk2QCZszOG6wAQThEHkMIFRum/IUcFROOW59oDJMkAGcATBIAMIyYJACMkCQAzNIAASbsBTBMEIAwRuwFMEwSADDGzAUwTBJAMEbMTByANY4PnDBMHQA3jkOe8A8Q7AIPHKwAiBF2Ml/B//+eA4E0DrMQAQRRjc4QBIozjDgy4wEBilDGAnEhjVfAAnERjW/QK7/DP0nXdyEBihpOFiwGX8H//54DgSQHFkwdADNzI3EDil9zA3ESzh4dB3MSX8H//54DASIm+CWUTBQVxA6zLAAOkiwCX8H//54DAObcHAGDYS7cGAAHBFpNXRwESB3WPvYvZj7OHhwMBRbPVhwKX8H//54DgOhMFgD6X8H//54BgNhG2g6ZLAQOmCwGDpcsAA6WLAO/wz//9tIPFOwCDxysAE4WLAaIF3Y3BFe/wr9zZvO/wD8w9v4PHOwADxysAE4yLAaIH2Y8TjQf/BUS3O4RA3ERjBQ0AmcNjTIAAY1AEChMHcAzYyOOfB6iTB5AMYaiTh8u6mEO3t4NAk4fHsZmPPtaDJ4qwtzyDQGrQk4wMAZONy7oFSGNz/QANSELGOsTv8A/FIkcySDcFg0DihXwQk4bKsRAQEwWFApfwf//ngMA1glcDp4ywg6UNADMN/UAdjz6cslcjpOywKoS+lSOgvQCTh8qxnY0BxaFn45L19lqF7/Av0COgbQGZvy3044MHoJMHgAzcyPW6g6eLAOObB57v8K/aCWUTBQVxl/B//+eAICTv8C/Wl/B//+eAYCjRugOkywDjBwSc7/Av2BMFgD6X8H//54DAIe/wz9MClFW67/BP0/ZQZlTWVEZZtlkmWpZaBlv2S2ZM1kxGTbZNCWGCgAAA",
|
||||
"text_start": 1082130432,
|
||||
"data": "HCuDQCoKgEB6CoBA0gqAQHYLgEDiC4BAkAuAQPYIgEAyC4BAcguAQOYKgECmCIBAGguAQKYIgEAECoBASgqAQHoKgEDSCoBAFgqAQFoJgECKCYBAEgqAQDQOgEB6CoBA+gyAQOwNgEDmB4BAFA6AQOYHgEDmB4BA5geAQOYHgEDmB4BA5geAQOYHgEDmB4BAlgyAQOYHgEAYDYBA7A2AQA==",
|
||||
"data_start": 1082403756
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"entry": 1077413318,
|
||||
"text": "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",
|
||||
"text_start": 1077411840,
|
||||
"data": "GGvIP/gIOEBECThAnAk4QEAKOECsCjhAWgo4QMAHOED8CThAPAo4QLAJOEBwBzhA5Ak4QHAHOEDSCDhAFgk4QEQJOECcCThA5Ag4QCoIOEBaCDhA4Ag4QP4MOEBECThAxAs4QLIMOEC8BjhA3Aw4QLwGOEC8BjhAvAY4QLwGOEC8BjhAvAY4QLwGOEC8BjhAYAs4QLwGOEDgCzhAsgw4QA==",
|
||||
"data_start": 1070164904
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"entry": 1077413318,
|
||||
"text": "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",
|
||||
"text_start": 1077411840,
|
||||
"data": "GGvIP/gIOEBECThAnAk4QEAKOECsCjhAWgo4QMAHOED8CThAPAo4QLAJOEBwBzhA5Ak4QHAHOEDSCDhAFgk4QEQJOECcCThA5Ag4QCoIOEBaCDhA4Ag4QP4MOEBECThAxAs4QLIMOEC8BjhA3Aw4QLwGOEC8BjhAvAY4QLwGOEC8BjhAvAY4QLwGOEC8BjhAYAs4QLwGOEDgCzhAsgw4QA==",
|
||||
"data_start": 1070164904
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
174
installer/bin/esptool/esptool/util.py
Normal file
174
installer/bin/esptool/esptool/util.py
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
# SPDX-FileCopyrightText: 2014-2022 Fredrik Ahlberg, Angus Gratton,
|
||||
# Espressif Systems (Shanghai) CO LTD, other contributors as noted.
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import re
|
||||
import struct
|
||||
import sys
|
||||
|
||||
|
||||
def byte(bitstr, index):
|
||||
return bitstr[index]
|
||||
|
||||
|
||||
def mask_to_shift(mask):
|
||||
"""Return the index of the least significant bit in the mask"""
|
||||
shift = 0
|
||||
while mask & 0x1 == 0:
|
||||
shift += 1
|
||||
mask >>= 1
|
||||
return shift
|
||||
|
||||
|
||||
def div_roundup(a, b):
|
||||
"""Return a/b rounded up to nearest integer,
|
||||
equivalent result to int(math.ceil(float(int(a)) / float(int(b))), only
|
||||
without possible floating point accuracy errors.
|
||||
"""
|
||||
return (int(a) + int(b) - 1) // int(b)
|
||||
|
||||
|
||||
def flash_size_bytes(size):
|
||||
"""Given a flash size of the type passed in args.flash_size
|
||||
(ie 512KB or 1MB) then return the size in bytes.
|
||||
"""
|
||||
if "MB" in size:
|
||||
return int(size[: size.index("MB")]) * 1024 * 1024
|
||||
elif "KB" in size:
|
||||
return int(size[: size.index("KB")]) * 1024
|
||||
else:
|
||||
raise FatalError("Unknown size %s" % size)
|
||||
|
||||
|
||||
def hexify(s, uppercase=True):
|
||||
format_str = "%02X" if uppercase else "%02x"
|
||||
return "".join(format_str % c for c in s)
|
||||
|
||||
|
||||
def pad_to(data, alignment, pad_character=b"\xFF"):
|
||||
"""Pad to the next alignment boundary"""
|
||||
pad_mod = len(data) % alignment
|
||||
if pad_mod != 0:
|
||||
data += pad_character * (alignment - pad_mod)
|
||||
return data
|
||||
|
||||
|
||||
def print_overwrite(message, last_line=False):
|
||||
"""Print a message, overwriting the currently printed line.
|
||||
|
||||
If last_line is False, don't append a newline at the end
|
||||
(expecting another subsequent call will overwrite this one.)
|
||||
|
||||
After a sequence of calls with last_line=False, call once with last_line=True.
|
||||
|
||||
If output is not a TTY (for example redirected a pipe),
|
||||
no overwriting happens and this function is the same as print().
|
||||
"""
|
||||
if sys.stdout.isatty():
|
||||
print("\r%s" % message, end="\n" if last_line else "")
|
||||
else:
|
||||
print(message)
|
||||
|
||||
|
||||
def expand_chip_name(chip_name):
|
||||
"""Change chip name to official form, e.g. `esp32s3beta2` -> `ESP32-S3(beta2)`"""
|
||||
# Put "-" after "esp32"
|
||||
chip_name = re.sub(r"(esp32)(?!$)", r"\1-", chip_name)
|
||||
# Put "()" around "betaN"
|
||||
chip_name = re.sub(r"(beta\d*)", r"(\1)", chip_name)
|
||||
# Uppercase everything before "(betaN)"
|
||||
chip_name = re.sub(r"^[^\(]+", lambda x: x.group(0).upper(), chip_name)
|
||||
return chip_name
|
||||
|
||||
|
||||
def strip_chip_name(chip_name):
|
||||
"""Strip chip name to normalized form, e.g. `ESP32-S3(beta2)` -> `esp32s3beta2`"""
|
||||
return re.sub(r"[-()]", "", chip_name.lower())
|
||||
|
||||
|
||||
class FatalError(RuntimeError):
|
||||
"""
|
||||
Wrapper class for runtime errors that aren't caused by internal bugs, but by
|
||||
ESP ROM responses or input content.
|
||||
"""
|
||||
|
||||
def __init__(self, message):
|
||||
RuntimeError.__init__(self, message)
|
||||
|
||||
@staticmethod
|
||||
def WithResult(message, result):
|
||||
"""
|
||||
Return a fatal error object that appends the hex values of
|
||||
'result' and its meaning as a string formatted argument.
|
||||
"""
|
||||
|
||||
err_defs = {
|
||||
# ROM error codes
|
||||
0x101: "Out of memory",
|
||||
0x102: "Invalid argument",
|
||||
0x103: "Invalid state",
|
||||
0x104: "Invalid size",
|
||||
0x105: "Requested resource not found",
|
||||
0x106: "Operation or feature not supported",
|
||||
0x107: "Operation timed out",
|
||||
0x108: "Received response was invalid",
|
||||
0x109: "CRC or checksum was invalid",
|
||||
0x10A: "Version was invalid",
|
||||
0x10B: "MAC address was invalid",
|
||||
# Flasher stub error codes
|
||||
0xC000: "Bad data length",
|
||||
0xC100: "Bad data checksum",
|
||||
0xC200: "Bad blocksize",
|
||||
0xC300: "Invalid command",
|
||||
0xC400: "Failed SPI operation",
|
||||
0xC500: "Failed SPI unlock",
|
||||
0xC600: "Not in flash mode",
|
||||
0xC700: "Inflate error",
|
||||
0xC800: "Not enough data",
|
||||
0xC900: "Too much data",
|
||||
0xFF00: "Command not implemented",
|
||||
}
|
||||
|
||||
err_code = struct.unpack(">H", result[:2])
|
||||
message += " (result was {}: {})".format(
|
||||
hexify(result), err_defs.get(err_code[0], "Unknown result")
|
||||
)
|
||||
return FatalError(message)
|
||||
|
||||
|
||||
class NotImplementedInROMError(FatalError):
|
||||
"""
|
||||
Wrapper class for the error thrown when a particular ESP bootloader function
|
||||
is not implemented in the ROM bootloader.
|
||||
"""
|
||||
|
||||
def __init__(self, bootloader, func):
|
||||
FatalError.__init__(
|
||||
self,
|
||||
"%s ROM does not support function %s."
|
||||
% (bootloader.CHIP_NAME, func.__name__),
|
||||
)
|
||||
|
||||
|
||||
class NotSupportedError(FatalError):
|
||||
def __init__(self, esp, function_name):
|
||||
FatalError.__init__(
|
||||
self,
|
||||
"Function %s is not supported for %s." % (function_name, esp.CHIP_NAME),
|
||||
)
|
||||
|
||||
|
||||
class UnsupportedCommandError(RuntimeError):
|
||||
"""
|
||||
Wrapper class for when ROM loader returns an invalid command response.
|
||||
|
||||
Usually this indicates the loader is running in Secure Download Mode.
|
||||
"""
|
||||
|
||||
def __init__(self, esp, op):
|
||||
if esp.secure_download_mode:
|
||||
msg = "This command (0x%x) is not supported in Secure Download Mode" % op
|
||||
else:
|
||||
msg = "Invalid (unsupported) command 0x%x" % op
|
||||
RuntimeError.__init__(self, msg)
|
||||
10
installer/install_upgrade.bat
Normal file
10
installer/install_upgrade.bat
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
@echo off
|
||||
echo Your firmware will be upgraded without factory reset.
|
||||
echo IF THIS IS YOUR FIRST FLASH ON THIS BOARD PLEASE USE install_with_factory_reset.bat INSTEAD!
|
||||
|
||||
set /p port="Enter COM port (for example COM5): "
|
||||
|
||||
python.exe bin/esptool/esptool.py --chip esp32 --port "%port%" --baud 460800 --before default_reset --after hard_reset write_flash -z --flash_mode dio --flash_freq 40m --flash_size 4MB 0x1000 firmware/bootloader.bin 0x8000 firmware/partitions.bin 0xe000 firmware/boot_app0.bin 0x10000 firmware/firmware.bin
|
||||
|
||||
echo "Firmware flashed"
|
||||
pause
|
||||
9
installer/install_upgrade.sh
Normal file
9
installer/install_upgrade.sh
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
#!/bin/bash
|
||||
echo "Your firmware will be upgraded without factory reset."
|
||||
echo "IF THIS IS YOUR FIRST FLASH ON THIS BOARD PLEASE USE install_with_factory_reset.sh INSTEAD!"
|
||||
|
||||
read -p "Enter COM port (for example /dev/ttyS5): " port
|
||||
|
||||
python3 ./bin/esptool/esptool.py --chip esp32 --port "$port" --baud 460800 --before default_reset --after hard_reset write_flash -z --flash_mode dio --flash_freq 40m --flash_size 4MB 0x1000 firmware/bootloader.bin 0x8000 firmware/partitions.bin 0xe000 firmware/boot_app0.bin 0x10000 firmware/firmware.bin
|
||||
|
||||
echo "Firmware flashed"
|
||||
14
installer/install_with_factory_reset.bat
Normal file
14
installer/install_with_factory_reset.bat
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
@echo off
|
||||
echo Your current configuration will be LOST!!!
|
||||
echo Your current configuration will be LOST!!!
|
||||
echo Your current configuration will be LOST!!!
|
||||
echo If you already have this board flashed with our firmware please use install_upgrade.bat instead!
|
||||
|
||||
set /p port="Enter COM port (for example COM5): "
|
||||
|
||||
python.exe bin/esptool/esptool.py --chip esp32 --port "%port%" --baud 460800 --before default_reset --after hard_reset write_flash -z --flash_mode dio --flash_freq 40m --flash_size 4MB 2686976 firmware/spiffs.bin
|
||||
|
||||
python.exe bin/esptool/esptool.py --chip esp32 --port "%port%" --baud 460800 --before default_reset --after hard_reset write_flash -z --flash_mode dio --flash_freq 40m --flash_size 4MB 0x1000 firmware/bootloader.bin 0x8000 firmware/partitions.bin 0xe000 firmware/boot_app0.bin 0x10000 firmware/firmware.bin
|
||||
|
||||
echo "Firmware flashed"
|
||||
pause
|
||||
0
installer/install_with_factory_reset.sh
Normal file
0
installer/install_with_factory_reset.sh
Normal file
|
|
@ -15,6 +15,12 @@ default_envs = ttgo-lora32-v21
|
|||
platform = espressif32 @ 6.3.1
|
||||
framework = arduino
|
||||
monitor_speed = 115200
|
||||
board_build.embed_files =
|
||||
data_embed/index.html.gz
|
||||
data_embed/style.css.gz
|
||||
data_embed/script.js.gz
|
||||
data_embed/bootstrap.css.gz
|
||||
data_embed/bootstrap.js.gz
|
||||
lib_deps =
|
||||
bblanchon/ArduinoJson@^6.20.2
|
||||
sandeepmistry/LoRa@^0.8.0
|
||||
|
|
@ -28,9 +34,11 @@ lib_deps =
|
|||
jgromes/RadioLib @ 6.1.0
|
||||
lewisxhe/XPowersLib@^0.1.8
|
||||
ayushsharma82/ElegantOTA@^3.1.0
|
||||
ottowinter/ESPAsyncWebServer-esphome@ 3.0.0
|
||||
esphome/AsyncTCP-esphome@ 2.1.1
|
||||
ottowinter/ESPAsyncWebServer-esphome@3.0.0
|
||||
esphome/AsyncTCP-esphome@2.1.1
|
||||
|
||||
extra_scripts =
|
||||
pre:tools/compress.py
|
||||
|
||||
[env:ttgo-lora32-v21]
|
||||
board = ttgo-lora32-v21
|
||||
|
|
@ -67,4 +75,11 @@ build_flags = -Werror -Wall -DTTGO_T_Beam_V1_0_SX1268 -DELEGANTOTA_USE_ASYNC_WEB
|
|||
|
||||
[env:ttgo-t-beam-v1_2_SX1262]
|
||||
board = ttgo-t-beam
|
||||
build_flags = -Werror -Wall -DTTGO_T_Beam_V1_2_SX1262 -DELEGANTOTA_USE_ASYNC_WEBSERVER=1
|
||||
build_flags = -Werror -Wall -DTTGO_T_Beam_V1_2_SX1262 -DELEGANTOTA_USE_ASYNC_WEBSERVER=1
|
||||
|
||||
[env:heltec_wireless_stick_lite]
|
||||
platform = espressif32
|
||||
board = heltec_wireless_stick_lite
|
||||
board_build.mcu = esp32c3
|
||||
board_build.f_cpu = 240000000L
|
||||
build_flags = -Werror -Wall -DHELTEC_CT62 -DELEGANTOTA_USE_ASYNC_WEBSERVER=1
|
||||
|
|
@ -13,6 +13,7 @@
|
|||
#include "digi_utils.h"
|
||||
#include "gps_utils.h"
|
||||
#include "bme_utils.h"
|
||||
#include "web_utils.h"
|
||||
#include "display.h"
|
||||
#include "utils.h"
|
||||
|
||||
|
|
@ -20,7 +21,7 @@
|
|||
Configuration Config;
|
||||
WiFiClient espClient;
|
||||
|
||||
String versionDate = "2024.01.28";
|
||||
String versionDate = "2024.02.23";
|
||||
int myWiFiAPIndex = 0;
|
||||
int myWiFiAPSize = Config.wifiAPs.size();
|
||||
WiFi_AP *currentWiFi = &Config.wifiAPs[myWiFiAPIndex];
|
||||
|
|
@ -34,8 +35,12 @@ uint32_t lastScreenOn = millis();
|
|||
|
||||
uint32_t lastWiFiCheck = 0;
|
||||
bool WiFiConnect = true;
|
||||
bool WiFiConnected = false;
|
||||
int lastStationModeState = 1;
|
||||
|
||||
bool WiFiAutoAPStarted = false;
|
||||
long WiFiAutoAPTime = false;
|
||||
|
||||
String batteryVoltage;
|
||||
|
||||
std::vector<String> lastHeardStation;
|
||||
|
|
@ -46,55 +51,63 @@ std::vector<String> packetBuffer_temp;
|
|||
String firstLine, secondLine, thirdLine, fourthLine, fifthLine, sixthLine, seventhLine, iGateBeaconPacket, iGateLoRaBeaconPacket;
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
#if defined(TTGO_T_LORA32_V2_1) || defined(HELTEC_V2)
|
||||
pinMode(batteryPin, INPUT);
|
||||
#endif
|
||||
#if defined(TTGO_T_LORA32_V2_1) || defined(HELTEC_V2) || defined(HELTEC_V3) || defined(ESP32_DIY_LoRa) || defined(ESP32_DIY_1W_LoRa)
|
||||
pinMode(internalLedPin, OUTPUT);
|
||||
#endif
|
||||
if (Config.externalVoltageMeasurement) {
|
||||
pinMode(Config.externalVoltagePin, INPUT);
|
||||
}
|
||||
#if defined(TTGO_T_Beam_V1_0) || defined(TTGO_T_Beam_V1_0_SX1268) || defined(TTGO_T_Beam_V1_2) || defined(TTGO_T_Beam_V1_2_SX1262)
|
||||
POWER_Utils::setup();
|
||||
#endif
|
||||
delay(1000);
|
||||
Utils::setupDisplay();
|
||||
WIFI_Utils::setup();
|
||||
LoRa_Utils::setup();
|
||||
Utils::validateDigiFreqs();
|
||||
iGateBeaconPacket = GPS_Utils::generateBeacon();
|
||||
iGateLoRaBeaconPacket = GPS_Utils::generateiGateLoRaBeacon();
|
||||
Utils::startServer();
|
||||
SYSLOG_Utils::setup();
|
||||
BME_Utils::setup();
|
||||
Serial.begin(115200);
|
||||
|
||||
#if defined(TTGO_T_LORA32_V2_1) || defined(HELTEC_V2)
|
||||
pinMode(batteryPin, INPUT);
|
||||
#endif
|
||||
#if defined(TTGO_T_LORA32_V2_1) || defined(HELTEC_V2) || defined(HELTEC_V3) || defined(ESP32_DIY_LoRa) || defined(ESP32_DIY_1W_LoRa)
|
||||
pinMode(internalLedPin, OUTPUT);
|
||||
#endif
|
||||
if (Config.externalVoltageMeasurement) {
|
||||
pinMode(Config.externalVoltagePin, INPUT);
|
||||
}
|
||||
#if defined(TTGO_T_Beam_V1_0) || defined(TTGO_T_Beam_V1_0_SX1268) || defined(TTGO_T_Beam_V1_2) || defined(TTGO_T_Beam_V1_2_SX1262)
|
||||
POWER_Utils::setup();
|
||||
#endif
|
||||
delay(1000);
|
||||
Utils::setupDisplay();
|
||||
WIFI_Utils::setup();
|
||||
LoRa_Utils::setup();
|
||||
Utils::validateDigiFreqs();
|
||||
iGateBeaconPacket = GPS_Utils::generateBeacon();
|
||||
iGateLoRaBeaconPacket = GPS_Utils::generateiGateLoRaBeacon();
|
||||
SYSLOG_Utils::setup();
|
||||
BME_Utils::setup();
|
||||
WEB_Utils::setup();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
if (stationMode==1 || stationMode==2 ) { // iGate (1 Only Rx / 2 Rx+Tx)
|
||||
WIFI_Utils::checkWiFi();
|
||||
if (!espClient.connected()) {
|
||||
APRS_IS_Utils::connect();
|
||||
WEB_Utils::loop();
|
||||
|
||||
WIFI_Utils::checkIfAutoAPShouldPowerOff();
|
||||
|
||||
if (!WiFiConnected) {
|
||||
thirdLine = Utils::getLocalIP();
|
||||
}
|
||||
APRS_IS_Utils::loop();
|
||||
} else if (stationMode==3 || stationMode==4) { // DigiRepeater (3 RxFreq=TxFreq / 4 RxFreq!=TxFreq)
|
||||
DIGI_Utils::loop();
|
||||
} else if (stationMode==5) { // iGate when WiFi and APRS available , DigiRepeater when not (RxFreq=TxFreq)
|
||||
Utils::checkWiFiInterval();
|
||||
if (WiFi.status() == WL_CONNECTED) { // iGate Mode
|
||||
thirdLine = Utils::getLocalIP();
|
||||
if (!espClient.connected()) {
|
||||
APRS_IS_Utils::connect();
|
||||
}
|
||||
if (lastStationModeState == 1) {
|
||||
iGateBeaconPacket = GPS_Utils::generateBeacon();
|
||||
lastStationModeState = 0;
|
||||
Utils::startServer();
|
||||
}
|
||||
APRS_IS_Utils::loop();
|
||||
} else { // DigiRepeater Mode
|
||||
DIGI_Utils::loop();
|
||||
|
||||
if (stationMode==1 || stationMode==2 ) { // iGate (1 Only Rx / 2 Rx+Tx)
|
||||
WIFI_Utils::checkWiFi();
|
||||
if (!espClient.connected()) {
|
||||
APRS_IS_Utils::connect();
|
||||
}
|
||||
APRS_IS_Utils::loop();
|
||||
} else if (stationMode==3 || stationMode==4) { // DigiRepeater (3 RxFreq=TxFreq / 4 RxFreq!=TxFreq)
|
||||
DIGI_Utils::loop();
|
||||
} else if (stationMode==5) { // iGate when WiFi and APRS available , DigiRepeater when not (RxFreq=TxFreq)
|
||||
Utils::checkWiFiInterval();
|
||||
if (WiFi.status() == WL_CONNECTED) { // iGate Mode
|
||||
thirdLine = Utils::getLocalIP();
|
||||
if (!espClient.connected()) {
|
||||
APRS_IS_Utils::connect();
|
||||
}
|
||||
if (lastStationModeState == 1) {
|
||||
iGateBeaconPacket = GPS_Utils::generateBeacon();
|
||||
lastStationModeState = 0;
|
||||
}
|
||||
APRS_IS_Utils::loop();
|
||||
} else { // DigiRepeater Mode
|
||||
DIGI_Utils::loop();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -26,213 +26,214 @@ extern String seventhLine;
|
|||
|
||||
namespace APRS_IS_Utils {
|
||||
|
||||
void upload(String line) {
|
||||
espClient.print(line + "\r\n");
|
||||
}
|
||||
|
||||
void connect(){
|
||||
int count = 0;
|
||||
String aprsauth;
|
||||
Serial.print("Connecting to APRS-IS ... ");
|
||||
while (!espClient.connect(Config.aprs_is.server.c_str(), Config.aprs_is.port) && count < 20) {
|
||||
Serial.println("Didn't connect with server...");
|
||||
delay(1000);
|
||||
espClient.stop();
|
||||
espClient.flush();
|
||||
Serial.println("Run client.stop");
|
||||
Serial.println("Trying to connect with Server: " + String(Config.aprs_is.server) + " AprsServerPort: " + String(Config.aprs_is.port));
|
||||
count++;
|
||||
Serial.println("Try: " + String(count));
|
||||
void upload(String line) {
|
||||
espClient.print(line + "\r\n");
|
||||
}
|
||||
if (count == 20) {
|
||||
Serial.println("Tried: " + String(count) + " FAILED!");
|
||||
} else {
|
||||
Serial.println("Connected!\n(Server: " + String(Config.aprs_is.server) + " / Port: " + String(Config.aprs_is.port) +")");
|
||||
aprsauth = "user " + Config.callsign + " pass " + Config.aprs_is.passcode + " vers CA2RXU_LoRa_iGate 1.2 filter t/m/" + Config.callsign + "/" + (String)Config.aprs_is.reportingDistance;// + "\r\n";
|
||||
upload(aprsauth);
|
||||
delay(200);
|
||||
}
|
||||
}
|
||||
|
||||
void checkStatus() {
|
||||
String wifiState, aprsisState;
|
||||
if (WiFi.status() == WL_CONNECTED) {
|
||||
wifiState = "OK";
|
||||
} else {
|
||||
wifiState = "--";
|
||||
if (!Config.display.alwaysOn) {
|
||||
display_toggle(true);
|
||||
}
|
||||
lastScreenOn = millis();
|
||||
}
|
||||
if (espClient.connected()) {
|
||||
aprsisState = "OK";
|
||||
} else {
|
||||
aprsisState = "--";
|
||||
if (!Config.display.alwaysOn) {
|
||||
display_toggle(true);
|
||||
}
|
||||
lastScreenOn = millis();
|
||||
}
|
||||
secondLine = "WiFi: " + wifiState + "/ APRS-IS: " + aprsisState;
|
||||
}
|
||||
|
||||
String createPacket(String packet) {
|
||||
if (stationMode>1) {
|
||||
return packet.substring(3, packet.indexOf(":")) + ",qAR," + Config.callsign + packet.substring(packet.indexOf(":"));// + "\n";
|
||||
} else {
|
||||
return packet.substring(3, packet.indexOf(":")) + ",qAO," + Config.callsign + packet.substring(packet.indexOf(":"));// + "\n";
|
||||
}
|
||||
}
|
||||
|
||||
void processLoRaPacket(String packet) {
|
||||
bool queryMessage = false;
|
||||
String aprsPacket, Sender, AddresseeAndMessage, Addressee, ackMessage, receivedMessage;
|
||||
if (packet != "") {
|
||||
#ifdef TextSerialOutputForApp
|
||||
Serial.println(packet.substring(3));
|
||||
#else
|
||||
Serial.print("Received Lora Packet : " + String(packet));
|
||||
#endif
|
||||
if ((packet.substring(0,3) == "\x3c\xff\x01") && (packet.indexOf("TCPIP") == -1) && (packet.indexOf("NOGATE") == -1) && (packet.indexOf("RFONLY") == -1)) {
|
||||
#ifndef TextSerialOutputForApp
|
||||
Serial.print(" ---> APRS LoRa Packet!");
|
||||
#endif
|
||||
Sender = packet.substring(3,packet.indexOf(">"));
|
||||
if (Sender != Config.callsign) { // avoid listening yourself by digirepeating
|
||||
AddresseeAndMessage = packet.substring(packet.indexOf("::")+2);
|
||||
Addressee = AddresseeAndMessage.substring(0,AddresseeAndMessage.indexOf(":"));
|
||||
Addressee.trim();
|
||||
|
||||
if (stationMode!=1 && Config.igateRepeatsLoRaPackets && Addressee != Config.callsign) { // if its not for me
|
||||
String digiRepeatedPacket = DIGI_Utils::generateDigiRepeatedPacket(packet.substring(3), Config.callsign);
|
||||
if (digiRepeatedPacket != "X") {
|
||||
delay(500);
|
||||
LoRa_Utils::sendNewPacket("APRS", digiRepeatedPacket);
|
||||
}
|
||||
}
|
||||
|
||||
if (packet.indexOf("::") > 10 && Addressee == Config.callsign) { // its a message for me!
|
||||
if (AddresseeAndMessage.indexOf("{")>0) { // ack?
|
||||
ackMessage = "ack" + AddresseeAndMessage.substring(AddresseeAndMessage.indexOf("{")+1);
|
||||
ackMessage.trim();
|
||||
delay(4000);
|
||||
//Serial.println(ackMessage);
|
||||
for(int i = Sender.length(); i < 9; i++) {
|
||||
Sender += ' ';
|
||||
}
|
||||
LoRa_Utils::sendNewPacket("APRS", Config.callsign + ">APLRG1,RFONLY,WIDE1-1::" + Sender + ":" + ackMessage);
|
||||
receivedMessage = AddresseeAndMessage.substring(AddresseeAndMessage.indexOf(":")+1, AddresseeAndMessage.indexOf("{"));
|
||||
} else {
|
||||
receivedMessage = AddresseeAndMessage.substring(AddresseeAndMessage.indexOf(":")+1);
|
||||
}
|
||||
if (receivedMessage.indexOf("?") == 0) {
|
||||
queryMessage = true;
|
||||
delay(2000);
|
||||
if (!Config.display.alwaysOn) {
|
||||
display_toggle(true);
|
||||
}
|
||||
LoRa_Utils::sendNewPacket("APRS", QUERY_Utils::process(receivedMessage, Sender, "LoRa"));
|
||||
lastScreenOn = millis();
|
||||
show_display(firstLine, secondLine, thirdLine, fourthLine, fifthLine, "Callsign = " + Sender, "TYPE --> QUERY", 0);
|
||||
}
|
||||
}
|
||||
if (!queryMessage) {
|
||||
aprsPacket = createPacket(packet);
|
||||
if (!Config.display.alwaysOn) {
|
||||
display_toggle(true);
|
||||
}
|
||||
lastScreenOn = millis();
|
||||
upload(aprsPacket);
|
||||
#ifndef TextSerialOutputForApp
|
||||
Serial.println(" ---> Uploaded to APRS-IS");
|
||||
#endif
|
||||
STATION_Utils::updateLastHeard(Sender);
|
||||
Utils::typeOfPacket(aprsPacket, "LoRa-APRS");
|
||||
show_display(firstLine, secondLine, thirdLine, fourthLine, fifthLine, sixthLine, seventhLine, 0);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
#ifndef TextSerialOutputForApp
|
||||
Serial.println(" ---> LoRa Packet Ignored (first 3 bytes or TCPIP/NOGATE/RFONLY)\n");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void processAPRSISPacket(String packet) {
|
||||
String Sender, AddresseeAndMessage, Addressee, receivedMessage;
|
||||
if (!packet.startsWith("#")){
|
||||
if (packet.indexOf("::")>0) {
|
||||
Sender = packet.substring(0,packet.indexOf(">"));
|
||||
AddresseeAndMessage = packet.substring(packet.indexOf("::")+2);
|
||||
Addressee = AddresseeAndMessage.substring(0, AddresseeAndMessage.indexOf(":"));
|
||||
Addressee.trim();
|
||||
if (Addressee == Config.callsign) { // its for me!
|
||||
if (AddresseeAndMessage.indexOf("{")>0) { // ack?
|
||||
String ackMessage = "ack" + AddresseeAndMessage.substring(AddresseeAndMessage.indexOf("{")+1);
|
||||
ackMessage.trim();
|
||||
delay(4000);
|
||||
//Serial.println(ackMessage);
|
||||
for(int i = Sender.length(); i < 9; i++) {
|
||||
Sender += ' ';
|
||||
}
|
||||
String ackPacket = Config.callsign + ">APLRG1,TCPIP,qAC::" + Sender + ":" + ackMessage;// + "\n";
|
||||
upload(ackPacket);
|
||||
receivedMessage = AddresseeAndMessage.substring(AddresseeAndMessage.indexOf(":")+1, AddresseeAndMessage.indexOf("{"));
|
||||
} else {
|
||||
receivedMessage = AddresseeAndMessage.substring(AddresseeAndMessage.indexOf(":")+1);
|
||||
}
|
||||
if (receivedMessage.indexOf("?") == 0) {
|
||||
#ifndef TextSerialOutputForApp
|
||||
Serial.println("Received Query APRS-IS : " + packet);
|
||||
#endif
|
||||
String queryAnswer = QUERY_Utils::process(receivedMessage, Sender, "APRSIS");
|
||||
//Serial.println("---> QUERY Answer : " + queryAnswer.substring(0,queryAnswer.indexOf("\n")));
|
||||
if (!Config.display.alwaysOn) {
|
||||
display_toggle(true);
|
||||
}
|
||||
lastScreenOn = millis();
|
||||
delay(500);
|
||||
upload(queryAnswer);
|
||||
SYSLOG_Utils::log("APRSIS Tx", queryAnswer,0,0,0);
|
||||
fifthLine = "APRS-IS ----> APRS-IS";
|
||||
sixthLine = Config.callsign;
|
||||
for (int j=sixthLine.length();j<9;j++) {
|
||||
sixthLine += " ";
|
||||
}
|
||||
sixthLine += "> " + Sender;
|
||||
seventhLine = "QUERY = " + receivedMessage;
|
||||
}
|
||||
} else {
|
||||
#ifndef TextSerialOutputForApp
|
||||
Serial.print("Received from APRS-IS : " + packet);
|
||||
#endif
|
||||
if ((stationMode==2 || stationMode==5) && STATION_Utils::wasHeard(Addressee)) {
|
||||
LoRa_Utils::sendNewPacket("APRS", LoRa_Utils::generatePacket(packet));
|
||||
display_toggle(true);
|
||||
lastScreenOn = millis();
|
||||
Utils::typeOfPacket(packet, "APRS-LoRa");
|
||||
}
|
||||
void connect(){
|
||||
int count = 0;
|
||||
String aprsauth;
|
||||
Serial.print("Connecting to APRS-IS ... ");
|
||||
while (!espClient.connect(Config.aprs_is.server.c_str(), Config.aprs_is.port) && count < 20) {
|
||||
Serial.println("Didn't connect with server...");
|
||||
delay(1000);
|
||||
espClient.stop();
|
||||
espClient.flush();
|
||||
Serial.println("Run client.stop");
|
||||
Serial.println("Trying to connect with Server: " + String(Config.aprs_is.server) + " AprsServerPort: " + String(Config.aprs_is.port));
|
||||
count++;
|
||||
Serial.println("Try: " + String(count));
|
||||
}
|
||||
if (count == 20) {
|
||||
Serial.println("Tried: " + String(count) + " FAILED!");
|
||||
} else {
|
||||
Serial.println("Connected!\n(Server: " + String(Config.aprs_is.server) + " / Port: " + String(Config.aprs_is.port) +")");
|
||||
aprsauth = "user " + Config.callsign + " pass " + Config.aprs_is.passcode + " vers CA2RXU_LoRa_iGate 1.3 filter t/m/" + Config.callsign + "/" + (String)Config.aprs_is.reportingDistance;// + "\r\n";
|
||||
upload(aprsauth);
|
||||
delay(200);
|
||||
}
|
||||
show_display(firstLine, secondLine, thirdLine, fourthLine, fifthLine, sixthLine, seventhLine, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void loop() {
|
||||
checkStatus();
|
||||
show_display(firstLine, secondLine, thirdLine, fourthLine, fifthLine, sixthLine, seventhLine, 0);
|
||||
while (espClient.connected()) {
|
||||
Utils::checkDisplayInterval();
|
||||
Utils::checkBeaconInterval();
|
||||
processLoRaPacket(LoRa_Utils::receivePacket());
|
||||
if (espClient.available()) {
|
||||
String aprsisPacket;
|
||||
aprsisPacket.concat(espClient.readStringUntil('\r'));
|
||||
processAPRSISPacket(aprsisPacket);
|
||||
}
|
||||
ElegantOTA.loop();
|
||||
void checkStatus() {
|
||||
String wifiState, aprsisState;
|
||||
if (WiFi.status() == WL_CONNECTED) {
|
||||
wifiState = "OK";
|
||||
} else {
|
||||
wifiState = "AP";
|
||||
if (!Config.display.alwaysOn) {
|
||||
display_toggle(true);
|
||||
}
|
||||
lastScreenOn = millis();
|
||||
}
|
||||
if (espClient.connected()) {
|
||||
aprsisState = "OK";
|
||||
} else {
|
||||
aprsisState = "--";
|
||||
if (!Config.display.alwaysOn) {
|
||||
display_toggle(true);
|
||||
}
|
||||
lastScreenOn = millis();
|
||||
}
|
||||
secondLine = "WiFi: " + wifiState + "/ APRS-IS: " + aprsisState;
|
||||
}
|
||||
}
|
||||
|
||||
String createPacket(String packet) {
|
||||
if (stationMode>1) {
|
||||
return packet.substring(3, packet.indexOf(":")) + ",qAR," + Config.callsign + packet.substring(packet.indexOf(":"));// + "\n";
|
||||
} else {
|
||||
return packet.substring(3, packet.indexOf(":")) + ",qAO," + Config.callsign + packet.substring(packet.indexOf(":"));// + "\n";
|
||||
}
|
||||
}
|
||||
|
||||
void processLoRaPacket(String packet) {
|
||||
bool queryMessage = false;
|
||||
String aprsPacket, Sender, AddresseeAndMessage, Addressee, ackMessage, receivedMessage;
|
||||
if (packet != "") {
|
||||
#ifdef TextSerialOutputForApp
|
||||
Serial.println(packet.substring(3));
|
||||
#else
|
||||
Serial.print("Received Lora Packet : " + String(packet));
|
||||
#endif
|
||||
if ((packet.substring(0,3) == "\x3c\xff\x01") && (packet.indexOf("TCPIP") == -1) && (packet.indexOf("NOGATE") == -1) && (packet.indexOf("RFONLY") == -1)) {
|
||||
#ifndef TextSerialOutputForApp
|
||||
Serial.print(" ---> APRS LoRa Packet!");
|
||||
#endif
|
||||
Sender = packet.substring(3,packet.indexOf(">"));
|
||||
if (Sender != Config.callsign) { // avoid listening yourself by digirepeating
|
||||
AddresseeAndMessage = packet.substring(packet.indexOf("::")+2);
|
||||
Addressee = AddresseeAndMessage.substring(0,AddresseeAndMessage.indexOf(":"));
|
||||
Addressee.trim();
|
||||
|
||||
if (stationMode!=1 && Config.igateRepeatsLoRaPackets && Addressee != Config.callsign) { // if its not for me
|
||||
String digiRepeatedPacket = DIGI_Utils::generateDigiRepeatedPacket(packet.substring(3), Config.callsign);
|
||||
if (digiRepeatedPacket != "X") {
|
||||
delay(500);
|
||||
LoRa_Utils::sendNewPacket("APRS", digiRepeatedPacket);
|
||||
}
|
||||
}
|
||||
|
||||
if (packet.indexOf("::") > 10 && Addressee == Config.callsign) { // its a message for me!
|
||||
if (AddresseeAndMessage.indexOf("{")>0) { // ack?
|
||||
ackMessage = "ack" + AddresseeAndMessage.substring(AddresseeAndMessage.indexOf("{")+1);
|
||||
ackMessage.trim();
|
||||
delay(4000);
|
||||
//Serial.println(ackMessage);
|
||||
for(int i = Sender.length(); i < 9; i++) {
|
||||
Sender += ' ';
|
||||
}
|
||||
LoRa_Utils::sendNewPacket("APRS", Config.callsign + ">APLRG1,RFONLY,WIDE1-1::" + Sender + ":" + ackMessage);
|
||||
receivedMessage = AddresseeAndMessage.substring(AddresseeAndMessage.indexOf(":")+1, AddresseeAndMessage.indexOf("{"));
|
||||
} else {
|
||||
receivedMessage = AddresseeAndMessage.substring(AddresseeAndMessage.indexOf(":")+1);
|
||||
}
|
||||
if (receivedMessage.indexOf("?") == 0) {
|
||||
queryMessage = true;
|
||||
delay(2000);
|
||||
if (!Config.display.alwaysOn) {
|
||||
display_toggle(true);
|
||||
}
|
||||
LoRa_Utils::sendNewPacket("APRS", QUERY_Utils::process(receivedMessage, Sender, "LoRa"));
|
||||
lastScreenOn = millis();
|
||||
show_display(firstLine, secondLine, thirdLine, fourthLine, fifthLine, "Callsign = " + Sender, "TYPE --> QUERY", 0);
|
||||
}
|
||||
}
|
||||
if (!queryMessage) {
|
||||
aprsPacket = createPacket(packet);
|
||||
if (!Config.display.alwaysOn) {
|
||||
display_toggle(true);
|
||||
}
|
||||
lastScreenOn = millis();
|
||||
upload(aprsPacket);
|
||||
#ifndef TextSerialOutputForApp
|
||||
Serial.println(" ---> Uploaded to APRS-IS");
|
||||
#endif
|
||||
STATION_Utils::updateLastHeard(Sender);
|
||||
Utils::typeOfPacket(aprsPacket, "LoRa-APRS");
|
||||
show_display(firstLine, secondLine, thirdLine, fourthLine, fifthLine, sixthLine, seventhLine, 0);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
#ifndef TextSerialOutputForApp
|
||||
Serial.println(" ---> LoRa Packet Ignored (first 3 bytes or TCPIP/NOGATE/RFONLY)\n");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void processAPRSISPacket(String packet) {
|
||||
String Sender, AddresseeAndMessage, Addressee, receivedMessage;
|
||||
if (!packet.startsWith("#")){
|
||||
if (packet.indexOf("::")>0) {
|
||||
Sender = packet.substring(0,packet.indexOf(">"));
|
||||
AddresseeAndMessage = packet.substring(packet.indexOf("::")+2);
|
||||
Addressee = AddresseeAndMessage.substring(0, AddresseeAndMessage.indexOf(":"));
|
||||
Addressee.trim();
|
||||
if (Addressee == Config.callsign) { // its for me!
|
||||
if (AddresseeAndMessage.indexOf("{")>0) { // ack?
|
||||
String ackMessage = "ack" + AddresseeAndMessage.substring(AddresseeAndMessage.indexOf("{")+1);
|
||||
ackMessage.trim();
|
||||
delay(4000);
|
||||
//Serial.println(ackMessage);
|
||||
for(int i = Sender.length(); i < 9; i++) {
|
||||
Sender += ' ';
|
||||
}
|
||||
String ackPacket = Config.callsign + ">APLRG1,TCPIP,qAC::" + Sender + ":" + ackMessage;// + "\n";
|
||||
upload(ackPacket);
|
||||
receivedMessage = AddresseeAndMessage.substring(AddresseeAndMessage.indexOf(":")+1, AddresseeAndMessage.indexOf("{"));
|
||||
} else {
|
||||
receivedMessage = AddresseeAndMessage.substring(AddresseeAndMessage.indexOf(":")+1);
|
||||
}
|
||||
if (receivedMessage.indexOf("?") == 0) {
|
||||
#ifndef TextSerialOutputForApp
|
||||
Serial.println("Received Query APRS-IS : " + packet);
|
||||
#endif
|
||||
String queryAnswer = QUERY_Utils::process(receivedMessage, Sender, "APRSIS");
|
||||
//Serial.println("---> QUERY Answer : " + queryAnswer.substring(0,queryAnswer.indexOf("\n")));
|
||||
if (!Config.display.alwaysOn) {
|
||||
display_toggle(true);
|
||||
}
|
||||
lastScreenOn = millis();
|
||||
delay(500);
|
||||
upload(queryAnswer);
|
||||
SYSLOG_Utils::log("APRSIS Tx", queryAnswer,0,0,0);
|
||||
fifthLine = "APRS-IS ----> APRS-IS";
|
||||
sixthLine = Config.callsign;
|
||||
for (int j=sixthLine.length();j<9;j++) {
|
||||
sixthLine += " ";
|
||||
}
|
||||
sixthLine += "> " + Sender;
|
||||
seventhLine = "QUERY = " + receivedMessage;
|
||||
}
|
||||
} else {
|
||||
#ifndef TextSerialOutputForApp
|
||||
Serial.print("Received from APRS-IS : " + packet);
|
||||
#endif
|
||||
if ((stationMode==2 || stationMode==5) && STATION_Utils::wasHeard(Addressee)) {
|
||||
LoRa_Utils::sendNewPacket("APRS", LoRa_Utils::generatePacket(packet));
|
||||
display_toggle(true);
|
||||
lastScreenOn = millis();
|
||||
Utils::typeOfPacket(packet, "APRS-LoRa");
|
||||
}
|
||||
}
|
||||
show_display(firstLine, secondLine, thirdLine, fourthLine, fifthLine, sixthLine, seventhLine, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void loop() {
|
||||
checkStatus();
|
||||
show_display(firstLine, secondLine, thirdLine, fourthLine, fifthLine, sixthLine, seventhLine, 0);
|
||||
while (espClient.connected()) {
|
||||
Utils::checkDisplayInterval();
|
||||
Utils::checkBeaconInterval();
|
||||
processLoRaPacket(LoRa_Utils::receivePacket());
|
||||
if (espClient.available()) {
|
||||
String aprsisPacket;
|
||||
aprsisPacket.concat(espClient.readStringUntil('\r'));
|
||||
processAPRSISPacket(aprsisPacket);
|
||||
}
|
||||
ElegantOTA.loop();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -13,144 +13,144 @@ extern String fifthLine;
|
|||
|
||||
namespace BME_Utils {
|
||||
|
||||
#ifdef BME280Sensor
|
||||
Adafruit_BME280 bme;
|
||||
#endif
|
||||
#ifdef BMP280Sensor
|
||||
Adafruit_BMP280 bme;
|
||||
#endif
|
||||
#ifdef BME680Sensor
|
||||
Adafruit_BME680 bme;
|
||||
#endif
|
||||
|
||||
void setup() {
|
||||
if (Config.bme.active) {
|
||||
bool status;
|
||||
status = bme.begin(0x76); // Don't forget to join pins for righ direction on BME280!
|
||||
if (!status) {
|
||||
Serial.println("Could not find a valid BME280 or BMP280 sensor, check wiring!");
|
||||
show_display("ERROR", "", "BME/BMP sensor active", "but no sensor found...");
|
||||
while (1); // sacar esto para que quede pegado si no encuentra BME280
|
||||
} else {
|
||||
#ifdef BME280Sensor
|
||||
Serial.println("init : BME280 Module ... done!");
|
||||
#endif
|
||||
#ifdef BMP280Sensor
|
||||
Serial.println("init : BMP280 Module ... done!");
|
||||
#endif
|
||||
#ifdef BME680Sensor
|
||||
Serial.println("init : BME680 Module ... done!");
|
||||
#endif
|
||||
}
|
||||
} else {
|
||||
Serial.println("(BME/BMP sensor not 'active' in 'igate_conf.json')");
|
||||
}
|
||||
}
|
||||
|
||||
String generateTempString(float bmeTemp) {
|
||||
String strTemp;
|
||||
strTemp = String((int)bmeTemp);
|
||||
switch (strTemp.length()) {
|
||||
case 1:
|
||||
return "00" + strTemp;
|
||||
break;
|
||||
case 2:
|
||||
return "0" + strTemp;
|
||||
break;
|
||||
case 3:
|
||||
return strTemp;
|
||||
break;
|
||||
default:
|
||||
return "-999";
|
||||
}
|
||||
}
|
||||
|
||||
String generateHumString(float bmeHum) {
|
||||
String strHum;
|
||||
strHum = String((int)bmeHum);
|
||||
switch (strHum.length()) {
|
||||
case 1:
|
||||
return "0" + strHum;
|
||||
break;
|
||||
case 2:
|
||||
return strHum;
|
||||
break;
|
||||
case 3:
|
||||
if ((int)bmeHum == 100) {
|
||||
return "00";
|
||||
} else {
|
||||
return "-99";
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return "-99";
|
||||
}
|
||||
}
|
||||
|
||||
String generatePresString(float bmePress) {
|
||||
String strPress;
|
||||
strPress = String((int)bmePress);
|
||||
switch (strPress.length()) {
|
||||
case 1:
|
||||
return "000" + strPress + "0";
|
||||
break;
|
||||
case 2:
|
||||
return "00" + strPress + "0";
|
||||
break;
|
||||
case 3:
|
||||
return "0" + strPress + "0";
|
||||
break;
|
||||
case 4:
|
||||
return strPress + "0";
|
||||
break;
|
||||
case 5:
|
||||
return strPress;
|
||||
break;
|
||||
default:
|
||||
return "-99999";
|
||||
}
|
||||
}
|
||||
|
||||
String readDataSensor() {
|
||||
String wx, tempStr, humStr, presStr;
|
||||
float newHum;
|
||||
|
||||
float newTemp = bme.readTemperature();
|
||||
#if defined(BME280Sensor) || defined(BME680Sensor)
|
||||
newHum = bme.readHumidity();
|
||||
#ifdef BME280Sensor
|
||||
Adafruit_BME280 bme;
|
||||
#endif
|
||||
#ifdef BMP280Sensor
|
||||
newHum = 0;
|
||||
Adafruit_BMP280 bme;
|
||||
#endif
|
||||
float newPress = (bme.readPressure() / 100.0F);
|
||||
|
||||
#ifdef BME680Sensor
|
||||
float newGas = bme.gas_resistance / 1000.0; // in Kilo ohms
|
||||
Adafruit_BME680 bme;
|
||||
#endif
|
||||
|
||||
//bme.readAltitude(SEALEVELPRESSURE_HPA) // this is for approximate Altitude Calculation.
|
||||
|
||||
if (isnan(newTemp) || isnan(newHum) || isnan(newPress)) {
|
||||
Serial.println("BME/BMP Module data failed");
|
||||
wx = ".../...g...t...r...p...P...h..b.....";
|
||||
fifthLine = "";
|
||||
return wx;
|
||||
} else {
|
||||
tempStr = generateTempString((newTemp * 1.8) + 32);
|
||||
#if defined(BME280Sensor) || defined(BME680Sensor)
|
||||
humStr = generateHumString(newHum);
|
||||
#endif
|
||||
#ifdef BMP280Sensor
|
||||
humStr = "..";
|
||||
#endif
|
||||
presStr = generatePresString(newPress + (HEIGHT_CORRECTION/CORRECTION_FACTOR));
|
||||
fifthLine = "BME-> " + String(int(newTemp))+"C " + humStr + "% " + presStr.substring(0,4) + "hPa";
|
||||
wx = ".../...g...t" + tempStr + "r...p...P...h" + humStr + "b" + presStr;
|
||||
#ifdef BME680Sensor
|
||||
wx += "Gas: " + String(newGas) + "Kohms ";
|
||||
#endif
|
||||
return wx;
|
||||
|
||||
void setup() {
|
||||
if (Config.bme.active) {
|
||||
bool status;
|
||||
status = bme.begin(0x76); // Don't forget to join pins for righ direction on BME280!
|
||||
if (!status) {
|
||||
Serial.println("Could not find a valid BME280 or BMP280 sensor, check wiring!");
|
||||
show_display("ERROR", "", "BME/BMP sensor active", "but no sensor found...");
|
||||
while (1); // sacar esto para que quede pegado si no encuentra BME280
|
||||
} else {
|
||||
#ifdef BME280Sensor
|
||||
Serial.println("init : BME280 Module ... done!");
|
||||
#endif
|
||||
#ifdef BMP280Sensor
|
||||
Serial.println("init : BMP280 Module ... done!");
|
||||
#endif
|
||||
#ifdef BME680Sensor
|
||||
Serial.println("init : BME680 Module ... done!");
|
||||
#endif
|
||||
}
|
||||
} else {
|
||||
Serial.println("(BME/BMP sensor not 'active' in 'igate_conf.json')");
|
||||
}
|
||||
}
|
||||
|
||||
String generateTempString(float bmeTemp) {
|
||||
String strTemp;
|
||||
strTemp = String((int)bmeTemp);
|
||||
switch (strTemp.length()) {
|
||||
case 1:
|
||||
return "00" + strTemp;
|
||||
break;
|
||||
case 2:
|
||||
return "0" + strTemp;
|
||||
break;
|
||||
case 3:
|
||||
return strTemp;
|
||||
break;
|
||||
default:
|
||||
return "-999";
|
||||
}
|
||||
}
|
||||
|
||||
String generateHumString(float bmeHum) {
|
||||
String strHum;
|
||||
strHum = String((int)bmeHum);
|
||||
switch (strHum.length()) {
|
||||
case 1:
|
||||
return "0" + strHum;
|
||||
break;
|
||||
case 2:
|
||||
return strHum;
|
||||
break;
|
||||
case 3:
|
||||
if ((int)bmeHum == 100) {
|
||||
return "00";
|
||||
} else {
|
||||
return "-99";
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return "-99";
|
||||
}
|
||||
}
|
||||
|
||||
String generatePresString(float bmePress) {
|
||||
String strPress;
|
||||
strPress = String((int)bmePress);
|
||||
switch (strPress.length()) {
|
||||
case 1:
|
||||
return "000" + strPress + "0";
|
||||
break;
|
||||
case 2:
|
||||
return "00" + strPress + "0";
|
||||
break;
|
||||
case 3:
|
||||
return "0" + strPress + "0";
|
||||
break;
|
||||
case 4:
|
||||
return strPress + "0";
|
||||
break;
|
||||
case 5:
|
||||
return strPress;
|
||||
break;
|
||||
default:
|
||||
return "-99999";
|
||||
}
|
||||
}
|
||||
|
||||
String readDataSensor() {
|
||||
String wx, tempStr, humStr, presStr;
|
||||
float newHum;
|
||||
|
||||
float newTemp = bme.readTemperature();
|
||||
#if defined(BME280Sensor) || defined(BME680Sensor)
|
||||
newHum = bme.readHumidity();
|
||||
#endif
|
||||
#ifdef BMP280Sensor
|
||||
newHum = 0;
|
||||
#endif
|
||||
float newPress = (bme.readPressure() / 100.0F);
|
||||
|
||||
#ifdef BME680Sensor
|
||||
float newGas = bme.gas_resistance / 1000.0; // in Kilo ohms
|
||||
#endif
|
||||
|
||||
//bme.readAltitude(SEALEVELPRESSURE_HPA) // this is for approximate Altitude Calculation.
|
||||
|
||||
if (isnan(newTemp) || isnan(newHum) || isnan(newPress)) {
|
||||
Serial.println("BME/BMP Module data failed");
|
||||
wx = ".../...g...t...r...p...P...h..b.....";
|
||||
fifthLine = "";
|
||||
return wx;
|
||||
} else {
|
||||
tempStr = generateTempString((newTemp * 1.8) + 32);
|
||||
#if defined(BME280Sensor) || defined(BME680Sensor)
|
||||
humStr = generateHumString(newHum);
|
||||
#endif
|
||||
#ifdef BMP280Sensor
|
||||
humStr = "..";
|
||||
#endif
|
||||
presStr = generatePresString(newPress + (HEIGHT_CORRECTION/CORRECTION_FACTOR));
|
||||
fifthLine = "BME-> " + String(int(newTemp))+"C " + humStr + "% " + presStr.substring(0,4) + "hPa";
|
||||
wx = ".../...g...t" + tempStr + "r...p...P...h" + humStr + "b" + presStr;
|
||||
#ifdef BME680Sensor
|
||||
wx += "Gas: " + String(newGas) + "Kohms ";
|
||||
#endif
|
||||
return wx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -3,84 +3,229 @@
|
|||
#include "configuration.h"
|
||||
#include "display.h"
|
||||
|
||||
Configuration::Configuration() {
|
||||
_filePath = "/igate_conf.json";
|
||||
if (!SPIFFS.begin(false)) {
|
||||
Serial.println("SPIFFS Mount Failed");
|
||||
return;
|
||||
}
|
||||
readFile(SPIFFS, _filePath.c_str());
|
||||
}
|
||||
|
||||
void Configuration::readFile(fs::FS &fs, const char *fileName) {
|
||||
|
||||
void Configuration::writeFile() {
|
||||
Serial.println("Saving config..");
|
||||
|
||||
StaticJsonDocument<1536> data;
|
||||
File configFile = fs.open(fileName, "r");
|
||||
DeserializationError error = deserializeJson(data, configFile);
|
||||
if (error) {
|
||||
Serial.println("Failed to read file, using default configuration");
|
||||
File configFile = SPIFFS.open("/igate_conf.json", "w");
|
||||
|
||||
if (wifiAPs[0].ssid != "") { // We don't want to save Auto AP empty SSID
|
||||
for (int i = 0; i < wifiAPs.size(); i++) {
|
||||
data["wifi"]["AP"][i]["ssid"] = wifiAPs[i].ssid;
|
||||
data["wifi"]["AP"][i]["password"] = wifiAPs[i].password;
|
||||
data["wifi"]["AP"][i]["latitude"] = wifiAPs[i].latitude;
|
||||
data["wifi"]["AP"][i]["longitude"] = wifiAPs[i].longitude;
|
||||
}
|
||||
}
|
||||
|
||||
JsonArray WiFiArray = data["wifi"]["AP"];
|
||||
for (int i = 0; i < WiFiArray.size(); i++) {
|
||||
WiFi_AP wifiap;
|
||||
wifiap.ssid = WiFiArray[i]["ssid"].as<String>();
|
||||
wifiap.password = WiFiArray[i]["password"].as<String>();
|
||||
wifiap.latitude = WiFiArray[i]["latitude"].as<double>();
|
||||
wifiap.longitude = WiFiArray[i]["longitude"].as<double>();
|
||||
data["wifi"]["autoAP"]["password"] = wifiAutoAP.password;
|
||||
data["wifi"]["autoAP"]["powerOff"] = wifiAutoAP.powerOff;
|
||||
|
||||
wifiAPs.push_back(wifiap);
|
||||
}
|
||||
data["callsign"] = callsign;
|
||||
data["stationMode"] = stationMode;
|
||||
data["iGateComment"] = iGateComment;
|
||||
|
||||
callsign = data["callsign"].as<String>();
|
||||
stationMode = data["stationMode"].as<int>();
|
||||
iGateComment = data["iGateComment"].as<String>();
|
||||
beaconInterval = data["other"]["beaconInterval"].as<int>();
|
||||
igateSendsLoRaBeacons = data["other"]["igateSendsLoRaBeacons"].as<bool>();
|
||||
igateRepeatsLoRaPackets = data["other"]["igateRepeatsLoRaPackets"].as<bool>();
|
||||
rememberStationTime = data["other"]["rememberStationTime"].as<int>();
|
||||
sendBatteryVoltage = data["other"]["sendBatteryVoltage"].as<bool>();
|
||||
externalVoltageMeasurement = data["other"]["externalVoltageMeasurement"].as<bool>();
|
||||
externalVoltagePin = data["other"]["externalVoltagePin"].as<int>();
|
||||
data["other"]["beaconInterval"] = beaconInterval;
|
||||
data["other"]["igateSendsLoRaBeacons"] = igateSendsLoRaBeacons;
|
||||
data["other"]["igateRepeatsLoRaPackets"] = igateRepeatsLoRaPackets;
|
||||
data["other"]["rememberStationTime"] = rememberStationTime;
|
||||
data["other"]["sendBatteryVoltage"] = sendBatteryVoltage;
|
||||
data["other"]["externalVoltageMeasurement"] = externalVoltageMeasurement;
|
||||
data["other"]["externalVoltagePin"] = externalVoltagePin;
|
||||
|
||||
digi.comment = data["digi"]["comment"].as<String>();
|
||||
digi.latitude = data["digi"]["latitude"].as<double>();
|
||||
digi.longitude = data["digi"]["longitude"].as<double>();
|
||||
data["digi"]["comment"] = digi.comment;
|
||||
data["digi"]["latitude"] = digi.latitude;
|
||||
data["digi"]["longitude"] = digi.longitude;
|
||||
|
||||
aprs_is.passcode = data["aprs_is"]["passcode"].as<String>();
|
||||
aprs_is.server = data["aprs_is"]["server"].as<String>();
|
||||
aprs_is.port = data["aprs_is"]["port"].as<int>();
|
||||
aprs_is.reportingDistance = data["aprs_is"]["reportingDistance"].as<int>();
|
||||
data["aprs_is"]["passcode"] = aprs_is.passcode;
|
||||
data["aprs_is"]["server"] = aprs_is.server;
|
||||
data["aprs_is"]["port"] = aprs_is.port;
|
||||
data["aprs_is"]["reportingDistance"] = aprs_is.reportingDistance;
|
||||
|
||||
loramodule.iGateFreq = data["lora"]["iGateFreq"].as<long>();
|
||||
loramodule.digirepeaterTxFreq = data["lora"]["digirepeaterTxFreq"].as<long>();
|
||||
loramodule.digirepeaterRxFreq = data["lora"]["digirepeaterRxFreq"].as<long>();
|
||||
loramodule.spreadingFactor = data["lora"]["spreadingFactor"].as<int>();
|
||||
loramodule.signalBandwidth = data["lora"]["signalBandwidth"].as<long>();
|
||||
loramodule.codingRate4 = data["lora"]["codingRate4"].as<int>();
|
||||
loramodule.power = data["lora"]["power"].as<int>();
|
||||
data["lora"]["iGateFreq"] = loramodule.iGateFreq;
|
||||
data["lora"]["digirepeaterTxFreq"] = loramodule.digirepeaterTxFreq;
|
||||
data["lora"]["digirepeaterRxFreq"] = loramodule.digirepeaterRxFreq;
|
||||
data["lora"]["spreadingFactor"] = loramodule.spreadingFactor;
|
||||
data["lora"]["signalBandwidth"] = loramodule.signalBandwidth;
|
||||
data["lora"]["codingRate4"] = loramodule.codingRate4;
|
||||
data["lora"]["power"] = loramodule.power;
|
||||
|
||||
display.alwaysOn = data["display"]["alwaysOn"].as<bool>();
|
||||
display.timeout = data["display"]["timeout"].as<int>();
|
||||
display.turn180 = data["display"]["turn180"].as<bool>();
|
||||
data["display"]["alwaysOn"] = display.alwaysOn;
|
||||
data["display"]["timeout"] = display.timeout;
|
||||
data["display"]["turn180"] = display.turn180;
|
||||
|
||||
syslog.active = data["syslog"]["active"].as<bool>();
|
||||
syslog.server = data["syslog"]["server"].as<String>();
|
||||
syslog.port = data["syslog"]["port"].as<int>();
|
||||
data["syslog"]["active"] = syslog.active;
|
||||
data["syslog"]["server"] = syslog.server;
|
||||
data["syslog"]["port"] = syslog.port;
|
||||
|
||||
bme.active = data["bme"]["active"].as<bool>();
|
||||
data["bme"]["active"] = bme.active;
|
||||
|
||||
ota.username = data["ota"]["username"].as<String>();
|
||||
ota.password = data["ota"]["password"].as<String>();
|
||||
data["ota"]["username"] = ota.username;
|
||||
data["ota"]["password"] = ota.password;
|
||||
|
||||
serializeJson(data, configFile);
|
||||
|
||||
configFile.close();
|
||||
|
||||
Serial.println("Config saved");
|
||||
}
|
||||
|
||||
void Configuration::validateConfigFile(String currentBeaconCallsign) {
|
||||
if (currentBeaconCallsign == "NOCALL-10") {
|
||||
Serial.println("Change Callsign in /data/igate_conf.json");
|
||||
show_display("------- ERROR -------", "Change your settings", "on 'igate_conf.json'", "--> File System image", 0);
|
||||
while (true) {
|
||||
delay(1000);
|
||||
bool Configuration::readFile() {
|
||||
Serial.println("Reading config..");
|
||||
|
||||
File configFile = SPIFFS.open("/igate_conf.json", "r");
|
||||
|
||||
if (configFile) {
|
||||
StaticJsonDocument<1536> data;
|
||||
|
||||
DeserializationError error = deserializeJson(data, configFile);
|
||||
if (error) {
|
||||
Serial.println("Failed to read file, using default configuration");
|
||||
}
|
||||
|
||||
JsonArray WiFiArray = data["wifi"]["AP"];
|
||||
for (int i = 0; i < WiFiArray.size(); i++) {
|
||||
WiFi_AP wifiap;
|
||||
wifiap.ssid = WiFiArray[i]["ssid"].as<String>();
|
||||
wifiap.password = WiFiArray[i]["password"].as<String>();
|
||||
wifiap.latitude = WiFiArray[i]["latitude"].as<double>();
|
||||
wifiap.longitude = WiFiArray[i]["longitude"].as<double>();
|
||||
|
||||
wifiAPs.push_back(wifiap);
|
||||
}
|
||||
|
||||
wifiAutoAP.password = data["wifi"]["autoAP"]["password"].as<String>();
|
||||
wifiAutoAP.powerOff = data["wifi"]["autoAP"]["powerOff"].as<int>();
|
||||
|
||||
callsign = data["callsign"].as<String>();
|
||||
stationMode = data["stationMode"].as<int>();
|
||||
iGateComment = data["iGateComment"].as<String>();
|
||||
beaconInterval = data["other"]["beaconInterval"].as<int>();
|
||||
igateSendsLoRaBeacons = data["other"]["igateSendsLoRaBeacons"].as<bool>();
|
||||
igateRepeatsLoRaPackets = data["other"]["igateRepeatsLoRaPackets"].as<bool>();
|
||||
rememberStationTime = data["other"]["rememberStationTime"].as<int>();
|
||||
sendBatteryVoltage = data["other"]["sendBatteryVoltage"].as<bool>();
|
||||
externalVoltageMeasurement = data["other"]["externalVoltageMeasurement"].as<bool>();
|
||||
externalVoltagePin = data["other"]["externalVoltagePin"].as<int>();
|
||||
|
||||
digi.comment = data["digi"]["comment"].as<String>();
|
||||
digi.latitude = data["digi"]["latitude"].as<double>();
|
||||
digi.longitude = data["digi"]["longitude"].as<double>();
|
||||
|
||||
aprs_is.passcode = data["aprs_is"]["passcode"].as<String>();
|
||||
aprs_is.server = data["aprs_is"]["server"].as<String>();
|
||||
aprs_is.port = data["aprs_is"]["port"].as<int>();
|
||||
aprs_is.reportingDistance = data["aprs_is"]["reportingDistance"].as<int>();
|
||||
|
||||
loramodule.iGateFreq = data["lora"]["iGateFreq"].as<long>();
|
||||
loramodule.digirepeaterTxFreq = data["lora"]["digirepeaterTxFreq"].as<long>();
|
||||
loramodule.digirepeaterRxFreq = data["lora"]["digirepeaterRxFreq"].as<long>();
|
||||
loramodule.spreadingFactor = data["lora"]["spreadingFactor"].as<int>();
|
||||
loramodule.signalBandwidth = data["lora"]["signalBandwidth"].as<long>();
|
||||
loramodule.codingRate4 = data["lora"]["codingRate4"].as<int>();
|
||||
loramodule.power = data["lora"]["power"].as<int>();
|
||||
|
||||
display.alwaysOn = data["display"]["alwaysOn"].as<bool>();
|
||||
display.timeout = data["display"]["timeout"].as<int>();
|
||||
display.turn180 = data["display"]["turn180"].as<bool>();
|
||||
|
||||
syslog.active = data["syslog"]["active"].as<bool>();
|
||||
syslog.server = data["syslog"]["server"].as<String>();
|
||||
syslog.port = data["syslog"]["port"].as<int>();
|
||||
|
||||
bme.active = data["bme"]["active"].as<bool>();
|
||||
|
||||
ota.username = data["ota"]["username"].as<String>();
|
||||
ota.password = data["ota"]["password"].as<String>();
|
||||
|
||||
if (wifiAPs.size() == 0) { // If we don't have any WiFi's from config we need to add "empty" SSID for AUTO AP
|
||||
WiFi_AP wifiap;
|
||||
wifiap.ssid = "";
|
||||
wifiap.password = "";
|
||||
wifiap.latitude = 0.0;
|
||||
wifiap.longitude = 0.0;
|
||||
|
||||
wifiAPs.push_back(wifiap);
|
||||
}
|
||||
configFile.close();
|
||||
Serial.println("Config read successfuly");
|
||||
return true;
|
||||
} else {
|
||||
Serial.println("Config file not found");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Configuration::init() {
|
||||
WiFi_AP wifiap;
|
||||
wifiap.ssid = "";
|
||||
wifiap.password = "";
|
||||
wifiap.latitude = 0.0;
|
||||
wifiap.longitude = 0.0;
|
||||
wifiAPs.push_back(wifiap);
|
||||
|
||||
wifiAutoAP.password = "1234567890";
|
||||
wifiAutoAP.powerOff = 15;
|
||||
|
||||
callsign = "N0CALL";
|
||||
stationMode = 1;
|
||||
iGateComment = "LoRa_APRS_iGate Development";
|
||||
|
||||
digi.comment = "LoRa_APRS_iGate Development";
|
||||
digi.latitude = 0.0;
|
||||
digi.longitude = 0.0;
|
||||
|
||||
aprs_is.passcode = "XYZVW";
|
||||
aprs_is.server = "rotate.aprs2.net";
|
||||
aprs_is.port = 14580;
|
||||
aprs_is.reportingDistance = 30;
|
||||
|
||||
loramodule.iGateFreq = 433775000;
|
||||
loramodule.digirepeaterTxFreq = 433775000;
|
||||
loramodule.digirepeaterRxFreq = 433900000;
|
||||
loramodule.spreadingFactor = 12;
|
||||
loramodule.signalBandwidth = 125000;
|
||||
loramodule.codingRate4 = 5;
|
||||
loramodule.power = 20;
|
||||
|
||||
display.alwaysOn = true;
|
||||
display.timeout = 4;
|
||||
display.turn180 = false;
|
||||
|
||||
syslog.active = false;
|
||||
syslog.server = "192.168.0.100";
|
||||
syslog.port = 514;
|
||||
|
||||
bme.active = false;
|
||||
|
||||
ota.username = "";
|
||||
ota.password = "";
|
||||
|
||||
beaconInterval = 15;
|
||||
igateSendsLoRaBeacons = false;
|
||||
igateRepeatsLoRaPackets = false;
|
||||
rememberStationTime = 30;
|
||||
sendBatteryVoltage = false;
|
||||
externalVoltageMeasurement = false;
|
||||
externalVoltagePin = 34;
|
||||
|
||||
Serial.println("todo escrito");
|
||||
}
|
||||
|
||||
Configuration::Configuration() {
|
||||
if (!SPIFFS.begin(false)) {
|
||||
Serial.println("SPIFFS Mount Failed");
|
||||
return;
|
||||
} else {
|
||||
Serial.println("montado");
|
||||
}
|
||||
bool exists = SPIFFS.exists("/igate_conf.json");
|
||||
if (!exists) {
|
||||
init();
|
||||
writeFile();
|
||||
ESP.restart();
|
||||
}
|
||||
readFile();
|
||||
}
|
||||
|
|
@ -7,61 +7,68 @@
|
|||
|
||||
class WiFi_AP {
|
||||
public:
|
||||
String ssid;
|
||||
String password;
|
||||
double latitude;
|
||||
double longitude;
|
||||
String ssid;
|
||||
String password;
|
||||
double latitude;
|
||||
double longitude;
|
||||
};
|
||||
|
||||
class WiFi_Auto_AP {
|
||||
public:
|
||||
String password;
|
||||
int powerOff;
|
||||
};
|
||||
|
||||
|
||||
class DIGI {
|
||||
public:
|
||||
String comment;
|
||||
double latitude;
|
||||
double longitude;
|
||||
String comment;
|
||||
double latitude;
|
||||
double longitude;
|
||||
};
|
||||
|
||||
class APRS_IS {
|
||||
public:
|
||||
String passcode;
|
||||
String server;
|
||||
int port;
|
||||
int reportingDistance;
|
||||
String passcode;
|
||||
String server;
|
||||
int port;
|
||||
int reportingDistance;
|
||||
};
|
||||
|
||||
class LoraModule {
|
||||
public:
|
||||
long iGateFreq;
|
||||
long digirepeaterTxFreq;
|
||||
long digirepeaterRxFreq;
|
||||
int spreadingFactor;
|
||||
long signalBandwidth;
|
||||
int codingRate4;
|
||||
int power;
|
||||
long iGateFreq;
|
||||
long digirepeaterTxFreq;
|
||||
long digirepeaterRxFreq;
|
||||
int spreadingFactor;
|
||||
long signalBandwidth;
|
||||
int codingRate4;
|
||||
int power;
|
||||
};
|
||||
|
||||
class Display {
|
||||
public:
|
||||
bool alwaysOn;
|
||||
int timeout;
|
||||
bool turn180;
|
||||
bool alwaysOn;
|
||||
int timeout;
|
||||
bool turn180;
|
||||
};
|
||||
|
||||
class SYSLOG {
|
||||
public:
|
||||
bool active;
|
||||
String server;
|
||||
int port;
|
||||
bool active;
|
||||
String server;
|
||||
int port;
|
||||
};
|
||||
|
||||
class BME {
|
||||
public:
|
||||
bool active;
|
||||
bool active;
|
||||
};
|
||||
|
||||
class OTA {
|
||||
public:
|
||||
String username;
|
||||
String password;
|
||||
String username;
|
||||
String password;
|
||||
};
|
||||
|
||||
|
||||
|
|
@ -69,31 +76,33 @@ public:
|
|||
class Configuration {
|
||||
public:
|
||||
|
||||
String callsign;
|
||||
int stationMode;
|
||||
String iGateComment;
|
||||
int beaconInterval;
|
||||
bool igateSendsLoRaBeacons;
|
||||
bool igateRepeatsLoRaPackets;
|
||||
int rememberStationTime;
|
||||
bool sendBatteryVoltage;
|
||||
bool externalVoltageMeasurement;
|
||||
int externalVoltagePin;
|
||||
std::vector<WiFi_AP> wifiAPs;
|
||||
DIGI digi;
|
||||
APRS_IS aprs_is;
|
||||
LoraModule loramodule;
|
||||
Display display;
|
||||
SYSLOG syslog;
|
||||
BME bme;
|
||||
OTA ota;
|
||||
String callsign;
|
||||
int stationMode;
|
||||
String iGateComment;
|
||||
int beaconInterval;
|
||||
bool igateSendsLoRaBeacons;
|
||||
bool igateRepeatsLoRaPackets;
|
||||
int rememberStationTime;
|
||||
bool sendBatteryVoltage;
|
||||
bool externalVoltageMeasurement;
|
||||
int externalVoltagePin;
|
||||
std::vector<WiFi_AP> wifiAPs;
|
||||
WiFi_Auto_AP wifiAutoAP;
|
||||
DIGI digi;
|
||||
APRS_IS aprs_is;
|
||||
LoraModule loramodule;
|
||||
Display display;
|
||||
SYSLOG syslog;
|
||||
BME bme;
|
||||
OTA ota;
|
||||
|
||||
|
||||
Configuration();
|
||||
void validateConfigFile(String currentBeaconCallsign);
|
||||
void init();
|
||||
void writeFile();
|
||||
Configuration();
|
||||
|
||||
private:
|
||||
void readFile(fs::FS &fs, const char *fileName) ;
|
||||
String _filePath;
|
||||
bool readFile();
|
||||
String _filePath;
|
||||
};
|
||||
|
||||
#endif
|
||||
254
src/display.cpp
254
src/display.cpp
|
|
@ -10,156 +10,156 @@ Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
|
|||
extern Configuration Config;
|
||||
|
||||
void setup_display() {
|
||||
Wire.begin(OLED_SDA, OLED_SCL);
|
||||
Wire.begin(OLED_SDA, OLED_SCL);
|
||||
|
||||
if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
|
||||
Serial.println(F("SSD1306 allocation failed"));
|
||||
for(;;); // Don't proceed, loop forever
|
||||
}
|
||||
if (Config.display.turn180) {
|
||||
display.setRotation(2);
|
||||
}
|
||||
display.clearDisplay();
|
||||
display.setTextColor(WHITE);
|
||||
display.setTextSize(1);
|
||||
display.setCursor(0, 0);
|
||||
display.ssd1306_command(SSD1306_SETCONTRAST);
|
||||
display.ssd1306_command(1);
|
||||
display.display();
|
||||
delay(1000);
|
||||
if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
|
||||
Serial.println(F("SSD1306 allocation failed"));
|
||||
for(;;); // Don't proceed, loop forever
|
||||
}
|
||||
if (Config.display.turn180) {
|
||||
display.setRotation(2);
|
||||
}
|
||||
display.clearDisplay();
|
||||
display.setTextColor(WHITE);
|
||||
display.setTextSize(1);
|
||||
display.setCursor(0, 0);
|
||||
display.ssd1306_command(SSD1306_SETCONTRAST);
|
||||
display.ssd1306_command(1);
|
||||
display.display();
|
||||
delay(1000);
|
||||
}
|
||||
|
||||
void display_toggle(bool toggle) {
|
||||
if (toggle) {
|
||||
display.ssd1306_command(SSD1306_DISPLAYON);
|
||||
} else {
|
||||
display.ssd1306_command(SSD1306_DISPLAYOFF);
|
||||
}
|
||||
if (toggle) {
|
||||
display.ssd1306_command(SSD1306_DISPLAYON);
|
||||
} else {
|
||||
display.ssd1306_command(SSD1306_DISPLAYOFF);
|
||||
}
|
||||
}
|
||||
|
||||
void show_display(String line1, int wait) {
|
||||
display.clearDisplay();
|
||||
display.setTextColor(WHITE);
|
||||
display.setTextSize(1);
|
||||
display.setCursor(0, 0);
|
||||
display.println(line1);
|
||||
display.ssd1306_command(SSD1306_SETCONTRAST);
|
||||
display.ssd1306_command(1);
|
||||
display.display();
|
||||
delay(wait);
|
||||
display.clearDisplay();
|
||||
display.setTextColor(WHITE);
|
||||
display.setTextSize(1);
|
||||
display.setCursor(0, 0);
|
||||
display.println(line1);
|
||||
display.ssd1306_command(SSD1306_SETCONTRAST);
|
||||
display.ssd1306_command(1);
|
||||
display.display();
|
||||
delay(wait);
|
||||
}
|
||||
|
||||
void show_display(String line1, String line2, int wait) {
|
||||
display.clearDisplay();
|
||||
display.setTextColor(WHITE);
|
||||
display.setTextSize(1);
|
||||
display.setCursor(0, 0);
|
||||
display.println(line1);
|
||||
display.setCursor(0, 8);
|
||||
display.println(line2);
|
||||
display.ssd1306_command(SSD1306_SETCONTRAST);
|
||||
display.ssd1306_command(1);
|
||||
display.display();
|
||||
delay(wait);
|
||||
display.clearDisplay();
|
||||
display.setTextColor(WHITE);
|
||||
display.setTextSize(1);
|
||||
display.setCursor(0, 0);
|
||||
display.println(line1);
|
||||
display.setCursor(0, 8);
|
||||
display.println(line2);
|
||||
display.ssd1306_command(SSD1306_SETCONTRAST);
|
||||
display.ssd1306_command(1);
|
||||
display.display();
|
||||
delay(wait);
|
||||
}
|
||||
|
||||
void show_display(String line1, String line2, String line3, int wait) {
|
||||
display.clearDisplay();
|
||||
display.setTextColor(WHITE);
|
||||
display.setTextSize(1);
|
||||
display.setCursor(0, 0);
|
||||
display.println(line1);
|
||||
display.setCursor(0, 8);
|
||||
display.println(line2);
|
||||
display.setCursor(0, 16);
|
||||
display.println(line3);
|
||||
display.ssd1306_command(SSD1306_SETCONTRAST);
|
||||
display.ssd1306_command(1);
|
||||
display.display();
|
||||
delay(wait);
|
||||
display.clearDisplay();
|
||||
display.setTextColor(WHITE);
|
||||
display.setTextSize(1);
|
||||
display.setCursor(0, 0);
|
||||
display.println(line1);
|
||||
display.setCursor(0, 8);
|
||||
display.println(line2);
|
||||
display.setCursor(0, 16);
|
||||
display.println(line3);
|
||||
display.ssd1306_command(SSD1306_SETCONTRAST);
|
||||
display.ssd1306_command(1);
|
||||
display.display();
|
||||
delay(wait);
|
||||
}
|
||||
|
||||
void show_display(String line1, String line2, String line3, String line4, int wait) {
|
||||
display.clearDisplay();
|
||||
display.setTextColor(WHITE);
|
||||
display.setTextSize(1);
|
||||
display.setCursor(0, 0);
|
||||
display.println(line1);
|
||||
display.setCursor(0, 8);
|
||||
display.println(line2);
|
||||
display.setCursor(0, 16);
|
||||
display.println(line3);
|
||||
display.setCursor(0, 24);
|
||||
display.println(line4);
|
||||
display.ssd1306_command(SSD1306_SETCONTRAST);
|
||||
display.ssd1306_command(1);
|
||||
display.display();
|
||||
delay(wait);
|
||||
display.clearDisplay();
|
||||
display.setTextColor(WHITE);
|
||||
display.setTextSize(1);
|
||||
display.setCursor(0, 0);
|
||||
display.println(line1);
|
||||
display.setCursor(0, 8);
|
||||
display.println(line2);
|
||||
display.setCursor(0, 16);
|
||||
display.println(line3);
|
||||
display.setCursor(0, 24);
|
||||
display.println(line4);
|
||||
display.ssd1306_command(SSD1306_SETCONTRAST);
|
||||
display.ssd1306_command(1);
|
||||
display.display();
|
||||
delay(wait);
|
||||
}
|
||||
|
||||
void show_display(String line1, String line2, String line3, String line4, String line5, int wait) {
|
||||
display.clearDisplay();
|
||||
display.setTextColor(WHITE);
|
||||
display.setTextSize(1);
|
||||
display.setCursor(0, 0);
|
||||
display.println(line1);
|
||||
display.setCursor(0, 8);
|
||||
display.println(line2);
|
||||
display.setCursor(0, 16);
|
||||
display.println(line3);
|
||||
display.setCursor(0, 24);
|
||||
display.println(line4);
|
||||
display.setCursor(0, 32);
|
||||
display.println(line5);
|
||||
display.ssd1306_command(SSD1306_SETCONTRAST);
|
||||
display.ssd1306_command(1);
|
||||
display.display();
|
||||
delay(wait);
|
||||
display.clearDisplay();
|
||||
display.setTextColor(WHITE);
|
||||
display.setTextSize(1);
|
||||
display.setCursor(0, 0);
|
||||
display.println(line1);
|
||||
display.setCursor(0, 8);
|
||||
display.println(line2);
|
||||
display.setCursor(0, 16);
|
||||
display.println(line3);
|
||||
display.setCursor(0, 24);
|
||||
display.println(line4);
|
||||
display.setCursor(0, 32);
|
||||
display.println(line5);
|
||||
display.ssd1306_command(SSD1306_SETCONTRAST);
|
||||
display.ssd1306_command(1);
|
||||
display.display();
|
||||
delay(wait);
|
||||
}
|
||||
|
||||
void show_display(String line1, String line2, String line3, String line4, String line5, String line6, int wait) {
|
||||
display.clearDisplay();
|
||||
display.setTextColor(WHITE);
|
||||
display.setTextSize(1);
|
||||
display.setCursor(0, 0);
|
||||
display.println(line1);
|
||||
display.setCursor(0, 8);
|
||||
display.println(line2);
|
||||
display.setCursor(0, 16);
|
||||
display.println(line3);
|
||||
display.setCursor(0, 24);
|
||||
display.println(line4);
|
||||
display.setCursor(0, 32);
|
||||
display.println(line5);
|
||||
display.setCursor(0, 40);
|
||||
display.println(line6);
|
||||
display.ssd1306_command(SSD1306_SETCONTRAST);
|
||||
display.ssd1306_command(1);
|
||||
display.display();
|
||||
delay(wait);
|
||||
display.clearDisplay();
|
||||
display.setTextColor(WHITE);
|
||||
display.setTextSize(1);
|
||||
display.setCursor(0, 0);
|
||||
display.println(line1);
|
||||
display.setCursor(0, 8);
|
||||
display.println(line2);
|
||||
display.setCursor(0, 16);
|
||||
display.println(line3);
|
||||
display.setCursor(0, 24);
|
||||
display.println(line4);
|
||||
display.setCursor(0, 32);
|
||||
display.println(line5);
|
||||
display.setCursor(0, 40);
|
||||
display.println(line6);
|
||||
display.ssd1306_command(SSD1306_SETCONTRAST);
|
||||
display.ssd1306_command(1);
|
||||
display.display();
|
||||
delay(wait);
|
||||
}
|
||||
|
||||
void show_display(String line1, String line2, String line3, String line4, String line5, String line6, String line7, int wait) {
|
||||
display.clearDisplay();
|
||||
display.setTextColor(WHITE);
|
||||
display.setTextSize(2);
|
||||
display.setCursor(0, 0);
|
||||
display.println(line1);
|
||||
display.setTextSize(1);
|
||||
display.setCursor(0, 16);
|
||||
display.println(line2);
|
||||
display.setCursor(0, 24);
|
||||
display.println(line3);
|
||||
display.setCursor(0, 32);
|
||||
display.println(line4);
|
||||
display.setCursor(0, 40);
|
||||
display.println(line5);
|
||||
display.setCursor(0, 48);
|
||||
display.println(line6);
|
||||
display.setCursor(0, 56);
|
||||
display.println(line7);
|
||||
display.ssd1306_command(SSD1306_SETCONTRAST);
|
||||
display.ssd1306_command(1);
|
||||
display.display();
|
||||
delay(wait);
|
||||
display.clearDisplay();
|
||||
display.setTextColor(WHITE);
|
||||
display.setTextSize(2);
|
||||
display.setCursor(0, 0);
|
||||
display.println(line1);
|
||||
display.setTextSize(1);
|
||||
display.setCursor(0, 16);
|
||||
display.println(line2);
|
||||
display.setCursor(0, 24);
|
||||
display.println(line3);
|
||||
display.setCursor(0, 32);
|
||||
display.println(line4);
|
||||
display.setCursor(0, 40);
|
||||
display.println(line5);
|
||||
display.setCursor(0, 48);
|
||||
display.println(line6);
|
||||
display.setCursor(0, 56);
|
||||
display.println(line7);
|
||||
display.ssd1306_command(SSD1306_SETCONTRAST);
|
||||
display.ssd1306_command(1);
|
||||
display.display();
|
||||
delay(wait);
|
||||
}
|
||||
|
|
@ -11,188 +11,188 @@ String distance;
|
|||
|
||||
namespace GPS_Utils {
|
||||
|
||||
String double2string(double n, int ndec) {
|
||||
String r = "";
|
||||
if (n>-1 && n<0) {
|
||||
r = "-";
|
||||
}
|
||||
int v = n;
|
||||
r += v;
|
||||
r += '.';
|
||||
for (int i=0;i<ndec;i++) {
|
||||
n -= v;
|
||||
n = 10 * abs(n);
|
||||
v = n;
|
||||
r += v;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
String double2string(double n, int ndec) {
|
||||
String r = "";
|
||||
if (n>-1 && n<0) {
|
||||
r = "-";
|
||||
}
|
||||
int v = n;
|
||||
r += v;
|
||||
r += '.';
|
||||
for (int i=0;i<ndec;i++) {
|
||||
n -= v;
|
||||
n = 10 * abs(n);
|
||||
v = n;
|
||||
r += v;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
String processLatitudeAPRS(double lat) {
|
||||
String degrees = double2string(lat,6);
|
||||
String north_south, latitude, convDeg3;
|
||||
float convDeg, convDeg2;
|
||||
String processLatitudeAPRS(double lat) {
|
||||
String degrees = double2string(lat,6);
|
||||
String north_south, latitude, convDeg3;
|
||||
float convDeg, convDeg2;
|
||||
|
||||
if (abs(degrees.toFloat()) < 10) {
|
||||
latitude += "0";
|
||||
if (abs(degrees.toFloat()) < 10) {
|
||||
latitude += "0";
|
||||
}
|
||||
Serial.println(latitude);
|
||||
if (degrees.indexOf("-") == 0) {
|
||||
north_south = "S";
|
||||
latitude += degrees.substring(1,degrees.indexOf("."));
|
||||
} else {
|
||||
north_south = "N";
|
||||
latitude += degrees.substring(0,degrees.indexOf("."));
|
||||
}
|
||||
convDeg = abs(degrees.toFloat()) - abs(int(degrees.toFloat()));
|
||||
convDeg2 = (convDeg * 60)/100;
|
||||
convDeg3 = String(convDeg2,6);
|
||||
latitude += convDeg3.substring(convDeg3.indexOf(".")+1,convDeg3.indexOf(".")+3) + "." + convDeg3.substring(convDeg3.indexOf(".")+3,convDeg3.indexOf(".")+5);
|
||||
latitude += north_south;
|
||||
return latitude;
|
||||
}
|
||||
Serial.println(latitude);
|
||||
if (degrees.indexOf("-") == 0) {
|
||||
north_south = "S";
|
||||
latitude += degrees.substring(1,degrees.indexOf("."));
|
||||
} else {
|
||||
north_south = "N";
|
||||
latitude += degrees.substring(0,degrees.indexOf("."));
|
||||
}
|
||||
convDeg = abs(degrees.toFloat()) - abs(int(degrees.toFloat()));
|
||||
convDeg2 = (convDeg * 60)/100;
|
||||
convDeg3 = String(convDeg2,6);
|
||||
latitude += convDeg3.substring(convDeg3.indexOf(".")+1,convDeg3.indexOf(".")+3) + "." + convDeg3.substring(convDeg3.indexOf(".")+3,convDeg3.indexOf(".")+5);
|
||||
latitude += north_south;
|
||||
return latitude;
|
||||
}
|
||||
|
||||
String processLongitudeAPRS(double lon) {
|
||||
String degrees = double2string(lon,6);
|
||||
String east_west, longitude, convDeg3;
|
||||
float convDeg, convDeg2;
|
||||
|
||||
if (abs(degrees.toFloat()) < 100) {
|
||||
longitude += "0";
|
||||
String processLongitudeAPRS(double lon) {
|
||||
String degrees = double2string(lon,6);
|
||||
String east_west, longitude, convDeg3;
|
||||
float convDeg, convDeg2;
|
||||
|
||||
if (abs(degrees.toFloat()) < 100) {
|
||||
longitude += "0";
|
||||
}
|
||||
if (abs(degrees.toFloat()) < 10) {
|
||||
longitude += "0";
|
||||
}
|
||||
if (degrees.indexOf("-") == 0) {
|
||||
east_west = "W";
|
||||
longitude += degrees.substring(1,degrees.indexOf("."));
|
||||
} else {
|
||||
east_west = "E";
|
||||
longitude += degrees.substring(0,degrees.indexOf("."));
|
||||
}
|
||||
convDeg = abs(degrees.toFloat()) - abs(int(degrees.toFloat()));
|
||||
convDeg2 = (convDeg * 60)/100;
|
||||
convDeg3 = String(convDeg2,6);
|
||||
longitude += convDeg3.substring(convDeg3.indexOf(".")+1,convDeg3.indexOf(".")+3) + "." + convDeg3.substring(convDeg3.indexOf(".")+3,convDeg3.indexOf(".")+5);
|
||||
longitude += east_west;
|
||||
return longitude;
|
||||
}
|
||||
if (abs(degrees.toFloat()) < 10) {
|
||||
longitude += "0";
|
||||
}
|
||||
if (degrees.indexOf("-") == 0) {
|
||||
east_west = "W";
|
||||
longitude += degrees.substring(1,degrees.indexOf("."));
|
||||
} else {
|
||||
east_west = "E";
|
||||
longitude += degrees.substring(0,degrees.indexOf("."));
|
||||
}
|
||||
convDeg = abs(degrees.toFloat()) - abs(int(degrees.toFloat()));
|
||||
convDeg2 = (convDeg * 60)/100;
|
||||
convDeg3 = String(convDeg2,6);
|
||||
longitude += convDeg3.substring(convDeg3.indexOf(".")+1,convDeg3.indexOf(".")+3) + "." + convDeg3.substring(convDeg3.indexOf(".")+3,convDeg3.indexOf(".")+5);
|
||||
longitude += east_west;
|
||||
return longitude;
|
||||
}
|
||||
|
||||
String generateBeacon() {
|
||||
String stationLatitude, stationLongitude, beaconPacket;
|
||||
if (stationMode==1 || stationMode==2 || (stationMode==5 && WiFi.status()==WL_CONNECTED && espClient.connected())) {
|
||||
stationLatitude = processLatitudeAPRS(currentWiFi->latitude);
|
||||
stationLongitude = processLongitudeAPRS(currentWiFi->longitude);
|
||||
beaconPacket = Config.callsign + ">APLRG1,WIDE1-1";
|
||||
if (stationMode!=6) {
|
||||
beaconPacket += ",qAC";
|
||||
}
|
||||
beaconPacket += ":=" + stationLatitude + "L" + stationLongitude;
|
||||
if (stationMode==1) {
|
||||
beaconPacket += "&";
|
||||
} else {
|
||||
beaconPacket += "a";
|
||||
}
|
||||
beaconPacket += Config.iGateComment;
|
||||
} else { //stationMode 3, 4 and 5
|
||||
if (stationMode==5) {
|
||||
String generateBeacon() {
|
||||
String stationLatitude, stationLongitude, beaconPacket;
|
||||
if (stationMode==1 || stationMode==2 || (stationMode==5 && WiFi.status()==WL_CONNECTED && espClient.connected())) {
|
||||
stationLatitude = processLatitudeAPRS(currentWiFi->latitude);
|
||||
stationLongitude = processLongitudeAPRS(currentWiFi->longitude);
|
||||
beaconPacket = Config.callsign + ">APLRG1,WIDE1-1";
|
||||
if (stationMode!=6) {
|
||||
beaconPacket += ",qAC";
|
||||
}
|
||||
beaconPacket += ":=" + stationLatitude + "L" + stationLongitude;
|
||||
if (stationMode==1) {
|
||||
beaconPacket += "&";
|
||||
} else {
|
||||
beaconPacket += "a";
|
||||
}
|
||||
beaconPacket += Config.iGateComment;
|
||||
} else { //stationMode 3, 4 and 5
|
||||
if (stationMode==5) {
|
||||
stationLatitude = processLatitudeAPRS(currentWiFi->latitude);
|
||||
stationLongitude = processLongitudeAPRS(currentWiFi->longitude);
|
||||
} else {
|
||||
stationLatitude = processLatitudeAPRS(Config.digi.latitude);
|
||||
stationLongitude = processLongitudeAPRS(Config.digi.longitude);
|
||||
}
|
||||
beaconPacket = Config.callsign + ">APLRG1,WIDE1-1:=" + stationLatitude + "L" + stationLongitude + "#" + Config.digi.comment;
|
||||
}
|
||||
return beaconPacket;
|
||||
}
|
||||
|
||||
String generateiGateLoRaBeacon() {
|
||||
String stationLatitude, stationLongitude, beaconPacket;
|
||||
stationLatitude = processLatitudeAPRS(currentWiFi->latitude);
|
||||
stationLongitude = processLongitudeAPRS(currentWiFi->longitude);
|
||||
} else {
|
||||
stationLatitude = processLatitudeAPRS(Config.digi.latitude);
|
||||
stationLongitude = processLongitudeAPRS(Config.digi.longitude);
|
||||
}
|
||||
beaconPacket = Config.callsign + ">APLRG1,WIDE1-1:=" + stationLatitude + "L" + stationLongitude + "#" + Config.digi.comment;
|
||||
beaconPacket = Config.callsign + ">APLRG1,RFONLY:=" + stationLatitude + "L" + stationLongitude;
|
||||
if (Config.bme.active) {
|
||||
beaconPacket += "_";
|
||||
} else {
|
||||
beaconPacket += "a";
|
||||
}
|
||||
return beaconPacket;
|
||||
}
|
||||
return beaconPacket;
|
||||
}
|
||||
|
||||
String generateiGateLoRaBeacon() {
|
||||
String stationLatitude, stationLongitude, beaconPacket;
|
||||
stationLatitude = processLatitudeAPRS(currentWiFi->latitude);
|
||||
stationLongitude = processLongitudeAPRS(currentWiFi->longitude);
|
||||
beaconPacket = Config.callsign + ">APLRG1,RFONLY:=" + stationLatitude + "L" + stationLongitude;
|
||||
if (Config.bme.active) {
|
||||
beaconPacket += "_";
|
||||
} else {
|
||||
beaconPacket += "a";
|
||||
double calculateDistanceTo(double latitude, double longitude) {
|
||||
return TinyGPSPlus::distanceBetween(currentWiFi->latitude,currentWiFi->longitude, latitude, longitude) / 1000.0;
|
||||
}
|
||||
return beaconPacket;
|
||||
}
|
||||
|
||||
double calculateDistanceTo(double latitude, double longitude) {
|
||||
return TinyGPSPlus::distanceBetween(currentWiFi->latitude,currentWiFi->longitude, latitude, longitude) / 1000.0;
|
||||
}
|
||||
String decodeEncodedGPS(String packet) {
|
||||
String GPSPacket = packet.substring(packet.indexOf(":!")+3);
|
||||
String encodedLatitude = GPSPacket.substring(0,4);
|
||||
String encodedLongtitude = GPSPacket.substring(4,8);
|
||||
|
||||
String decodeEncodedGPS(String packet) {
|
||||
String GPSPacket = packet.substring(packet.indexOf(":!")+3);
|
||||
String encodedLatitude = GPSPacket.substring(0,4);
|
||||
String encodedLongtitude = GPSPacket.substring(4,8);
|
||||
|
||||
int Y1 = int(encodedLatitude[0]);
|
||||
int Y2 = int(encodedLatitude[1]);
|
||||
int Y3 = int(encodedLatitude[2]);
|
||||
int Y4 = int(encodedLatitude[3]);
|
||||
float decodedLatitude = 90.0 - ((((Y1-33) * pow(91,3)) + ((Y2-33) * pow(91,2)) + ((Y3-33) * 91) + Y4-33) / 380926.0);
|
||||
|
||||
int X1 = int(encodedLongtitude[0]);
|
||||
int X2 = int(encodedLongtitude[1]);
|
||||
int X3 = int(encodedLongtitude[2]);
|
||||
int X4 = int(encodedLongtitude[3]);
|
||||
float decodedLongitude = -180.0 + ((((X1-33) * pow(91,3)) + ((X2-33) * pow(91,2)) + ((X3-33) * 91) + X4-33) / 190463.0);
|
||||
distance = String(calculateDistanceTo(decodedLatitude, decodedLongitude),1);
|
||||
return String(decodedLatitude,5) + "N / " + String(decodedLongitude,5) + "E / " + distance + "km";
|
||||
}
|
||||
|
||||
String getReceivedGPS(String packet) {
|
||||
String infoGPS;
|
||||
if (packet.indexOf(":!") > 10) {
|
||||
infoGPS = packet.substring(packet.indexOf(":!")+2);
|
||||
} else if (packet.indexOf(":=") > 10) {
|
||||
infoGPS = packet.substring(packet.indexOf(":=")+2);
|
||||
int Y1 = int(encodedLatitude[0]);
|
||||
int Y2 = int(encodedLatitude[1]);
|
||||
int Y3 = int(encodedLatitude[2]);
|
||||
int Y4 = int(encodedLatitude[3]);
|
||||
float decodedLatitude = 90.0 - ((((Y1-33) * pow(91,3)) + ((Y2-33) * pow(91,2)) + ((Y3-33) * 91) + Y4-33) / 380926.0);
|
||||
|
||||
int X1 = int(encodedLongtitude[0]);
|
||||
int X2 = int(encodedLongtitude[1]);
|
||||
int X3 = int(encodedLongtitude[2]);
|
||||
int X4 = int(encodedLongtitude[3]);
|
||||
float decodedLongitude = -180.0 + ((((X1-33) * pow(91,3)) + ((X2-33) * pow(91,2)) + ((X3-33) * 91) + X4-33) / 190463.0);
|
||||
distance = String(calculateDistanceTo(decodedLatitude, decodedLongitude),1);
|
||||
return String(decodedLatitude,5) + "N / " + String(decodedLongitude,5) + "E / " + distance + "km";
|
||||
}
|
||||
String Latitude = infoGPS.substring(0,8);
|
||||
String Longitude = infoGPS.substring(9,18);
|
||||
|
||||
float convertedLatitude, convertedLongitude;
|
||||
String firstLatPart = Latitude.substring(0,2);
|
||||
String secondLatPart = Latitude.substring(2,4);
|
||||
String thirdLatPart = Latitude.substring(Latitude.indexOf(".")+1,Latitude.indexOf(".")+3);
|
||||
String firstLngPart = Longitude.substring(0,3);
|
||||
String secondLngPart = Longitude.substring(3,5);
|
||||
String thirdLngPart = Longitude.substring(Longitude.indexOf(".")+1,Longitude.indexOf(".")+3);
|
||||
convertedLatitude = firstLatPart.toFloat() + (secondLatPart.toFloat()/60) + (thirdLatPart.toFloat()/(60*100));
|
||||
convertedLongitude = firstLngPart.toFloat() + (secondLngPart.toFloat()/60) + (thirdLngPart.toFloat()/(60*100));
|
||||
|
||||
String LatSign = String(Latitude[7]);
|
||||
String LngSign = String(Longitude[8]);
|
||||
if (LatSign == "S") {
|
||||
convertedLatitude = -convertedLatitude;
|
||||
}
|
||||
if (LngSign == "W") {
|
||||
convertedLongitude = -convertedLongitude;
|
||||
}
|
||||
distance = String(calculateDistanceTo(convertedLatitude, convertedLongitude),1);
|
||||
return String(convertedLatitude,5) + "N / " + String(convertedLongitude,5) + "E / " + distance + "km";
|
||||
}
|
||||
String getReceivedGPS(String packet) {
|
||||
String infoGPS;
|
||||
if (packet.indexOf(":!") > 10) {
|
||||
infoGPS = packet.substring(packet.indexOf(":!")+2);
|
||||
} else if (packet.indexOf(":=") > 10) {
|
||||
infoGPS = packet.substring(packet.indexOf(":=")+2);
|
||||
}
|
||||
String Latitude = infoGPS.substring(0,8);
|
||||
String Longitude = infoGPS.substring(9,18);
|
||||
|
||||
String getDistance(String packet) {
|
||||
int encodedBytePosition = 0;
|
||||
if (packet.indexOf(":!") > 10) {
|
||||
encodedBytePosition = packet.indexOf(":!") + 14;
|
||||
float convertedLatitude, convertedLongitude;
|
||||
String firstLatPart = Latitude.substring(0,2);
|
||||
String secondLatPart = Latitude.substring(2,4);
|
||||
String thirdLatPart = Latitude.substring(Latitude.indexOf(".")+1,Latitude.indexOf(".")+3);
|
||||
String firstLngPart = Longitude.substring(0,3);
|
||||
String secondLngPart = Longitude.substring(3,5);
|
||||
String thirdLngPart = Longitude.substring(Longitude.indexOf(".")+1,Longitude.indexOf(".")+3);
|
||||
convertedLatitude = firstLatPart.toFloat() + (secondLatPart.toFloat()/60) + (thirdLatPart.toFloat()/(60*100));
|
||||
convertedLongitude = firstLngPart.toFloat() + (secondLngPart.toFloat()/60) + (thirdLngPart.toFloat()/(60*100));
|
||||
|
||||
String LatSign = String(Latitude[7]);
|
||||
String LngSign = String(Longitude[8]);
|
||||
if (LatSign == "S") {
|
||||
convertedLatitude = -convertedLatitude;
|
||||
}
|
||||
if (LngSign == "W") {
|
||||
convertedLongitude = -convertedLongitude;
|
||||
}
|
||||
distance = String(calculateDistanceTo(convertedLatitude, convertedLongitude),1);
|
||||
return String(convertedLatitude,5) + "N / " + String(convertedLongitude,5) + "E / " + distance + "km";
|
||||
}
|
||||
if (packet.indexOf(":=") > 10) {
|
||||
encodedBytePosition = packet.indexOf(":=") + 14;
|
||||
|
||||
String getDistance(String packet) {
|
||||
int encodedBytePosition = 0;
|
||||
if (packet.indexOf(":!") > 10) {
|
||||
encodedBytePosition = packet.indexOf(":!") + 14;
|
||||
}
|
||||
if (packet.indexOf(":=") > 10) {
|
||||
encodedBytePosition = packet.indexOf(":=") + 14;
|
||||
}
|
||||
if (encodedBytePosition != 0) {
|
||||
if (String(packet[encodedBytePosition]) == "G" || String(packet[encodedBytePosition]) == "Q" || String(packet[encodedBytePosition]) == "[" || String(packet[encodedBytePosition]) == "H") {
|
||||
return decodeEncodedGPS(packet);
|
||||
} else {
|
||||
return getReceivedGPS(packet);
|
||||
}
|
||||
} else {
|
||||
return " _ / _ / _ ";
|
||||
}
|
||||
}
|
||||
if (encodedBytePosition != 0) {
|
||||
if (String(packet[encodedBytePosition]) == "G" || String(packet[encodedBytePosition]) == "Q" || String(packet[encodedBytePosition]) == "[" || String(packet[encodedBytePosition]) == "H") {
|
||||
return decodeEncodedGPS(packet);
|
||||
} else {
|
||||
return getReceivedGPS(packet);
|
||||
}
|
||||
} else {
|
||||
return " _ / _ / _ ";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -26,197 +26,197 @@ float snr;
|
|||
|
||||
namespace LoRa_Utils {
|
||||
|
||||
void setFlag(void) {
|
||||
#ifdef HAS_SX126X
|
||||
transmissionFlag = true;
|
||||
#endif
|
||||
}
|
||||
void setFlag(void) {
|
||||
#ifdef HAS_SX126X
|
||||
transmissionFlag = true;
|
||||
#endif
|
||||
}
|
||||
|
||||
void setup() {
|
||||
#ifdef HAS_SX127X
|
||||
SPI.begin(LORA_SCK, LORA_MISO, LORA_MOSI, LORA_CS);
|
||||
LoRa.setPins(LORA_CS, LORA_RST, LORA_IRQ);
|
||||
long freq;
|
||||
if (stationMode==1 || stationMode==2) {
|
||||
freq = Config.loramodule.iGateFreq;
|
||||
} else {
|
||||
freq = Config.loramodule.digirepeaterTxFreq;
|
||||
void setup() {
|
||||
#ifdef HAS_SX127X
|
||||
SPI.begin(LORA_SCK, LORA_MISO, LORA_MOSI, LORA_CS);
|
||||
LoRa.setPins(LORA_CS, LORA_RST, LORA_IRQ);
|
||||
long freq;
|
||||
if (stationMode==1 || stationMode==2) {
|
||||
freq = Config.loramodule.iGateFreq;
|
||||
} else {
|
||||
freq = Config.loramodule.digirepeaterTxFreq;
|
||||
}
|
||||
if (!LoRa.begin(freq)) {
|
||||
Serial.println("Starting LoRa failed!");
|
||||
show_display("ERROR", "Starting LoRa failed!");
|
||||
while (true) {
|
||||
delay(1000);
|
||||
}
|
||||
}
|
||||
LoRa.setSpreadingFactor(Config.loramodule.spreadingFactor);
|
||||
LoRa.setSignalBandwidth(Config.loramodule.signalBandwidth);
|
||||
LoRa.setCodingRate4(Config.loramodule.codingRate4);
|
||||
LoRa.enableCrc();
|
||||
LoRa.setTxPower(Config.loramodule.power);
|
||||
Serial.print("init : LoRa Module ... done!");
|
||||
#endif
|
||||
#ifdef HAS_SX126X
|
||||
SPI.begin(RADIO_SCLK_PIN, RADIO_MISO_PIN, RADIO_MOSI_PIN);
|
||||
float freq = (float)Config.loramodule.iGateFreq/1000000;
|
||||
int state = radio.begin(freq);
|
||||
if (state == RADIOLIB_ERR_NONE) {
|
||||
Serial.print("Initializing SX126X LoRa Module");
|
||||
} else {
|
||||
Serial.println("Starting LoRa failed!");
|
||||
while (true);
|
||||
}
|
||||
radio.setDio1Action(setFlag);
|
||||
radio.setSpreadingFactor(Config.loramodule.spreadingFactor);
|
||||
radio.setBandwidth(Config.loramodule.signalBandwidth);
|
||||
radio.setCodingRate(Config.loramodule.codingRate4);
|
||||
#if defined(ESP32_DIY_1W_LoRa)
|
||||
radio.setRfSwitchPins(RADIO_RXEN, RADIO_TXEN);
|
||||
#endif
|
||||
#if defined(HELTEC_V3) || defined(TTGO_T_Beam_V1_0_SX1268) || defined(TTGO_T_Beam_V1_2_SX1262)
|
||||
state = radio.setOutputPower(Config.loramodule.power + 2); // values available: 10, 17, 22 --> if 20 in tracker_conf.json it will be updated to 22.
|
||||
#endif
|
||||
#ifdef ESP32_DIY_1W_LoRa_GPS
|
||||
state = radio.setOutputPower(Config.loramodule.power); // max value 20 (when 20dB in setup 30dB in output as 400M30S has Low Noise Amp)
|
||||
#endif
|
||||
if (state == RADIOLIB_ERR_NONE) {
|
||||
Serial.println("init : LoRa Module ... done!");
|
||||
} else {
|
||||
Serial.println("Starting LoRa failed!");
|
||||
while (true);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
if (!LoRa.begin(freq)) {
|
||||
Serial.println("Starting LoRa failed!");
|
||||
show_display("ERROR", "Starting LoRa failed!");
|
||||
while (true) {
|
||||
delay(1000);
|
||||
}
|
||||
}
|
||||
LoRa.setSpreadingFactor(Config.loramodule.spreadingFactor);
|
||||
LoRa.setSignalBandwidth(Config.loramodule.signalBandwidth);
|
||||
LoRa.setCodingRate4(Config.loramodule.codingRate4);
|
||||
LoRa.enableCrc();
|
||||
LoRa.setTxPower(Config.loramodule.power);
|
||||
Serial.print("init : LoRa Module ... done!");
|
||||
#endif
|
||||
#ifdef HAS_SX126X
|
||||
SPI.begin(RADIO_SCLK_PIN, RADIO_MISO_PIN, RADIO_MOSI_PIN);
|
||||
float freq = (float)Config.loramodule.iGateFreq/1000000;
|
||||
int state = radio.begin(freq);
|
||||
if (state == RADIOLIB_ERR_NONE) {
|
||||
Serial.print("Initializing SX126X LoRa Module");
|
||||
} else {
|
||||
Serial.println("Starting LoRa failed!");
|
||||
while (true);
|
||||
}
|
||||
radio.setDio1Action(setFlag);
|
||||
radio.setSpreadingFactor(Config.loramodule.spreadingFactor);
|
||||
radio.setBandwidth(Config.loramodule.signalBandwidth);
|
||||
radio.setCodingRate(Config.loramodule.codingRate4);
|
||||
#if defined(ESP32_DIY_1W_LoRa)
|
||||
radio.setRfSwitchPins(RADIO_RXEN, RADIO_TXEN);
|
||||
#endif
|
||||
#if defined(HELTEC_V3) || defined(TTGO_T_Beam_V1_0_SX1268) || defined(TTGO_T_Beam_V1_2_SX1262)
|
||||
state = radio.setOutputPower(Config.loramodule.power + 2); // values available: 10, 17, 22 --> if 20 in tracker_conf.json it will be updated to 22.
|
||||
#endif
|
||||
#ifdef ESP32_DIY_1W_LoRa_GPS
|
||||
state = radio.setOutputPower(Config.loramodule.power); // max value 20 (when 20dB in setup 30dB in output as 400M30S has Low Noise Amp)
|
||||
#endif
|
||||
if (state == RADIOLIB_ERR_NONE) {
|
||||
Serial.println("init : LoRa Module ... done!");
|
||||
} else {
|
||||
Serial.println("Starting LoRa failed!");
|
||||
while (true);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void sendNewPacket(const String &typeOfMessage, const String &newPacket) {
|
||||
#if defined(TTGO_T_LORA32_V2_1) || defined(HELTEC_V2) || defined(HELTEC_V3) || defined(ESP32_DIY_LoRa) || defined(ESP32_DIY_1W_LoRa)
|
||||
digitalWrite(internalLedPin,HIGH);
|
||||
#endif
|
||||
#ifdef HAS_SX127X
|
||||
LoRa.beginPacket();
|
||||
LoRa.write('<');
|
||||
if (typeOfMessage == "APRS") {
|
||||
LoRa.write(0xFF);
|
||||
} else if (typeOfMessage == "LoRa") {
|
||||
LoRa.write(0xF8);
|
||||
void sendNewPacket(const String &typeOfMessage, const String &newPacket) {
|
||||
#if defined(TTGO_T_LORA32_V2_1) || defined(HELTEC_V2) || defined(HELTEC_V3) || defined(ESP32_DIY_LoRa) || defined(ESP32_DIY_1W_LoRa)
|
||||
digitalWrite(internalLedPin,HIGH);
|
||||
#endif
|
||||
#ifdef HAS_SX127X
|
||||
LoRa.beginPacket();
|
||||
LoRa.write('<');
|
||||
if (typeOfMessage == "APRS") {
|
||||
LoRa.write(0xFF);
|
||||
} else if (typeOfMessage == "LoRa") {
|
||||
LoRa.write(0xF8);
|
||||
}
|
||||
LoRa.write(0x01);
|
||||
LoRa.write((const uint8_t *)newPacket.c_str(), newPacket.length());
|
||||
LoRa.endPacket();
|
||||
#endif
|
||||
#ifdef HAS_SX126X
|
||||
int state = radio.transmit("\x3c\xff\x01" + newPacket);
|
||||
if (state == RADIOLIB_ERR_NONE) {
|
||||
//Serial.println(F("success!"));
|
||||
} else if (state == RADIOLIB_ERR_PACKET_TOO_LONG) {
|
||||
Serial.println(F("too long!"));
|
||||
} else if (state == RADIOLIB_ERR_TX_TIMEOUT) {
|
||||
Serial.println(F("timeout!"));
|
||||
} else {
|
||||
Serial.print(F("failed, code "));
|
||||
Serial.println(state);
|
||||
}
|
||||
#endif
|
||||
#if defined(TTGO_T_LORA32_V2_1) || defined(HELTEC_V2) || defined(HELTEC_V3) || defined(ESP32_DIY_LoRa) || defined(ESP32_DIY_1W_LoRa)
|
||||
digitalWrite(internalLedPin,LOW);
|
||||
#endif
|
||||
SYSLOG_Utils::log("Tx", newPacket,0,0,0);
|
||||
Serial.print("---> LoRa Packet Tx : ");
|
||||
Serial.println(newPacket);
|
||||
}
|
||||
LoRa.write(0x01);
|
||||
LoRa.write((const uint8_t *)newPacket.c_str(), newPacket.length());
|
||||
LoRa.endPacket();
|
||||
#endif
|
||||
#ifdef HAS_SX126X
|
||||
int state = radio.transmit("\x3c\xff\x01" + newPacket);
|
||||
if (state == RADIOLIB_ERR_NONE) {
|
||||
//Serial.println(F("success!"));
|
||||
} else if (state == RADIOLIB_ERR_PACKET_TOO_LONG) {
|
||||
Serial.println(F("too long!"));
|
||||
} else if (state == RADIOLIB_ERR_TX_TIMEOUT) {
|
||||
Serial.println(F("timeout!"));
|
||||
} else {
|
||||
Serial.print(F("failed, code "));
|
||||
Serial.println(state);
|
||||
}
|
||||
#endif
|
||||
#if defined(TTGO_T_LORA32_V2_1) || defined(HELTEC_V2) || defined(HELTEC_V3) || defined(ESP32_DIY_LoRa) || defined(ESP32_DIY_1W_LoRa)
|
||||
digitalWrite(internalLedPin,LOW);
|
||||
#endif
|
||||
SYSLOG_Utils::log("Tx", newPacket,0,0,0);
|
||||
Serial.print("---> LoRa Packet Tx : ");
|
||||
Serial.println(newPacket);
|
||||
}
|
||||
|
||||
String generatePacket(String aprsisPacket) {
|
||||
String firstPart, messagePart;
|
||||
aprsisPacket.trim();
|
||||
firstPart = aprsisPacket.substring(0, aprsisPacket.indexOf(","));
|
||||
messagePart = aprsisPacket.substring(aprsisPacket.indexOf("::")+2);
|
||||
return firstPart + ",TCPIP,WIDE1-1," + Config.callsign + "::" + messagePart;
|
||||
}
|
||||
String generatePacket(String aprsisPacket) {
|
||||
String firstPart, messagePart;
|
||||
aprsisPacket.trim();
|
||||
firstPart = aprsisPacket.substring(0, aprsisPacket.indexOf(","));
|
||||
messagePart = aprsisPacket.substring(aprsisPacket.indexOf("::")+2);
|
||||
return firstPart + ",TCPIP,WIDE1-1," + Config.callsign + "::" + messagePart;
|
||||
}
|
||||
|
||||
String packetSanitization(String packet) {
|
||||
Serial.println(packet);
|
||||
if (packet.indexOf("\0")>0) {
|
||||
packet.replace("\0","000");
|
||||
String packetSanitization(String packet) {
|
||||
Serial.println(packet);
|
||||
if (packet.indexOf("\0")>0) {
|
||||
packet.replace("\0","");
|
||||
}
|
||||
if (packet.indexOf("\r")>0) {
|
||||
packet.replace("\r","");
|
||||
}
|
||||
if (packet.indexOf("\n")>0) {
|
||||
packet.replace("\n","");
|
||||
}
|
||||
return packet;
|
||||
}
|
||||
if (packet.indexOf("\r")>0) {
|
||||
packet.replace("\r","RRR");
|
||||
}
|
||||
if (packet.indexOf("\n")>0) {
|
||||
packet.replace("\n","NNN");
|
||||
}
|
||||
return packet;
|
||||
}
|
||||
|
||||
String receivePacket() {
|
||||
String loraPacket = "";
|
||||
#ifdef HAS_SX127X
|
||||
int packetSize = LoRa.parsePacket();
|
||||
if (packetSize) {
|
||||
while (LoRa.available()) {
|
||||
int inChar = LoRa.read();
|
||||
loraPacket += (char)inChar;
|
||||
}
|
||||
rssi = LoRa.packetRssi();
|
||||
snr = LoRa.packetSnr();
|
||||
freqError = LoRa.packetFrequencyError();
|
||||
String receivePacket() {
|
||||
String loraPacket = "";
|
||||
#ifdef HAS_SX127X
|
||||
int packetSize = LoRa.parsePacket();
|
||||
if (packetSize) {
|
||||
while (LoRa.available()) {
|
||||
int inChar = LoRa.read();
|
||||
loraPacket += (char)inChar;
|
||||
}
|
||||
rssi = LoRa.packetRssi();
|
||||
snr = LoRa.packetSnr();
|
||||
freqError = LoRa.packetFrequencyError();
|
||||
}
|
||||
#endif
|
||||
#ifdef HAS_SX126X
|
||||
if (transmissionFlag) {
|
||||
transmissionFlag = false;
|
||||
radio.startReceive();
|
||||
int state = radio.readData(loraPacket);
|
||||
if (state == RADIOLIB_ERR_NONE) {
|
||||
Serial.println("LoRa Rx ---> " + loraPacket);
|
||||
rssi = radio.getRSSI();
|
||||
snr = radio.getSNR();
|
||||
freqError = radio.getFrequencyError();
|
||||
} else if (state == RADIOLIB_ERR_RX_TIMEOUT) {
|
||||
// timeout occurred while waiting for a packet
|
||||
} else if (state == RADIOLIB_ERR_CRC_MISMATCH) {
|
||||
Serial.println(F("CRC error!"));
|
||||
} else {
|
||||
Serial.print(F("failed, code "));
|
||||
Serial.println(state);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
// // // // // //
|
||||
if ((loraPacket.indexOf("\0")!=-1) || (loraPacket.indexOf("\r")!=-1) || (loraPacket.indexOf("\n")!=-1)) {
|
||||
loraPacket = packetSanitization(loraPacket);
|
||||
}
|
||||
// // // // // //
|
||||
#ifndef TextSerialOutputForApp
|
||||
if (loraPacket!="") {
|
||||
Serial.println("(RSSI:" +String(rssi) + " / SNR:" + String(snr) + " / FreqErr:" + String(freqError) + ")");
|
||||
}
|
||||
#endif
|
||||
if (Config.syslog.active && (stationMode==1 || stationMode==2 || (stationMode==5 && WiFi.status()==WL_CONNECTED)) && loraPacket!="") {
|
||||
SYSLOG_Utils::log("Rx", loraPacket, rssi, snr, freqError);
|
||||
}
|
||||
return loraPacket;
|
||||
}
|
||||
#endif
|
||||
#ifdef HAS_SX126X
|
||||
if (transmissionFlag) {
|
||||
transmissionFlag = false;
|
||||
radio.startReceive();
|
||||
int state = radio.readData(loraPacket);
|
||||
if (state == RADIOLIB_ERR_NONE) {
|
||||
Serial.println("LoRa Rx ---> " + loraPacket);
|
||||
rssi = radio.getRSSI();
|
||||
snr = radio.getSNR();
|
||||
freqError = radio.getFrequencyError();
|
||||
} else if (state == RADIOLIB_ERR_RX_TIMEOUT) {
|
||||
// timeout occurred while waiting for a packet
|
||||
} else if (state == RADIOLIB_ERR_CRC_MISMATCH) {
|
||||
Serial.println(F("CRC error!"));
|
||||
} else {
|
||||
Serial.print(F("failed, code "));
|
||||
Serial.println(state);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
// // // // // //
|
||||
if ((loraPacket.indexOf("\0")!=-1) || (loraPacket.indexOf("\r")!=-1) || (loraPacket.indexOf("\n")!=-1)) {
|
||||
loraPacket = packetSanitization(loraPacket);
|
||||
}
|
||||
// // // // // //
|
||||
#ifndef TextSerialOutputForApp
|
||||
if (loraPacket!="") {
|
||||
Serial.println("(RSSI:" +String(rssi) + " / SNR:" + String(snr) + " / FreqErr:" + String(freqError) + ")");
|
||||
}
|
||||
#endif
|
||||
if (Config.syslog.active && (stationMode==1 || stationMode==2 || (stationMode==5 && WiFi.status()==WL_CONNECTED)) && loraPacket!="") {
|
||||
SYSLOG_Utils::log("Rx", loraPacket, rssi, snr, freqError);
|
||||
}
|
||||
return loraPacket;
|
||||
}
|
||||
|
||||
void changeFreqTx() {
|
||||
delay(500);
|
||||
#ifdef HAS_SX127X
|
||||
LoRa.setFrequency(Config.loramodule.digirepeaterTxFreq);
|
||||
#endif
|
||||
#ifdef HAS_SX126X
|
||||
float freq = (float)Config.loramodule.digirepeaterTxFreq/1000000;
|
||||
radio.setFrequency(freq);
|
||||
#endif
|
||||
}
|
||||
void changeFreqTx() {
|
||||
delay(500);
|
||||
#ifdef HAS_SX127X
|
||||
LoRa.setFrequency(Config.loramodule.digirepeaterTxFreq);
|
||||
#endif
|
||||
#ifdef HAS_SX126X
|
||||
float freq = (float)Config.loramodule.digirepeaterTxFreq/1000000;
|
||||
radio.setFrequency(freq);
|
||||
#endif
|
||||
}
|
||||
|
||||
void changeFreqRx() {
|
||||
delay(500);
|
||||
#ifdef HAS_SX127X
|
||||
LoRa.setFrequency(Config.loramodule.digirepeaterRxFreq);
|
||||
#endif
|
||||
#ifdef HAS_SX126X
|
||||
float freq = (float)Config.loramodule.digirepeaterRxFreq/1000000;
|
||||
radio.setFrequency(freq);
|
||||
#endif
|
||||
}
|
||||
void changeFreqRx() {
|
||||
delay(500);
|
||||
#ifdef HAS_SX127X
|
||||
LoRa.setFrequency(Config.loramodule.digirepeaterRxFreq);
|
||||
#endif
|
||||
#ifdef HAS_SX126X
|
||||
float freq = (float)Config.loramodule.digirepeaterRxFreq/1000000;
|
||||
radio.setFrequency(freq);
|
||||
#endif
|
||||
}
|
||||
|
||||
}
|
||||
56
src/ota_utils.cpp
Normal file
56
src/ota_utils.cpp
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
#include <ESPAsyncWebServer.h>
|
||||
#include <ElegantOTA.h>
|
||||
#include <AsyncTCP.h>
|
||||
#include "configuration.h"
|
||||
#include "display.h"
|
||||
#include "ota_utils.h"
|
||||
|
||||
extern Configuration Config;
|
||||
extern uint32_t lastScreenOn;
|
||||
|
||||
unsigned long ota_progress_millis = 0;
|
||||
|
||||
namespace OTA_Utils {
|
||||
|
||||
void setup(AsyncWebServer *server) {
|
||||
if (Config.ota.username != "" && Config.ota.password != "") {
|
||||
ElegantOTA.begin(server, Config.ota.username.c_str(), Config.ota.password.c_str());
|
||||
} else {
|
||||
ElegantOTA.begin(server);
|
||||
}
|
||||
|
||||
ElegantOTA.setAutoReboot(true);
|
||||
ElegantOTA.onStart(onOTAStart);
|
||||
ElegantOTA.onProgress(onOTAProgress);
|
||||
ElegantOTA.onEnd(onOTAEnd);
|
||||
}
|
||||
|
||||
void onOTAStart() {
|
||||
Serial.println("OTA update started!");
|
||||
display_toggle(true);
|
||||
lastScreenOn = millis();
|
||||
show_display("", "", "", " OTA update started!", "", "", "", 1000);
|
||||
}
|
||||
|
||||
void onOTAProgress(size_t current, size_t final) {
|
||||
if (millis() - ota_progress_millis > 1000) {
|
||||
display_toggle(true);
|
||||
lastScreenOn = millis();
|
||||
ota_progress_millis = millis();
|
||||
Serial.printf("OTA Progress Current: %u bytes, Final: %u bytes\n", current, final);
|
||||
show_display("", "", " OTA Progress : " + String((current*100)/final) + "%", "", "", "", "", 100);
|
||||
}
|
||||
}
|
||||
|
||||
void onOTAEnd(bool success) {
|
||||
display_toggle(true);
|
||||
lastScreenOn = millis();
|
||||
if (success) {
|
||||
Serial.println("OTA update finished successfully!");
|
||||
show_display("", "", " OTA update success!", "", " Rebooting ...", "", "", 4000);
|
||||
} else {
|
||||
Serial.println("There was an error during OTA update!");
|
||||
show_display("", "", " OTA update fail!", "", "", "", "", 4000);
|
||||
}
|
||||
}
|
||||
}
|
||||
16
src/ota_utils.h
Normal file
16
src/ota_utils.h
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
#ifndef OTA_UTILS_H_
|
||||
#define OTA_UTILS_H_
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <ESPAsyncWebServer.h>
|
||||
|
||||
namespace OTA_Utils {
|
||||
|
||||
void setup(AsyncWebServer *server);
|
||||
void onOTAStart();
|
||||
void onOTAProgress(size_t current, size_t final);
|
||||
void onOTAEnd(bool success);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -20,124 +20,121 @@ extern Configuration Config;
|
|||
|
||||
namespace POWER_Utils {
|
||||
|
||||
bool BatteryIsConnected = false;
|
||||
String batteryVoltage = "";
|
||||
String batteryChargeDischargeCurrent = "";
|
||||
bool BatteryIsConnected = false;
|
||||
String batteryVoltage = "";
|
||||
String batteryChargeDischargeCurrent = "";
|
||||
|
||||
void activateMeasurement() {
|
||||
#if defined(HAS_AXP192) || defined(HAS_AXP2101)
|
||||
PMU.disableTSPinMeasure();
|
||||
PMU.enableBattDetection();
|
||||
PMU.enableVbusVoltageMeasure();
|
||||
PMU.enableBattVoltageMeasure();
|
||||
PMU.enableSystemVoltageMeasure();
|
||||
#endif
|
||||
}
|
||||
|
||||
void activateLoRa() {
|
||||
#ifdef HAS_AXP192
|
||||
PMU.setLDO2Voltage(3300);
|
||||
PMU.enableLDO2();
|
||||
#endif
|
||||
#ifdef HAS_AXP2101
|
||||
PMU.setALDO2Voltage(3300);
|
||||
PMU.enableALDO2();
|
||||
#endif
|
||||
}
|
||||
|
||||
void deactivateLoRa() {
|
||||
#ifdef HAS_AXP192
|
||||
PMU.disableLDO2();
|
||||
#endif
|
||||
#ifdef HAS_AXP2101
|
||||
PMU.disableALDO2();
|
||||
#endif
|
||||
}
|
||||
|
||||
bool begin(TwoWire &port) {
|
||||
#if defined (TTGO_T_LORA32_V2_1) || defined(HELTEC_V2) || defined(ESP32_DIY_LoRa) || defined(HELTEC_V3) || defined(ESP32_DIY_1W_LoRa)
|
||||
return true;
|
||||
#endif
|
||||
|
||||
#ifdef HAS_AXP192
|
||||
bool result = PMU.begin(Wire, AXP192_SLAVE_ADDRESS, I2C_SDA, I2C_SCL);
|
||||
if (result) {
|
||||
PMU.disableDC2();
|
||||
PMU.disableLDO2();
|
||||
PMU.disableLDO3();
|
||||
PMU.setDC1Voltage(3300);
|
||||
PMU.enableDC1();
|
||||
PMU.setProtectedChannel(XPOWERS_DCDC3);
|
||||
PMU.disableIRQ(XPOWERS_AXP192_ALL_IRQ);
|
||||
void activateMeasurement() {
|
||||
#if defined(HAS_AXP192) || defined(HAS_AXP2101)
|
||||
PMU.disableTSPinMeasure();
|
||||
PMU.enableBattDetection();
|
||||
PMU.enableVbusVoltageMeasure();
|
||||
PMU.enableBattVoltageMeasure();
|
||||
PMU.enableSystemVoltageMeasure();
|
||||
#endif
|
||||
}
|
||||
return result;
|
||||
#endif
|
||||
|
||||
#ifdef HAS_AXP2101
|
||||
bool result = PMU.begin(Wire, AXP2101_SLAVE_ADDRESS, I2C_SDA, I2C_SCL);
|
||||
if (result) {
|
||||
PMU.disableDC2();
|
||||
PMU.disableDC3();
|
||||
PMU.disableDC4();
|
||||
PMU.disableDC5();
|
||||
PMU.disableALDO1();
|
||||
PMU.disableALDO4();
|
||||
PMU.disableBLDO1();
|
||||
PMU.disableBLDO2();
|
||||
PMU.disableDLDO1();
|
||||
PMU.disableDLDO2();
|
||||
PMU.setDC1Voltage(3300);
|
||||
PMU.enableDC1();
|
||||
PMU.setButtonBatteryChargeVoltage(3300);
|
||||
PMU.enableButtonBatteryCharge();
|
||||
PMU.disableIRQ(XPOWERS_AXP2101_ALL_IRQ);
|
||||
void activateLoRa() {
|
||||
#ifdef HAS_AXP192
|
||||
PMU.setLDO2Voltage(3300);
|
||||
PMU.enableLDO2();
|
||||
#endif
|
||||
#ifdef HAS_AXP2101
|
||||
PMU.setALDO2Voltage(3300);
|
||||
PMU.enableALDO2();
|
||||
#endif
|
||||
}
|
||||
return result;
|
||||
#endif
|
||||
}
|
||||
|
||||
void setup() {
|
||||
Wire.end();
|
||||
#ifdef HAS_AXP192
|
||||
Wire.begin(SDA, SCL);
|
||||
if (begin(Wire)) {
|
||||
Serial.println("AXP192 init done!");
|
||||
} else {
|
||||
Serial.println("AXP192 init failed!");
|
||||
void deactivateLoRa() {
|
||||
#ifdef HAS_AXP192
|
||||
PMU.disableLDO2();
|
||||
#endif
|
||||
#ifdef HAS_AXP2101
|
||||
PMU.disableALDO2();
|
||||
#endif
|
||||
}
|
||||
activateLoRa();
|
||||
activateMeasurement();
|
||||
PMU.setChargerTerminationCurr(XPOWERS_AXP192_CHG_ITERM_LESS_10_PERCENT);
|
||||
PMU.setChargeTargetVoltage(XPOWERS_AXP192_CHG_VOL_4V2);
|
||||
PMU.setChargerConstantCurr(XPOWERS_AXP192_CHG_CUR_780MA);
|
||||
PMU.setSysPowerDownVoltage(2600);
|
||||
#endif
|
||||
|
||||
#ifdef HAS_AXP2101
|
||||
Wire.begin(SDA, SCL);
|
||||
if (begin(Wire)) {
|
||||
Serial.println("AXP2101 init done!");
|
||||
} else {
|
||||
Serial.println("AXP2101 init failed!");
|
||||
bool begin(TwoWire &port) {
|
||||
#if defined(HAS_AXP192)
|
||||
bool result = PMU.begin(Wire, AXP192_SLAVE_ADDRESS, I2C_SDA, I2C_SCL);
|
||||
if (result) {
|
||||
PMU.disableDC2();
|
||||
PMU.disableLDO2();
|
||||
PMU.disableLDO3();
|
||||
PMU.setDC1Voltage(3300);
|
||||
PMU.enableDC1();
|
||||
PMU.setProtectedChannel(XPOWERS_DCDC3);
|
||||
PMU.disableIRQ(XPOWERS_AXP192_ALL_IRQ);
|
||||
}
|
||||
return result;
|
||||
#elif defined(HAS_AXP2101)
|
||||
bool result = PMU.begin(Wire, AXP2101_SLAVE_ADDRESS, I2C_SDA, I2C_SCL);
|
||||
if (result) {
|
||||
PMU.disableDC2();
|
||||
PMU.disableDC3();
|
||||
PMU.disableDC4();
|
||||
PMU.disableDC5();
|
||||
PMU.disableALDO1();
|
||||
PMU.disableALDO4();
|
||||
PMU.disableBLDO1();
|
||||
PMU.disableBLDO2();
|
||||
PMU.disableDLDO1();
|
||||
PMU.disableDLDO2();
|
||||
PMU.setDC1Voltage(3300);
|
||||
PMU.enableDC1();
|
||||
PMU.setButtonBatteryChargeVoltage(3300);
|
||||
PMU.enableButtonBatteryCharge();
|
||||
PMU.disableIRQ(XPOWERS_AXP2101_ALL_IRQ);
|
||||
}
|
||||
return result;
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
activateLoRa();
|
||||
activateMeasurement();
|
||||
PMU.setPrechargeCurr(XPOWERS_AXP2101_PRECHARGE_200MA);
|
||||
PMU.setChargerTerminationCurr(XPOWERS_AXP2101_CHG_ITERM_25MA);
|
||||
PMU.setChargeTargetVoltage(XPOWERS_AXP2101_CHG_VOL_4V2);
|
||||
PMU.setChargerConstantCurr(XPOWERS_AXP2101_CHG_CUR_800MA);
|
||||
PMU.setSysPowerDownVoltage(2600);
|
||||
#endif
|
||||
}
|
||||
|
||||
/*void lowerCpuFrequency() {
|
||||
#if defined(TTGO_T_Beam_V1_0) || defined(TTGO_T_Beam_V1_0_SX1268) || defined(TTGO_T_Beam_V1_2) || defined(ESP32_DIY_LoRa_GPS) || defined(TTGO_T_LORA32_V2_1_GPS) || defined(TTGO_T_LORA32_V2_1_TNC) || defined(ESP32_DIY_1W_LoRa_GPS) || defined(TTGO_T_Beam_V1_2_SX1262)
|
||||
if (setCpuFrequencyMhz(80)) {
|
||||
logger.log(logging::LoggerLevel::LOGGER_LEVEL_INFO, "Main", "CPU frequency set to 80MHz");
|
||||
} else {
|
||||
logger.log(logging::LoggerLevel::LOGGER_LEVEL_WARN, "Main", "CPU frequency unchanged");
|
||||
|
||||
void setup() {
|
||||
Wire.end();
|
||||
#ifdef HAS_AXP192
|
||||
Wire.begin(SDA, SCL);
|
||||
if (begin(Wire)) {
|
||||
Serial.println("AXP192 init done!");
|
||||
} else {
|
||||
Serial.println("AXP192 init failed!");
|
||||
}
|
||||
activateLoRa();
|
||||
activateMeasurement();
|
||||
PMU.setChargerTerminationCurr(XPOWERS_AXP192_CHG_ITERM_LESS_10_PERCENT);
|
||||
PMU.setChargeTargetVoltage(XPOWERS_AXP192_CHG_VOL_4V2);
|
||||
PMU.setChargerConstantCurr(XPOWERS_AXP192_CHG_CUR_780MA);
|
||||
PMU.setSysPowerDownVoltage(2600);
|
||||
#endif
|
||||
|
||||
#ifdef HAS_AXP2101
|
||||
Wire.begin(SDA, SCL);
|
||||
if (begin(Wire)) {
|
||||
Serial.println("AXP2101 init done!");
|
||||
} else {
|
||||
Serial.println("AXP2101 init failed!");
|
||||
}
|
||||
activateLoRa();
|
||||
activateMeasurement();
|
||||
PMU.setPrechargeCurr(XPOWERS_AXP2101_PRECHARGE_200MA);
|
||||
PMU.setChargerTerminationCurr(XPOWERS_AXP2101_CHG_ITERM_25MA);
|
||||
PMU.setChargeTargetVoltage(XPOWERS_AXP2101_CHG_VOL_4V2);
|
||||
PMU.setChargerConstantCurr(XPOWERS_AXP2101_CHG_CUR_800MA);
|
||||
PMU.setSysPowerDownVoltage(2600);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
}*/
|
||||
|
||||
/*void lowerCpuFrequency() {
|
||||
#if defined(TTGO_T_Beam_V1_0) || defined(TTGO_T_Beam_V1_0_SX1268) || defined(TTGO_T_Beam_V1_2) || defined(ESP32_DIY_LoRa_GPS) || defined(TTGO_T_LORA32_V2_1_GPS) || defined(TTGO_T_LORA32_V2_1_TNC) || defined(ESP32_DIY_1W_LoRa_GPS) || defined(TTGO_T_Beam_V1_2_SX1262)
|
||||
if (setCpuFrequencyMhz(80)) {
|
||||
logger.log(logging::LoggerLevel::LOGGER_LEVEL_INFO, "Main", "CPU frequency set to 80MHz");
|
||||
} else {
|
||||
logger.log(logging::LoggerLevel::LOGGER_LEVEL_WARN, "Main", "CPU frequency unchanged");
|
||||
}
|
||||
#endif
|
||||
}*/
|
||||
|
||||
}
|
||||
|
|
@ -6,15 +6,13 @@
|
|||
|
||||
namespace POWER_Utils {
|
||||
|
||||
void activateMeasurement();
|
||||
void activateMeasurement();
|
||||
void activateLoRa();
|
||||
void deactivateLoRa();
|
||||
bool begin(TwoWire &port);
|
||||
void setup();
|
||||
//void lowerCpuFrequency();
|
||||
|
||||
void activateLoRa();
|
||||
void deactivateLoRa();
|
||||
|
||||
bool begin(TwoWire &port);
|
||||
void setup();
|
||||
|
||||
//void lowerCpuFrequency();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -10,40 +10,40 @@ extern int stationMode;
|
|||
|
||||
namespace QUERY_Utils {
|
||||
|
||||
String process(String query, String station, String queryOrigin) {
|
||||
String answer;
|
||||
if (query=="?APRS?" || query=="?aprs?" || query=="?Aprs?" || query=="H" || query=="h" || query=="HELP" || query=="Help" || query=="help" || query=="?") {
|
||||
answer = "?APRSV ?APRSP ?APRSL ?APRSH ?WHERE callsign";
|
||||
} else if (query=="?APRSV" || query=="?aprsv" || query=="?Aprsv") {
|
||||
answer = "CA2RXU_LoRa_iGate 1.2 v" + versionDate + " sM" + String(stationMode);
|
||||
} else if (query=="?APRSP" || query=="?aprsp" || query=="?Aprsp") {
|
||||
answer = "iGate QTH: " + String(currentWiFi->latitude,2) + " " + String(currentWiFi->longitude,2);
|
||||
} else if (query=="?APRSL" || query=="?aprsl" || query=="?Aprsl") {
|
||||
if (lastHeardStation.size() == 0) {
|
||||
answer = "No Station Listened in the last " + String(Config.rememberStationTime) + "min.";
|
||||
} else {
|
||||
for (int i=0; i<lastHeardStation.size(); i++) {
|
||||
answer += lastHeardStation[i].substring(0,lastHeardStation[i].indexOf(",")) + " ";
|
||||
String process(String query, String station, String queryOrigin) {
|
||||
String answer;
|
||||
if (query=="?APRS?" || query=="?aprs?" || query=="?Aprs?" || query=="H" || query=="h" || query=="HELP" || query=="Help" || query=="help" || query=="?") {
|
||||
answer = "?APRSV ?APRSP ?APRSL ?APRSH ?WHERE callsign";
|
||||
} else if (query=="?APRSV" || query=="?aprsv" || query=="?Aprsv") {
|
||||
answer = "CA2RXU_LoRa_iGate 1.3 v" + versionDate + " sM" + String(stationMode);
|
||||
} else if (query=="?APRSP" || query=="?aprsp" || query=="?Aprsp") {
|
||||
answer = "iGate QTH: " + String(currentWiFi->latitude,2) + " " + String(currentWiFi->longitude,2);
|
||||
} else if (query=="?APRSL" || query=="?aprsl" || query=="?Aprsl") {
|
||||
if (lastHeardStation.size() == 0) {
|
||||
answer = "No Station Listened in the last " + String(Config.rememberStationTime) + "min.";
|
||||
} else {
|
||||
for (int i=0; i<lastHeardStation.size(); i++) {
|
||||
answer += lastHeardStation[i].substring(0,lastHeardStation[i].indexOf(",")) + " ";
|
||||
}
|
||||
answer.trim();
|
||||
}
|
||||
} else if (query.indexOf("?APRSH") == 0 || query.indexOf("?aprsh") == 0 || query.indexOf("?Aprsh") == 0) {
|
||||
// sacar callsign despues de ?APRSH
|
||||
Serial.println("escuchaste a X estacion? en las ultimas 24 o 8 horas?");
|
||||
answer = "APRSH on development 73!";
|
||||
} else if (query.indexOf("?WHERE") == 0) {
|
||||
// agregar callsign para completar donde esta X callsign --> posicion
|
||||
Serial.println("estaciones escuchadas directo (ultimos 30 min)");
|
||||
answer = "?WHERE on development 73!";
|
||||
}
|
||||
for(int i = station.length(); i < 9; i++) {
|
||||
station += ' ';
|
||||
}
|
||||
if (queryOrigin == "APRSIS") {
|
||||
return Config.callsign + ">APLRG1,TCPIP,qAC::" + station + ":" + answer;// + "\n";
|
||||
} else { //} if (queryOrigin == "LoRa") {
|
||||
return Config.callsign + ">APLRG1,RFONLY,WIDE1-1::" + station + ":" + answer;
|
||||
}
|
||||
answer.trim();
|
||||
}
|
||||
} else if (query.indexOf("?APRSH") == 0 || query.indexOf("?aprsh") == 0 || query.indexOf("?Aprsh") == 0) {
|
||||
// sacar callsign despues de ?APRSH
|
||||
Serial.println("escuchaste a X estacion? en las ultimas 24 o 8 horas?");
|
||||
answer = "APRSH on development 73!";
|
||||
} else if (query.indexOf("?WHERE") == 0) {
|
||||
// agregar callsign para completar donde esta X callsign --> posicion
|
||||
Serial.println("estaciones escuchadas directo (ultimos 30 min)");
|
||||
answer = "?WHERE on development 73!";
|
||||
}
|
||||
for(int i = station.length(); i < 9; i++) {
|
||||
station += ' ';
|
||||
}
|
||||
if (queryOrigin == "APRSIS") {
|
||||
return Config.callsign + ">APLRG1,TCPIP,qAC::" + station + ":" + answer;// + "\n";
|
||||
} else { //} if (queryOrigin == "LoRa") {
|
||||
return Config.callsign + ">APLRG1,RFONLY,WIDE1-1::" + station + ":" + answer;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -12,93 +12,93 @@ extern String fourthLine;
|
|||
|
||||
namespace STATION_Utils {
|
||||
|
||||
void deleteNotHeard() {
|
||||
for (int i=0; i<lastHeardStation.size(); i++) {
|
||||
String deltaTimeString = lastHeardStation[i].substring(lastHeardStation[i].indexOf(",")+1);
|
||||
uint32_t deltaTime = deltaTimeString.toInt();
|
||||
if ((millis() - deltaTime) < Config.rememberStationTime*60*1000) {
|
||||
lastHeardStation_temp.push_back(lastHeardStation[i]);
|
||||
}
|
||||
}
|
||||
lastHeardStation.clear();
|
||||
for (int j=0; j<lastHeardStation_temp.size(); j++) {
|
||||
lastHeardStation.push_back(lastHeardStation_temp[j]);
|
||||
}
|
||||
lastHeardStation_temp.clear();
|
||||
}
|
||||
|
||||
void updateLastHeard(String station) {
|
||||
deleteNotHeard();
|
||||
bool stationHeard = false;
|
||||
for (int i=0; i<lastHeardStation.size(); i++) {
|
||||
if (lastHeardStation[i].substring(0,lastHeardStation[i].indexOf(",")) == station) {
|
||||
lastHeardStation[i] = station + "," + String(millis());
|
||||
stationHeard = true;
|
||||
}
|
||||
}
|
||||
if (!stationHeard) {
|
||||
lastHeardStation.push_back(station + "," + String(millis()));
|
||||
void deleteNotHeard() {
|
||||
for (int i=0; i<lastHeardStation.size(); i++) {
|
||||
String deltaTimeString = lastHeardStation[i].substring(lastHeardStation[i].indexOf(",")+1);
|
||||
uint32_t deltaTime = deltaTimeString.toInt();
|
||||
if ((millis() - deltaTime) < Config.rememberStationTime*60*1000) {
|
||||
lastHeardStation_temp.push_back(lastHeardStation[i]);
|
||||
}
|
||||
}
|
||||
lastHeardStation.clear();
|
||||
for (int j=0; j<lastHeardStation_temp.size(); j++) {
|
||||
lastHeardStation.push_back(lastHeardStation_temp[j]);
|
||||
}
|
||||
lastHeardStation_temp.clear();
|
||||
}
|
||||
|
||||
fourthLine = "Stations (" + String(Config.rememberStationTime) + "min) = ";
|
||||
if (lastHeardStation.size() < 10) {
|
||||
fourthLine += " ";
|
||||
}
|
||||
fourthLine += String(lastHeardStation.size());
|
||||
void updateLastHeard(String station) {
|
||||
deleteNotHeard();
|
||||
bool stationHeard = false;
|
||||
for (int i=0; i<lastHeardStation.size(); i++) {
|
||||
if (lastHeardStation[i].substring(0,lastHeardStation[i].indexOf(",")) == station) {
|
||||
lastHeardStation[i] = station + "," + String(millis());
|
||||
stationHeard = true;
|
||||
}
|
||||
}
|
||||
if (!stationHeard) {
|
||||
lastHeardStation.push_back(station + "," + String(millis()));
|
||||
}
|
||||
|
||||
#ifndef TextSerialOutputForApp ////// This is just for debugging
|
||||
Serial.print("Stations Near (last " + String(Config.rememberStationTime) + " minutes): ");
|
||||
for (int k=0; k<lastHeardStation.size(); k++) {
|
||||
Serial.print(lastHeardStation[k].substring(0,lastHeardStation[k].indexOf(","))); Serial.print(" ");
|
||||
}
|
||||
Serial.println("");
|
||||
#endif
|
||||
}
|
||||
fourthLine = "Stations (" + String(Config.rememberStationTime) + "min) = ";
|
||||
if (lastHeardStation.size() < 10) {
|
||||
fourthLine += " ";
|
||||
}
|
||||
fourthLine += String(lastHeardStation.size());
|
||||
|
||||
bool wasHeard(String station) {
|
||||
deleteNotHeard();
|
||||
for (int i=0; i<lastHeardStation.size(); i++) {
|
||||
if (lastHeardStation[i].substring(0,lastHeardStation[i].indexOf(",")) == station) {
|
||||
Serial.println(" ---> Listened Station");
|
||||
return true;
|
||||
}
|
||||
#ifndef TextSerialOutputForApp ////// This is just for debugging
|
||||
Serial.print("Stations Near (last " + String(Config.rememberStationTime) + " minutes): ");
|
||||
for (int k=0; k<lastHeardStation.size(); k++) {
|
||||
Serial.print(lastHeardStation[k].substring(0,lastHeardStation[k].indexOf(","))); Serial.print(" ");
|
||||
}
|
||||
Serial.println("");
|
||||
#endif
|
||||
}
|
||||
Serial.println(" ---> Station not Heard for last 30 min (Not Tx)\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
void checkBuffer() {
|
||||
for (int i=0; i<packetBuffer.size(); i++) {
|
||||
String deltaTimeString = packetBuffer[i].substring(0,packetBuffer[i].indexOf(","));
|
||||
uint32_t deltaTime = deltaTimeString.toInt();
|
||||
if ((millis() - deltaTime) < 60*1000) { // cambiar a 15 segundos?
|
||||
packetBuffer_temp.push_back(packetBuffer[i]);
|
||||
}
|
||||
bool wasHeard(String station) {
|
||||
deleteNotHeard();
|
||||
for (int i=0; i<lastHeardStation.size(); i++) {
|
||||
if (lastHeardStation[i].substring(0,lastHeardStation[i].indexOf(",")) == station) {
|
||||
Serial.println(" ---> Listened Station");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Serial.println(" ---> Station not Heard for last 30 min (Not Tx)\n");
|
||||
return false;
|
||||
}
|
||||
packetBuffer.clear();
|
||||
for (int j=0; j<packetBuffer_temp.size(); j++) {
|
||||
packetBuffer.push_back(packetBuffer_temp[j]);
|
||||
}
|
||||
packetBuffer_temp.clear();
|
||||
|
||||
// BORRAR ESTO !!
|
||||
for (int i=0; i<packetBuffer.size(); i++) {
|
||||
Serial.println(packetBuffer[i]);
|
||||
}
|
||||
//
|
||||
}
|
||||
void checkBuffer() {
|
||||
for (int i=0; i<packetBuffer.size(); i++) {
|
||||
String deltaTimeString = packetBuffer[i].substring(0,packetBuffer[i].indexOf(","));
|
||||
uint32_t deltaTime = deltaTimeString.toInt();
|
||||
if ((millis() - deltaTime) < 60*1000) { // cambiar a 15 segundos?
|
||||
packetBuffer_temp.push_back(packetBuffer[i]);
|
||||
}
|
||||
}
|
||||
packetBuffer.clear();
|
||||
for (int j=0; j<packetBuffer_temp.size(); j++) {
|
||||
packetBuffer.push_back(packetBuffer_temp[j]);
|
||||
}
|
||||
packetBuffer_temp.clear();
|
||||
|
||||
void updatePacketBuffer(String packet) {
|
||||
if ((packet.indexOf(":!") == -1) && (packet.indexOf(":=") == -1) && (packet.indexOf(":>") == -1) && (packet.indexOf(":`") == -1)) {
|
||||
String sender = packet.substring(3,packet.indexOf(">"));
|
||||
String tempAddressee = packet.substring(packet.indexOf("::") + 2);
|
||||
String addressee = tempAddressee.substring(0,tempAddressee.indexOf(":"));
|
||||
addressee.trim();
|
||||
String message = tempAddressee.substring(tempAddressee.indexOf(":")+1);
|
||||
//Serial.println(String(millis()) + "," + sender + "," + addressee + "," + message);
|
||||
packetBuffer.push_back(String(millis()) + "," + sender + "," + addressee + "," + message);
|
||||
checkBuffer();
|
||||
// BORRAR ESTO !!
|
||||
for (int i=0; i<packetBuffer.size(); i++) {
|
||||
Serial.println(packetBuffer[i]);
|
||||
}
|
||||
//
|
||||
}
|
||||
|
||||
void updatePacketBuffer(String packet) {
|
||||
if ((packet.indexOf(":!") == -1) && (packet.indexOf(":=") == -1) && (packet.indexOf(":>") == -1) && (packet.indexOf(":`") == -1)) {
|
||||
String sender = packet.substring(3,packet.indexOf(">"));
|
||||
String tempAddressee = packet.substring(packet.indexOf("::") + 2);
|
||||
String addressee = tempAddressee.substring(0,tempAddressee.indexOf(":"));
|
||||
addressee.trim();
|
||||
String message = tempAddressee.substring(tempAddressee.indexOf(":")+1);
|
||||
//Serial.println(String(millis()) + "," + sender + "," + addressee + "," + message);
|
||||
packetBuffer.push_back(String(millis()) + "," + sender + "," + addressee + "," + message);
|
||||
checkBuffer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
113
src/utils.cpp
113
src/utils.cpp
|
|
@ -1,7 +1,3 @@
|
|||
#include <ESPAsyncWebServer.h>
|
||||
#include <ElegantOTA.h>
|
||||
#include <AsyncTCP.h>
|
||||
#include <SPIFFS.h>
|
||||
#include <WiFi.h>
|
||||
#include "configuration.h"
|
||||
#include "station_utils.h"
|
||||
|
|
@ -16,8 +12,6 @@
|
|||
#include "display.h"
|
||||
#include "utils.h"
|
||||
|
||||
AsyncWebServer server(80);
|
||||
|
||||
extern WiFiClient espClient;
|
||||
extern Configuration Config;
|
||||
extern String versionDate;
|
||||
|
|
@ -40,21 +34,13 @@ extern int rssi;
|
|||
extern float snr;
|
||||
extern int freqError;
|
||||
extern String distance;
|
||||
extern String versionDate;
|
||||
extern uint32_t lastWiFiCheck;
|
||||
extern bool WiFiConnect;
|
||||
extern bool WiFiConnected;
|
||||
|
||||
String name;
|
||||
String email;
|
||||
|
||||
unsigned long ota_progress_millis = 0;
|
||||
|
||||
namespace Utils {
|
||||
|
||||
void notFound(AsyncWebServerRequest *request) {
|
||||
request->send(404, "text/plain", "Not found");
|
||||
}
|
||||
|
||||
void processStatus() {
|
||||
String status = Config.callsign + ">APLRG1,WIDE1-1";
|
||||
if (stationMode==1 || stationMode==2 || (stationMode==5 && WiFi.status() == WL_CONNECTED)) {
|
||||
|
|
@ -77,7 +63,11 @@ namespace Utils {
|
|||
}
|
||||
|
||||
String getLocalIP() {
|
||||
return "IP : " + String(WiFi.localIP()[0]) + "." + String(WiFi.localIP()[1]) + "." + String(WiFi.localIP()[2]) + "." + String(WiFi.localIP()[3]);
|
||||
if (!WiFiConnected) {
|
||||
return "IP : 192.168.4.1";
|
||||
} else {
|
||||
return "IP : " + String(WiFi.localIP()[0]) + "." + String(WiFi.localIP()[1]) + "." + String(WiFi.localIP()[2]) + "." + String(WiFi.localIP()[3]);
|
||||
}
|
||||
}
|
||||
|
||||
void setupDisplay() {
|
||||
|
|
@ -85,8 +75,8 @@ namespace Utils {
|
|||
#if defined(TTGO_T_LORA32_V2_1) || defined(HELTEC_V2) || defined(HELTEC_V3) || defined(ESP32_DIY_LoRa) || defined(ESP32_DIY_1W_LoRa)
|
||||
digitalWrite(internalLedPin,HIGH);
|
||||
#endif
|
||||
Serial.println("\nStarting iGate: " + Config.callsign + " Version: " + versionDate);
|
||||
show_display(" LoRa APRS", "", " ( iGATE )", "", "", "Richonguzman / CA2RXU", " " + versionDate, 4000);
|
||||
Serial.println("\nStarting Station: " + Config.callsign + " Version: " + versionDate);
|
||||
show_display(" LoRa APRS", "", " ( iGATE & Digi )", "", "", "Richonguzman / CA2RXU", " " + versionDate, 4000);
|
||||
#if defined(TTGO_T_LORA32_V2_1) || defined(HELTEC_V2) || defined(HELTEC_V3) || defined(ESP32_DIY_LoRa) || defined(ESP32_DIY_1W_LoRa)
|
||||
digitalWrite(internalLedPin,LOW);
|
||||
#endif
|
||||
|
|
@ -254,9 +244,11 @@ namespace Utils {
|
|||
void validateDigiFreqs() {
|
||||
if (stationMode==4) {
|
||||
if (abs(Config.loramodule.digirepeaterTxFreq - Config.loramodule.digirepeaterRxFreq) < 125000) {
|
||||
Serial.println("Tx Freq less than 125kHz from Rx Freq ---> NOT VALID, check 'data/igate_conf.json'");
|
||||
show_display("Tx Freq is less than ", "125kHz from Rx Freq", "change it on : /data/", "igate_conf.json", 0);
|
||||
while (1);
|
||||
Serial.println("Tx Freq less than 125kHz from Rx Freq ---> NOT VALID");
|
||||
show_display("Tx Freq is less than ", "125kHz from Rx Freq", "device will autofix", "and then reboot", 1000);
|
||||
Config.loramodule.digirepeaterTxFreq = Config.loramodule.digirepeaterRxFreq; // Inform about that but then change the digirepeaterTxFreq to digirepeaterRxFreq and reset the device
|
||||
Config.writeFile();
|
||||
ESP.restart();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -318,83 +310,4 @@ namespace Utils {
|
|||
}
|
||||
}
|
||||
|
||||
void onOTAStart() {
|
||||
Serial.println("OTA update started!");
|
||||
display_toggle(true);
|
||||
lastScreenOn = millis();
|
||||
show_display("", "", "", " OTA update started!", "", "", "", 1000);
|
||||
}
|
||||
|
||||
void onOTAProgress(size_t current, size_t final) {
|
||||
if (millis() - ota_progress_millis > 1000) {
|
||||
display_toggle(true);
|
||||
lastScreenOn = millis();
|
||||
ota_progress_millis = millis();
|
||||
Serial.printf("OTA Progress Current: %u bytes, Final: %u bytes\n", current, final);
|
||||
show_display("", "", " OTA Progress : " + String((current*100)/final) + "%", "", "", "", "", 100);
|
||||
}
|
||||
}
|
||||
|
||||
void onOTAEnd(bool success) {
|
||||
display_toggle(true);
|
||||
lastScreenOn = millis();
|
||||
if (success) {
|
||||
Serial.println("OTA update finished successfully!");
|
||||
show_display("", "", " OTA update success!", "", " Rebooting ...", "", "", 4000);
|
||||
} else {
|
||||
Serial.println("There was an error during OTA update!");
|
||||
show_display("", "", " OTA update fail!", "", "", "", "", 4000);
|
||||
}
|
||||
}
|
||||
|
||||
void startServer() {
|
||||
if (stationMode==1 || stationMode==2 || (stationMode==5 && WiFi.status()==WL_CONNECTED)) {
|
||||
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
|
||||
request->send(200, "text/plain", "Hi " + Config.callsign + ", \n\nthis is your (Richonguzman/CA2RXU) LoRa APRS iGate , version " + versionDate + "\n\nTo update your firmware or filesystem go to: http://" + getLocalIP().substring(getLocalIP().indexOf(":")+3) + "/update\n\n\n73!");
|
||||
});
|
||||
|
||||
server.on("/test", HTTP_GET, [](AsyncWebServerRequest *request) {
|
||||
request->send(SPIFFS, "/test_info_1.html", "text/html");//"application/json");
|
||||
});
|
||||
|
||||
server.on("/test2", HTTP_GET, [](AsyncWebServerRequest *request) {
|
||||
request->send(SPIFFS, "/test1.html", "text/html");
|
||||
});
|
||||
|
||||
if (Config.ota.username != "" && Config.ota.password != "") {
|
||||
ElegantOTA.begin(&server, Config.ota.username.c_str(), Config.ota.password.c_str());
|
||||
} else {
|
||||
ElegantOTA.begin(&server);
|
||||
}
|
||||
ElegantOTA.setAutoReboot(true);
|
||||
ElegantOTA.onStart(onOTAStart);
|
||||
ElegantOTA.onProgress(onOTAProgress);
|
||||
ElegantOTA.onEnd(onOTAEnd);
|
||||
|
||||
server.on("/process_form.php", HTTP_POST, [](AsyncWebServerRequest *request){
|
||||
String message;
|
||||
|
||||
if (request->hasParam("email", true) && request->hasParam("name", true)) {
|
||||
email = request->getParam("email", true)->value();
|
||||
name = request->getParam("name", true)->value();
|
||||
|
||||
String responseMessage = "Received EMAIL: " + email + ", NAME: " + name;
|
||||
|
||||
// Assuming you're sending an HTTP response, for example, in an HTTP server context
|
||||
request->send(200, "text/plain", responseMessage);
|
||||
} else {
|
||||
// Handle the case where one or both parameters are missing
|
||||
request->send(400, "text/plain", "Both EMAIL and NAME parameters are required.");
|
||||
}
|
||||
});
|
||||
|
||||
server.onNotFound(notFound);
|
||||
|
||||
server.serveStatic("/", SPIFFS, "/");
|
||||
|
||||
server.begin();
|
||||
Serial.println("init : OTA Server ... done!");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -17,7 +17,6 @@ namespace Utils {
|
|||
void onOTAStart();
|
||||
void onOTAProgress(size_t current, size_t final);
|
||||
void onOTAEnd(bool success);
|
||||
void startServer();
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
192
src/web_utils.cpp
Normal file
192
src/web_utils.cpp
Normal file
|
|
@ -0,0 +1,192 @@
|
|||
#include "ota_utils.h"
|
||||
#include "web_utils.h"
|
||||
#include "configuration.h"
|
||||
|
||||
extern Configuration Config;
|
||||
|
||||
extern const char web_index_html[] asm("_binary_data_embed_index_html_gz_start");
|
||||
extern const char web_index_html_end[] asm("_binary_data_embed_index_html_gz_end");
|
||||
extern const size_t web_index_html_len = web_index_html_end - web_index_html;
|
||||
|
||||
extern const char web_style_css[] asm("_binary_data_embed_style_css_gz_start");
|
||||
extern const char web_style_css_end[] asm("_binary_data_embed_style_css_gz_end");
|
||||
extern const size_t web_style_css_len = web_style_css_end - web_style_css;
|
||||
|
||||
extern const char web_script_js[] asm("_binary_data_embed_script_js_gz_start");
|
||||
extern const char web_script_js_end[] asm("_binary_data_embed_script_js_gz_end");
|
||||
extern const size_t web_script_js_len = web_script_js_end - web_script_js;
|
||||
|
||||
extern const char web_bootstrap_css[] asm("_binary_data_embed_bootstrap_css_gz_start");
|
||||
extern const char web_bootstrap_css_end[] asm("_binary_data_embed_bootstrap_css_gz_end");
|
||||
extern const size_t web_bootstrap_css_len = web_bootstrap_css_end - web_bootstrap_css;
|
||||
|
||||
extern const char web_bootstrap_js[] asm("_binary_data_embed_bootstrap_js_gz_start");
|
||||
extern const char web_bootstrap_js_end[] asm("_binary_data_embed_bootstrap_js_gz_end");
|
||||
extern const size_t web_bootstrap_js_len = web_bootstrap_js_end - web_bootstrap_js;
|
||||
|
||||
namespace WEB_Utils {
|
||||
|
||||
AsyncWebServer server(80);
|
||||
|
||||
void loop() {
|
||||
|
||||
}
|
||||
|
||||
void handleNotFound(AsyncWebServerRequest *request) {
|
||||
AsyncWebServerResponse *response = request->beginResponse(404, "text/plain", "Not found");
|
||||
response->addHeader("Cache-Control", "max-age=3600");
|
||||
request->send(response);
|
||||
}
|
||||
|
||||
void handleStatus(AsyncWebServerRequest *request) {
|
||||
request->send(200, "text/plain", "OK");
|
||||
}
|
||||
|
||||
void handleHome(AsyncWebServerRequest *request) {
|
||||
AsyncWebServerResponse *response = request->beginResponse_P(200, "text/html", (const uint8_t*)web_index_html, web_index_html_len);
|
||||
response->addHeader("Content-Encoding", "gzip");
|
||||
request->send(response);
|
||||
}
|
||||
|
||||
void handleReadConfiguration(AsyncWebServerRequest *request) {
|
||||
File file = SPIFFS.open("/igate_conf.json");
|
||||
|
||||
String fileContent;
|
||||
while(file.available()){
|
||||
fileContent += String((char)file.read());
|
||||
}
|
||||
|
||||
request->send(200, "application/json", fileContent);
|
||||
}
|
||||
|
||||
void handleWriteConfiguration(AsyncWebServerRequest *request) {
|
||||
Serial.println("Got new config from www");
|
||||
|
||||
int networks = request->getParam("wifi.APs", true)->value().toInt();
|
||||
|
||||
Config.wifiAPs = {};
|
||||
|
||||
for (int i=0; i<networks; i++) {
|
||||
WiFi_AP wifiap;
|
||||
wifiap.ssid = request->getParam("wifi.AP." + String(i) + ".ssid", true)->value();
|
||||
wifiap.password = request->getParam("wifi.AP." + String(i) + ".password", true)->value();
|
||||
wifiap.latitude = request->getParam("wifi.AP." + String(i) + ".latitude", true)->value().toDouble();
|
||||
wifiap.longitude = request->getParam("wifi.AP." + String(i) + ".longitude", true)->value().toDouble();
|
||||
|
||||
Config.wifiAPs.push_back(wifiap);
|
||||
}
|
||||
|
||||
Config.callsign = request->getParam("callsign", true)->value();
|
||||
Config.stationMode = request->getParam("stationMode", true)->value().toInt();
|
||||
Config.iGateComment = request->getParam("iGateComment", true)->value();
|
||||
|
||||
Config.wifiAutoAP.password = request->getParam("wifi.autoAP.password", true)->value();
|
||||
Config.wifiAutoAP.powerOff = request->getParam("wifi.autoAP.powerOff", true)->value().toInt();
|
||||
|
||||
Config.digi.comment = request->getParam("digi.comment", true)->value();
|
||||
Config.digi.latitude = request->getParam("digi.latitude", true)->value().toDouble();
|
||||
Config.digi.longitude = request->getParam("digi.longitude", true)->value().toDouble();
|
||||
|
||||
Config.aprs_is.passcode = request->getParam("aprs_is.passcode", true)->value();
|
||||
Config.aprs_is.server = request->getParam("aprs_is.server", true)->value();
|
||||
Config.aprs_is.port = request->getParam("aprs_is.port", true)->value().toInt();
|
||||
Config.aprs_is.reportingDistance = request->getParam("aprs_is.reportingDistance", true)->value().toInt();
|
||||
|
||||
Config.loramodule.iGateFreq = request->getParam("lora.iGateFreq", true)->value().toInt();
|
||||
|
||||
if (request->hasParam("lora.digirepeaterTxFreq", true)) {
|
||||
Config.loramodule.digirepeaterTxFreq = request->getParam("lora.digirepeaterTxFreq", true)->value().toInt();
|
||||
}
|
||||
|
||||
if (request->hasParam("lora.digirepeaterRxFreq", true)) {
|
||||
Config.loramodule.digirepeaterRxFreq = request->getParam("lora.digirepeaterRxFreq", true)->value().toInt();
|
||||
}
|
||||
|
||||
Config.loramodule.spreadingFactor = request->getParam("lora.spreadingFactor", true)->value().toInt();
|
||||
Config.loramodule.signalBandwidth = request->getParam("lora.signalBandwidth", true)->value().toInt();
|
||||
Config.loramodule.codingRate4 = request->getParam("lora.codingRate4", true)->value().toInt();
|
||||
Config.loramodule.power = request->getParam("lora.power", true)->value().toInt();
|
||||
|
||||
Config.display.alwaysOn = request->hasParam("display.alwaysOn", true);
|
||||
|
||||
if (!Config.display.alwaysOn) {
|
||||
Config.display.timeout = request->getParam("display.timeout", true)->value().toInt();
|
||||
}
|
||||
|
||||
Config.display.turn180 = request->hasParam("display.turn180", true);
|
||||
|
||||
Config.syslog.active = request->hasParam("syslog.active", true);
|
||||
|
||||
if (Config.syslog.active) {
|
||||
Config.syslog.server = request->getParam("syslog.server", true)->value();
|
||||
Config.syslog.port = request->getParam("syslog.port", true)->value().toInt();
|
||||
}
|
||||
|
||||
Config.bme.active = request->hasParam("bme.active", true);
|
||||
|
||||
Config.ota.username = request->getParam("ota.username", true)->value();
|
||||
Config.ota.password = request->getParam("ota.password", true)->value();
|
||||
|
||||
Config.beaconInterval = request->getParam("other.beaconInterval", true)->value().toInt();
|
||||
Config.igateSendsLoRaBeacons = request->hasParam("other.igateSendsLoRaBeacons", true);
|
||||
Config.igateRepeatsLoRaPackets = request->hasParam("other.igateRepeatsLoRaPackets", true);
|
||||
Config.rememberStationTime = request->getParam("other.rememberStationTime", true)->value().toInt();
|
||||
Config.sendBatteryVoltage = request->hasParam("other.sendBatteryVoltage", true);
|
||||
Config.externalVoltageMeasurement = request->hasParam("other.externalVoltageMeasurement", true);
|
||||
|
||||
if (Config.externalVoltageMeasurement) {
|
||||
Config.externalVoltagePin = request->getParam("other.externalVoltagePin", true)->value().toInt();
|
||||
}
|
||||
|
||||
Config.writeFile();
|
||||
|
||||
AsyncWebServerResponse *response = request->beginResponse(302, "text/html", "");
|
||||
response->addHeader("Location", "/");
|
||||
request->send(response);
|
||||
|
||||
ESP.restart();
|
||||
}
|
||||
|
||||
void handleStyle(AsyncWebServerRequest *request) {
|
||||
AsyncWebServerResponse *response = request->beginResponse_P(200, "text/css", (const uint8_t*)web_style_css, web_style_css_len);
|
||||
response->addHeader("Content-Encoding", "gzip");
|
||||
request->send(response);
|
||||
}
|
||||
|
||||
void handleScript(AsyncWebServerRequest *request) {
|
||||
AsyncWebServerResponse *response = request->beginResponse_P(200, "text/javascript", (const uint8_t*)web_script_js, web_script_js_len);
|
||||
response->addHeader("Content-Encoding", "gzip");
|
||||
request->send(response);
|
||||
}
|
||||
|
||||
void handleBootstrapStyle(AsyncWebServerRequest *request) {
|
||||
AsyncWebServerResponse *response = request->beginResponse_P(200, "text/css", (const uint8_t*)web_bootstrap_css, web_bootstrap_css_len);
|
||||
response->addHeader("Content-Encoding", "gzip");
|
||||
response->addHeader("Cache-Control", "max-age=3600");
|
||||
request->send(response);
|
||||
}
|
||||
|
||||
void handleBootstrapScript(AsyncWebServerRequest *request) {
|
||||
AsyncWebServerResponse *response = request->beginResponse_P(200, "text/javascript", (const uint8_t*)web_bootstrap_js, web_bootstrap_js_len);
|
||||
response->addHeader("Content-Encoding", "gzip");
|
||||
response->addHeader("Cache-Control", "max-age=3600");
|
||||
request->send(response);
|
||||
}
|
||||
|
||||
void setup() {
|
||||
server.on("/", HTTP_GET, handleHome);
|
||||
server.on("/status", HTTP_GET, handleStatus);
|
||||
server.on("/configuration.json", HTTP_GET, handleReadConfiguration);
|
||||
server.on("/configuration.json", HTTP_POST, handleWriteConfiguration);
|
||||
server.on("/style.css", HTTP_GET, handleStyle);
|
||||
server.on("/script.js", HTTP_GET, handleScript);
|
||||
server.on("/bootstrap.css", HTTP_GET, handleBootstrapStyle);
|
||||
server.on("/bootstrap.js", HTTP_GET, handleBootstrapScript);
|
||||
|
||||
OTA_Utils::setup(&server); // Include OTA Updater for WebServer
|
||||
|
||||
server.onNotFound(handleNotFound);
|
||||
|
||||
server.begin();
|
||||
}
|
||||
}
|
||||
31
src/web_utils.h
Normal file
31
src/web_utils.h
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
#ifndef WEB_UTILS_H_
|
||||
#define WEB_UTILS_H_
|
||||
|
||||
#include <ESPAsyncWebServer.h>
|
||||
#include <ESPmDNS.h>
|
||||
#include <Arduino.h>
|
||||
#include <SPIFFS.h>
|
||||
#include <WiFi.h>
|
||||
|
||||
|
||||
namespace WEB_Utils {
|
||||
|
||||
void loop();
|
||||
|
||||
void handleNotFound(AsyncWebServerRequest *request);
|
||||
void handleStatus(AsyncWebServerRequest *request);
|
||||
void handleHome(AsyncWebServerRequest *request);
|
||||
|
||||
//void handleReadConfiguration(AsyncWebServerRequest *request);
|
||||
//void handleWriteConfiguration(AsyncWebServerRequest *request);
|
||||
|
||||
void handleStyle(AsyncWebServerRequest *request);
|
||||
void handleScript(AsyncWebServerRequest *request);
|
||||
void handleBootstrapStyle(AsyncWebServerRequest *request);
|
||||
void handleBootstrapScript(AsyncWebServerRequest *request);
|
||||
|
||||
void setup();
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -10,93 +10,141 @@ extern int myWiFiAPIndex;
|
|||
extern int myWiFiAPSize;
|
||||
extern int stationMode;
|
||||
extern uint32_t previousWiFiMillis;
|
||||
extern bool WiFiConnected;
|
||||
extern long WiFiAutoAPTime;
|
||||
extern bool WiFiAutoAPStarted;
|
||||
|
||||
namespace WIFI_Utils {
|
||||
|
||||
void checkWiFi() {
|
||||
if ((WiFi.status() != WL_CONNECTED) && ((millis() - previousWiFiMillis) >= 30*1000)) {
|
||||
Serial.print(millis());
|
||||
Serial.println("Reconnecting to WiFi...");
|
||||
WiFi.disconnect();
|
||||
WiFi.reconnect();
|
||||
previousWiFiMillis = millis();
|
||||
}
|
||||
}
|
||||
|
||||
void startWiFi() {
|
||||
int wifiCounter = 0;
|
||||
WiFi.mode(WIFI_STA);
|
||||
WiFi.disconnect();
|
||||
delay(500);
|
||||
unsigned long start = millis();
|
||||
show_display("", "", "Connecting to Wifi:", "", currentWiFi->ssid + " ...", 0);
|
||||
Serial.print("\nConnecting to WiFi '"); Serial.print(currentWiFi->ssid); Serial.println("' ...");
|
||||
WiFi.begin(currentWiFi->ssid.c_str(), currentWiFi->password.c_str());
|
||||
while (WiFi.status() != WL_CONNECTED && wifiCounter<2) {
|
||||
delay(500);
|
||||
#if defined(TTGO_T_LORA32_V2_1) || defined(HELTEC_V2) || defined(HELTEC_V3) || defined(ESP32_DIY_LoRa) || defined(ESP32_DIY_1W_LoRa)
|
||||
digitalWrite(internalLedPin,HIGH);
|
||||
#endif
|
||||
Serial.print('.');
|
||||
delay(500);
|
||||
#if defined(TTGO_T_LORA32_V2_1) || defined(HELTEC_V2) || defined(HELTEC_V3) || defined(ESP32_DIY_LoRa) || defined(ESP32_DIY_1W_LoRa)
|
||||
digitalWrite(internalLedPin,LOW);
|
||||
#endif
|
||||
if ((millis() - start) > 10000){
|
||||
delay(1000);
|
||||
if(myWiFiAPIndex >= (myWiFiAPSize-1)) {
|
||||
myWiFiAPIndex = 0;
|
||||
if (stationMode==5) {
|
||||
wifiCounter++;
|
||||
}
|
||||
} else {
|
||||
myWiFiAPIndex++;
|
||||
void checkWiFi() {
|
||||
if ((WiFi.status() != WL_CONNECTED) && ((millis() - previousWiFiMillis) >= 30*1000) && !WiFiAutoAPStarted) {
|
||||
Serial.print(millis());
|
||||
Serial.println("Reconnecting to WiFi...");
|
||||
WiFi.disconnect();
|
||||
WiFi.reconnect();
|
||||
previousWiFiMillis = millis();
|
||||
}
|
||||
currentWiFi = &Config.wifiAPs[myWiFiAPIndex];
|
||||
start = millis();
|
||||
Serial.print("\nConnecting to WiFi '"); Serial.print(currentWiFi->ssid); Serial.println("' ...");
|
||||
show_display("", "", "Connecting to Wifi:", "", currentWiFi->ssid + " ...", 0);
|
||||
WiFi.disconnect();
|
||||
WiFi.begin(currentWiFi->ssid.c_str(), currentWiFi->password.c_str());
|
||||
}
|
||||
}
|
||||
#if defined(TTGO_T_LORA32_V2_1) || defined(HELTEC_V2) || defined(HELTEC_V3) || defined(ESP32_DIY_LoRa) || defined(ESP32_DIY_1W_LoRa)
|
||||
digitalWrite(internalLedPin,LOW);
|
||||
#endif
|
||||
if (WiFi.status() == WL_CONNECTED) {
|
||||
Serial.print("Connected as ");
|
||||
Serial.println(WiFi.localIP());
|
||||
show_display("", "", " Connected!!", "" , " loading ...", 1000);
|
||||
} else if (WiFi.status() != WL_CONNECTED && stationMode==5) {
|
||||
Serial.println("\nNot connected to WiFi! (DigiRepeater Mode)");
|
||||
show_display("", "", " WiFi Not Connected!", " DigiRepeater MODE" , " loading ...", 2000);
|
||||
}
|
||||
}
|
||||
|
||||
void setup() {
|
||||
if (stationMode==1 || stationMode==2) {
|
||||
if (stationMode==1) {
|
||||
Serial.println("stationMode ---> iGate (only Rx)");
|
||||
} else {
|
||||
Serial.println("stationMode ---> iGate (Rx + Tx)");
|
||||
}
|
||||
startWiFi();
|
||||
btStop();
|
||||
} else if (stationMode==3 || stationMode==4) {
|
||||
if (stationMode==3) {
|
||||
Serial.println("stationMode ---> DigiRepeater (Rx freq == Tx freq)");
|
||||
} else {
|
||||
Serial.println("stationMode ---> DigiRepeater (Rx freq != Tx freq)");
|
||||
}
|
||||
WiFi.mode(WIFI_OFF);
|
||||
btStop();
|
||||
} else if (stationMode==5) {
|
||||
Serial.println("stationMode ---> iGate when Wifi/APRS available (DigiRepeater when not)");
|
||||
} else {
|
||||
Serial.println("stationMode ---> NOT VALID, check '/data/igate_conf.json'");
|
||||
show_display("------- ERROR -------", "stationMode Not Valid", "change it on : /data/", "igate_conf.json", 0);
|
||||
while (1);
|
||||
void startWiFi() {
|
||||
bool startAP = false;
|
||||
if (currentWiFi->ssid == "") {
|
||||
startAP = true;
|
||||
} else {
|
||||
int wifiCounter = 0;
|
||||
WiFi.mode(WIFI_STA);
|
||||
WiFi.disconnect();
|
||||
delay(500);
|
||||
unsigned long start = millis();
|
||||
show_display("", "", "Connecting to Wifi:", "", currentWiFi->ssid + " ...", 0);
|
||||
Serial.print("\nConnecting to WiFi '"); Serial.print(currentWiFi->ssid); Serial.println("' ...");
|
||||
WiFi.begin(currentWiFi->ssid.c_str(), currentWiFi->password.c_str());
|
||||
while (WiFi.status() != WL_CONNECTED && wifiCounter<myWiFiAPSize) {
|
||||
delay(500);
|
||||
#if defined(TTGO_T_LORA32_V2_1) || defined(HELTEC_V2) || defined(HELTEC_V3) || defined(ESP32_DIY_LoRa) || defined(ESP32_DIY_1W_LoRa)
|
||||
digitalWrite(internalLedPin,HIGH);
|
||||
#endif
|
||||
Serial.print('.');
|
||||
delay(500);
|
||||
#if defined(TTGO_T_LORA32_V2_1) || defined(HELTEC_V2) || defined(HELTEC_V3) || defined(ESP32_DIY_LoRa) || defined(ESP32_DIY_1W_LoRa)
|
||||
digitalWrite(internalLedPin,LOW);
|
||||
#endif
|
||||
if ((millis() - start) > 10000){
|
||||
delay(1000);
|
||||
if(myWiFiAPIndex >= (myWiFiAPSize-1)) {
|
||||
myWiFiAPIndex = 0;
|
||||
if (stationMode==5) {
|
||||
wifiCounter++;
|
||||
}
|
||||
} else {
|
||||
myWiFiAPIndex++;
|
||||
}
|
||||
wifiCounter++;
|
||||
currentWiFi = &Config.wifiAPs[myWiFiAPIndex];
|
||||
start = millis();
|
||||
Serial.print("\nConnecting to WiFi '"); Serial.print(currentWiFi->ssid); Serial.println("' ...");
|
||||
show_display("", "", "Connecting to Wifi:", "", currentWiFi->ssid + " ...", 0);
|
||||
WiFi.disconnect();
|
||||
WiFi.begin(currentWiFi->ssid.c_str(), currentWiFi->password.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
#if defined(TTGO_T_LORA32_V2_1) || defined(HELTEC_V2) || defined(HELTEC_V3) || defined(ESP32_DIY_LoRa) || defined(ESP32_DIY_1W_LoRa)
|
||||
digitalWrite(internalLedPin,LOW);
|
||||
#endif
|
||||
if (WiFi.status() == WL_CONNECTED) {
|
||||
Serial.print("Connected as ");
|
||||
Serial.println(WiFi.localIP());
|
||||
show_display("", "", " Connected!!", "" , " loading ...", 1000);
|
||||
} else if (WiFi.status() != WL_CONNECTED) {
|
||||
startAP = true;
|
||||
|
||||
Serial.println("\nNot connected to WiFi! Starting Auto AP");
|
||||
show_display("", "", " WiFi Not Connected!", "" , " loading ...", 1000);
|
||||
}
|
||||
WiFiConnected = !startAP;
|
||||
if (startAP) {
|
||||
Serial.println("\nNot connected to WiFi! Starting Auto AP");
|
||||
show_display("", "", " Starting Auto AP", " Please connect to it " , " loading ...", 1000);
|
||||
|
||||
WiFi.mode(WIFI_MODE_NULL);
|
||||
|
||||
WiFi.mode(WIFI_AP);
|
||||
WiFi.softAP(Config.callsign + " AP", "1234567890");
|
||||
|
||||
WiFiAutoAPTime = millis();
|
||||
WiFiAutoAPStarted = true;
|
||||
}
|
||||
}
|
||||
|
||||
void checkIfAutoAPShouldPowerOff() {
|
||||
if (WiFiAutoAPStarted && Config.wifiAutoAP.powerOff > 0) {
|
||||
if (WiFi.softAPgetStationNum() > 0) {
|
||||
WiFiAutoAPTime = 0;
|
||||
} else {
|
||||
if (WiFiAutoAPTime == 0) {
|
||||
WiFiAutoAPTime = millis();
|
||||
} else if ((millis() - WiFiAutoAPTime) > Config.wifiAutoAP.powerOff * 60 * 1000) {
|
||||
Serial.println("Stopping auto AP");
|
||||
|
||||
WiFiAutoAPStarted = false;
|
||||
WiFi.softAPdisconnect(true);
|
||||
|
||||
Serial.println("Auto AP stopped (timeout)");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void setup() {
|
||||
if (stationMode==1 || stationMode==2) {
|
||||
if (stationMode==1) {
|
||||
Serial.println("stationMode ---> iGate (only Rx)");
|
||||
} else {
|
||||
Serial.println("stationMode ---> iGate (Rx + Tx)");
|
||||
}
|
||||
startWiFi();
|
||||
btStop();
|
||||
} else if (stationMode==3 || stationMode==4) {
|
||||
if (stationMode==3) {
|
||||
Serial.println("stationMode ---> DigiRepeater (Rx freq == Tx freq)");
|
||||
} else {
|
||||
Serial.println("stationMode ---> DigiRepeater (Rx freq != Tx freq)");
|
||||
}
|
||||
WiFi.mode(WIFI_OFF);
|
||||
btStop();
|
||||
} else if (stationMode==5) {
|
||||
Serial.println("stationMode ---> iGate when Wifi/APRS available (DigiRepeater when not)");
|
||||
} else {
|
||||
Serial.println("stationMode ---> NOT VALID");
|
||||
show_display("------- ERROR -------", "stationMode Not Valid", "device will autofix", "and then reboot", 1000);
|
||||
|
||||
Config.stationMode = 1; // Inform about that but then change the station mode to 1 and reset the device
|
||||
Config.writeFile();
|
||||
|
||||
ESP.restart();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -7,7 +7,9 @@ namespace WIFI_Utils {
|
|||
|
||||
void checkWiFi();
|
||||
void startWiFi();
|
||||
void checkIfAutoAPShouldPowerOff();
|
||||
void setup();
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
20
tools/compress.py
Normal file
20
tools/compress.py
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import gzip
|
||||
|
||||
files = [
|
||||
'data_embed/index.html',
|
||||
'data_embed/script.js',
|
||||
'data_embed/style.css',
|
||||
'data_embed/bootstrap.js',
|
||||
'data_embed/bootstrap.css',
|
||||
]
|
||||
|
||||
|
||||
for src in files:
|
||||
out = src + ".gz"
|
||||
|
||||
|
||||
with open(src, 'rb') as f:
|
||||
content = f.read()
|
||||
|
||||
with open(out, 'wb') as f:
|
||||
f.write(gzip.compress(content, compresslevel=9))
|
||||
Loading…
Reference in a new issue