move watchos app sources here (which should be rewritten anyway, and usable only in very specific conditions)

This commit is contained in:
Evgeny Zinoviev 2023-01-04 04:04:03 +03:00
parent d549f428cb
commit bb32e56ca2
45 changed files with 2542 additions and 0 deletions

3
.gitignore vendored
View File

@ -21,3 +21,6 @@ CMakeListsPrivate.txt
/localwebsite/config.local.php
/localwebsite/cache
/localwebsite/test.php
/watchos/InfiniSolar/Pods
xcuserdata

View File

@ -0,0 +1,11 @@
{
"colors" : [
{
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,81 @@
{
"images" : [
{
"idiom" : "watch",
"role" : "notificationCenter",
"scale" : "2x",
"size" : "24x24",
"subtype" : "38mm"
},
{
"idiom" : "watch",
"role" : "notificationCenter",
"scale" : "2x",
"size" : "27.5x27.5",
"subtype" : "42mm"
},
{
"idiom" : "watch",
"role" : "companionSettings",
"scale" : "2x",
"size" : "29x29"
},
{
"idiom" : "watch",
"role" : "companionSettings",
"scale" : "3x",
"size" : "29x29"
},
{
"idiom" : "watch",
"role" : "appLauncher",
"scale" : "2x",
"size" : "40x40",
"subtype" : "38mm"
},
{
"idiom" : "watch",
"role" : "appLauncher",
"scale" : "2x",
"size" : "44x44",
"subtype" : "40mm"
},
{
"idiom" : "watch",
"role" : "appLauncher",
"scale" : "2x",
"size" : "50x50",
"subtype" : "44mm"
},
{
"idiom" : "watch",
"role" : "quickLook",
"scale" : "2x",
"size" : "86x86",
"subtype" : "38mm"
},
{
"idiom" : "watch",
"role" : "quickLook",
"scale" : "2x",
"size" : "98x98",
"subtype" : "42mm"
},
{
"idiom" : "watch",
"role" : "quickLook",
"scale" : "2x",
"size" : "108x108",
"subtype" : "44mm"
},
{
"idiom" : "watch-marketing",
"scale" : "1x",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>InfiniSolar WatchKit App</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
</array>
<key>WKWatchKitApp</key>
<true/>
</dict>
</plist>

View File

@ -0,0 +1,28 @@
{
"images" : [
{
"idiom" : "watch",
"scale" : "2x",
"screen-width" : "<=145"
},
{
"idiom" : "watch",
"scale" : "2x",
"screen-width" : ">161"
},
{
"idiom" : "watch",
"scale" : "2x",
"screen-width" : ">145"
},
{
"idiom" : "watch",
"scale" : "2x",
"screen-width" : ">183"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,53 @@
{
"assets" : [
{
"filename" : "Circular.imageset",
"idiom" : "watch",
"role" : "circular"
},
{
"filename" : "Extra Large.imageset",
"idiom" : "watch",
"role" : "extra-large"
},
{
"filename" : "Graphic Bezel.imageset",
"idiom" : "watch",
"role" : "graphic-bezel"
},
{
"filename" : "Graphic Circular.imageset",
"idiom" : "watch",
"role" : "graphic-circular"
},
{
"filename" : "Graphic Corner.imageset",
"idiom" : "watch",
"role" : "graphic-corner"
},
{
"filename" : "Graphic Extra Large.imageset",
"idiom" : "watch",
"role" : "graphic-extra-large"
},
{
"filename" : "Graphic Large Rectangular.imageset",
"idiom" : "watch",
"role" : "graphic-large-rectangular"
},
{
"filename" : "Modular.imageset",
"idiom" : "watch",
"role" : "modular"
},
{
"filename" : "Utilitarian.imageset",
"idiom" : "watch",
"role" : "utilitarian"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,28 @@
{
"images" : [
{
"idiom" : "watch",
"scale" : "2x",
"screen-width" : "<=145"
},
{
"idiom" : "watch",
"scale" : "2x",
"screen-width" : ">161"
},
{
"idiom" : "watch",
"scale" : "2x",
"screen-width" : ">145"
},
{
"idiom" : "watch",
"scale" : "2x",
"screen-width" : ">183"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,18 @@
{
"images" : [
{
"idiom" : "watch",
"scale" : "2x",
"screen-width" : ">161"
},
{
"idiom" : "watch",
"scale" : "2x",
"screen-width" : ">183"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,18 @@
{
"images" : [
{
"idiom" : "watch",
"scale" : "2x",
"screen-width" : ">161"
},
{
"idiom" : "watch",
"scale" : "2x",
"screen-width" : ">183"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,18 @@
{
"images" : [
{
"idiom" : "watch",
"scale" : "2x",
"screen-width" : ">161"
},
{
"idiom" : "watch",
"scale" : "2x",
"screen-width" : ">183"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,28 @@
{
"images" : [
{
"idiom" : "watch",
"scale" : "2x",
"screen-width" : "<=145"
},
{
"idiom" : "watch",
"scale" : "2x",
"screen-width" : ">161"
},
{
"idiom" : "watch",
"scale" : "2x",
"screen-width" : ">145"
},
{
"idiom" : "watch",
"scale" : "2x",
"screen-width" : ">183"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,18 @@
{
"images" : [
{
"idiom" : "watch",
"scale" : "2x",
"screen-width" : ">161"
},
{
"idiom" : "watch",
"scale" : "2x",
"screen-width" : ">183"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,28 @@
{
"images" : [
{
"idiom" : "watch",
"scale" : "2x",
"screen-width" : "<=145"
},
{
"idiom" : "watch",
"scale" : "2x",
"screen-width" : ">161"
},
{
"idiom" : "watch",
"scale" : "2x",
"screen-width" : ">145"
},
{
"idiom" : "watch",
"scale" : "2x",
"screen-width" : ">183"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,28 @@
{
"images" : [
{
"idiom" : "watch",
"scale" : "2x",
"screen-width" : "<=145"
},
{
"idiom" : "watch",
"scale" : "2x",
"screen-width" : ">161"
},
{
"idiom" : "watch",
"scale" : "2x",
"screen-width" : ">145"
},
{
"idiom" : "watch",
"scale" : "2x",
"screen-width" : ">183"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,59 @@
//
// ComplicationController.swift
// InfiniSolar WatchKit Extension
//
// Created by Evgeny Zinoviev on 03.08.2021.
//
import ClockKit
class ComplicationController: NSObject, CLKComplicationDataSource {
// MARK: - Complication Configuration
func getComplicationDescriptors(handler: @escaping ([CLKComplicationDescriptor]) -> Void) {
let descriptors = [
CLKComplicationDescriptor(identifier: "complication", displayName: "InfiniSolar", supportedFamilies: CLKComplicationFamily.allCases)
// Multiple complication support can be added here with more descriptors
]
// Call the handler with the currently supported complication descriptors
handler(descriptors)
}
func handleSharedComplicationDescriptors(_ complicationDescriptors: [CLKComplicationDescriptor]) {
// Do any necessary work to support these newly shared complication descriptors
}
// MARK: - Timeline Configuration
func getTimelineEndDate(for complication: CLKComplication, withHandler handler: @escaping (Date?) -> Void) {
// Call the handler with the last entry date you can currently provide or nil if you can't support future timelines
handler(nil)
}
func getPrivacyBehavior(for complication: CLKComplication, withHandler handler: @escaping (CLKComplicationPrivacyBehavior) -> Void) {
// Call the handler with your desired behavior when the device is locked
handler(.showOnLockScreen)
}
// MARK: - Timeline Population
func getCurrentTimelineEntry(for complication: CLKComplication, withHandler handler: @escaping (CLKComplicationTimelineEntry?) -> Void) {
// Call the handler with the current timeline entry
handler(nil)
}
func getTimelineEntries(for complication: CLKComplication, after date: Date, limit: Int, withHandler handler: @escaping ([CLKComplicationTimelineEntry]?) -> Void) {
// Call the handler with the timeline entries after the given date
handler(nil)
}
// MARK: - Sample Templates
func getLocalizableSampleTemplate(for complication: CLKComplication, withHandler handler: @escaping (CLKComplicationTemplate?) -> Void) {
// This method will be called once per supported complication, and the results will be cached
handler(nil)
}
}

View File

@ -0,0 +1,22 @@
//
// ContentView.swift
// InfiniSolar WatchKit Extension
//
// Created by Evgeny Zinoviev on 03.08.2021.
//
//
//import SwiftUI
//
//struct ContentView: View {
// var body: some View {
// MainInverterView()
//
//
// }
//}
//
//struct ContentView_Previews: PreviewProvider {
// static var previews: some View {
// ContentView()
// }
//}

View File

@ -0,0 +1,67 @@
//
// GenerationView.swift
// InfiniSolar WatchKit Extension
//
// Created by Evgeny Zinoviev on 08.08.2021.
//
import SwiftUI
struct GenerationView: View {
@ObservedObject var state = InverterGenerationState()
var body: some View {
VStack(alignment: .leading) {
Text("Generation")
.font(.title2)
.fontWeight(.thin)
Spacer().frame(height: 10)
if self.state.failed == true {
Text("Error while fetching info.")
.multilineTextAlignment(.leading)
Spacer().frame(height: 10)
Button(action:{
self.state.fetch()
}) {
Text("Retry")
}
} else if !self.state.done {
ProgressView().progressViewStyle(CircularProgressViewStyle())
} else {
Text("Today: ")
+ Text(String(self.state.today) + " Wh").fontWeight(.thin)
if self.state.yesterday > 0 {
Spacer().frame(height: 5)
Text("Yesterday: ")
+ Text(String(self.state.yesterday) + " Wh").fontWeight(.thin)
}
if self.state.dayBeforeYesterday > 0 {
Spacer().frame(height: 5)
Text("The day before yesterday: ")
+ Text(String(self.state.dayBeforeYesterday) + " Wh").fontWeight(.thin)
}
}
}.frame(
minWidth: 0,
maxWidth: .infinity,
minHeight: 0,
maxHeight: .infinity,
alignment: .topLeading
).onAppear() {
self.state.fetch()
}.onDisappear() {
self.state.stop()
}
}
}
struct GenerationView_Previews: PreviewProvider {
static var previews: some View {
GenerationView()
}
}

View File

@ -0,0 +1,43 @@
//
// InfiniSolarApp.swift
// InfiniSolar WatchKit Extension
//
// Created by Evgeny Zinoviev on 03.08.2021.
//
import SwiftUI
@main
struct InfiniSolarApp: App {
@SceneBuilder var body: some Scene {
WindowGroup {
NavigationView {
ScrollView(.vertical) {
VStack(alignment: .leading) {
InverterView()
self.divider()
RoomView()
self.divider()
PumpView()
}
.frame(
minWidth: 0,
maxWidth: .infinity,
minHeight: 0,
maxHeight: .infinity,
alignment: .topLeading
)
}
}
}
WKNotificationScene(controller: NotificationController.self, category: "myCategory")
}
func divider() -> some View {
return Divider()
.padding(EdgeInsets(top: 12, leading: 4, bottom: 12, trailing: 4))
}
}

View File

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>InfiniSolar WatchKit Extension</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>CLKComplicationPrincipalClass</key>
<string>$(PRODUCT_MODULE_NAME).ComplicationController</string>
<key>NSExtension</key>
<dict>
<key>NSExtensionAttributes</key>
<dict>
<key>WKAppBundleIdentifier</key>
<string>io.ch1p.InfiniSolar.watchkitapp</string>
</dict>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.watchkit</string>
</dict>
<key>WKWatchOnly</key>
<true/>
</dict>
</plist>

View File

@ -0,0 +1,114 @@
//
// InverterGenerationState.swift
// InfiniSolar WatchKit Extension
//
// Created by Evgeny Zinoviev on 08.08.2021.
//
import Alamofire
import SwiftyJSON
extension Date {
static var yesterday: Date { return Date().dayBefore }
static var beforeYesterday: Date { return Date().dayBefore2 }
var dayBefore: Date {
return Calendar.current.date(byAdding: .day, value: -1, to: noon)!
}
var dayBefore2: Date {
return Calendar.current.date(byAdding: .day, value: -2, to: noon)!
}
var noon: Date {
return Calendar.current.date(bySettingHour: 12, minute: 0, second: 0, of: self)!
}
var month: Int {
return Calendar.current.component(.month, from: self)
}
}
public class InverterGenerationState: ObservableObject {
@Published var today: Int
@Published var yesterday: Int
@Published var dayBeforeYesterday: Int
@Published var failed: Bool
var request: DataRequest?
var done: Bool
init() {
self.request = nil
self.today = 0
self.yesterday = 0
self.dayBeforeYesterday = 0
self.failed = false
self.done = false
}
func fetch() {
let today = Date()
let yesterday = Date.yesterday
let dayBeforeYesterday = Date.beforeYesterday
let cToday = Calendar.current.dateComponents([.day, .month, .year], from: today)
let cYday1 = Calendar.current.dateComponents([.day, .month, .year], from: yesterday)
let cYday2 = Calendar.current.dateComponents([.day, .month, .year], from: dayBeforeYesterday)
// shit, this looks like javascript in 2005 :(
// but it's my second day using swift, please treat me easy lol
// load today
self.getDayGenerated(arguments: [cToday.year!, cToday.month!, cToday.day!]) { wh in
self.today = wh
if cToday.month == cYday1.month {
// load yesterday
self.getDayGenerated(arguments: [cYday1.year!, cYday1.month!, cYday1.day!]) { wh in
self.yesterday = wh
if cToday.month == cYday2.month {
// load the day before yesterday
self.getDayGenerated(arguments: [cYday2.year!, cYday2.month!, cYday2.day!]) { wh in
self.dayBeforeYesterday = wh
self.done = true
}
} else {
self.done = true
}
}
} else {
self.done = true
}
}
}
func getDayGenerated(arguments: [Int], onComplete: @escaping (Int) -> ()) {
let args = arguments.map(String.init)
.joined(separator: ",")
self.request = AF.request("http://192.168.5.223:8380/get-day-generated/?args="+args).responseJSON { response in
self.request = nil
switch response.result {
case .success(let value):
let json = JSON(value)
onComplete(json["data"]["wh"].int ?? 0)
case .failure(let error):
switch (error) {
case .explicitlyCancelled:
print("InverterGenerationState: request has been canceled")
break
default:
print("InverterGenerationState: oops, something failed")
print(error)
self.failed = true
}
}
}
}
func stop() {
if self.request != nil {
self.request?.cancel()
self.request = nil
}
}
}

View File

@ -0,0 +1,78 @@
//
// InverterStatusFetcher.swift
// InfiniSolar WatchKit Extension
//
// Created by Evgeny Zinoviev on 03.08.2021.
//
import Alamofire
import SwiftyJSON
public class InverterState: ObservableObject {
@Published var status: InverterStatus
@Published var fetchError: Bool = false
var timer: Timer?
var request: DataRequest?
func startFetching() {
if self.timer != nil {
self.stopFetching()
}
self.timer = Timer.scheduledTimer(timeInterval: 1,
target: self,
selector: #selector(fetchStatus),
userInfo: nil,
repeats: true)
// self.timer?.fire()
}
func stopFetching() {
if self.request != nil {
self.request?.cancel()
self.request = nil
}
self.fetchError = false
self.timer?.invalidate()
self.timer = nil
}
@objc func fetchStatus() {
self.fetchError = false
self.request = AF.request("http://192.168.5.223:8380/get-status/").responseJSON {
response in
switch response.result {
case .success(let value):
let json = JSON(value)
self.status.activePower = json["data"]["ac_output_active_power"]["value"].int ?? 0
self.status.batteryVoltage = json["data"]["battery_voltage"]["value"].float ?? 0
self.status.batteryCapacity = json["data"]["battery_capacity"]["value"].int ?? 0
self.status.pvInputPower = json["data"]["pv1_input_power"]["value"].int ?? 0
case .failure(let error):
switch (error) {
case .explicitlyCancelled:
print("InverterStatusFetcher: request has been canceled")
break
default:
self.fetchError = true
self.timer?.invalidate()
self.timer = nil
}
}
self.request = nil
}
}
init() {
self.fetchError = false
self.status = InverterStatus(batteryVoltage: 0, batteryCapacity: 0, activePower: 0, pvInputPower: 0)
self.timer = nil
self.request = nil
}
}

View File

@ -0,0 +1,29 @@
//
// InverterStatus.swift
// InfiniSolar WatchKit Extension
//
// Created by Evgeny Zinoviev on 03.08.2021.
//
import Foundation
struct InverterStatus: Hashable {
public var batteryVoltage: Float
public var batteryCapacity: Int
public var activePower: Int
public var pvInputPower: Int
init(batteryVoltage: Float, batteryCapacity: Int, activePower: Int, pvInputPower: Int) {
self.batteryVoltage = batteryVoltage
self.batteryCapacity = batteryCapacity
self.activePower = activePower
self.pvInputPower = pvInputPower
}
func hasData() -> Bool {
return self.batteryVoltage != 0
|| self.batteryCapacity != 0
|| self.activePower != 0
|| self.pvInputPower != 0
}
}

View File

@ -0,0 +1,75 @@
//
// MainInverterView.swift
// InfiniSolar WatchKit Extension
//
// Created by Evgeny Zinoviev on 08.08.2021.
//
import SwiftUI
struct InverterView: View {
@ObservedObject var state = InverterState()
@State var isPresented = false
var body: some View {
VStack(alignment: .leading) {
Text("Inverter")
.font(.title2)
.fontWeight(.thin)
Spacer().frame(height: 10)
// inverter data
if self.state.fetchError == true {
Text("Error while fetching status.")
.multilineTextAlignment(.leading)
Spacer().frame(height: 10)
Button(action:{
self.state.startFetching()
}) {
Text("Retry")
}
// } else if !self.state.status.hasData() {
// ProgressView()
// .progressViewStyle(CircularProgressViewStyle())
} else {
Group {
Text(String(self.state.status.batteryVoltage) + " V")
+ Text("" + String(self.state.status.batteryCapacity) + " %").fontWeight(.thin)
Spacer().frame(height: 1)
Text("Active load is ").fontWeight(.thin)
+ Text(String(self.state.status.activePower) + " Wh")
if self.state.status.pvInputPower > 0 {
Divider()
Text("Consuming ").fontWeight(.thin)
+ Text(String(self.state.status.pvInputPower) + " Wh")
+ Text(" from panels").fontWeight(.thin)
}
Spacer().frame(height: 15)
NavigationLink(destination: GenerationView(), isActive: $isPresented) {
Text("Generation stats")
.onTapGesture{
self.isPresented = true
self.state.stopFetching()
}
}
}
}
}
.onAppear() {
self.state.startFetching()
}
}
}
struct InverterView_Previews: PreviewProvider {
static var previews: some View {
InverterView()
}
}

View File

@ -0,0 +1,33 @@
//
// NotificationController.swift
// InfiniSolar WatchKit Extension
//
// Created by Evgeny Zinoviev on 03.08.2021.
//
import WatchKit
import SwiftUI
import UserNotifications
class NotificationController: WKUserNotificationHostingController<NotificationView> {
override var body: NotificationView {
return NotificationView()
}
override func willActivate() {
// This method is called when watch view controller is about to be visible to user
super.willActivate()
}
override func didDeactivate() {
// This method is called when watch view controller is no longer visible
super.didDeactivate()
}
override func didReceive(_ notification: UNNotification) {
// This method is called when a notification needs to be presented.
// Implement it if you use a dynamic notification interface.
// Populate your dynamic notification interface as quickly as possible.
}
}

View File

@ -0,0 +1,20 @@
//
// NotificationView.swift
// InfiniSolar WatchKit Extension
//
// Created by Evgeny Zinoviev on 03.08.2021.
//
import SwiftUI
struct NotificationView: View {
var body: some View {
Text("Hello, World!")
}
}
struct NotificationView_Previews: PreviewProvider {
static var previews: some View {
NotificationView()
}
}

View File

@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,86 @@
//
// PumpState.swift
// InfiniSolar WatchKit Extension
//
// Created by Evgeny Zinoviev on 09.08.2021.
//
import Alamofire
import SwiftyJSON
public class PumpState: ObservableObject {
@Published var isEnabled: Bool
@Published var error: Bool
@Published var loading: Bool
@Published var setting: Bool
var request: DataRequest?
init() {
self.loading = true
self.error = false
self.isEnabled = false
self.request = nil
self.setting = false
}
func fetch() {
self.request = AF.request("http://192.168.5.223:8382/get/").responseJSON { response in
self.loading = false
switch response.result {
case .success(let value):
let json = JSON(value)
self.isEnabled = json["data"].string == "on"
case .failure(let error):
switch (error) {
case .explicitlyCancelled:
break
default:
print(error)
self.error = true
}
}
// self.loading = false
self.request = nil
}
}
func setState(on: Bool) {
self.setting = true
self.request = AF.request("http://192.168.5.223:8382/" + (on ? "on" : "off") + "/").responseJSON { response in
self.setting = false
switch response.result {
case .success(_):
self.isEnabled = on
case .failure(let error):
switch (error) {
case .explicitlyCancelled:
break
default:
print(error)
self.error = true
}
}
self.request = nil
}
}
func abort() {
if self.request != nil {
self.request?.cancel()
self.request = nil
}
self.error = false
self.loading = true
self.isEnabled = false
}
}

View File

@ -0,0 +1,60 @@
//
// MainPumpView.swift
// InfiniSolar WatchKit Extension
//
// Created by Evgeny Zinoviev on 09.08.2021.
//
import SwiftUI
struct PumpView: View {
@ObservedObject var state = PumpState()
var body: some View {
VStack(alignment: .leading) {
Text("Water pump")
.font(.title2)
.fontWeight(.thin)
Spacer().frame(height: 10)
if self.state.loading == true {
Text("Loading...")
.fontWeight(.thin)
}
else if self.state.error == true {
Text("Connection error.")
}
else {
if self.state.isEnabled == true {
Text("The pump is ").fontWeight(.thin)
+ Text("turned on")
Spacer().frame(height: 10)
Button(self.state.setting ? "..." : "Turn off") {
self.state.setState(on: false)
}
} else {
Text("The pump is ").fontWeight(.thin)
+ Text("turned off")
Spacer().frame(height: 10)
Button(self.state.setting ? "..." : "Turn on") {
self.state.setState(on: true)
}
}
}
}
.onAppear() {
self.state.fetch()
}
.onDisappear() {
self.state.abort()
}
}
}
struct PumpView_Previews: PreviewProvider {
static var previews: some View {
PumpView()
}
}

View File

@ -0,0 +1,20 @@
{
"aps": {
"alert": {
"body": "Test message",
"title": "Optional title",
"subtitle": "Optional subtitle"
},
"category": "myCategory",
"thread-id": "5280"
},
"WatchKit Simulator Actions": [
{
"title": "First Button",
"identifier": "firstButtonAction"
}
],
"customKey": "Use this file to define a testing payload for your notifications. The aps dictionary specifies the category, alert text and title. The WatchKit Simulator Actions array can provide info for one or more action buttons in addition to the standard Dismiss button. Any other top level keys are custom payload. If you have multiple such JSON files in your project, you'll be able to select them when choosing to debug the notification interface of your Watch App."
}

View File

@ -0,0 +1,84 @@
//
// RoomState.swift
// InfiniSolar WatchKit Extension
//
// Created by Evgeny Zinoviev on 09.08.2021.
//
import Alamofire
import SwiftyJSON
extension Double {
/// Rounds the double to decimal places value
func rounded(toPlaces places:Int) -> Double {
let divisor = pow(10.0, Double(places))
return (self * divisor).rounded() / divisor
}
}
public class RoomState: ObservableObject {
@Published var temp: Double
@Published var rh: Double
@Published var error: Bool
var timer: Timer?
var request: DataRequest?
init() {
self.error = false
self.timer = nil
self.request = nil
self.temp = 0
self.rh = 0
}
func start() {
if self.timer != nil {
self.stop()
}
self.timer = Timer.scheduledTimer(timeInterval: 5,
target: self,
selector: #selector(fetch),
userInfo: nil,
repeats: true)
self.timer?.fire()
}
func stop() {
if self.request != nil {
self.request?.cancel()
self.request = nil
}
self.error = false
self.timer?.invalidate()
self.timer = nil
}
@objc func fetch() {
self.request = AF.request("http://192.168.5.223:8381/read/").responseJSON { response in
self.request = nil
switch response.result {
case .success(let value):
let j = JSON(value)
self.temp = (j["temp"].double ?? 0).rounded(toPlaces: 2)
self.rh = (j["humidity"].double ?? 0).rounded(toPlaces: 2)
case .failure(let error):
switch (error) {
case .explicitlyCancelled:
break
default:
self.error = true
self.timer?.invalidate()
self.timer = nil
}
}
}
}
}

View File

@ -0,0 +1,42 @@
//
// MainRoomView.swift
// InfiniSolar WatchKit Extension
//
// Created by Evgeny Zinoviev on 08.08.2021.
//
import SwiftUI
struct RoomView: View {
@ObservedObject var state = RoomState()
var body: some View {
VStack(alignment: .leading) {
Text("Room")
.font(.title2)
.fontWeight(.thin)
Spacer().frame(height: 10)
if self.state.error {
Text("Failed to fetch data from si7021d.")
}
else {
Text("Temperature is ").fontWeight(.thin) + Text(String(self.state.temp) + " °C")
Text("Rel. humidity is ").fontWeight(.thin) + Text(String(self.state.rh) + " %")
}
}
.onAppear() {
self.state.start()
}
.onDisappear() {
self.state.stop()
}
}
}
struct RoomView_Previews: PreviewProvider {
static var previews: some View {
RoomView()
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:InfiniSolar.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>

View File

@ -0,0 +1,33 @@
//
// InfiniSolarTests.swift
// InfiniSolarTests
//
// Created by Evgeny Zinoviev on 03.08.2021.
//
import XCTest
@testable import InfiniSolar_WatchKit_Extension
class InfiniSolarTests: XCTestCase {
override func setUpWithError() throws {
// Put setup code here. This method is called before the invocation of each test method in the class.
}
override func tearDownWithError() throws {
// Put teardown code here. This method is called after the invocation of each test method in the class.
}
func testExample() throws {
// This is an example of a functional test case.
// Use XCTAssert and related functions to verify your tests produce the correct results.
}
func testPerformanceExample() throws {
// This is an example of a performance test case.
self.measure {
// Put the code you want to measure the time of here.
}
}
}

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
</dict>
</plist>

View File

@ -0,0 +1,42 @@
//
// InfiniSolarUITests.swift
// InfiniSolarUITests
//
// Created by Evgeny Zinoviev on 03.08.2021.
//
import XCTest
class InfiniSolarUITests: XCTestCase {
override func setUpWithError() throws {
// Put setup code here. This method is called before the invocation of each test method in the class.
// In UI tests it is usually best to stop immediately when a failure occurs.
continueAfterFailure = false
// In UI tests its important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.
}
override func tearDownWithError() throws {
// Put teardown code here. This method is called after the invocation of each test method in the class.
}
func testExample() throws {
// UI tests must launch the application that they test.
let app = XCUIApplication()
app.launch()
// Use recording to get started writing UI tests.
// Use XCTAssert and related functions to verify your tests produce the correct results.
}
func testLaunchPerformance() throws {
if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *) {
// This measures how long it takes to launch your application.
measure(metrics: [XCTApplicationLaunchMetric()]) {
XCUIApplication().launch()
}
}
}
}

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
</dict>
</plist>

View File

@ -0,0 +1,37 @@
# Uncomment the next line to define a global platform for your project
# platform :ios, '9.0'
target 'InfiniSolar' do
# Comment the next line if you don't want to use dynamic frameworks
#use_frameworks!
# Pods for InfiniSolar
target 'InfiniSolarTests' do
inherit! :search_paths
# Pods for testing
end
target 'InfiniSolarUITests' do
# Pods for testing
end
end
target 'InfiniSolar WatchKit App' do
# Comment the next line if you don't want to use dynamic frameworks
#use_frameworks!
# Pods for InfiniSolar WatchKit App
end
target 'InfiniSolar WatchKit Extension' do
# Comment the next line if you don't want to use dynamic frameworks
#use_frameworks!
# Pods for InfiniSolar WatchKit Extension
pod 'Alamofire', '~> 5.1'
pod 'SwiftyJSON', '~> 4.0'
pod 'SwiftSocket'
end

View File

@ -0,0 +1,20 @@
PODS:
- Alamofire (5.4.3)
- SwiftyJSON (4.3.0)
DEPENDENCIES:
- Alamofire (~> 5.1)
- SwiftyJSON (~> 4.0)
SPEC REPOS:
trunk:
- Alamofire
- SwiftyJSON
SPEC CHECKSUMS:
Alamofire: e447a2774a40c996748296fa2c55112fdbbc42f9
SwiftyJSON: 6faa0040f8b59dead0ee07436cbf76b73c08fd08
PODFILE CHECKSUM: 4af73cac3a538d18238200492e84f50cb3fe8db3
COCOAPODS: 1.10.2